JavaのDockerイメージ

- eclipce temrin
- maven
- oralcle
- microsoft
- openJDK
- amazoncorett
- AdoptOpenJDK
参考文献

現在は更新されなくなったイメージ
OpenJDK
結論から言うと現在は非推薦イメージとなった模様。
現に上記のDockerHubのページでも非推薦になったことや、早期に代替イメージを探すように促すメッセージが書かれている。
[Docker HubのOpenJDKイメージの利用を更新するためのアドバイス - 赤帽エンジニアブログ]https://rheb.hatenablog.com/entry/updating-docker-hubs-openjdk-image
OpenJDK Java 17 docker image - Stack Overflow
AdoptOpenJDK
こちらもDockerのオフィシャルイメージであるが、eclipse-temurinに代えられて2021年8月から更新されなくなっている。
AdoptOpenJDK は Eclipse Adoptium になる
そもそものAdoptOpenJDKのプロジェクトの運営をAdoptからEclipce Fundationに移管し、プロジェクト名称もAdoptOpenJDKからEclipce Adoptiumプロジェクトになったことが背景にあるっぽい。

2024年2月に候補になるイメージ
- 複数プロジェクトやディストリビューションが存在するが、用途が決まれば使用するべきイメージも自ずと決まる。(JDKやJREなどを全て突っ込んだイメージとなるとベースイメージの段階で激重になるためだと思われる。
- マルチステージビルド前提になりつつあるっぽい?
Eclipse temurin
- Ubuntuベース
- 現時点で最新LTSであるバージョン21まで対応していそうであるが、以下のものとか2GBあるので結構重い。
eclipse-temurin:21-jre
eclipse-temurin:21-jdk
Maven
Javaアプリケーションビルド時におなじみのmavenが含まれるイメージ。
MavenそのものやJavaアプリケーションのビルドに必要なモジュールは含まれているものの、JDKやJREが含まれているわけでないので、ビルド専用イメージで実行には別のJREが必要となる。
新しいバージョンで使うタグとしては以下とか?
maven:3.9-eclipse-temurin-21
Amazon Corretto
ベースはAmazon Linuxという独自のものになっている。
AWS実行向けという認識。
MicrosoftOpenJDK
調査中
Distoress
軽量化を実行しつつ、alpine版を使ったときに問題となるglibcベースに作られている。
シェルが存在しない分、セキュアだと言われている。
Distroless Dockerイメージ(OpenJDK)を試す
参考

イメージの選び方
ベースイメージに脆弱性がない
私たちがDockerイメージを作成する際は、通常DockerHub上にある何かしらのベースイメージを使う。ベースイメージを使うことによって、ゼロからではなくある程度完成された基盤を用いてDockerイメージを作成できるため便利であるが、ベースイメージに脆弱性がある場合にはそれらも引き継いでしまうため注意が必要。
軽量ベースイメージを使用する。
xxx:lastastのタグがついたイメージは完全なOSをベースに作成している場合が多く、容量が大きくなってしまう可能性があある。追加のバイナリ等必要かどうかを考慮し、slimイメージなどを検討するのが良い。
また、Googleがメンテナンスしているイメージにdistoressというものが存在し、
alpineイメージの特性を知る
軽量イメージを使うとなった場合に、上記のslimイメージのほかにalpineイメージが挙げられることが多い。
ただし、多くのLinuxディストリビューションがglibcというベースにしているのに対して、alpineはmusl libcベースに作られており、これがかえってビルド時間を長くしたり不具合を生むことがある模様。
どういうことかと言うと、まずLinuxOSには何かしらのC言語標準ライブラリが搭載されていることが多く、私たちがアプリケーションをLinuxOSなどで動かすときはこのC言語標準ライブラリがアプリケーションとOSのやり取りを仲介している。
このC言語標準ライブラリについては多くのLinuxにはglibcが用いられているが、alipineイメージのLinuxにはmusl libcというものが用いられている。このmuslというのは軽量化を目的としてglibcとは異なる実装をしている。ただ、多くのLinuxアプリケーションがこのglibcに依存しているため、安易にalpineイメージを選択すると思わぬ挙動が発生することがあるということ。
PythonをAlpineイメージで安易に動かす問題点
仕事でPythonコンテナをデプロイする人向けのDockerfile (1): オールマイティ編
思わぬ挙動を引き起こす例にとしては、上記で触れているPythonのパッケージのうちC拡張ライブラリを使用したものが存在する。PyPI(Pythonのライブラリをホストする場所)ではLinux向けにC拡張ライブラリを含むパッケージをビルドするための規約(manylinux1)があり、この形式に沿ったバイナリ形式のパッケージは多くのLinuxディストリビューションで高速にインストールできる。しかし、AlpineLinuxはglibcと実装が違うためにこの規約とマッチしないため、互換上の問題が生じることがある。
Using Alpine can make Python Docker builds 50× slower
こちらで言われているのは以下の通りだと思われる。
- 通常のパッケージインストールにおいては必要なソースをダウンロードして、コンパイルしてからインストールを行う。
- PyPIにはPythonWheelというコンパイル済みバイナリ形式のパッケージがホスティングされている。PythonWheel形式を使えば、前述した必要なソースのダウンロードとコンパイルが短縮されるのでインストールの高速化が図れる。
- ただしglibcを前提としたPythonWheelはmusl libcの動作を想定していないため、Alpineでは.zipやら.tar.gzなどのビルド前のソースコードを落とし、これをalpine向けにwheel形式にコンパイルしている。なのでインストールにすごく時間がかかるということらしい。
alpineでC言語依存モジュールを pip install すると激重になる話 - Qiita
実行の場合にはJRE、ビルドの場合はJDKが含まれる
先ほど軽量イメージのほうが良いという話をしたが、JavaアプリケーションをビルドするにはJDK、アプリケーションの実行にはJREが必要となる。
まず、Javaはコンパイル言語であるため実行時に元のソースコードは不要でビルド環境によって作成されたアーティファクトのみが必要である。そのため実行時にはJREのみが含まれていれば良い。
開発〜ビルドまではローカルで行って、それを本番環境で実行する際には、JREが含まれるイメージに.jarファイルをコピーするのが基本戦略となる。
JRE含むベースイメージ、例えばAdoptOpenJDKのベースイメージは500MBほどと結構なサイズがあるが、Jlinkというものを使えばJava実行に必要なカスタムランタイムイメージを作成できるため、容量を削減できるらしい。
DockerコミュニティがメンテしているOpenJDKイメージを使って遭遇した問題
SpringBootのdockerイメージを必要最小限に絞りたい(2019年9月版)
JREはJDKに含まれるものなので、実行環境のみ必要という場合はJREを用意できれば良いが、Dockerで開発環境まで作りたい場合は当然ビルド環境も必要なのでJDKが必要となる。
参考

Eclipse temurin
OpenJDK21 | OpenJDK17 | OpenJDK11 | OpenJDK8 | |
---|---|---|---|---|
Ubuntu | ⚪︎ | ⚪︎ | ⚪︎ | ⚪︎ |
Amazon Corretto
OpenJDK21 | OpenJDK17 | OpenJDK11 | OpenJDK8 | |
---|---|---|---|---|
Amazon Linux | ⚪︎ | .⚪︎ | ⚪︎ | ⚪︎ |
Microsoft OpenJDK
OpenJDK 21 | OpenJDK 17 | OpenJDK 11 | OpenJDK 8 | |
---|---|---|---|---|
Ubuntu 22.04 | 21-ubuntu | 17-ubuntu | 11-ubuntu | N/A |
CBL Mariner 2.0 | 21-mariner | 17-mariner | 11-mariner | 8-mariner |
Distoress
OpenJDK 21 | OpenJDK 17 | OpenJDK 11 | OpenJDK 8 | |
---|---|---|---|---|
Distoress | ⚪︎ | ⚪︎ | N/A | N/A |

Gradle or Maven + Eclipce-temurinを使う
- 大前提としてマルチステージビルドありきで考える。ビルド用イメージ(JDK)と実行用イメージ(JRE)の2つを用意して、最終的な成果物はJREの方になるイメージ。
- SpringBootのアプリケーションをDockerで動かすことを考えたときに、特にサーバー構成なども決まっていないような状況だったら、Eclipce Temurinで良いのではないかと考えている。理由としては以下の点が挙げられる。
- JDKとJREを両方含んでいる。
- SpringBootの公式チュートリアルでeclipce-temurinが使われている。
- ただ、別環境でビルドした.jarファイルを放り込むのでなければ別途ビルドツールが必要であるが、Eclipce Temurinにgradleは入ってなさそう。

ホットスワップの利用
上記を見る限りはdocker-compose.ymlファイルでビルドと実行のコンテナを分離するとホットスワップができるとのこと。まだ確認はしていない。
version: '3.7'
services:
builder:
image: gradle:jdk11
working_dir: /home/gradle/project
volumes:
- ./build:/home/gradle/project/build
- ./src:/home/gradle/project/src
- ./build.gradle:/home/gradle/project/build.gradle
command: gradle build --continuous -x test -x testClasses
api:
image: openjdk:13-alpine
volumes:
- ./build:/app
depends_on:
- builder
command: java -cp "/app/classes/java/main:/app/dependencies/*:/app/resources/main" com.example.Application
plugins {
id 'org.springframework.boot' version '2.2.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.1.0-SNAPSHOT'
sourceCompatibility = '11'
configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
task copyLibs(type: Copy) {
from configurations.runtimeClasspath
into "${buildDir}/dependencies"
}
build.dependsOn(copyLibs)
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.h2database:h2'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}

Dockerfileの例
FROM gradle:jdk21 as builder
COPY ./src /home/source/java
WORKDIR /home/source/java
USER root
RUN chown gradle -R gradle /home/source/java
USER gradle
RUN gradle clean build
FROM eclipse-temurin:21.0.2_13-jre-jammy as runner
WORKDIR /home/application/java
COPY "/home/source/java/build/libs/demo-0.0.1-SNAPSHOT.jar" .
EXPOSE 8080
ENTRYPOINT [ "java", "-jar", "/home/application/java/demo-0.0.1-SNAPSHOT.jar"]
上記を参考にして少し改変。ビルド時に生成される.jarのファイル名がdemo-0.0.1-SNAPSHOT.jarになっているが、これはdemoという名前でプロジェクトを作成した場合である。

gradle clean buildの設定
jar {
archiveFileName = "${project.name}-${project.version}.jar"
}
build.gradle
ファイルで上記のようにjarディレクティブでarchiveFileNameを指定することで、build/libに出力されるjarファイルの名前を変えることができる。
archiveFileNameの指定がない場合は、デフォルトで上記のようにプロジェクト名とバージョンをハイフンで繋いだものが設定される。
▼参考
project.name
はsettings.gradle
で、project.version
はbuild.gradle
内で設定する。
rootProject.name = 'demo'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
▼参考