서론
컨테이너의 운영환경에서는 어플리케이션 docker image가 작을 수록 빠르게 실행하고 확장할 수 있다.
특히, container orchestration 환경(추후)에서는 image 크기는 중요한데 Java의 경우 다른 언어에 비해 image가 매우 크다.
운영환경에서 1G 이상의 docker container 크기를 가지고 사용한 경우를 많이 봐왔음..
단계별 Dockerfile
1. build
1
2
3
4
5
6
7
8
9
10
11
12
|
##############################
# STEP 1 build
##############################
FROM gradle-jdk17 AS build
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY gradle.properties .
COPY settings.gradle .
COPY src src
RUN chmod +x ./gradlew
RUN ./gradlew bootJar
|
gradle 빌드 진행한다.
2. jdeps, jlink를 사용하여 경량화한다.
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
|
##############################
# STEP 2 Analyze jar file and extract only what is needed
##############################
FROM corretto-jdk17 AS corretto-jdk
COPY --from=build /home/gradle/build/libs/*.jar /app/app.jar
RUN mkdir /app/unpacked && \
cd /app/unpacked && \
unzip ../app.jar && \
cd .. && \
$JAVA_HOME/bin/jdeps \
--ignore-missing-deps \
--print-module-deps \
-q \
--recursive \
--multi-release 17 \
--class-path="./unpacked/BOOT-INF/lib/*" \
--module-path="./unpacked/BOOT-INF/lib/*" \
./app.jar > /deps.info
RUN apk add --no-cache binutils
# Build small JRE image
RUN $JAVA_HOME/bin/jlink \
--verbose \ # 상세한 추적을 활성화 하여 로깅
--add-modules $(cat /deps.info) \
--strip-debug \ # 디버그 정보를 제거
--no-man-pages \ # 리소스의 man page를 제거
--no-header-files \
--compress=2 \ # 리소스를 압축
--output /customjre
|
- jdeps를 사용해 자바 런타임 모듈 의존성 추출
- jlink를 사용하여 사용자 정의 JRE 만들기
3. alpine base 이미지 사용하여 이미지 경량화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
##############################
# STEP 3 Build a small image
##############################
FROM alpine:3.18.2
ENV JAVA_HOME=/jre
ENV PATH="${JAVA_HOME}/bin:${PATH}"
ENV WORKDIR=/app
COPY --from=corretto-jdk /customjre $JAVA_HOME
RUN mkdir /app
COPY --from=build /home/gradle/build/libs/*.jar $WORKDIR/app.jar
WORKDIR $WORKDIR
EXPOSE 8080
COPY distribution/docker/java_options.sh /app/java_options.sh
RUN chmod 755 $WORKDIR/java_options.sh
RUN mkdir -p $WORKDIR/plugin && wget -O $WORKDIR/plugin/dd-java-agent.jar 'https://dtdg.co/latest-java-tracer'
RUN sed -i 's/^#networkaddress\.cache\.ttl=-1/networkaddress\.cache\.ttl=10/' $JAVA_HOME/conf/security/java.security
ENTRYPOINT source /app/java_options.sh $ENV_PROFILE $ENV_DOCKER_TAG && /jre/bin/java $JAVA_OPTS -jar /app/app.jar $*
|
- step1 단계의 jar파일, step2 단계의 JRE를 COPY
- JAVA_OPTS을 위한 java_options.sh COPY
networkaddress
cache ttl 변경을 위한 sed 명령어 수행
- container내부 설정을 변경하기 위함, ttl뿐아니라 다른 것도 이런식으로 변경
- java_options.sh이라는 스크립트에 jvm 옵션들 넣어두고 환경변수를 가지고 jar실행하도록 함
완성된 Dockerfile
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
FROM gradle:8.5.0-jdk17 AS gradle-jdk17
FROM amazoncorretto:17-alpine3.18-jdk AS corretto-jdk17
##############################
# STEP 1 build
##############################
FROM gradle-jdk17 AS build
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY gradle.properties .
COPY settings.gradle .
COPY src src
RUN chmod +x ./gradlew
RUN ./gradlew bootJar
##############################
# STEP 2 Analyze jar file and extract only what is needed
##############################
FROM corretto-jdk17 AS corretto-jdk
COPY --from=build /home/gradle/build/libs/*.jar /app/app.jar
RUN mkdir /app/unpacked && \
cd /app/unpacked && \
unzip ../app.jar && \
cd .. && \
$JAVA_HOME/bin/jdeps \
--ignore-missing-deps \
--print-module-deps \
-q \
--recursive \
--multi-release 17 \
--class-path="./unpacked/BOOT-INF/lib/*" \
--module-path="./unpacked/BOOT-INF/lib/*" \
./app.jar > /deps.info
RUN apk add --no-cache binutils
# Build small JRE image
RUN $JAVA_HOME/bin/jlink \
--verbose \
--add-modules $(cat /deps.info) \
--strip-debug \
--no-man-pages \
--no-header-files \
--compress=2 \
--output /customjre
##############################
# STEP 3 Build a small image
##############################
FROM alpine:3.18.2
ENV JAVA_HOME=/jre
ENV PATH="${JAVA_HOME}/bin:${PATH}"
ENV WORKDIR=/app
COPY --from=corretto-jdk /customjre $JAVA_HOME
RUN mkdir /app
COPY --from=build /home/gradle/build/libs/*.jar $WORKDIR/app.jar
WORKDIR $WORKDIR
EXPOSE 8080
COPY distribution/docker/java_options.sh /app/java_options.sh
RUN chmod 755 $WORKDIR/java_options.sh
RUN mkdir -p $WORKDIR/plugin && wget -O $WORKDIR/plugin/dd-java-agent.jar 'https://dtdg.co/latest-java-tracer'
RUN sed -i 's/^#networkaddress\.cache\.ttl=-1/networkaddress\.cache\.ttl=10/' $JAVA_HOME/conf/security/java.security
ENTRYPOINT source /app/java_options.sh $ENV_PROFILE $ENV_DOCKER_TAG && /jre/bin/java $JAVA_OPTS -jar /app/app.jar $*
|
결과
기존 알려진 dockerfile들 대비 20% 크기인 200MB도 안되게 경량화 되어 운영이 가능하다.
참고