Spring Boot 2.3 버전부터 내장 애플리케이션을 Docker Image로 만드는 라이브러리가 포함되어 배포되었다. 내 기억에 초기에는 Jib를 사용했었던 것 같은데, 2.3.8 버전을 사용해보니 BuildPack을 사용하도록 변경되어있었다. BuildPack에 대한 내용은 아래 사이트를 참고하자.
백문이불여일견 기존에 개발하고 있던 프로젝트(2.2.6)를 한번 2.3.8로 버전을 올리고, 내장된 BuildPack 라이브러리를 통해 Docker Image를 만들어 보겠다.
[주의사항]
Spring 2.3 버전부턴 javax 패키지 하위에 있던 validation class들이 spring-boot-starter-validation이라는 스타터 라이브러리로 분리되어 배포된다. 2.3 버전 이하에선 spring-boot-starter-web 하위에 포함되어있었기에 2.2 버전을 사용하다가 2.3 버전으로 올릴 때 javax 패키지 하위에 있는 validation class를 사용한 코드가 있는 경우 에러가 난다.
gradle wrapper를 통해 빌드를 실행하고, plain 모드로 각 task가 어떻게 진행되는지 봤다. bootBuildImage task를 실행하면, buildPack에서 애플리케이션을 도커 이미지로 만들기 위해 필요한 buildpack 도커 이미지를 먼저 pulling 한다.
그 이후, 도커 이미지를 만들면서 실행된 각종 단계들이 로그로 찍히게 된다. 참고로 buildpack은 애플리케이션이 구동되기 위해 필요한 jvm 사이즈를 알아서 계산해서 넣어준다. 이는 컨테이너로 애플리케이션을 구동할 때 메모리를 최적으로 사용하도록 하기 위함인 것 같다.
다음은 직접 Dockerfile을 통해 도커 이미지로 만드는 방법이다. 가장 먼저 build.gradle에 다음과 같이 task를 추가하자.
// separate jar into app and library layers
task moveLib {
doLast {
def unpackDir = "$buildDir/unpack"
ant.move(file: "${unpackDir}/app/BOOT-INF/lib", toFile: "${unpackDir}/lib")
}
}
task unpackJar(type: Copy) {
def unpackDir = "$buildDir/unpack"
delete unpackDir
from zipTree(jar.getArchiveFile())
into "$unpackDir/app"
finalizedBy moveLib
}
build {
finalizedBy unpackJar
}
fatJar 파일을 app과 lib디렉토리로 분리한다. 다음은 Dockerfile을 다음과 같이 작성해보자.
# Build Stage
FROM gradle:6.8.0-jdk8 AS builder
ENV APP_HOME=/usr/app/
WORKDIR $APP_HOME
# Only copy dependency-related files
COPY build.gradle settings.gradle ./
# Copy Sourcefile
COPY src src
# build soruce file with dependnecies
RUN gradle clean build --console=plain -x test
# Runtime Stage
FROM openjdk:8-jre-alpine
ENV APP_HOME=/usr/app/
WORKDIR $APP_HOME
ARG buildDir=$APP_HOME/build/unpack
COPY --from=builder ${buildDir}/lib BOOT-INF/lib
COPY --from=builder ${buildDir}/app .
EXPOSE 8080
CMD ["java", "org.springframework.boot.loader.JarLauncher"]
그리고 다음 명령어를 통해 Dockerfile을 빌드하고 도커 이미지를 만들어보자. -f 옵션 뒤에 붙는 부분은 내 Dockerfile이 다른 디렉터리 하위에 있기 때문이다. Dockerfile의 위치를 설정하는 옵션으로 프로젝트 루트 경로에 Dockerfile이 바로 있다면 작성 안 해줘도 된다.
docker buid -t user-test-dockerfile -f docker/Dockerfile .
도커 이미지가 생성된 사이즈를 보니, 기존에 build.gradlew에서 bootJar Task에서 layered 구문으로 fatJar를 분리하고, Dockerifle에서 build stage를 통해 빌드한 이미지 사이즈보다 크게 나온것을 확인할 수 있었다(ㅡ,ㅡ;;).
Dockerfile은 각 명령어 라인들이 도커파일 이미지의 layer가 되고 용량을 잡아먹기 때문에 최소화하는 게 좋다.
docker history 명령어를 통해 buildpack으로 생성된 이미지의 layer를 확인해보니 꽤 많이 생겨있는 것을 확인할 수 있는데, 이 점은 단점일 수도 장점일 수 도있다. 왜냐하면 도커는 기본적으로 도커 이미지를 만들 때 각 layer에 대한 캐싱 기능을 지원한다. 따라서 상세하게 나뉜 layer는 빌드 타임을 많이 줄일 수 있다는 장점이 있으면서, 도커 이미지의 크기가 커질 수도 있다는 단점이 존재한다.
또한 buildPack의 경우, 도커 이미지를 만들때 필요한 커스텀 기능들이 많이 부족하다, EXPOSE 또는 파일을 옮기기 위한 COPY의 기능, RUN 등 옵션들을 지정할 수 없는 것 같다. build image를 변경하면 해결이 되는지는 모르겠지만,, buildpack을 이용한 dockerizing은 아직 시기상조일 것 같다.
'Programming > Spring' 카테고리의 다른 글
[Spring Cloud] Spring Cloud Gateway - 다운스트림 로그 확인 (1) | 2020.11.06 |
---|---|
[Spring Boot] Filter (2) -Response Body Modify (5) | 2020.08.04 |
[Spring Boot] Filter (1) - Request Body Modify (0) | 2020.07.15 |
[SpringBoot] Dockerizing a Spring Boot Application (0) | 2019.11.19 |
[Spring Boot] Java Servlet Filter for Logging Request Parameter (2) | 2019.11.12 |