Spring Boot アプリケーションは、Dockerfiles を使用してコンテナー化するか、Cloud Native Buildpacks を使用して、どこでも実行できる最適化された docker 互換コンテナーイメージを作成できます。

1. 効率的なコンテナーイメージ

Spring Boot fat jar を docker イメージとしてパッケージ化することは簡単に可能です。ただし、docker イメージのように fat jar をコピーして実行することには、さまざまな欠点があります。fat jar を解凍せずに実行すると、常にある程度のオーバーヘッドが発生します。コンテナー化された環境では、これが顕著になる可能性があります。もう 1 つの課題は、アプリケーションのコードとそのすべての依存関係を Docker イメージの 1 つのレイヤーに配置することが最適ではないことです。使用する Spring Boot のバージョンをアップグレードするよりも頻繁にコードを再コンパイルする可能性があるため、多くの場合、物事をもう少し分離する方がよいでしょう。アプリケーションクラスの前のレイヤーに jar ファイルを配置した場合、Docker は多くの場合、最下層を変更するだけでよく、キャッシュから他のファイルを取得できます。

1.1. fat jar のアンパック

コンテナーからアプリケーションを実行している場合は、実行可能 jar を使用できますが、それを分解して別の方法で実行することも多くの場合利点です。特定の PaaS 実装では、実行前にアーカイブを解凍することもできます。例: Cloud Foundry はこのように動作します。解凍されたアーカイブを実行する 1 つの方法は、次のように適切なランチャーを起動することです。

$ jar -xf myapp.jar
$ java org.springframework.boot.loader.JarLauncher

これは、実際には、起動時に(jar のサイズに応じて)展開されていないアーカイブから実行するよりもわずかに高速です。実行時には、違いは期待できません。

jar ファイルを解凍したら、JarLauncher の代わりに「自然な」main メソッドでアプリを実行することにより、起動時間をさらに速くすることができます。例:

