Custom JRE로 경령화된 Dockering II
업데이트:
개요
- 이전 포스트인 Custom JRE로 경령화된 Dockering I에서 이야기한 내용과 절차는 동일하지만, 공식적으로 제공하는 Layered Jars1를 사용하는 방법을 설명한다.
- 이 포스트에서는 기존 방식과 차별화된 방식에 대한 설명을 진행하므로, 기본 절차를 이해하고 싶다면 이전 포스트를 읽은 다음에 보는 것을 추천한다.
Layered Jars
- Layered Jars는 기존의 Fat jars의 형태인 모든 의존성과 리소스 파일들을 단일 jar 파일로 패키징 하는 방법을 아래와 같이 필요한 레이어로 패키징을 활용하는 기능이다.
./app ├─ /dependencies ## for any dependency whose version does not contain SNAPSHOT. ├─ /spring-boot-loader ## for the jar loader classes. ├─ /snapshot-dependencies ## for any dependency whose version contains SNAPSHOT. └─ /application ## for application classes and resources.
Setting
- Layered 기능을 사용하기 위해서는 아래와 같이 빌드 툴 별 설정이 필요하지만, Spring Boot 2.3.0 부터 빌드 툴의 설정 없이 사용이 가능하다.
Maven(pom.xml)
```xml
### Gradle(build.gradle)
bootJar { layered() }
## Structure
- 기본적으로 사용하는 구조는 "/BOOT-INF/layers.idx"에 아래와 같이 정의된다.
```sh
- "dependencies":
- "BOOT-INF/lib/"
- "spring-boot-loader":
- "org/"
- "snapshot-dependencies":
- "application":
- "BOOT-INF/classes/"
- "BOOT-INF/classpath.idx"
- "BOOT-INF/layers.idx"
- "META-INF/"
Customizing
- 필요에 따라 각 빌드 툴 별 설정 방식으로 layer를 정의하여 사용할 수 있다.
Maven(layer.xml)
```xml
### Gradle(build.gradle)
bootJar { layered { application { intoLayer(“spring-boot-loader”) { include “org/springframework/boot/loader/*” } intoLayer(“application”) } dependencies { intoLayer(“snapshot-dependencies”) { include “::SNAPSHOT” } intoLayer(“company-dependencies”) { include “com:gracefulsoul:*” } intoLayer(“dependencies”) } layerOrder = [“spring-boot-loader”, “application”, “company-dependencies”, “snapshot-dependencies”, “dependencies”] } }
# Dockerfile
- 기존 방식과 Layered Jars와 차이를 분석한다.
## Stage 1
```sh
# Stage 1. Create custom JRE
FROM amazoncorretto:21-alpine AS jrebuilder
# Add the application jar to the container
COPY ./build/libs/hello-docker-*-SNAPSHOT.jar app.jar
# Install binutils
RUN apk add --no-cache binutils
# Extract jar file and generate custom JRE using dependency
RUN java -Djarmode=tools -jar app.jar extract --layers --launcher \
&& DEPENDENCY=$(jdeps --ignore-missing-deps --print-module-deps --recursive --multi-release 21 --class-path="/app/dependencies/BOOT-INF/lib/*" /app.jar) \
&& ${JAVA_HOME}/bin/jlink \
--verbose \
--add-modules ${DEPENDENCY} \
--strip-debug \
--no-man-pages \
--no-header-files \
--compress=2 \
--output customjre
- 이전에는 jar 명령어를 이용하여 압축 해제한 폴더를 사용했다면, 이번에는 java 명령어 내 “jarmode” 중 “tools”를 사용하여 압축을 해제한다. | Option | Description | |:——–|:——–| | –launcher | 스프링 부트 런처 추출. | | –layers | 레이어를 활용한 추출. |
Stage 2
# Stage 2. Make container for application
FROM alpine:3.20
ENV JAVA_HOME=/jre
ENV PATH="${JAVA_HOME}/bin:${PATH}"
ARG DEPENDENCY=/app
# Add Maintainer Info
LABEL maintainer="GracefulSoul on <gracefulsoul@github.com>"
# Copy custom JRE
COPY --from=jrebuilder /customjre ${JAVA_HOME}
# Copy extract files in jar
COPY --from=jrebuilder ${DEPENDENCY}/dependencies/ ${DEPENDENCY}/
COPY --from=jrebuilder ${DEPENDENCY}/snapshot-dependencies/ ${DEPENDENCY}/
COPY --from=jrebuilder ${DEPENDENCY}/spring-boot-loader/ ${DEPENDENCY}/
COPY --from=jrebuilder ${DEPENDENCY}/application/ ${DEPENDENCY}/
# Move work directory
WORKDIR ${DEPENDENCY}
# Run application
ENTRYPOINT [ "java", "org.springframework.boot.loader.launch.JarLauncher" ]
- 기존 BOOT-INF, META-INF에서 필요한 폴더를 나누어 썼듯이 Layer로 분리된 각 폴더에서 필요한 폴더를 사용한다.
- 애플리케이션 실행은 기존과 다르게 Main Class를 실행하는 것이 아닌, Layered Jars를 같이 추출된 Launcher를 사용하여 Main Class를 실행한다.
정리
- 코드를 보면 이전 화와 크게 다를 바 없는 내용이지만, 복잡한 구성의 애플리케이션에서는 단순 빌드된 Fat Jars를 원하는 형태로 배치하기 위해 소모되는 자원과 시간은 개발자에게 불필요하다.
- Spring Boot는 초기 사상과 같이 개발자는 비즈니스 로직에 더 집중할 수 있는 다양한 환경을 구성해주며, 이러한 기능들은 실무에 다양하게 활용할 수 있다.
여담
- Layered Jars는 Spring Boot 3.3부터 공식적으로 지원하는 Class Data Sharing(CDS) [^Spring-Boot-3.3-Release-Notes]를 사용하기 위해 공유할 자원을 분리하여 애플리케이션을 실행 할 때에도 사용된다.
- jarmode의 경우도, 해당 버전부터 layertools가 deprecated 되었으로 아래 내용을 참고하였으면한다.
- 3.3 이상 : java -Djarmode=tools -jar app.jar extract –layers –launcher
- 3.3 미만 : java -Djarmode=layertools -jar app.jar
이전
Reference
※ Sample Code는 여기에서 확인 가능합니다.
댓글남기기