LoginSignup
3
5

More than 3 years have passed since last update.

Spring Boot の Docker Image を小さくする方法

Last updated at Posted at 2020-06-14

要約

  • カスタム 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.basejava.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

その他参考文献

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5