AWS 기술 블로그
Amazon Corretto OpenJDK를 사용한 Java 기반 애플리케이션 컨테이너 경량화
서론
컨테이너로 배포되는 애플리케이션은 컨테이너 이미지의 크기가 작을수록 빠르게 실행하고 확장할 수 있으며 이미지 보관 및 전송에 드는 비용이 절감됩니다. 특히 서버리스 컴퓨팅 엔진인 AWS Fargate는 호스트 머신에 컨테이너 이미지를 캐싱하지 않기 때문에 애플리케이션을 실행할 때 컨테이너 이미지의 크기는 더 중요합니다. 그러나 자바 애플리케이션은 JVM(Java Virtual Machine)이 함께 배포되어야 하기 때문에 Go 언어와 같은 바이너리 형태로 배포되는 애플리케이션에 비해서 컨테이너 이미지의 크기가 매우 큽니다. 이는 경량화된 Distroless 이미지를 사용해도 마찬가지입니다.
본 게시물에서는 Amazon Web Services에서 제공하는 Amazon Corretto Docker Image의 Amazon Corretto OpenJDK 내장 CLI와 도커 멀티 스테이지 빌드 (Docker Multi-stage build) 기능을 사용하여 자바 애플리케이션과 함께 배포되는 JVM의 크기를 최소화하는 방법에 대해 설명합니다.
솔루션 개요
본 게시물 에서는 amazoncorretto:11.0.20-alpine 컨테이너 이미지에 경량화를 적용해 보았습니다. Distroless 이미지인 gcr.io/distroless/java11-debian11은 비교를 위해 사용 되었습니다. 두 이미지의 크기는 아래와 같이 각각 271MB, 204MB로 Distroless 이미지의 크기가 더 작은 것을 확인할 수 있습니다.
스프링 부트 샘플 앱(Spring boot sample app)을 기준으로 Distroless와 amazoncorretto 도커 이미지로 빌드한 결과를 확인해 보겠습니다. 컨테이너 빌드에 사용한 도커파일(Dockerfile)은 아래와 같습니다.
샘플 애플리케이션을 포함하여 빌드한 이미지의 크기는 아래와 같습니다. Amazon Corretto 기반 이미지가 356 MB, Distroless 기반 이미지는 292 MB 입니다.
Amazon Corretto OpenJDK는 50여개의 모듈로 구성되어 있습니다. 컨테이너를 빌드할 때 애플리케이션에서 사용하는 모듈만 컨테이너 이미지에 추가하여 JVM의 크기를 줄일 수 있습니다. 이를 위해 Amazon Corretto OpenJDK에 이미 포함되어 있는 jdeps와 jlink 를 사용합니다. 먼저 jdeps로 빌드된 결과물(jar 또는 war)의 자바 런타임 의존성을 분석하여 추출한 뒤 jlink로 필요한 모듈만 추가한 사용자 정의 JRE(Java Runtime Environment)를 만들어 alpine:3.18.2 이미지에 추가하는 형태로 도커파일을 구성할 수 있습니다.

