springboot+docker イメージのサイズ縮小に失敗
helloworld するだけのコンテナが 121MB ではデカすぎる。JDK11 なんだからコンテナ向けの小さいイメージにしたい。 今回は上手くいかなかった手順を記録。
失敗1 springboot プロジェクトの jdeps は信用できない。
依存モジュールを調べる java.base, java.logging に依存していることが分かる
> jdeps --list-deps ./build/libs/kbhello-0.1.0.jar java.base java.logging
最小限のJRE作成
$ rm -rf ./jre-mini $ jlink --compress=2 --module-path %JAVA_HOME%/jmods --add-modules java.base,java.logging --output jre-mini
サイズ確認
$ du -sh jre-mini/ 36M jre-mini/
jre-min から実行したが失敗。java.sql.SQLException??。jdeps はそんなこといってなかったが?
$ ./jre-mini/bin/java -jar ./build/libs/kbhello-0.1.0.jar Exception in thread "main" java.lang.reflect.InvocationTargetException at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48) at org.springframework.boot.loader.Launcher.launch(Launcher.java:87) at org.springframework.boot.loader.Launcher.launch(Launcher.java:50) at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51) Caused by: java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.context.ApplicationContextInitializer : org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:465) at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:444) at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:435) at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:271) at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:252) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1277) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1265) at kbhello.Application.main(Application.java:18) ... 8 more Caused by: java.lang.NoClassDefFoundError: java/sql/SQLException at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:168) at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:461) ... 15 more 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) ... 17 more
参考にしたここ によるとspringbootだと jdeps が正確じゃないそうです。
失敗2 Multi-Stage Builds しなかった
↑の参考ページから --add-modules をパクった。
$ rm -rf ./jre-mini $ jlink --compress=2 \ --module-path $JAVA_HOME/jmods \ --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 \ --output jre-mini
サイズ確認
$ du -sh jre-mini/ 57M jre-mini/
この Dockerfile でイメージ生成したが、コンテナは起動しなかった。
FROM alpine:3.9 RUN mkdir -p /opt/jre COPY ./jre-mini/ opt/jre RUN mkdir /app COPY ./build/libs/kbhello-0.1.0.jar /app ENV JAVA_OPTS="" ENTRYPOINT [ "sh", "-c", "/opt/jre/bin/java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app/kbhello-0.1.0.jar" ]
ENTRYPOINT を下のようにして
ENTRYPOINT ["sh", "-c", "while true; do echo hello world; sleep 1; done"]
コンテナに入って java -version してみるが、not found と怒られた。
# docker exec -it 7a6b2b8e007e sh ~ # /opt/jre/bin/java -version sh: /opt/jre/bin/java: not found
コンテナにはいり 'less /opt/jre/bin/java' してみると、 /lib64/ld-linux-x86-64.so.2
参照しているようだった。
コンテナ内には /lib64
なんてなかった。 /lib
のそれっぽいやつにリンクしてみた。
mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
これでも java -version は確認できなかった。
今思うと、jlink した環境では /lib64
があり、そこを参照したのだろう。やはり Multi-Stage Builds が必要なのだ。
失敗失敗失敗
glibc いれたりもした。glibc イメージを使ったりもした。いろいろやったが Multi-Stage Builds は試さなかった。いずれも失敗に終わった。