JUINTINATION
스프링부트 + MySQL 프로젝트 도커라이징하기 본문
지난번에 책을 보면서 따라서 만든 간단한 게시판 프로젝트인 Board-Clone 프로젝트를 도커라이징해봤다. Express.js를 사용한 스퍼트 프로젝트에도 적용해본 적이 있는데 그 내용을 내가 안 적어둬서 처음에 조금 헤맸다..
여러 블로그와 학교 선배의 깃허브 코드를 참고해가면서 Dockerfile을 작성해나가는데 Express.js를 도커라이징할 때와 큰 차이는 없었지만 약간씩 달라서 좀 당황하긴 했다. 그리고 jdk 17 버전으로 한 사람이 너무 적어서 비교군을 찾기도 쉽지 않았다. 그래도 아무튼 성공하긴 했는데 그 내용을 또 까먹기 전에 적어보자.
Dockerfile
FROM gradle:7.3.3-jdk17 AS builder
WORKDIR /build
COPY . /build
RUN ./gradlew clean build -x test
LABEL maintainer="juintination"
LABEL email="juintin@kakao.com"
FROM khipu/openjdk17-alpine
WORKDIR /app
COPY --from=builder /build/build/libs/*-SNAPSHOT.jar ./app.jar
EXPOSE 8080
ENTRYPOINT [ "java" ]
CMD [ "-jar", "app.jar" ]
이번에 작성한 Dockerfile은 멀티스테이지 빌드를 사용하여 빌드 단계(builder stage)와 실행 단계(runtime stage)를 분리한다. 이렇게 하면 빌드에 필요한 도구(Gradle 등)를 실행 이미지에 포함시키지 않고, 더 작고 경량의 이미지를 생성할 수 있다. 아래는 각 단계별 설명이다.
빌드 단계(builder stage)
먼저 build.gradle을 빌드하려면 베이스 이미지로 jdk와 gradle을 포함한 이미지를 사용해야 한다. 여기서 나는 스프링 부트 3.2.3 버전을 jdk 17로 사용하여 프로젝트를 작성하였기 때문에 다음과 같이 베이스 이미지를 명시한다.
FROM gradle:7.3.3-jdk17 AS builder
여기서 AS builder 부분에서 멀티스테이지 빌드에서 빌드 단계를 분리하고 이름을 builder로 지정하여 나중에 이 이름을 사용하여 이 단계의 결과물을 참조할 수 있다.
WORKDIR /build
COPY . /build
이후 WORKDIR 명령어를 이용하여 작업 디렉터리를 /build
으로 설정한 다음 COPY 명령어를 사용하여 프로젝트의 모든 파일을 작업 디렉터리에 복사한다.
RUN ./gradlew clean build -x test
RUN 명령어를 이용하여 build.gradle를 빌드한다. 이 때 clean build 명령어를 통해 이전 빌드 결과물을 삭제하고 새로 빌드하며, -x test 옵션을 통해 테스트를 생략하여 빌드한다.
- 만약 이전 gradlew에 실행 권한이 없다면 로컬 환경에서
$ chmod +x ./gradlew
명령어를 실행하거나, 위에서 설명한 RUN 명령어 위에RUN chmod +x ./gradlew
를 추가하면 된다.
LABEL maintainer="juintination"
LABEL email="juintin@kakao.com"
그리고 각 단계 사이에 LABEL 명령어를 이용하여 작성자가 나임을 명시하였다.
실행 단계 (runtime stage)
FROM khipu/openjdk17-alpine
OpenJDK 17을 포함한 경량의 Alpine Linux 기반 이미지인 khipu/openjdk17-alpine
이미지를 사용하여 실행 환경을 설정한다.
WORKDIR /app
WORKDIR 명령어를 이용하여 작업 디렉터리를 /app
으로 설정한다.
COPY --from=builder /build/build/libs/*-SNAPSHOT.jar ./app.jar
COPY 명령어로 builder 단계에서 빌드된 JAR 파일을 현재 단계의 /app
디렉토리로 복사하고, 파일명을 app.jar로 변경한다. 여기서 --from=builder는 이전 builder 단계를 참조한다는 뜻이다.
EXPOSE 8080
EXPOSE 명령어를 사용하여 Docker 컨테이너가 8080번 포트를 외부에 노출하도록 명시한다.
ENTRYPOINT [ "java" ]
CMD [ "-jar", "app.jar" ]
마지막으로 ENTRYPOINT와 명령어로 컨테이너가 실행될 때 java 명령어를 기본 엔트리포인트로 설정한 뒤, CMD 명령어로 java 명령어에 대한 인수로 -jar app.jar를 제공하여 최종적으로 ENTRYPOINT와 함께 사용하여 $ java -jar app.jar
을 실행하도록 한다.
docker-compose.yaml
version: "3.7"
services:
db:
image: mysql:8
ports:
- "3306:3306"
restart: "unless-stopped"
environment:
- MYSQL_ROOT_PASSWORD=admin
- MYSQL_ROOT_HOST=%
- MYSQL_DATABASE=bootex
networks:
- system
api:
image: board-image
ports:
- "8080:8080"
restart: "unless-stopped"
depends_on:
- db
networks:
- system
networks:
system:
driver: bridge
docker-compose 파일은 Express.js를 사용한 스퍼트 프로젝트에 적용한 파일 내용과 그리 다르지 않다.
application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://root:admin@db:3306/bootex
spring.datasource.username=root
spring.datasource.password=admin
기존의 spring.datasource.url=jdbc:mysql://localhost:3306/bootex
부분을 spring.datasource.url=jdbc:mysql://root:admin@db:3306/bootex
로 변경했다. 로컬 호스트에 있는 mysql을 이용하는 것이 아닌 docker-compose 파일에서 정의된 "db"라는 이름의 MySQL 서비스를 이용한다는 것이다.
Docker로 애플리케이션 실행하기
먼저 프로젝트를 다음의 명령어로 클론한다.
$ git clone https://github.com/juintination/Board-Clone.git
이후 다음 명령어를 통해 프로젝트가 있는 board 디렉터리로 이동한다.
$ cd Board-Clone/board
그리고 다음 명령어를 통해 이 프로젝트를 도커 이미지 파일로 만든다. 이 때 docker-compose 파일에 board-image라는 이름의 이미지 파일을 사용할 것이라고 명시해뒀기 때문에 이미지 파일의 이름을 바꾸고 싶으면 docker-compose 파일도 수정해줘야 한다.
$ docker build -t board-image .
마지막으로 다음 명령어를 통해 docker compose 파일에 맞게 애플리케이션을 실행한다.
$ docker compose up -d
그리고 Docker Desktop을 들어가서 Containers에서 확인해보면 다음과 같이 정상적으로 실행되는 것을 확인할 수 있다.
이 상태에서 localhost:8080/board/list에 들어가보면 다음과 같이 아무런 게시글도 없는 게시판이 보이게 된다.
마지막으로 이 컨테이너를 종료하려면 다음 명령어를 입력하면 된다.
$ docker compose down
결론
처음에 Dockerfile을 다음과 같이 작성했었다.
FROM openjdk:17-jdk
LABEL maintainer="juintination"
LABEL email="juintin@kakao.com"
ARG JAR_FILE=build/libs/board-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT [ "java" ]
CMD [ "-jar", "app.jar" ]
이렇게 되면 이 프로젝트를 클론한 사용자가 애플리케이션을 실행하기 위해 jdk 17 버전을 사용하고 있는 상태에서 직접 $ ./gradlew clean build -x test
명령어를 실행한 후에 나머지 작업을 진행해야 한다는 문제가 있다.
계속 도커 파일을 수정하면서 오류는 발생하는데 에러 메시지가 나오지를 않아서 뭐가 문제인지 생각해봤는데 결국 원인은 정말 간단했다.. 정확한 이유는 뭔지 모르겠는데 FROM 명령어 부분에서 jdk를 불러올 때 문제가 있었던 것 같다. openjdk:17-jdk에서 khipu/openjdk17-alpine로 수정하니까 정상적으로 실행됐는데 이는 여러 번의 테스트를 거치는 과정에서 jar 파일은 컨테이너 안에서 생성되는 것이 아니라 로컬 환경에 이미 생성되어 있었기 때문에 실행이 정상적으로 된다는 사실을 알게 되어서 더 공부하게 되었다.
추가적으로 도커 이미지 파일을 만들고 도커 컴포즈 파일로 실행하는 과정도 줄여서 .sh 파일로 만들까 했는데 생각해보니 그렇게까지 할 필요는 없을 것 같아서 일단은 그만뒀다.
그리고 아직 이 프로젝트는 스프링 시큐리티 관련 공부가 부족하여 회원가입이나 로그인 등과 같은 기능이 구현이 되지 않아 실제로 사용할 수는 없다. 스프링 부트 버전이 올라가면서 deprecated되고 사라진 기능이 너무 많아서 어지러운데 더 열심히 해야겠다..
'StudyNote' 카테고리의 다른 글
비지터(Visitor) 패턴 (1) | 2024.06.26 |
---|---|
빌더(Builder) 패턴 (2) | 2024.04.30 |
Mac OS 자바 버전 여러 개 관리하기 (0) | 2024.02.14 |
Express.js를 사용한 스퍼트 프로젝트 (2) | 2024.02.08 |
도커(Docker) 가볍게 입문해보기 - 4 (0) | 2024.02.03 |