LoginSignup
6
1

More than 1 year has passed since last update.

こんばんは、GxP高田です。
これはグロースエクスパートナーズ Advent Calendar 2022のカレンダーの22日目の記事です。2が多めで縁起がいいですね。
サンタクロースも走り回る師走を忙しくお過ごしの皆様に、さっと読めて来年からと言わず明日から使える知識をお届けしたいと思います。

Javaアプリのコンテナベースイメージどれにする?

アプリの実行環境として、Dockerに代表されるようなコンテナで実行されることがほとんどになってきました。
Javaを稼働させるためには、Java実行環境(Java Runtime Environment)が必要になるため、どうしてもコンテナイメージサイズが大きくなりがちです。どのイメージがいいのか、イメージサイズ、セキュリティ、メンテナンスが続くのか、などで大いにお悩みなのではないでしょうか。
JDK11から利用可能になったjlinkというツールを使うと、必要な機能に絞った可搬性の高いカスタムJREを作成できます。カスタムJREによって、ベースイメージにJDKを含む必要がなくなるため、ベースイメージ選択肢を広げることができます。

早速やってみましょう。

環境

今回はwindows11のwsl上でeclipse-temulin17.0.5で実行しています

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.1 LTS"

$ java -version
openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment Temurin-17.0.5+8 (build 17.0.5+8)
OpenJDK 64-Bit Server VM Temurin-17.0.5+8 (build 17.0.5+8, mixed mode, sharing)

アプリは2020年のアドベントカレンダーの記事「Testcontainersでspring Data JPAをいい感じにテストする」で作ったものを再利用します。springboot3にupdateしたり、RESTful APIを追加していますが本記事とは無関係なので割愛します。
リポジトリはこちら
なお、以降に出てくるコマンドはチェックアウトしたディレクトリで実行しているものとします。

コンパイルなど

まずはコンパイル。gradleアプリなのでbuildコマンドでjarまで作成しましょう。

$ ./gradlew build
$ ls -l ./buid/libs/
合計 39800
drwxrwxrwx 1 root root     4096 12月 20 13:39 ./
drwxrwxrwx 1 root root     4096 12月 20 13:39 ../
-rwxrwxrwx 1 root root     9826 12月 20 13:39 testcontainer-spring-demo-2022.12-plain.jar*
-rwxrwxrwx 1 root root 40741269 12月 20 13:39 testcontainer-spring-demo-2022.12.jar*

カスタムJREを作ろう

jdepsで依存するモジュールを調べる

jlinkでは、モジュールという単位で必要な機能を列挙してカスタムJREを作成します。JDK9以降には、必要なモジュールを知るために、jdepsというコマンドラインツールが用意されています。
jdepsにはいろいろなオプションがありますが、jlinkのモジュールを判断するために必要なのは--class-path, --print-module-deps, --ignore-missing-depsです。
使い方はヘルプを見てみましょう。出力が多いので絞って結果を表示します。

$ jdeps --help
...
  -cp <path>
  -classpath <path>
  --class-path <path>           クラス・ファイルを検索する場所を指定します
...
  --print-module-deps           モジュール依存性のカンマ区切りリスト
                                を出力する--list-reduced-depsと同じです。
                                この出力は、これらのモジュールとその推移的な
                                依存性を含むカスタム・イメージを作成するために
                                jlink --add-modulesで使用できます。
  --ignore-missing-deps         欠落している依存性を無視します。
...

依存関係の指定方法
springbootアプリはjar内部に/BOOT-INF/lib/というディレクトリに依存ライブラリを保持し、起動時に読み込む仕組みを持っています。これらのjarをjdepsのクラスパスに含める必要があります。

jdeps: 依存ライブラリ指定なしで(正しくない!)

$ jdeps --print-module-deps --ignore-missing-deps ./build/lib/testcontainer-spring-demo-2022.12.jar
java.base,java.logging

jdeps: 依存ライブラリ指定あり

# jarを展開
$ unzip testcontainer-spring-demo-2022.12.jar -d flatten 
$ jdeps -R -cp "flatten/BOOT-INF/lib/*" --print-module-deps --ignore-missing-deps --multi-release 17 build/libs/testcontainer-spring-demo-2022.12.jar
java.base,java.compiler,java.desktop,java.instrument,java.management,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql.rowset,jdk.jfr,jdk.unsupported

出力結果のモジュール数が2と15で全然違いますね。

jlinkでカスタムJREを作ってみる

jdepsコマンドの結果により依存するモジュールが分かったところで、いよいよカスタムJREを作成します。jlinkコマンドを実行します。

$ jlink \
         --add-modules java.base,java.compiler,java.desktop,java.instrument,java.management,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql.rowset,jdk.jfr,jdk.unsupported \
         --strip-debug \
         --no-man-pages \
         --no-header-files \
         --compress=2 \
         --output ./javaruntime

jlinkコマンドのオプション
add-modules以外の指定したオプションについては
Oracleのjlinkコマンドman jlinkを確認してください

javaruntimeディレクトリが作成されます。サイズを測ってみましょう。54MBという結果でした。

$ du -sh javaruntime/
54M     javaruntime/

コンテナ化した際の容量も比較してみましょう。ベースイメージが異なるので比較にあまり意味はないですが、小さくなった実感は得られるのではないでしょうか。

$ docker images
REPOSITORY                             TAG            IMAGE ID       CREATED              SIZE
local/demo-2022.12/eclipse-temurin17   latest         ff176a736649   About a minute ago   545MB
local/demo-2022.12/custom-jre          latest         36130c388e5b   2 minutes ago        166MB

eclipse-temurin17としているイメージはeclipse-temurin:17.0.5_8-jdkをベースイメージにアプリのjarファイルを追加したもの、custom-jreはjlinkで作成したカスタムJREを格納したイメージです。

イメージの作成方法
詳細はリポジトリ中のDockerfile, Dockerfile.eclipse-temurin17を参照してください

まとめ

jdeps、jlinkというコマンドラインツールを用いてカスタムJREを作る方法をご紹介しました。Microsoft AzureでJavaを稼働させる際の公式ドキュメント、eclipse-temurinのdockerhubの説明にも手順が紹介されているなど、徐々に浸透してきている印象ですが、springbootでのモジュール一覧の取得方法などのノウハウと合わせて紹介しました。
JDK8時代の開発のノウハウをアップデートする一助になると幸いです。

おまけ

カスタムJREはjfrでプロファイルできるの?

できますよ。
JavaにはJava Fright Recorderという、パフォーマンス分析のための情報収集ツールが存在します。カスタムJREでもjdk.jfrモジュールをjlinkコマンドのadd-modulesオプションに加えることで可能になります。

サンプルのDockerfileでは起動設定でjfrを有効にしています。

ENTRYPOINT ["java","-XX:StartFlightRecording=name=app-profile,filename=/app-profile.jfr,dumponexit=true,maxsize=10m,settings=profile,disk=true","-jar","/app.jar"]

アプリを起動するとオプションのfilenameで指定した場所にファイルが作成されます。

docker exec -ti testcontainers-spring-demo-app-1 /bin/bash              
root@71b169124d59:/# ls -l
total 39852
-rw-r--r--   1 root root        0 Dec 20 15:39 app-profile.jfr

参考

6
1
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
6
1