最適な Java の Docker イメージを選びたい
Java アプリケーションを Docker コンテナ上で実行しようとしたときに、ベースイメージとしてどの Docker イメージを選ぶのがよいかを考えてみます。
はじめに
Java で Web アプリケーションを開発して運用、というと、昔は Tomcat をインストールしたサーバに JAR ファイルや WAR ファイルを配布してデプロイしていたような記憶が微かに残っているのですが、数年前からは Spring Boot のように組み込み Tomcat などを採用し、Maven や Gradle のビルドオートメーションツールの力を借りて Java アプリケーションの実行に必要な JAR ファイルをひとまとめにした fat JAR (uber JAR) を構築し、単体の JAR ファイルだけを Java がインストールされているサーバに配布してデプロイ… とすることが圧倒的に多くなった気がしています。
また最近だと、よりデプロイやサーバのプロビジョニングを単純化することなどを目的として、JRE もしくは JDK を含むベースイメージにアプリケーションの fat JAR を載せた Docker イメージを構築することがよくあるかと思います。
この Docker イメージの構築に利用する Java (JDK/JRE) のベースイメージとして Docker Hub や Docker Store で提供されている Docker イメージを使おうとした場合、どのような現実的な選択肢 (Docker イメージ) が存在して、それらからどの基準で利用ケースに合ったベースイメージを選定すべきなのかを考えてみます。
どのような Docker イメージがあるのかを知る
Docker Hub や Docker Store にて “Java” で検索をすると、様々な組織・個人が提供する Java 関連の Docker イメージが見つかるのですが、その Docker イメージが信頼できるものなのか、また継続的なメンテナンスが期待できるのかなどを考慮すると、選択肢としては概ね以下に挙げるものに絞られるでしょう (2018 年 5 月現在)。
Oracle Java
Oracle からは、Java 8, JRE の Docker イメージのみが oracle-serverjre-8 として Docker Store にて提供されています。
この Docker イメージを docker pull
するには、Docker Store 上でのチェックアウトの手続きなどが必要となります。具体的な手順については、次のブログエントリを一読されることをおすすめします。
Docker Store から Oracle 公式イメージを取得する - #chiroito ’s blog
oracle-serverjre-8 ではそのベースイメージとして、GitHub 上の Dockerfile に記述されているように oraclelinux:7-slim
、すなわち RHEL ベースの Oracle Linux を利用しています。
なお Oralce Java は JDK の Docker イメージが提供されていませんが、これは oracle/docker-images の issue にあるように、
Our legal team have advised that the Oracle JDK license does not permit us to ship the JDK as a container at this time. Therefore, we will not be shipping a complete container.
とのことで、すなわちライセンス的に提供できないことが理由のようです。なので、仮に Oracle Java の JDK の Docker イメージが必要になる場合は、github.com/oracle/docker-images の OracleJava を参考に自前で docker build
し、プライベートな Docker レジストリに docker push
しておくか、もしくは Java アプリケーションの JAR ファイルを含める Dockerfile に JDK のインストール手順も直接記述するなどの対応が必要になるでしょう。
OpenJDK
Docker コミュニティがメンテナンスしている、OpenJDK を利用した公式 Docker イメージです。以前は Docker Hub の java のリポジトリで提供されていましたが、このリポジトリは deprecated になっています。現在のリポジトリは openjdk です。
こちらは先の Oracle Java とは異なり、JRE だけでなく JDK の Docker イメージも提供しています。また提供されている Java のバージョンも、7, 8, 9, 10 に加えて、early access 版の 11 も存在しています。
ベースイメージは Debian, Alpine Linux, Windows (Windows Server Core, Nano Server) と豊富に取り揃えられています。ただし、Debian 以外のベースイメージでは提供される Java のバージョンが制限されていたり、JDK のみの提供だったりすることに注意が必要です。詳しくは以下の表をご覧ください。
ベースイメージ | 7 | 8 | 9 | 10 | 11 (EA) |
---|---|---|---|---|---|
Debian | JDK / JRE | JDK / JRE | JDK / JRE | JDK / JRE | JDK / JRE |
Alpine Linux1 | JDK / JRE | JDK / JRE | - | - | - |
Windows (Server core) | - | JDK | JDK | - | - |
Windows (Nano server) | - | JDK | JDK | - | - |
なお Debian の OpenJDK パッケージには、グラフィックやサウンドなど UI に関わるライブラリなどを除外した headless なパッケージと、そうでないパッケージの 2 種類が存在します。同様に Debian をベースイメージとする Docker イメージも、headless な JRE / JDK のイメージとそうでないイメージが提供されています。前者の headless な JRE / JDK のイメージには、そのラベルに 8-jre-slim
のように -slim
のサフィックスが付与されています。
IBM Java
IBM による Java の JVM 実装 を基にした Docker イメージです。Docker Hub のリポジトリは ibmjava です。
Java のバージョンは Java 8 のみ2、Java の環境は SDK, JRE, SFJ (Small footprint JRE、クラウド環境向けに Java コントロールパネルなどを除去し、ディスクやメモリのフットプリントを小さくしたもの) の 3 種類が提供されています。
ベースイメージは Ubuntu と Alpine Linux の 2 種類です。
AdoptOpenJDK
各種環境 (Linux, Windows, macOS, etc.) 向けのビルド済み OpenJDK を提供している AdoptOpenJDK による Docker イメージです。Docker Hub のリポジトリは こちらにあるように、Java のバージョンおよび JVM 実装ごとに分けられています。
AdoptOpenJDK では、HotSpot VM を用いたビルド済み OpenJDK による Docker イメージの他に、オープンソース化された IBM の JVM 実装である Eclipse OpenJ9 を利用してビルドされた OpenJDK の Docker イメージも提供しています。
Eclipse OpenJ9 については同サイトにおける パフォーマンス比較 からの引用になりますが、
- メモリフットプリントが小さい
- 起動後の時点で、HotSpot VM に対して 66% 少ないメモリ使用量
- 処理をこなした状態で、63% 少ないメモリ使用量
- 起動が速い
- OpenJ9 にて shared classes および Ahead-of-Time (AOT) の機能を使うことで、HotSpot VM に対して 36% 短い起動時間
- スループットが高い
- スループットがピーク性能に達するまでにかかる時間が HotSpot VM より早い
- CPU リソースが制限された環境下だと特にスループットの違いが顕著に現れる
といった特長があり、それゆえクラウド環境での利用では HotSpot VM よりも向いている、ということのようです。
Java のバージョンはそれぞれ 8, 9, 10 が提供されていて3、いずれもバージョンも VM も (JRE はなく) JDK のみとなります。ベースイメージは Ubuntu と Alpine Linux の 2 種類です。
最適な Docker イメージを選ぶ
ここまでは主だった提供元に着目して、それぞれどのような Docker イメージを提供しているのかをざっと見てきました。ここからは、実際にどの Docker イメージを採用すべきかを考えてみます。
まずはここまでのおさらいを兼ねて、それぞれの Docker イメージのベースイメージや Java バージョンなどを整理しておきます。
Docker Hub / store | ベースイメージ | VM | Java バージョン | JDK / JRE |
---|---|---|---|---|
Oracle Java | Oracle Linux | HotSpot | 8 | JRE |
OpenJDK | Debian | HotSpot | 7, 8, 9, 10, 11(EA) | JDK / JRE |
OpenJDK | Alpine Linux | HotSpot | 7, 8 | JDK / JRE |
OpenJDK | Windows | HotSpot | 8, 9 | JDK |
IBM Java | Ubuntu, Alpine Linux | J9 | 8 | SDK / JRE / SFJ |
AdoptOpenJDK | Ubuntu, Alpine Linux | HotSpot | 8, 9, 10 | JDK |
AdoptOpenJDK | Ubuntu, Alpine Linux | OpenJ9 | 8, 9 | JDK |
続いて、それぞれの代表的な Docker イメージについてそのイメージサイズを確認して見てみましょう。ここでは以下のスクリプトにあるように、事前に最小サイズになると想定されるイメージのタグを雑に列挙しておき、実際に docker pull
してイメージサイズを確認しています。
IMAGES = %w(
store/oracle/serverjre:8
openjdk:7-jre-slim
openjdk:7-jre-alpine
openjdk:8-jre-slim
openjdk:8-jre-alpine
openjdk:9-jre-slim
openjdk:10-jre-slim
ibmjava:sfj-alpine
adoptopenjdk/openjdk8:alpine
adoptopenjdk/openjdk8-openj9:alpine
adoptopenjdk/openjdk9:alpine
adoptopenjdk/openjdk9-openj9:alpine
adoptopenjdk/openjdk10:alpine
)
# store/oracle/serverjre:8 を docker pull するために、事前に docker login しておく
IMAGES.each do |image|
%x{docker pull #{image}}
result = %x{docker image inspect #{image} | jq -r '.[0].Size'}.to_i
puts "#{image} #{result / 1000 / 1000} MB"
end
Docker Hub / store | Java バージョン (JVM 実装) | サイズ (イメージ) |
---|---|---|
Oracle Java | 8 | 279 MB (store/oracle/serverjre:8 ) |
OpenJDK | 7 | 120 MB (openjdk:7-jre-alpine ) |
OpenJDK | 8 | 81 MB (openjdk:8-jre-alpine ) |
OpenJDK | 9 | 286 MB (openjdk:9-jre-slim ) |
OpenJDK | 10 | 285 MB (openjdk:10-jre-slim ) |
IBM Java | 8 | 107 MB (ibmjava:sfj-alpine ) |
AdoptOpenJDK | 8 (HotSpot) | 193 MB (adoptopenjdk/openjdk8:alpine ) |
AdoptOpenJDK | 8 (OpenJ9) | 208 MB (adoptopenjdk/openjdk8-openj9:alpine ) |
AdoptOpenJDK | 9 (HotSpot) | 365 MB (adoptopenjdk/openjdk9:alpine ) |
AdoptOpenJDK | 9 (OpenJ9) | 347 MB (adoptopenjdk/openjdk9-openj9:alpine ) |
AdoptOpenJDK | 10 (HotSpot) | 362 MB (adoptopenjdk/openjdk10:alpine ) |
(5/16 時点の結果です)
さて、これで Docker イメージを選ぶための材料が揃ったとして、具体的にどのような場合にどの Docker イメージがおすすめできるのかを、想定ケースを挙げて考えてみます。
とにかく最新バージョンの Java を使いたい!
Java の最新バージョンへの追従、という観点では、開発中バージョン early access 版の Docker イメージを提供していることから OpenJDK が最優先の候補として挙げられます。次点は、現時点で Java 10 の Docker イメージを提供している AdoptOpenJDK となるでしょう。
JRE ではなくて JDK を含む Docker イメージが必要だ
プロダクション環境にデプロイした Java アプリケーションに何らかの問題が発生し、原因追求のために docker exec CONTAINER_ID bash
して jstack
でスレッドダンプをとったり jmap
でヒープダンプを採取したい… という場合は、JRE ではなく JDK を含んだ Docker が必要になることでしょう。この場合は JDK の Docker イメージを提供している OpenJDK か AdoptOpenJDK が選択肢として挙げられます。
なお IBM Java の SDK や AdoptOpenJDK の OpenJ9 版では、jstack
などのコマンドが存在しないようですが、J9 / OpenJ9 の事情はよくわからないのでスレッドダンプを採取する代替手段が存在するかどうかは不明です。
なるべくイメージサイズが小さいものを使いたい
なるべく小さい Docker イメージ、となると、Alpine Linux をベースイメージとするものや、Debian における headless OpenJDK のように不要な機能を削ぎ落とした Docker イメージが候補に挙げられます。具体的には OpenJDK、IBM Java あたりになることでしょう。
Java のバージョンにあまりこだわりがないのであれば、OpenJDK の Alpine Linux ベースのイメージ openjdk:8-jre-alpine
が 100 MB を下回るほどに小さいので、こちらを使うのがよいかもしれません。
なお AdoptOpenJDK も Alpine Linux ベースの Docker イメージを提供していますが、JRE ではなく JDK の提供であることが原因なのか、非圧縮状態でのイメージサイズが比較的大きい部類になります。
Windows で使いたい
Windows から離れて久しいので Docker on Windows の事情がさっぱりわからないのですが、OpenJDK が Windows Server Core や Nano Server ベースの Docker イメージを提供しているので、こちらを最優先にするとよいのではないでしょうか?
またありものの Docker イメージではないのですが、Oracle Java の Windows 向け Dockerfile が Oracle から提供されているので、自前での docker build
を厭わないならこちらを利用する手段もあるでしょう。
まとめ
ここまで長々と書いて来ましたが、上記したいくつかの想定ケースを見てもわかるとおり「何としても Oracle Java を使いたい!」といった制約がなければ OpenJDK の Docker イメージが第一の選択肢になる ことでしょう。
ただし、今後の AdoptOpenJDK の進展次第ではこちらも有力な選択肢の一つになることが考えられます。
関連リソース
あわせて読みたい外部記事です。
- Javaのサポートについてのまとめ - Qiita
- 今回紹介したそれぞれの提供元における Java のサポート方針がまとめられています
- 長期間運用することが想定されるシステムで Java の Docker イメージを利用する場合は、この観点で Docker イメージを選択のもありでしょう
- Docker + Java Microservices: Choosing the Base Image for Java 8/9 Microservices (on Linux and Windows)
- 本ブログエントリと同様に、Java の Docker イメージ選びについて書かれたエントリです
-
Alpine Linux をベースイメージとする OpenJDK の Java バージョン 9 以降が存在しないのは、Alpine Linux 側に OpenJDK 9 以降のパッケージが存在しないことによる ものらしいです ↩
-
Docker Hub で ibmjava の tags を見る限りでは Java 9 のイメージも存在するように見えますが、GitHub の commit log を見るに beta リリース時の残骸っぽいです ↩
-
OpenJDK 10 の OpenJ9 については、現時点ではまだ nightly build しか存在していません。 ↩