[Spring] Spring Boot 이미지 빌드 후 Docker 실행

klmin·2024년 10월 27일

version : 3.3.4
OS : windows 10 Pro

Spring Boot 프로젝트를 이미지 파일로 빌드한 후에 Docker에서 실행하는 법을 기록한다.

1. 기본설정 (docker-compose.yml)

docker-compose.yml 파일은 로컬 개발 환경에서 MySQL과 Redis를 Docker로 실행할 수 있도록 설정되어 있다.

만약 Spring Boot를 로컬에서 실행하지 않고 도커 내에 배포를 하려면
application.yml과 docker-compose를 수정하고 DockerFile을 추가해야 한다.

docker-compose.yml

services:
  mysql:
    image: 'mysql:8.0'
    environment:
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      TZ: Asia/Seoul
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    ports:
      - '3307:3306'
    volumes:
      - "./mysql/data:/var/lib/mysql"
      - "./src/main/sql:/docker-entrypoint-initdb.d"
    restart: always

  redis:
    image: 'redis:latest'
    command: ["redis-server", "--requirepass", "klmin6394"]
    volumes:
      - ./redis/data:/data
    ports:
      - '6379:6379'
    restart: always

2. 기본설정 (application-docker.yml)

application-docker.yml Spring Boot가 로컬에서 실행될 때 Docker로 실행되는 MySQL과 Redis 컨테이너에 연결할 수 있도록 설정한다.

application-docker.yml

spring:
  docker:
    compose:
      enabled: true

  datasource:
    url: jdbc:mysql://localhost:3307/product
    username: product
    password: product@1234
    driver-class-name: com.mysql.cj.jdbc.Driver

  data:
    redis:
      host: localhost
      port: 6379
      password: klmin6394

3. 배포용 docker-compose 추가

배포 환경에서는 Spring Boot도 Docker 컨테이너로 실행된다. docker-compose.prod.yml 파일은 배포 시 MySQL과 Redis와 함께 Spring Boot 애플리케이션을 컨테이너로 구성한다.

docker-compose.prod.yml

services:
  mysql:
    image: 'mysql:8.0'
    environment:
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      TZ: Asia/Seoul
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    ports:
      - '3307:3306'
    volumes:
      - "./mysql/data:/var/lib/mysql"
      - "./src/main/sql:/docker-entrypoint-initdb.d"
    networks:
      - product_api_server_network
    restart: always

  redis:
    image: 'redis:latest'
    command: ["redis-server", "--requirepass", "klmin6394"]
    volumes:
      - ./redis/data:/data
    ports:
      - '6379:6379'
    networks:
      - product_api_server_network
    restart: always


  app:
    image: 'product-api-server'
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - '8080:8080'
    networks:
      - product_api_server_network
    depends_on:
      - mysql
      - redis

networks:
  product_api_server_network:

현재 로컬에 MySQL이 3306 포트로 실행 중이어서 Docker에서 실행된 MySQL을 외부에서 접근하기 위해 3307 포트로 설정해주었다.
Docker 내부 통신에서는 MySQL 기본 포트인 3306을 그대로 사용하도록 지정했다.

  • app : Spring Boot 애플리케이션을 실행할 서비스의 이름이다.
    임의로 app이라고 작성.
  • image: 컨테이너 생성 시 사용할 Docker 이미지 이름을 지정한다.
    Docker Compose는 이 이름의 이미지를 Docker Hub에서 가져오거나 로컬에 동일한 이름의 이미지가 있다면 그것을 사용한다.
    여기서는 product-api-server로 지정.
  • build : Docker 이미지를 빌드하기 위해 사용한다.
    • context : Docker가 빌드를 수행할 때 참조할 파일들의 경로를 지정한다.
      현재 디렉토리(.)로 설정함.
    • dockerfile : Docker 이미지를 빌드할 때 사용할 기준 파일이다.
      Dockerfile을 기준으로 하여 product-api-server라는 이름의 이미지를 생성한다.
  • ports : (외부):(내부). 내부에서 사용할 포트와 외부에서 사용할 포트를 지정한다.
  • networks : Docker 내부에서 사용할 네트워크의 이름을 지정한다.
    product_api_server_network라는 네트워크를 생성하여 mysql, redis, app 서비스가 서로 통신할 수 있도록 설정했다.
  • depends_on : app서비스가 의존하는 서비스들을 명시한다.
    이 설정으로 app이 시작되기 전에 mysqlredis가 먼저 시작된다.

