要約
- カスタム JRE とマルチステージビルドを利用すれば Spring Boot の Docker Image を削減することができます。
- 以下が結果の要約になります。公式の AdoptOpenJDK の Docker Image よりも 1/5 以下の容量になっていることがわかります。
AdoptOpenJDK | AdoptOpenJDK-Alpine | Custom Runtime |
---|---|---|
436 MB | 358 MB | 85.5 MB |
サンプルコード
- 今回説明するサンプルコードは以下に格納しています。こちらのサンプルコードをもとに以降の説明を行っていきます。
手順① jdeps で必要なモジュールを取得
jdeps
コマンドを利用して、SpringBoot アプリケーションの依存モジュールを調べます。
しかし、SpringBoot の実行可能 jar ファイルを jdeps
で調べても java.base
と java.logging
しか出力されず、この JRE だけでは SpringBoot は起動しません。そこで以下のサイトで紹介されていた Shell Script を用いて依存モジュールを取得します。
#!/bin/sh
# jdeps-spring-boot
set -eu
readonly TARGET_JAR=$1
readonly TARGET_VER=$2
# jarを展開するディレクトリ
readonly TMP_DIR="/tmp/app-jar"
mkdir -p ${TMP_DIR}
trap 'rm -rf ${TMP_DIR}' EXIT
# jarを展開
unzip -q "${TARGET_JAR}" -d "${TMP_DIR}"
# 出力
jdeps \
-classpath \'${TMP_DIR}/BOOT-INF/lib/*:${TMP_DIR}/BOOT-INF/classes:${TMP_DIR}\' \
--print-module-deps \
--ignore-missing-deps \
--module-path ${TMP_DIR}/BOOT-INF/lib/javax.activation-api-1.2.0.jar \
--recursive \
--multi-release ${TARGET_VER} \
-quiet \
${TMP_DIR}/org ${TMP_DIR}/BOOT-INF/classes ${TMP_DIR}/BOOT-INF/lib/*.jar
Shell Script のファイル名を get-springboot-module.sh
としたとき、 以下のように実行します。
./get-springboot-module.sh <SpringBoot の jar> <Java Version>
実行例:
./get-springboot-module.sh demo-0.0.1-SNAPSHOT.jar 11
実行結果に必要なモジュールが出力されます。
java.base,java.desktop,java.instrument,java.management.rmi,java.naming,java.prefs,java.scripting,java.security.jgss,java.sql,jdk.httpserver,jdk.unsupported
注意点
./get-springboot-module.sh
を実行するときは Java 12 以上のバージョンで実行します。
Java 11 では javafx.media
を解析する際、存在しないクラスを解析しようとすると NullPointerException が発生するバグがあるためです。これを回避するには Java 12 から追加された --ignore-missing-deps
を指定する必要があります。
手順② カスタム JRE を作成
- 以下のような Dockerfile を用意します。
-
jlink --add-modules
には手順①で出力したモジュールを記載します。
FROM adoptopenjdk/openjdk11:alpine AS java-build
WORKDIR /jlink
ENV PATH $JAVA_HOME/bin:$PATH
RUN jlink --strip-debug --no-header-files --no-man-pages --compress=2 --module-path $JAVA_HOME \
--add-modules java.base,java.desktop,java.instrument,java.management.rmi,java.naming,java.prefs,java.scripting,java.security.jgss,java.sql,jdk.httpserver,jdk.unsupported \
--output jre-min
手順③ マルチステージビルドを利用してカスタム JRE 上で SpringBoot を起動
-
手順②で用意した Dockerfile を追記していきます。
-
alpine linux 上で Java を起動するために必要な設定は以下のサイトを参考に追加しました
FROM adoptopenjdk/openjdk11:alpine AS java-build
WORKDIR /jlink
ENV PATH $JAVA_HOME/bin:$PATH
RUN jlink --strip-debug --no-header-files --no-man-pages --compress=2 --module-path $JAVA_HOME \
--add-modules java.base,java.desktop,java.instrument,java.management.rmi,java.naming,java.prefs,java.scripting,java.security.jgss,java.sql,jdk.httpserver,jdk.unsupported \
--output jre-min
FROM alpine:3.12.0
USER root
RUN apk --update add --no-cache ca-certificates curl openssl binutils xz \
&& GLIBC_VER="2.28-r0" \
&& ALPINE_GLIBC_REPO="https://github.com/sgerrand/alpine-pkg-glibc/releases/download" \
&& GCC_LIBS_URL="https://archive.archlinux.org/packages/g/gcc-libs/gcc-libs-8.2.1%2B20180831-1-x86_64.pkg.tar.xz" \
&& GCC_LIBS_SHA256=e4b39fb1f5957c5aab5c2ce0c46e03d30426f3b94b9992b009d417ff2d56af4d \
&& ZLIB_URL="https://archive.archlinux.org/packages/z/zlib/zlib-1%3A1.2.9-1-x86_64.pkg.tar.xz" \
&& ZLIB_SHA256=bb0959c08c1735de27abf01440a6f8a17c5c51e61c3b4c707e988c906d3b7f67 \
&& curl -Ls https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub \
&& curl -Ls ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-${GLIBC_VER}.apk > /tmp/${GLIBC_VER}.apk \
&& apk add /tmp/${GLIBC_VER}.apk \
&& curl -Ls ${GCC_LIBS_URL} -o /tmp/gcc-libs.tar.xz \
&& echo "${GCC_LIBS_SHA256} /tmp/gcc-libs.tar.xz" | sha256sum -c - \
&& mkdir /tmp/gcc \
&& tar -xf /tmp/gcc-libs.tar.xz -C /tmp/gcc \
&& mv /tmp/gcc/usr/lib/libgcc* /tmp/gcc/usr/lib/libstdc++* /usr/glibc-compat/lib \
&& strip /usr/glibc-compat/lib/libgcc_s.so.* /usr/glibc-compat/lib/libstdc++.so* \
&& curl -Ls ${ZLIB_URL} -o /tmp/libz.tar.xz \
&& echo "${ZLIB_SHA256} /tmp/libz.tar.xz" | sha256sum -c - \
&& mkdir /tmp/libz \
&& tar -xf /tmp/libz.tar.xz -C /tmp/libz \
&& mv /tmp/libz/usr/lib/libz.so* /usr/glibc-compat/lib \
&& apk del binutils \
&& rm -rf /tmp/${GLIBC_VER}.apk /tmp/gcc /tmp/gcc-libs.tar.xz /tmp/libz /tmp/libz.tar.xz /var/cache/apk/*
COPY --from=java-build /jlink/jre-min /opt/jre-min
COPY ./demo-0.0.1-SNAPSHOT.jar /opt/demo/demo-0.0.1-SNAPSHOT.jar
ENV PATH /opt/jre-min/bin:$PATH
EXPOSE 8080
WORKDIR /
CMD ["java", "-jar", "/opt/demo/demo-0.0.1-SNAPSHOT.jar"]
実行
- 手順③ で作成した Dockerfile があるディレクトリに SpringBoot の jar ファイルを格納し、以下のコマンドを実行します
docker build -t demo-official-openjdk-custom-runtime:latest .
- image ができたことを確認します
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
demo-official-openjdk-custom-runtime latest 49049142374f About an hour ago 85.5MB
- 以下のコマンドで起動することを確認します。
docker run -d -p 8080:8080 --name demo-official-openjdk-custom-runtime demo-official-openjdk-custom-runtime:latest