最適な 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 イメージを提供している OpenJDKAdoptOpenJDK が選択肢として挙げられます。

なお IBM Java の SDK や AdoptOpenJDK の OpenJ9 版では、jstack などのコマンドが存在しないようですが、J9 / OpenJ9 の事情はよくわからないのでスレッドダンプを採取する代替手段が存在するかどうかは不明です。

なるべくイメージサイズが小さいものを使いたい

なるべく小さい Docker イメージ、となると、Alpine Linux をベースイメージとするものや、Debian における headless OpenJDK のように不要な機能を削ぎ落とした Docker イメージが候補に挙げられます。具体的には OpenJDKIBM 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 の進展次第ではこちらも有力な選択肢の一つになることが考えられます。

関連リソース

あわせて読みたい外部記事です。


  1. Alpine Linux をベースイメージとする OpenJDK の Java バージョン 9 以降が存在しないのは、Alpine Linux 側に OpenJDK 9 以降のパッケージが存在しないことによる ものらしいです 

  2. Docker Hub で ibmjava の tags を見る限りでは Java 9 のイメージも存在するように見えますが、GitHub の commit log を見るに beta リリース時の残骸っぽいです 

  3. OpenJDK 10 の OpenJ9 については、現時点ではまだ nightly build しか存在していません。