4. 배포용 애플리케이션 설정 파일 (application-prod.yml)

application-prod.yml 파일은 배포 환경에서 사용되며 Docker Compose에서 정의한 서비스 이름으로 MySQL과 Redis에 연결하도록 설정한다.

application-prod.yml

spring:
  docker:
    compose:
      enabled: true

  datasource:
    url: jdbc:mysql://mysql:3306/product
    username: product
    password: product@1234
    driver-class-name: com.mysql.cj.jdbc.Driver

  data:
    redis:
      host: redis
      port: 6379
      password: klmin6394
  • mysql : url이 jdbc:mysql://localhost:3306/product가 아니고
    jdbc:mysql://mysql:3306/product 이다.
    Docker-compose에서 지정한 서비스명을 써줘야 한다.
  • redis : host가 localhost가 아니고 redis이다.
    myslq과 동일하게 Docker-compose에서 지정한 서비스명을 써줘야 한다.

5. Dockerfile 설정

Dockerfile : 애플리케이션을 배포 가능한 이미지로 빌드하기 위한 설정 파일이다.
Dockerfile에 작성된 명령어들은 순차적으로 실행되며 최종적으로 하나의 Docker 이미지가 생성된다.
이 이미지를 기반으로 컨테이너를 실행할 수 있다.

gradle build 후 명령어로 실행
빌드를 미리 한 후 docker-compose 명령어를 실행한다.

// Dockerfile

FROM openjdk:21-jdk-slim
COPY build/libs/product-api-server.jar /product-api-server.jar
ENTRYPOINT ["java", "-jar", "/product-api-server.jar", "--spring.profiles.active=prod"]
  • FROM : 어떤 기본 이미지를 사용할지 지정.
    여기서는 OpenJDK 21을 포함한 slim 버전의 이미지를 기반으로 한다.
  • COPY : 파일이나 디렉토리를 호스트(로컬 시스템)에서 컨테이너 이미지 내부로 복사한다.
    호스트의 build/libs/product-api-server.jar 파일을 컨테이너 이미지 내부의 /product-api-server.jar 위치로 복사한다.
  • ENTRYPOINT : 컨테이너가 실행될 때 기본으로 실행할 명령을 설정한다.
    주로 애플리케이션 실행을 위한 명령어를 정의한다.
    ["java", "-jar", "/product-api-server.jar", "--spring.profiles.active=prod"]는 컨테이너가 시작될 때 Java로 JAR 파일을 실행하고 이때 profile은 prod로 실행된다.

6. Docker Compose 명령어로 실행

// Docker-compose 명령어
docker-compose -f docker-compose.prod.yml up --build -d

docker-compose.prod.yml 파일을 사용해 Docker 이미지를 빌드하고 이를 기반으로 컨테이너를 백그라운드에서 실행하는 명령.

  • docker-compose : Docker Compose 명령어로 여러 컨테이너로 구성된 애플리케이션을 정의하고 실행하기 위해 사용한다.
  • -f docker-compose.prod.yml : -f 옵션은 사용할 Compose 설정 파일을 지정하는 옵션이다.
    여기서는 docker-compose.prod.yml 파일을 사용한다.
  • up : 설정 파일에 정의된 모든 서비스를 빌드하고 실행하는 명령어이다.
  • --build : 서비스에 필요한 이미지를 강제로 다시 빌드하도록 지정하는 옵션이다.
    기존에 빌드된 이미지가 있더라도 이를 무시하고 새로운 이미지를 빌드하여 최신 상태의 애플리케이션을 배포한다.
  • -d : 백그라운드 모드에서 컨테이너를 실행하도록 지정한다.
    이 옵션을 사용하면 터미널을 닫더라도 컨테이너가 계속 실행되며 터미널에 실행 로그가 출력되지 않는다.

dockerfile로 이미지를 만들고 컨테이너에 실행이 되었다.

docker desktop에서 생성된 모습이다.

실행 로그이다.

요청을 해보면 정상적으로 요청이 가는것을 확인할 수 있다.

멀티스테이지 빌드

