티스토리 뷰
개요
- Java 애플리케이션은 JVM(Java Virtual Machine)이 함께 컨테이너에 배포되어야 하기 때문에 비교적 타 언어로 배포되는 컨테이너보다 용량이 매우 크므로, 이 부분을 해소하기 위한 경량화된 Dockerizng을 Amazon Crretto JDK를 이용하여 설명하고자한다.
- 여기서는 Custom JRE를 생성하는 Docker의 Multi-Stage builds를 활용하여 실제 빌드하는 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 Binutils
컨테이너 내부에서 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
Jdeps
- Jdeps는 JAVA Class의 의존성 분석을 위한 도구로, 모듈화가 적용된 JAVA 9 버전 이상부터 사용이 가능하다.
--ignore-missing-deps | 외존성이 존재하지 않는 모듈은 제외. |
--print-module-deps | Jlink에서 요구하는 포멧에 맞는 쉼표로 구분된 모듈 의존성 목록을 출력. |
--recursive | 모든 런타임 종속성을 재귀적으로 탐색. |
--multi-release ${VERSION} | 의존성을 분석할 JAVA 버전을 ${VERSION}에 명시. 단, 모듈화가 적용된 9 버전 이상만 적용. |
--class-path="${PATH}" | 의존성을 분석할 class 파일들을 찾을 위치를 ${PATH}에 지정. |
Jlink
- Jlink는 Jdeps를 이용하여 분석된 의존성을 활용하여 최적화된 Custom JRE를 구성할 수 있는 도구이다.
--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 Linux 이미지를 아래의 환경 설정을 이용하여 사용한다.
- 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
Spring-Boot-Docker_Springio-Guides
Multi-Stage-builds-Docker
Amazon-Crretto
Binutils
Jdeps_Oracle-Document
Jlink_Oracle-Document
Alpine_Linux-Wiki
※ Sample Code는 여기에서 확인 가능합니다.
'개발' 카테고리의 다른 글
Java Garbage Collection에 대한 설명 (0) | 2024.11.12 |
---|---|
Java Generic에 대한 설명 (6) | 2024.11.11 |
Spring Cache에 대한 설명과 예제 (0) | 2024.11.10 |
AWS S3 SDK 샘플 코드 (6) | 2024.11.09 |
AWS S3와 완벽히 호환하는 MinIO Object Storage에 대한 설명 (2) | 2024.11.08 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- jdeps
- object storage
- sample code
- Promise
- multi stage biluild
- functional programing
- aws s3
- async
- barman
- db lank
- graecful shutdown
- Spring
- bouncy castle
- patametertype
- javascript
- PostgreSQL
- mybtis
- hot-backup
- await
- docker
- extensibility
- repmgr
- nosuchmethodexception
- bean
- minio
- reusability
- ASYNCHRONOUS
- JWT
- point cut
- java
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함