Custom JRE로 경령화된 Dockering I
업데이트:
개요
- Java 애플리케이션은 JVM(Java Virtual Machine)이 함께 컨테이너에 배포되어야 하기 때문에 비교적 타 언어로 배포되는 컨테이너보다 용량이 매우 크므로, 이 부분을 해소하기 위한 경량화된 Dockerizng1을 Amazon Crretto JDK2를 이용하여 설명하고자한다.
- 여기서는 Custom JRE를 생성하는 Docker의 Multi-Stage builds3를 활용하여 실제 빌드하는 JAVA 애플리케이션을 경량화한다.
Stage 1
- Stage 1에서는 애플리케이션 의존 관계를 분석하여 경량화된 Custom JRE를 만드는 부분에 중점을 둔다.
- 부가적으로 빌드된 jar 파일을 필요한 파일들만 사용하기 위하여 압축 해제한다.
# 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 mkdir -p /app && (cd /app; jar -xf /app.jar) \
&& DEPENDENCY=$(jdeps --ignore-missing-deps --print-module-deps --recursive --multi-release 21 --class-path="/app/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
Image
- Custom JRE는 JDK에서 필요한 의존성만 추출해야 하므로 기본 이미지는 JDK 이미지로 사용하고, jrebuilder로 명명한다.
Copy application
- Gradle을 이용하여 빌드된 jar파일은 /build/libs/ 경로 아래 생성되며, 해당 jar 파일을 사용하기 위해 컨테이너 내부로 복사한다.
Install Binutils4
- 컨테이너 내부에서 jlink 명령어를 사용하기 위해서 objcopy가 필요한데, 이는 binutils를 설치하여 사용할 수 있다.
- 만일 해당 부분을 설치하지 않는다면 아래의 오류를 마주할 수 있다.
Error: java.io.IOException: Cannot run program "objcopy": error=2, No such file or directory
Generate custom JRE
- 이제 가장 중요한 Custom JRE를 만드는 부분이다.
- “/app” 폴더를 만든 후 해당 경로에 애플리케이션 jar 파일을 압축 해제해준다.
- 빌드된 jar 파일 구성은 아래와 같다.
./hello-docker ├─ /BOOT-INF │ ├─ /classes ## Folder where the class file where the │ │ ## JAVA file is compiled is stored. │ └─ /lib ## Folder where the dependency injected jar │ ## library is stored. ├─ /META-INF ## Folder where the meta data and setting data. └─ /org
Jdeps5
- Jdeps는 JAVA Class의 의존성 분석을 위한 도구로, 모듈화가 적용된 JAVA 9 버전 이상부터 사용이 가능하다.
Option | Description |
---|---|
–ignore-missing-deps | 외존성이 존재하지 않는 모듈은 제외. |
–print-module-deps | Jlink에서 요구하는 포멧에 맞는 쉼표로 구분된 모듈 의존성 목록을 출력. |
–recursive | 모든 런타임 종속성을 재귀적으로 탐색. |
–multi-release ${VERSION} | 의존성을 분석할 JAVA 버전을 ${VERSION}에 명시. 단, 모듈화가 적용된 9 버전 이상만 적용. |
–class-path=”${PATH}” | 의존성을 분석할 class 파일들을 찾을 위치를 ${PATH}에 지정. |
Jlink6
- Jlink는 Jdeps를 이용하여 분석된 의존성을 활용하여 최적화된 Custom JRE를 구성할 수 있는 도구이다.
Option | Description |
---|---|
–verbose | 진행 중 상세 내역을 출력. |
–add-modules ${DEPENDENCY} | jdeps를 이용하여 분석한 쉼표로 구분된 모듈 의존성 목록 |
–strip-debug | 디버그 정보를 제거. |
–no-man-pages | man page(특정 명령이나 자원들의 메뉴얼을 출력하는 영역) 미사용. |
–no-header-files | header files(로직이 저장된 파일) 미사용. |
–compress=2 | 리소스 압축 여부. 0 : 미사용, 1 : 상수 문자열 공유, 2 : 압축을 의미하며, 보편적으로 2를 사용. |
–output=${PATH} | 리소스를 저장할 위치를 ${PATH}에 지정. |
Stage 2
- Stage 2에서는 Stage 1에서 분석된 의존성을 통해 완성된 Custom JRE와 압축 해제한 jar에서 필요한 파일로 이미지를 구성하여 경량화된 JAVA 애플리케이션 컨테이너를 구성한다.
# 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}/BOOT-INF/lib ${DEPENDENCY}/lib
COPY --from=jrebuilder ${DEPENDENCY}/META-INF ${DEPENDENCY}/META-INF
COPY --from=jrebuilder ${DEPENDENCY}/BOOT-INF/classes ${DEPENDENCY}
# Move work directory
WORKDIR ${DEPENDENCY}
# Run application
ENTRYPOINT [ "java", "-cp", "${DEPENDENCY}:${DEPENDENCY}/lib/*", "gracefulsoul.HelloDockerApplication" ]
Image
- Stage 1에서 Custom JRE를 만들었으므로, 경량화된 Alpine Linux7 이미지를 아래의 환경 설정을 이용하여 사용한다.
- JAVA_HOME 환경 변수를 추가하기 위하여 JAVA_HOME은 “/jre”로 준 후 PATH에 “JAVA_HOME/bin”을 추가한다.
- “/app” 폴더를 전역 변수로 사용하기 위한 DEPENDENCY를 정의한다.
Copy custom JRE
- Stage 1에서 생성한 Custom JRE가 저장된 “/customjre” 폴더를 “/jre”로 복사한다.
Copy extract files in jar
- jar 파일에서 JAVA 애플리케이션 구동을 위해 필요한 아래의 폴더들을 복사한다.
- 의존성 주입된 Library가 저장된 “/app/BOOT-INF/lib” 폴더를 “/app/lib” 폴더로 복사한다.
- 메타 데이터와 설정 정보를 저장된 “/app/META-INF” 폴더를 “/app/META-INF” 폴더로 복사한다.
- 컴파일된 class 파일이 저장된 “/app/BOOT-INF/classes” 폴더를 “/app” 폴더로 복사한다.
Move work directory
- JAVA 애플리케이션 실행 위치인 “/app” 위치를 컨테이너 기본 위치로 설정한다.
Run application
- ENTRYPOINT인 컨테이너가 실행될 때 기본으로 동작하는 명령어는 아래의 조합으로 완성한다.
- “java” 명령어는 JAVA 애플리케이션 실행을 위한 명령어이다.
- “-cp /app:/app/lib/“은 classpath를 지정하는 명령어로, 컴파일된 class 파일 루트 위치인 “/app”과 의존성으로 추가된 Library들을 포함하는 “/app/lib/“을 이어준다.
- 실행하고자 하는 Main Class 이름을 “package.className” 형태인 “gracefulsoul.HelloDockerApplication”로 정의한다.
정리
- 위에서 하나씩 살펴본 JAVA 애플리케이션 경량화 Docker Conatiner는 MSA 구성에 있어서 아주 기본적인 서비스 빌드 방법의 하나를 살펴보았다.
- 애플리케이션을 돌리기 위한 컨테이너는 동작에 필요한 최소한의 리소스를 이용한 컨테이너 경량화는 배포 크기의 감소와 성능 향상, 비용 감소 등의 이점이 있으므로 선택이 아닌 필수이다.
Reference
※ Sample Code는 여기에서 확인 가능합니다.
댓글남기기