Dockerfile에 gradle build도 포함한다.
Dockerfile에서 여러 FROM 명령을 사용하여 여러 스테이지로 나누어 이미지를 빌드하는 방식이다.

빌드에 필요한 도구와 최종 실행 환경을 분리함으로써 최종 이미지에 빌드 도구나 불필요한 파일을 포함하지 않도록 하여 이미지 크기를 줄이고 보안성을 높이는 데 유용하다.

  • 주요개념
    • 빌드 스테이지 : Dockerfile에서 여러 개의 FROM 명령을 사용하여 여러 스테이지를 정의할 수 있다.
      각 FROM 명령은 새로운 스테이지를 시작하며 이 스테이지에서 필요한 작업(예: 컴파일, 테스트, 빌드 등)을 수행할 수 있다.
    • 실행 스테이지 : COPY --from=<스테이지 이름> 명령을 사용하여 빌드 스테이지에서 생성된 결과물(예: 빌드된 애플리케이션 파일)을 선택적으로 복사할 수 있다.
      이렇게 하면 중간 스테이지의 불필요한 빌드 도구나 파일을 최종 이미지에 포함하지 않고, 필요한 파일만 가져와 실행 환경을 가볍게 만들 수 있다.
    • 최종 이미지 최적화 : 멀티 스테이지 빌드를 사용하면 최종 이미지에 필요한 파일만 포함하게 되어 이미지 크기를 줄일 수 있고 보안성도 높일 수 있다.
  • 이점
    1. 일관된 빌드 환경 유지
    2. 자동화된 빌드 및 배포 가능
    3. Docker 캐시 활용으로 빌드 시간 최적화
    4. 불필요한 파일 제외로 이미지 크기 감소 및 보안 강화
// build.gradle

tasks.named("bootJar") {
    mainClass = 'com.product.ProductApplication' // 실제 메인 클래스 경로로 설정
}

mainClass를 설정하지 않으면 bootJar 작업 중 메인 클래스를 찾지 못해 빌드 오류가 발생할 수 있다.
//Dockerfile

# 1단계: 빌드 스테이지
FROM gradle:8.10.2-jdk21 AS builder
WORKDIR /app

# Gradle 빌드 파일만 먼저 복사하여 종속성 캐시를 활용
COPY build.gradle settings.gradle /app/
RUN gradle build -x test --no-daemon

# 소스 코드 복사 및 빌드
COPY src /app/src
RUN gradle bootJar -x test --no-daemon

# 2단계: 실행 스테이지
FROM openjdk:21-jdk
COPY --from=builder /app/build/libs/product-api-server.jar /product-api-server.jar

# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "/product-api-server.jar", "--spring.profiles.active=prod"]
  • 첫 번째 스테이지 (빌드 스테이지)
    • gradle:8.10.2-jdk21 이미지를 사용하여 Gradle 빌드 환경을 설정한다.
    • build.gradlesettings.gradle 파일을 먼저 복사하여 종속성을 캐시한다.
    • gradle bootJar -x test를 실행하여 JAR 파일을 빌드한다.
      테스트 코드는 제외시켜놨다.
    • 이 스테이지에서는 최종 이미지에 불필요한 Gradle과 소스 코드가 포함된다.
      하지만 멀티 스테이지 빌드를 통해 이 스테이지의 결과물을 최종 이미지에 그대로 포함시키지 않는다
  • 두번째 스테이지(실행 스테이지)
    • openjdk:21-jdk 이미지를 사용하여 실행 환경을 설정한다.
    • 첫 번째 스테이지에서 생성된 최종 JAR 파일만 복사해와 /app 디렉토리에 배치한다.
    • ENTRYPOINT로 JAR 파일을 실행한다.

//Docker dompose 명령어
 
docker-compose -f docker-compose.prod.yml up --build -d

docker-compose.prod.yml 파일을 사용해 이미지를 빌드하고 컨테이너를 백그라운드에서 실행한다.



Windows 10 로컬에서 도커 서비스 연동 : https://velog.io/@klmin/spring-boot-docker-compose-%EB%82%B4%EC%9A%A9-%EA%B8%B0%EB%A1%9D

참고 : https://docs.docker.com/build/concepts/dockerfile/

https://codenme.tistory.com/76

profile
웹 개발자

0개의 댓글