Edited at

OpenJDK11のdokcerイメージ(1GB)が大きいのでalpine linux+ jlinkで小さいイメージ(85MB)を作成する

More than 1 year has passed since last update.


はじめに

openjdk 11のdocker imageは、docker hubのofficial respoitoryで公開されています。

https://hub.docker.com/_/openjdk/

ただ・・・これを使う上でちょっと気になるのが、


  • イメージサイズがそもそも大きい。約1GBもあります。

  • jlinkを使っても、300MB以上ある

という問題があります。もっと小さくしたいな。という所で、どれくらい小さくできるか試してみました!


前提


  • Java Flight Recorderが動くこと

  • Spring Boot アプリケーションが動くこと


結果

1GB→85MBと1/10以下に圧縮できました:grinning:

個人的には大満足です。

docker images でサイズを確認した結果は下記の通りです。

imageの種類
jlink使用
size

openjdk:11-jdk
しない
1GB

openjdk:11-jdk
する
468MB

独自alpinelinux
しない
336MB

独自alpinelinux
する
84.6MB

jdk11はea版です。そのうちちゃんとopenjdk 11のofficial imageでも小さいイメージが作れるようになると思います。

独自alpinelinuxは、

 https://hub.docker.com/r/hirokimatsumoto/alpine-openjdk-11/

です。


問題

OpenJDK 11のofficial imageの問題を詳細に記載します。


イメージサイズが大きい

$ sudo docker images |grep jdk

docker.io/openjdk 11-jdk f684efd78557 2 weeks ago 979 MB
$

jreではちょっとflightrecorderが動かないので、jdkを使いたいのに・・・ちょっと大きすぎます。

参考:Qiita - OpenJDK11-JRE-SLIMじゃFlightRecorderは動きません。


openjdk 11(official)ではjlink使っても小さくならない。

下記issueでも同じ問題を取り上げています。

https://github.com/docker-library/openjdk/issues/217

試しにやると・・・下記のようになぜか400MBほどになり、imageサイズも450MBくらいになってしまいます。

$ docker run -it --rm openjdk:11-jdk /bin/sh

# ls -l /usr/lib/jvm/java-11-openjdk-amd64/lib/server/
total 34944
-rw-r--r-- 1 root root 1322 Jul 27 03:41 Xusage.txt
-r--r--r-- 1 root root 18210816 Jul 27 22:22 classes.jsa
-rw-r--r-- 1 root root 14440 Jul 27 03:41 libjsig.so
-rw-r--r-- 1 root root 17551048 Jul 27 03:41 libjvm.so  #まだ小さい
圧縮してみる
# jlink \
--module-path /opt/java/jmods \
--compress=2 \
--add-modules java.base,java.logging,jdk.jfr \
--no-header-files \
--no-man-pages \
--output /opt/jdk-11-mini-runtime
# ls -l /opt/jdk-11-mini-runtime/lib/server/
total 414452
-rw-r--r-- 1 root root 1322 Aug 14 09:41 Xusage.txt
-rw-r--r-- 1 root root 25384 Aug 14 09:41 libjsig.so
-rw-r--r-- 1 root root 424362808 Aug 14 09:41 libjvm.so #なんでや・・・
#


イメージサイズ小さくするぞ!

alpine linuxを使う、そしてjlinkを使って小さくするというアプローチをとってみます。


alpine linuxのベースのbuild用イメージを作成する。

他の人でも約に立つかな?と思って公開してます。

image: https://hub.docker.com/r/hirokimatsumoto/alpine-openjdk-11/

docker file: https://hub.docker.com/r/hirokimatsumoto/alpine-openjdk-11/~/dockerfile/

checksumしてなくてすみません…。


jlinkを使って小さい実行イメージを作る。

multi stageを使ってビルドします。


  1. 上記で作ったalpine linux + openjdk 11のimageをベースに、jlinkで小さいjava runtime moduleを作成します。

  2. runtime module、jar moduleをコピーしたalphine linuxのimageを作成します。

FROM hirokimatsumoto/alpine-openjdk-11:latest as jlink-package

# First: generate java runtime module by jlink.

RUN jlink \
--module-path /opt/java/jmods \
--compress=2 \
--add-modules jdk.jfr,jdk.management.agent,java.base,java.logging,java.xml,jdk.unsupported,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
--no-header-files \
--no-man-pages \
--output /opt/jdk-11-mini-runtime

# Second: generate run image.
FROM alpine:3.8

ENV JAVA_HOME=/opt/jdk-11-mini-runtime
ENV PATH="$PATH:$JAVA_HOME/bin"

COPY --from=jlink-package /opt/jdk-11-mini-runtime /opt/jdk-11-mini-runtime
COPY target/k8s-jmc-sample-0.1.0-SNAPSHOT.jar /opt/spring-boot/

EXPOSE 30001 7199
CMD java \
-XX:StartFlightRecording=name=sample,filename=/spring-boot/jfr/sample.jfr,delay=30s,maxage=2h,maxsize=10m,dumponexit=true,settings=/spring-boot/config/profile.jfc \
-Dcom.sun.management.jmxremote.port=7199 \
-Dcom.sun.management.jmxremote.rmi.port=7199 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-jar /opt/spring-boot/k8s-jmc-sample-0.1.0-SNAPSHOT.jar \
--server.port=${SPRING_SERVER_PORT} \
--spring.config.location=/spring-boot/config/application.yaml

実行アプリケーションは、

https://github.com/h-r-k-matsumoto/k8s-jmc-sample

です。


ハマったこと


Spring Bootアプリケーションをjdepsで依存関係見ても正確ではない

最初jdepsを使い、下記のように必要なmoduleを確認してました。

> jdeps --list-deps .\target\k8s-jmc-sample-0.1.0-SNAPSHOT.jar

java.base
java.logging
>

これでruntime作っても・・・実行時に、下記のようにエラーになります。

Caused by: java.lang.ClassNotFoundException: java.sql.SQLException

at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588)
at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:93)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)

うーん、spring boot内部でauto configurationしている箇所は上手く追跡できないのかな・・・。

めんどくさかったので、

 https://dev.solita.fi/2018/01/24/Java9-modules-Spring-Boot-2-Docker.html

みて適当に追加しました。


参考