그림 1. Docker Build Pipeline
jdeps, jlink로 JVM 크기 줄여보기
jdeps를 사용하여 자바 런타임 모듈 의존성을 추출하기
우선 경량화에 적용할 샘플 앱에서 사용하는 모듈을 식별해 보겠습니다. 로컬 환경에서 빌드된 결과물인 sample-app.jar의 압축을 해제하면 아래와 같은 폴더구조를 가지게 됩니다.
위 내용을 참고로 jdeps를 실행하여 의존성이 있는 모듈을 추출하겠습니다. 좀 더 다양한 옵션을 확인하고자 한다면 jdeps 문서 를 참고하시기 바랍니다.
jlink를 사용하여 사용자 정의 JRE 만들기
우리는 jdeps를 사용하여 애플리케이션이 의존하는 자바 런타임 모듈을 추출하는 데 성공했습니다. 다음으로 추출한 모듈로 사용자 정의 JRE를 만들어 보겠습니다.
필요 모듈을 설치되어 있는 jdk에서 검색하여 특정 폴더로 구성(packing) 하였습니다. 생성된 customjre는 sample-app.jar를 실행하기 위한 최소 모듈만 담고 있으며 최소한의 CLI만 포함된 상태로 생성 됩니다. 생성된 customjre의 크기는 아래와 같습니다.
기존의 로컬 환경에 설치된 corretto-11.0.19의 크기와 비교해 보겠습니다.
만약 customjre를 사용하지 않았다면, 251MB 가량의 불필요한 모듈과 파일을 가지고 자바 애플리케이션이 실행 되었을 것 입니다. 감소율을 계산 해보자면 기존 corretto 이미지 대비 83.96%의 용량을 절감 한 것을 볼 수 있습니다.
corretto | customjre | 감소율 |
299MB | 48MB | 83.95% |
컨테이너 경량화 도커파일 작성해보기
이제 jlink와 jdeps를 이용하여 컨테이너 이미지 크기를 경량화하는 도커파일을 작성해 보겠습니다. 아래의 도커파일은 자바 애플리케이션이 이미 jar 파일로 빌드된 상태임을 가정하고 작성하였습니다.
멀티스테이지로 이루어진 빌드 단계를 설명하겠습니다.
- Stage 1
- amazoncorretto:11.0.20-alpine 이미지를 베이스 이미지로 사용하여 jdeps를 이용한 의존성 분석 및 분석 결과를 생성합니다.
- Stage 2
- Stage 1과 동일한 이미지와 Stage 1에서 생성된 분석결과를 활용하여 jlink를 사용해 customjre 생성합니다.
- Stage 3
- alpine:3.18.2 이미지를 베이스로 Stage 2에서 생성된 customjre를 사용하여 최종 이미지 생성. 보안을 위해 별도의 유저를 생성하여 sample-app.jar를 실행합니다.
도커파일로 빌드를 실행한 결과는 아래와 같습니다.
이제 만들어진 sample-app 컨테이너가 정상 작동 하는지 확인해 보겠습니다.
결과 확인
우리는 지금까지 Amazon Corretto OpenJDK에 포함된 jdeps, jlink와 도커 멀티스테이지 빌드와 함께 사용하여 경량화된 자바 애플리케이션 컨테이너 이미지를 생성했습니다. 생성한 컨테이너 이미지 레이어를 dive CLI를 통해 확인한 결과는 아래와 같습니다.
경량화를 하지 않은 amazoncorretto:11.0.20-alpine을 사용하여 빌드한 컨테이너 이미지 레이어와 비교해 보겠습니다.
경량화를 진행하기 전과 진행 후의 컨테이너를 Amazon ECR에서 결과를 비교해 보겠습니다.

그림 2. amazoncorretto 이미지 빌드 결과

그림 3. Distroless 이미지 빌드결과

그림 4. customjre이용한 빌드 결과
해당 결과를 표로 정리한다면 아래와 같습니다. Distroless 이미지 기반 애플리케이션 이미지보다 Amazon Corretto 기반 애플리케이션 이미지의 크기가 더 작은 것을 확인할 수 있습니다.
BaseImage | General Build | Custom Build | Amazon ECR Size | Container Size 감소율 |
amazoncorretto:11.0.20-alpine | 356MB | 142MB | 114.87MB | 60.11% |
gcr.io/distroless/java11-debian11 | 292MB | CLI 미포함 | 156.08MB | amazon corretto Customer Build구성 과 비교시 51.37% |
결론
이번 게시물 에서는 AWS에서 제공하는 Amazon Corretto OpenJDK를 사용하여 자바 애플리케이션 컨테이너의 크기를 경감시키는 방법을 소개했습니다.
Amazon Corretto OpenJDK와 함께 제공되는 jdeps, jlink를 사용하여 애플리케이션이 사용하지 않는 불필요한 런타임 모듈을 제거한 사용자 정의 JRE를 생성했습니다. 그리고 그 과정을 멀티 스테이지 도커파일로 생성하여 컨테이너 이미지를 빌드할 때 자동 적용되도록 했습니다.
그 결과, 이미지 크기가 60% 경감되어 ECR 저장, 데이터 전송 비용, 애플리케이션 시작 및 스케일 아웃 시간이 40% 이상 개선될 것이라고 기대됩니다. 특히, Amazon ECS와 Amazon EKS 그리고 AWS Fargate와 함께 컨테이너 애플리케이션을 사용하는 경우 효율을 극대화될 것입니다. 이 게시물에서 진행한 샘플 코드는 github에서 확인 할 수 있습니다.