$ jar -xf myapp.jar
$ java -cp BOOT-INF/classes:BOOT-INF/lib/* com.example.MyApplication
アプリケーションのメインメソッド上で JarLauncher を使用すると、予測可能なクラスパス順序の利点が追加されます。jar には、クラスパスを構築するときに JarLauncher によって使用される classpath.idx ファイルが含まれています。

1.2. Docker イメージのレイヤー化

最適化された Docker イメージの作成を容易にするために、Spring Boot はレイヤーインデックスファイルを jar に追加することをサポートしています。レイヤーのリストと、レイヤー内に含める必要のある jar のパーツを提供します。インデックス内のレイヤーのリストは、レイヤーを Docker/OCI イメージに追加する順序に基づいて並べられています。すぐに使用できる、次のレイヤーがサポートされています。

  • dependencies (定期的にリリースされる依存関係の場合)

  • spring-boot-loader (org/springframework/boot/loader のすべての)

  • snapshot-dependencies (スナップショットの依存関係)

  • application (アプリケーションのクラスとリソース)

layers.idx ファイルの例を次に示します。

- "dependencies":
  - BOOT-INF/lib/library1.jar
  - BOOT-INF/lib/library2.jar
- "spring-boot-loader":
  - org/springframework/boot/loader/JarLauncher.class
  - org/springframework/boot/loader/jar/JarEntry.class
- "snapshot-dependencies":
  - BOOT-INF/lib/library3-SNAPSHOT.jar
- "application":
  - META-INF/MANIFEST.MF
  - BOOT-INF/classes/a/b/C.class

この階層化は、アプリケーションのビルド間で変更される可能性に基づいてコードを分離するように設計されています。ライブラリコードはビルド間で変更される可能性が低いため、ツールがキャッシュからレイヤーを再利用できるように独自のレイヤーに配置されます。アプリケーションコードはビルド間で変更される可能性が高いため、別のレイヤーに分離されます。

Spring Boot は、layers.idx を使用して war ファイルのレイヤー化もサポートします。

Maven の場合、アーカイブにレイヤーインデックスを追加する方法の詳細については、階層化された jar または war のパッケージ化のセクションを参照してください。 Gradle については、Gradle プラグインのドキュメントのパッケージ化階層化された jar または war セクションを参照してください。

2. Dockerfiles

Spring Boot fat jar を Dockerfile の数行で docker イメージに変換することは可能ですが、レイヤーリング機能を使用して最適化された docker イメージを作成します。レイヤーインデックスファイルを含む jar を作成すると、spring-boot-jarmode-layertools jar が jar への依存関係として追加されます。クラスパス上のこの jar を使用すると、特別なモードでアプリケーションを起動できます。これにより、ブートストラップコードで、アプリケーションとはまったく異なるもの(たとえば、レイヤーを抽出するもの)を実行できます。

layertools モードは、起動スクリプトを含む完全に実行可能な Spring Boot アーカイブでは使用できません。layertools で使用することを目的とした jar ファイルをビルドするときは、起動スクリプト構成を無効にします。

layertools jar モードで jar を起動する方法は次のとおりです。

$ java -Djarmode=layertools -jar my-app.jar

これにより、次の出力が提供されます。

Usage:
  java -Djarmode=layertools -jar my-app.jar

Available commands:
  list     List layers from the jar that can be extracted
  extract  Extracts layers from the jar for image creation
  help     Help about any command

extract コマンドを使用すると、アプリケーションをレイヤーに簡単に分割して、dockerfile に追加できます。これは、jarmode を使用した Dockerfile の例です。

FROM adoptopenjdk:11-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM adoptopenjdk:11-jre-hotspot
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

上記の Dockerfile が現在のディレクトリにあると仮定すると、docker build . を使用して docker イメージを構築できます。または、次の例に示すように、アプリケーション jar へのパスをオプションで指定できます。

$ docker build --build-arg JAR_FILE=path/to/myapp.jar .

これはマルチステージの dockerfile です。ビルダーステージは、後で必要になるディレクトリを抽出します。各 COPY コマンドは、jarmode によって抽出されたレイヤーに関連しています。

もちろん、Dockerfile は jarmode を使用せずに作成できます。unzip と mv の組み合わせを使用して、適切なレイヤーに移動できますが、jarmode はそれを単純化します。

3. Cloud Native Buildpacks

Dockerfile は、docker イメージを構築するための 1 つの方法にすぎません。docker イメージを作成する別の方法は、buildpacks を使用して、Maven または Gradle プラグインから直接行うことです。Cloud Foundry や Heroku などのアプリケーションプラットフォームを使用したことがある場合は、おそらく buildpack を使用したことがあります。Buildpacks は、アプリケーションを取得して、プラットフォームが実際に実行できるものに変換するプラットフォームの一部です。例: Cloud Foundry の Java buildpack は、.jar ファイルをプッシュしていることを認識し、関連する JRE を自動的に追加します。

Cloud Native Buildpacks を使用すると、どこでも実行できる Docker 互換のイメージを作成できます。Spring Boot には、Maven と Gradle の両方を直接サポートする buildpack が含まれています。つまり、1 つのコマンドを入力するだけで、ローカルで実行されている Docker デーモンに適切なイメージをすばやく取得できます。

Maven および Gradle で buildpacks を使用する方法については、個々のプラグインのドキュメントを参照してください。

Paketo Spring Boot buildpack [GitHub] (英語) も更新され、layers.idx ファイルをサポートするようになりました。そのため、Paketo Spring Boot buildpack [GitHub] (英語) に適用されたカスタマイズは、buildpack によって作成されたイメージに反映されます。
再現性のあるビルドとコンテナーイメージのキャッシュを実現するために、Buildpacks はアプリケーションリソースのメタデータ(ファイルの「最終変更」情報など)を操作できます。アプリケーションが実行時にそのメタデータに依存しないようにする必要があります。Spring Boot は静的リソースを提供するときにその情報を使用できますが、これは spring.web.resources.cache.use-last-modified で無効にすることができます

4. 次のステップ

効率的なコンテナーイメージを構築する方法を学習したら、Kubernetes などのクラウドプラットフォームへのアプリケーションのデプロイについて読むことができます。