tjtjtjのメモ

自分のためのメモです

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 は試さなかった。いずれも失敗に終わった。