안녕하세요! Devloo입니다. 🙂 이번 시간에는 Spring Boot 애플리케이션을 Docker로 컨테이너화할 때 알아두면 좋은 10가지 모범 사례를 살펴보겠습니다.
Docker는 개발자가 애플리케이션을 컨테이너로 패키징하여 어떤 플랫폼에서도 쉽게 배포하고 실행할 수 있게 해주는 강력한 도구입니다. Spring Boot 애플리케이션을 Docker로 컨테이너화할 때는 애플리케이션이 원활하고 효율적으로 실행되도록 몇 가지 모범 사례를 따르는 것이 중요합니다.
이 글에서는 이러한 모범 사례를 살펴보고, Spring Boot 애플리케이션을 Docker로 컨테이너화하는 데 도움이 되는 코드 예제와 설명을 제공하겠습니다. 자 그럼 시작해볼까요 🙂 ?
1. 적절한 베이스 이미지 선택하기
Spring Boot 애플리케이션을 Docker로 컨테이너화할 때는 애플리케이션에 맞는 베이스 이미지를 선택하는 것이 중요합니다. 베이스 이미지는 애플리케이션에 필요한 기본 운영 체제와 종속성을 제공합니다. 적절한 베이스 이미지를 선택하면 Docker 컨테이너에서 애플리케이션이 원활하고 효율적으로 실행될 수 있습니다.
Spring Boot 애플리케이션에는 OpenJDK 베이스 이미지를 사용하는 것이 좋습니다. OpenJDK는 Java Development Kit (JDK)의 오픈 소스 구현체로, Java 런타임 환경을 제공합니다. OpenJDK 베이스 이미지는 Java 8, Java 11, Java 16 등 다양한 버전으로 제공됩니다. 다음은 OpenJDK 11 베이스 이미지를 사용하는 Dockerfile 예제입니다:
FROM openjdk:11
COPY target/my-application.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
이 예제에서는 openjdk:11 베이스 이미지를 사용하여 Spring Boot 애플리케이션을 위한 Docker 이미지를 생성합니다. my-application.jar
파일을 컨테이너에 복사하고, java
명령을 사용하여 애플리케이션을 실행합니다.
Spring Boot 애플리케이션에 적합한 베이스 이미지를 사용하면 Docker 컨테이너에서 애플리케이션이 원활하고 효율적으로 실행될 수 있습니다. OpenJDK는 경량이고 안전한 Java 런타임 환경을 제공하기 때문에 Java 애플리케이션에 많이 사용됩니다.
2. 슬림 이미지 생성하기
Spring Boot 애플리케이션을 Docker로 컨테이너화할 때는 Docker 이미지의 크기를 가능한 한 작게 유지하는 것이 중요합니다. 작은 이미지 크기는 이미지 전송 시간을 단축시키고, 저장소 요구 사항을 줄이며, 컨테이너 시작 시간을 단축하는 등의 여러 가지 장점을 제공합니다.
Dockerfile에서 멀티스테이지 빌드를 사용하면 이미지 크기를 줄일 수 있습니다. 멀티스테이지 빌드는 여러 FROM
지시어를 사용하여 빌드 프로세스의 각 단계를 정의합니다. 각 단계는 고유한 지시어와 종속성을 가질 수 있으며, 최종 이미지에는 마지막 단계의 파일과 종속성만 포함됩니다. 다음은 멀티스테이지 빌드를 사용하여 슬림한 Spring Boot 이미지를 생성하는 Dockerfile 예제입니다:
# 첫 번째 단계: 애플리케이션 빌드
FROM maven:3.8.3-jdk-11 AS build
COPY . /app
WORKDIR /app
RUN mvn package -DskipTests
# 두 번째 단계: 슬림 이미지 생성
FROM openjdk:11-jre-slim
COPY --from=build /app/target/my-application.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
이 예제에서 첫 번째 단계는 Maven 베이스 이미지를 사용하여 Spring Boot 애플리케이션을 빌드하고 jar 파일을 생성합니다. 두 번째 단계는 Java 런타임 환경만 포함하는 슬림한 버전의 OpenJDK 베이스 이미지를 사용합니다.
COPY --from=build
지시어는 첫 번째 단계에서 생성된 jar 파일을 두 번째 단계로 복사하며, ENTRYPOINT
지시어는 컨테이너가 시작될 때 실행할 명령을 지정합니다.
이와 같이 멀티스테이지 빌드를 사용하면 Spring Boot 애플리케이션을 실행하는 데 필요한 종속성과 파일만 포함하는 슬림한 Docker 이미지를 생성할 수 있습니다. 이를 통해 이미지 크기를 줄이고 애플리케이션의 성능을 향상시킬 수 있습니다.
3. 환경 변수 사용하기
Spring Boot 애플리케이션을 Docker로 컨테이너화할 때는 환경 변수를 사용하여 애플리케이션을 구성하는 것이 중요합니다. 환경 변수를 사용하면 Docker 이미지를 다시 빌드하지 않고도 애플리케이션의 구성을 변경할 수 있습니다.
Spring Boot 애플리케이션은 application.properties
또는 application.yml
파일을 통해 구성 속성을 지정할 수 있습니다. 이러한 속성은 런타임에 환경 변수로 재정의할 수 있으며, Spring Boot는 이를 자동으로 매핑해줍니다. 다음은 Spring Boot 애플리케이션의 활성 프로파일을 설정하는 환경 변수를 Dockerfile에 지정하는 예제입니다:
FROM openjdk:11
ENV SPRING_PROFILES_ACTIVE=production
COPY target/my-application.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
이 예제에서는 SPRING_PROFILES_ACTIVE
환경 변수를 production
으로 설정하여 Spring Boot 애플리케이션의 프로덕션 프로파일을 활성화합니다.
컨테이너가 시작되면 ENTRYPOINT 지시어에 지정된 java
명령이 -jar
옵션과 함께 실행되어 Spring Boot 애플리케이션이 시작됩니다. 환경 변수를 설정했기 때문에 애플리케이션은 자동으로 프로덕션 프로파일을 사용하게 됩니다.
이처럼 환경 변수를 사용하면 Docker 이미지를 다시 빌드하지 않고도 Spring Boot 애플리케이션의 구성을 쉽게 변경할 수 있습니다. 컨테이너를 실행할 때
-e
옵션을 사용하여 환경 변수를 설정하거나 Docker Compose 파일을 사용하여 환경 변수를 정의할 수 있습니다.
4. Docker Compose 사용하기
Spring Boot 애플리케이션을 Docker로 컨테이너화할 때, Docker Compose를 사용하여 애플리케이션의 서비스와 종속성을 정의하는 것이 중요합니다. Docker Compose는 멀티 컨테이너 Docker 애플리케이션을 정의하고 실행하는 도구입니다. 이를 통해 애플리케이션의 서비스, 네트워크 및 볼륨을 하나의 파일에서 정의하여 쉽게 관리하고 배포할 수 있습니다. 다음은 Spring Boot 애플리케이션과 MySQL 데이터베이스를 정의하는 Docker Compose 파일 예제입니다:
version: '3'
services:
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: my-secret-pw
MYSQL_DATABASE: my-database
volumes:
- db_data:/var/lib/mysql
web:
build: .
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/my-database
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: my-secret-pw
volumes:
db_data:
이 예제에서는 db
와 web
두 가지 서비스를 정의했습니다. db
서비스는 공식 MySQL 이미지를 사용하고, 환경 변수를 통해 루트 비밀번호와 데이터베이스 이름을 설정합니다. 또한 지속적인 스토리지를 위해 db_data
라는 이름의 볼륨을 생성합니다.
web
서비스는 현재 디렉토리(.
)를 빌드 컨텍스트로 사용하여 Spring Boot 애플리케이션을 빌드하고 8080 포트를 노출합니다. 또한 데이터베이스 URL, 사용자 이름, 비밀번호를 환경 변수로 설정하여 Spring Boot 애플리케이션이 MySQL 데이터베이스에 연결할 수 있도록 합니다.
이처럼 Docker Compose를 사용하면 Spring Boot 애플리케이션과 그 종속성을 쉽게 관리하고 배포할 수 있습니다. Docker Compose 파일에 정의된 모든 서비스를 단일 명령으로 시작할 수 있으며, 필요에 따라 서비스를 확장하거나 축소할 수 있습니다. 또한 Docker Compose를 사용하여 볼륨, 네트워크 및 환경 변수와 같은 추가 구성 옵션을 정의할 수 있어 애플리케이션을 더욱 쉽게 관리하고 배포할 수 있습니다.
5. 리버스 프록시 사용하기
Spring Boot 애플리케이션을 Docker로 컨테이너화할 때는 리버스 프록시를 사용하여 들어오는 트래픽을 처리하고 이를 애플리케이션 컨테이너로 분배하는 것이 중요합니다. 리버스 프록시는 애플리케이션과 인터넷 사이에 위치하여 특정 규칙에 따라 요청을 애플리케이션 컨테이너로 전달하는 서버입니다.
리버스 프록시를 사용하면 여러 가지 장점이 있습니다. 로드 밸런싱, SSL 종료, 보안 향상 등이 그것입니다. 리버스 프록시를 통해 들어오는 트래픽을 여러 컨테이너에 고르게 분배하고, SSL 연결을 프록시 레벨에서 종료하여 애플리케이션 컨테이너의 부하를 줄이며, 추가적인 보안 계층을 추가할 수 있습니다. 다음은 Spring Boot 애플리케이션과 Nginx 리버스 프록시를 정의하는 Docker Compose 파일 예제입니다:
version: '3'
services:
web:
build: .
environment:
SPRING_PROFILES_ACTIVE: production
ports:
- "8080:8080"
proxy:
image: nginx
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- web
이 예제에서는 web
과 proxy
두 가지 서비스를 정의했습니다. web
서비스는 Spring Boot 애플리케이션을 빌드하고 8080 포트를 노출합니다. proxy
서비스는 공식 Nginx 이미지를 사용하고, nginx.conf
파일에 정의된 규칙에 따라 요청을 web
서비스로 전달합니다.
다음은 web
서비스로 요청을 전달하기 위한 규칙을 정의하는 nginx.conf
파일 예제입니다:
events {
}
http {
server {
listen 80;
location / {
proxy_pass http://web:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
이 예제에서는 proxy_pass
지시어를 사용하여 8080 포트의 web
서비스로 요청을 전달합니다. 또한 다양한 헤더를 설정하여 원본 클라이언트 IP와 프로토콜 정보를 유지합니다.
리버스 프록시를 사용하면 Docker화된 Spring Boot 애플리케이션의 확장성, 보안, 성능을 향상시킬 수 있습니다. 리버스 프록시를 통해 들어오는 트래픽을 여러 컨테이너에 고르게 분배하고, 추가 보안 계층을 추가하며, SSL 연결을 프록시 레벨에서 종료하여 애플리케이션 컨테이너의 부하를 줄일 수 있습니다.
6. 헬스 체크 사용하기
Spring Boot 애플리케이션을 Docker로 컨테이너화할 때는 헬스 체크를 통해 애플리케이션의 상태를 모니터링하고 정상적으로 실행되는지 확인하는 것이 중요합니다. 헬스 체크를 사용하면 애플리케이션이 비정상 상태일 때 이를 감지하고, 자동으로 복구하거나 상태에 따라 스케일링을 조정할 수 있습니다.
Docker 이미지에 헬스 체크를 추가하려면 Dockerfile에서 HEALTHCHECK
지시어를 사용할 수 있습니다. HEALTHCHECK
지시어는 Docker에게 애플리케이션의 상태를 확인하는 방법을 알려줍니다. 다음은 Spring Boot 애플리케이션에 헬스 체크를 추가하는 Dockerfile 예제입니다:
FROM openjdk:11
COPY target/my-application.jar app.jar
HEALTHCHECK --interval=5s \
--timeout=3s \
CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "/app.jar"]
이 예제에서는 HEALTHCHECK
지시어를 사용하여 Spring Boot 애플리케이션의 상태를 확인합니다. --interval
옵션은 헬스 체크를 실행할 주기를 지정하고, --timeout
옵션은 응답을 기다리는 시간을 설정합니다. CMD
지시어는 애플리케이션의 /actuator/health
엔드포인트를 확인하는 curl
명령을 실행합니다.
컨테이너를 실행한 후 docker ps
명령을 사용하여 컨테이너의 상태를 확인할 수 있습니다:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e8e1a6440e5e my-application:1.0 "java -jar /app.jar" 5 seconds ago Up 4 seconds 0.0.0.0:8080->8080/tcp my-application
$ docker inspect --format='{{json .State.Health}}' my-application
{"Status":"healthy","FailingStreak":0,"Log":[{"Start":"2023-03-25T09:21:08.272130387Z","End":"2023-03-25T09:21:08.310105965Z","ExitCode":0,"Output":"\n"}]}
이 예제에서 docker ps
명령은 컨테이너가 8080 포트에서 실행 중임을 보여줍니다. docker inspect
명령은 컨테이너의 상태를 표시하며, 현재는 healthy
상태입니다. 헬스 체크가 실패하면 컨테이너는 unhealthy
로 표시되며, Docker Compose나 Kubernetes와 같은 도구를 사용하여 자동으로 복구하거나 스케일링할 수 있습니다.
이처럼 헬스 체크를 사용하면 Docker화된 Spring Boot 애플리케이션의 신뢰성과 가용성을 크게 향상시킬 수 있습니다. 헬스 체크를 통해 애플리케이션의 문제를 자동으로 감지하고 복구하여 사용자가 항상 애플리케이션을 이용할 수 있게 할 수 있습니다.
7. Docker 캐싱 사용하기
Spring Boot 애플리케이션을 Docker로 컨테이너화할 때는 Docker 캐싱을 사용하여 빌드 프로세스를 가속화하고 새로운 Docker 이미지를 빌드하는 시간을 줄이는 것이 중요합니다. Docker 캐싱을 통해 이전에 빌드된 Docker 이미지의 레이어를 재사용함으로써 매번 이미지를 빌드할 때마다 해당 레이어를 다시 빌드할 필요가 없습니다. 다음은 Docker 캐싱을 사용하여 빌드 프로세스를 가속화하는 Dockerfile 예제입니다:
FROM openjdk:11 as builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src/ ./src/
RUN mvn package -DskipTests
FROM openjdk:11
COPY --from=builder /app/target/my-application.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
이 예제에서는 멀티스테이지 빌드를 사용하여 먼저 별도의 레이어에서 Spring Boot 애플리케이션을 빌드한 다음, 빌드된 jar 파일을 최종 이미지에 복사합니다. 빌드 프로세스를 별도의 레이어로 분리함으로써 Docker 캐싱을 활용하여 매번 이미지를 빌드할 때마다 종속성을 다시 빌드하는 것을 피할 수 있습니다.
빌드 프로세스의 첫 번째 단계에서는 openjdk:11
베이스 이미지를 사용하고 pom.xml
파일을 컨테이너에 복사합니다. 그런 다음 mvn dependency:go-offline
명령을 실행하여 애플리케이션에 필요한 모든 종속성을 다운로드합니다. 이 명령은 필요한 모든 종속성을 로컬에 다운로드하여 이후 빌드를 가속화합니다.
두 번째 단계에서는 openjdk:11
베이스 이미지를 사용하고 소스 코드를 컨테이너에 복사합니다. 그런 다음 mvn package
명령을 실행하여 애플리케이션 jar 파일을 빌드합니다. 이전 단계에서 종속성을 이미 다운로드했기 때문에 Docker는 캐시된 레이어를 사용하여 종속성 다운로드 단계를 건너뜁니다.
마지막으로, COPY --from=builder
지시어는 빌더 단계에서 빌드된 jar 파일을 최종 이미지에 복사하고, ENTRYPOINT
지시어는 컨테이너가 시작될 때 실행할 명령을 지정합니다.
이처럼 Docker 캐싱을 사용하면 새로운 Docker 이미지를 빌드하는 시간을 줄이고 배포 프로세스를 가속화할 수 있습니다. Docker 캐싱을 활용하면 불필요한 재빌드를 피하고 Docker 이미지를 최대한 빠르고 효율적으로 빌드할 수 있습니다.
8. .dockerignore 파일 사용하기
Spring Boot 애플리케이션을 Docker로 컨테이너화할 때, .dockerignore
파일을 사용하여 Docker 빌드 컨텍스트에서 불필요한 파일과 디렉터리를 제외하는 것이 중요합니다. 빌드 컨텍스트는 Docker가 이미지를 빌드할 때 사용하는 파일과 디렉터리의 집합입니다. .dockerignore
파일을 사용하면 Docker 이미지에 필요하지 않은 파일과 디렉터리를 제외하여 빌드 컨텍스트의 크기를 줄이고 빌드 성능을 향상시킬 수 있습니다. 다음은 Spring Boot 애플리케이션에 대한 .dockerignore
파일 예제입니다:
# 루트 디렉터리의 모든 파일을 제외합니다
*
# src 디렉터리를 포함합니다
!src/
# pom.xml 파일을 포함합니다
!pom.xml
# target 디렉터리와 그 내용을 제외합니다
target/
이 예제에서는 .dockerignore
파일을 사용하여 루트 디렉터리의 모든 파일을 제외하고(*
), 빌드에 필요한 src/
디렉터리와 pom.xml
파일을 포함합니다. 또한, 빌드 아티팩트가 포함된 target/
디렉터리를 제외합니다.
.dockerignore
파일을 사용하면 빌드 컨텍스트의 크기를 줄이고 빌드 성능을 향상시킬 수 있습니다. Docker는 빌드 컨텍스트에 포함된 파일과 디렉터리만 복사하고, .dockerignore
파일에 제외된 파일과 디렉터리는 무시합니다.
또한, .dockerignore
파일을 사용하면 Docker 이미지의 보안도 향상시킬 수 있습니다. 불필요한 파일과 디렉터리를 제외함으로써 Docker 이미지의 공격 표면을 줄이고, 민감한 정보나 자격 증명이 노출될 위험을 최소화할 수 있습니다. 예를 들어, 빌드 디렉터리에 구성 파일이나 자격 증명이 저장된 경우 .dockerignore
파일에서 제외하여 Docker 이미지에 포함되지 않도록 할 수 있습니다.
.dockerignore
파일은 Git 리포지토리에서 파일과 디렉터리를 제외하는 데 사용하는 .gitignore
파일과 유사한 문법을 따릅니다. .gitignore
파일에 익숙하다면 .dockerignore
파일도 쉽게 사용할 수 있을 것입니다.
요약하면,
.dockerignore
파일을 사용하는 것은 Spring Boot 애플리케이션을 Docker로 컨테이너화할 때 좋은 관행입니다. 이를 통해 빌드 컨텍스트의 크기를 줄이고 빌드 성능을 향상시키며 Docker 이미지의 보안을 강화할 수 있습니다.
9. 라벨 사용하기
Spring Boot 애플리케이션을 Docker로 컨테이너화할 때는 라벨을 사용하여 Docker 이미지에 메타데이터를 추가하는 것이 중요합니다. 라벨은 Docker 이미지에 키-값 쌍 형태로 추가할 수 있으며, 이미지의 버전, 유지 관리자, 빌드 날짜 등의 추가 정보를 제공합니다. 다음은 Spring Boot 애플리케이션에 메타데이터를 추가하는 Dockerfile 예제입니다:
FROM openjdk:11
LABEL maintainer="John Doe <john.doe@example.com>"
LABEL version="1.0"
LABEL description="My Spring Boot application"
COPY target/my-application.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
이 예제에서는 LABEL
지시어를 사용하여 Docker 이미지에 메타데이터를 추가합니다. 유지 관리자, 버전, 설명에 대한 라벨을 추가하여 Docker 이미지에 대한 정보를 제공합니다. 이러한 라벨은 Docker 이미지의 내용을 이해하고 빌드 방법을 알 수 있도록 도와줍니다.
Docker 이미지의 라벨은 docker inspect
명령을 사용하여 확인할 수 있습니다:
$ docker inspect my-application
[
{
"Id": "sha256:...",
"RepoTags": [
"my-application:latest"
],
"Labels": {
"maintainer": "John Doe <john.doe@example.com>",
"version": "1.0",
"description": "My Spring Boot application"
},
...
}
]
이 예제에서 docker inspect
명령은 my-application
Docker 이미지의 라벨을 보여줍니다. 라벨은 이미지에 대한 추가 정보를 제공하며, 사용자가 이미지의 내용을 이해하고 사용하는 데 도움이 됩니다.
이처럼 라벨을 사용하면 Docker 이미지의 사용성과 유지보수성을 향상시킬 수 있습니다. 메타데이터를 Docker 이미지에 추가하면 사용자가 이미지의 내용을 이해하고 빌드 방법을 알 수 있으며, 디버깅, 문제 해결 및 유지 관리에 유용한 정보를 제공할 수 있습니다.
10. 컨테이너 오케스트레이션 사용하기
Spring Boot 애플리케이션을 Docker로 컨테이너화할 때는 프로덕션 환경에서 애플리케이션을 관리하고 확장하기 위해 컨테이너 오케스트레이션 도구를 사용하는 것이 중요합니다. 컨테이너 오케스트레이션 도구는 Docker 컨테이너의 배포, 확장, 관리를 자동화하여 분산 환경에서 다수의 컨테이너를 효율적으로 관리할 수 있게 해줍니다.
Kubernetes, Docker Swarm, Apache Mesos와 같은 도구는 Docker 컨테이너 오케스트레이션에 널리 사용됩니다. 이들 도구는 로드 밸런싱, 자동 스케일링, 서비스 검색, 롤링 업데이트 등의 기능을 제공하여 애플리케이션이 항상 가용하고 사용자에게 응답할 수 있도록 돕습니다. 다음은 Spring Boot 애플리케이션을 위한 Kubernetes 배포 파일 예제입니다:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-application
labels:
app: my-application
spec:
replicas: 3
selector:
matchLabels:
app: my-application
template:
metadata:
labels:
app: my-application
spec:
containers:
- name: my-application
image: my-registry/my-application:1.0
ports:
- containerPort: 8080
이 예제에서는 Kubernetes 배포 파일을 사용하여 Spring Boot 애플리케이션을 배포합니다. 배포 파일은 애플리케이션의 복제본을 3개 실행하고, 선택자를 사용하여 배포에 포함될 포드를 식별합니다. 또한 애플리케이션을 실행할 컨테이너 이미지와 애플리케이션이 수신할 포트를 지정합니다.
이처럼 컨테이너 오케스트레이션 도구를 사용하면 Docker화된 Spring Boot 애플리케이션의 확장성, 신뢰성, 가용성을 크게 향상시킬 수 있습니다. 이러한 도구를 통해 분산 환경에서 애플리케이션을 쉽게 관리하고 확장할 수 있어 애플리케이션이 항상 가용하고 사용자에게 빠르게 응답할 수 있도록 보장할 수 있습니다.
마무리
Spring Boot 애플리케이션을 Docker로 컨테이너화하는 과정은 복잡할 수 있지만, 위에서 설명한 모범 사례를 따르면 애플리케이션이 Docker 컨테이너에서 원활하고 효율적으로 실행될 수 있습니다. 이러한 모범 사례를 실천함으로써 Docker의 장점을 최대한 활용하고 애플리케이션을 다양한 플랫폼에 쉽게 배포할 수 있습니다.
끝까지 읽어주셔서 정말 감사합니다. 궁금하신 점은 댓글로 남겨주세요 ~! 🙂