해당 프로젝트는 Redis 실습 프로젝트를 재구성했다.
프로젝트 패키지 구조도 조금 변경했으므로 깃허브에서 확인하길 바란다.
📄 Dockerfile 작성
Dockerfile을 작성하기 전에 Redis 실전 프로젝트까지 진행했던 부분을 수정해줘야 한다. application.yml에 코드를 수정 및 추가해 준다.
1
2
3
4
5
| spring:
data:
redis:
host: redis
port: 6379
|
Redis 컨테이너의 이름을 redis라고 지을건데 이 때 Redis를 포함한 서비스들은 내부 네트워크로 연결된다.
따라서 application.yml 파일에 Redis의 호스트를 redis로 설정해야 한다.
Docker Compose를 사용하면 여러 컨테이너를 하나의 프로젝트처럼 관리할 수 있으며, 기본적으로 이들 컨테이너는 동일한 네트워크에 연결된다. 즉, 컨테이너 내에서 서로의 서비스 이름으로 접근할 수 있다. 따라서, Redis 컨테이너의 이름을 redis로 설정했다면, 다른 컨테이너에서는 redis라는 호스트명으로 Redis에 접속할 수 있는 것이다.
또한 spring.datasource.url을 jdbc:h2:mem:testdb로 변경해준다.
우리는 H2 데이터베이스를 도커 컨테이너로 구동할 예정이기 때문에 개발 환경에서는 메모리 내 데이터베이스를 사용하겠다.
이제 Dockerfile을 작성해보자.
프로젝트의 root 경로에 Dockerfile을 만들어준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # BUILD stage
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
COPY gradle gradle
COPY build.gradle settings.gradle gradlew ./
RUN ./gradlew --no-daemon dependencies
COPY src src
RUN ./gradlew --no-daemon clean build
# RUN stage
FROM eclipse-temurin:21-jre
COPY --from=builder /app/build/libs/*.jar /app/app.jar
ENTRYPOINT ["java"]
CMD ["-jar","/app/app.jar"]
|
이제 이 명령들을 하나씩 알아보자.
🏗️ BUILD stage
1
| FROM eclipse-temurin:21-jdk AS builder
|
- Eclipse Temurin의 JDK 21 버전을 기반 이미지로 사용한다.
AS builder로 이 단계에 이름을 지정하여 나중에 참조할 수 있게 한다.
- 컨테이너 내부에
/app 디렉토리를 생성하고 작업 디렉토리로 설정한다. 이후 명령어 들은 /app 디렉토리 안에서 실행된다.
- 현재 프로젝트의
gradle 폴더를 컨테이너의 /app/gradle로 복사한다.
gradle 폴더에는 Gradle Wrapper 관련 파일들이 포함되어 있다.
1
| COPY build.gradle settings.gradle gradlew ./
|
- 세 개의 파일(
build.gradle, settings.gradle, gradlew)을 컨테이너 내의 현재 디렉토리(./ -> /app)로 복사한다.
build.gradle: 프로젝트의 주요 빌드 설정 파일로, 의존성, 플러그인, 태스크 등을 정의한다.
settings.gradle: 멀티 모듈 프로젝트의 설정을 정의하는 파일이다. 단일 모듈 프로젝트에서도 존재할 수 있다.
gradlew: Gradle Wrapper 실행 스크립트로, 특정 버전의 Gradle을 다운로드하고 실행하는 데 사용된다.
- 빌드 환경의 일관성을 유지하기 위해 버전이 고정된 gradle 이미지를 사용하지 않고 프로젝트에 지정된 버전의 gradle을 복사한 것이다.
1
| RUN ./gradlew --no-daemon dependencies
|
- 의존성을 다운로드 한다.
- 자세한 설명은
RUN ./gradlew --no-daemon clean build에서 하겠다.
- 프로젝트의 소스 코드를 src 폴더를 컨테이너의 /app/src에 복사한다.
1
| RUN ./gradlew --no-daemon clean build
|
- Gradle 빌드를 실행한다.
--no-daemon 옵션은 빌드 후 Gradle 데몬을 종료해 리소스를 절약한다.
clean 이전 빌드 아티팩트를 지우고 build 새로운 빌드를 실행한다.
clean을 사용하는 이유는 이전 빌드에서 남은 파일들이 영향을 줄 수 있는 가능성을 배제하기 위해 clean을 먼저 실행해서 깨끗한 상태에서 빌드를 실행한다.
- 의존성 설치 명령과 빌드 명령을 분리해서 캐시 활용도를 높였다.
🚀 RUN stage
1
| FROM eclipse-temurin:21-jre
|
- 런타임 환경에서는 JDK가 아닌 JRE를 사용해 최종 이미지 크기를 줄인다.
1
| COPY --from=builder /app/build/libs/*.jar /app/app.jar
|
- 이전 빌드 스테이지(
builder)에서 생성된 .jar 파일을 가져와 런타임 스테이지의 /app 디렉토리에 복사한다.
- 최종적으로 실행될
.jar 파일이 /app/app.jar로 배치된다.
- 컨테이너가 실행될 때 기본적으로 Java 명령어를 실행하도록 설정한다.
- 이 명령어는 변경할 수 없는 명령어이다. (자세한 것은 ENTRYPOINT 설명 참고)
1
| CMD ["-jar", "/app/app.jar"]
|
java 명령어의 기본 인자로 .jar 파일을 지정한다.
ENTRYPOINT와 결합하여 컨테이너가 시작되면 자동으로 java -jar /app/app.jar 명령어가 실행된다.
해당 도커 파일은 멀티 스테이지 빌드다.
멀티 스테이지 빌드란, Dockerfile에서 여러 빌드 단계를 정의하고 각 단계의 결과를 최종 이미지로 가져오는 방식이다. 이 과정에서 불필요한 빌드 도구나 파일을 최종 이미지에 포함시키지 않아 이미지 크기를 최소화하고, 보안 및 성능을 개선할 수 있다.
해당 도커파일에서는
- Stage1 (build)에서는 jdk21과 Gradle을 포함한 무거운 빌드 환경에서 애플리케이션을 빌드한다.
- Stage2 (run)에서는 빌드된 애플리케이션만을 가져와, 가벼운 JRE 환경에서 실행한다.
🎼 Docker Compose 작성
프로젝트 root 경로에 docker-compose.yml 파일을 만들어준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| version: "3"
services:
db:
container_name: h2
image: oscarfonts/h2:latest
ports:
- 1521:1521
- 8081:81
environment:
H2_OPTIONS: -ifNotExists
volumes:
- ./h2/:/opt/h2-data
restart: always
redis:
container_name: redis
image: redis:alpine
ports:
- 6379:6379
volumes:
- ./data/:/data
app:
build:
context: .
dockerfile: Dockerfile
container_name: dockermaster
ports:
- 8080:8080
environment:
SPRING_DATASOURCE_URL: jdbc:h2:tcp://h2:1521/dockermaster
SPRING_DATASOURCE_USERNAME: sa
SPRING_DATASOURCE_PASSWORD:
SPRING_JPA_HIBERNATE_DDL_AUTO: update
depends_on:
- db
- redis
|
이제 이 명령들을 하나씩 알아보자.
- Docker Compose 파일 형식의 버전을 지정한다.
- 실행할 서비스들을 정의한다. 이 파일에는 세개의 서비스(db, redis, app)가 정의되어 있다.
1
2
3
4
5
6
7
8
9
10
11
| db:
container_name: h2
image: oscarfonts/h2:latest
ports:
- 1521:1521
- 8081:81
environment:
H2_OPTIONS: -ifNotExists
volumes:
- ./h2/:/opt/h2-data
restart: always
|
container_name: h2: 컨테이너 이름을 ‘h2’로 지정한다.
image: oscarfonts/h2:latest: Docker Hub의 oscarfonts/h2 이미지의 최신 버전을 사용한다.
ports: 호스트의 1521 포트를 컨테이너의 1521 포트로, 8081 포트를 81 포트로 매핑한다. H2는 기본적으로 1521 포트를 사용하고, 웹 콘솔은 81 포트를 사용한다.
environment: 환경 변수를 설정한다. ifNotExists 옵션은 데이터베이스가 존재하지 않을 때만 생성하도록 한다.
volumes: 호스트의 ./h2/ 디렉토리를 컨테이너의 /opt/h2-data에 마운트한다. 이 디렉토리는 H2 DB 파일을 저장하는 위치다.
restart: always: 컨테이너가 종료되면 항상 자동으로 재시작되도록 설정한다.
1
2
3
4
5
6
7
| redis:
container_name: redis
image: redis:alpine
ports:
- 6379:6379
volumes:
- ./data/:/data
|
container_name: redis: 컨테이너 이름을 ‘redis’로 지정한다.
image: redis:alpine: Docker Hub의 공식 Redis 이미지의 Alpine 버전을 사용합니다. Alpine 버전은 경량화된 Linux 배포판을 기반으로 하여 이미지 크기가 작다.
ports: 호스트의 6379 포트를 컨테이너의 6379 포트로 매핑한다. 6379는 Redis의 기본 포트이다.
volumes: 호스트의 ./data/ 디렉토리를 컨테이너의 /data 디렉토리에 마운트합니다. 이를 통해 Redis 데이터를 호스트 시스템에 영구적으로 저장할 수 있다.
🐳Docker로 실행하기
- 프로젝트 루트 디렉토리에서 다음 명령어를 실행한다.
1
| docker compose up --build
|
- 브라우저에서
http://localhost:8080/swagger-ui/index.html으로 접속해 API 문서를 확인한다.
Leave a comment