Spring Bootはバージョン2.3.0をリリースした。これはビルドパックによるDockerのサポート、レイヤ化されたイメージ、グレースフルシャットダウンのサポート、livenessとreadinessプローブを追加している。もう一つの注目すべき変更点は、LTSバージョンの8と11のサポートを維持しつつ、Java 14をサポートしていることだ。
ビルドパックはDockerfilesの代替品だ。ビルドパックは、Dockerコンテナ内のアプリケーションを実行するために必要なソフトウェアを自動的に検出する。例えば、それはアプリケーションで使用されているJavaのバージョンを検出する。そのバージョンに基づいて、ビルドパックはビルドパックの中で指定されたJREを選択して、Dockerイメージをビルドする。MavenやGradleでは、以下のコマンドでDockerイメージを作成する。
spring-boot:build-image
ビルドパックをベースにしたDockerイメージを作成するためには何の設定も必要なかったことに注意してください。
bootBuildImage タスクを使ってビルドパックの設定を変更できる。例えば、ビルドファイル内の以下のSpring Boot Maven Pluginの設定では、Dockerイメージ名を変更する方法を示している。
<configuration>
<image>
<name> infoq.com/${project.artifactId}:${project.version}</name>
</image>
<configuration>
Dockerイメージ名の指定はコマンドラインからもできる。
mvn/gradle bootBuildImage --imageName=infoq.com/my-app:v1
Dockerイメージはレイヤで動作する。最新レイヤとしてアプリケーションアーティファクトを追加することは、イメージのディスクサイズを小さくできる。開発者は通常、アプリケーションの成果物を JAR ファイルとして保存する。その欠点は、JARファイルにはコードのように頻繁に変更される要素が含まれることだ。しかし、JARファイルには依存関係のような変更頻度の低い要素も含まれる。Dockerイメージ内のバージョン間の変更は差分として保存される。アプリケーションのバージョンごとにJARファイルが保存されている場合、差分はかなり大きくなり、ディスク容量を多く消費する。ビルドパックは、より頻繁に変化するものに基づいてアプリケーションを複数のレイヤに分割することで、必要なスペースを削減する。
アーティファクトを複数のレイヤに分割する機能は、Dockerfile内でも利用できる。ビルドパックはいくつかの形式の設定を提供するが、Dockerfilesは結果のイメージを完全にコントロールできる。そのため、開発者はビルドパックよりもDockerfileを好むことがある。Dockerfileの使用を選択する際には、アーティファクトのレイヤを分割することをお勧めする。ビルドパックに比べれば手間はかかるが、一回限りの作業だ。
最初に、spring-boot-maven-pluginを設定し、レイヤ化されたJARを生成する。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
</plugins>
</build>
このプラグインの設定により、アプリケーションが4つの部分に分割されていることを確認する。それらは依存関係、spring-boot-loader、スナップショットの依存性、アプリケーションだ。
下のスニペットはマルチステージDockerfileを示している。最初のステップでは、アプリケーションは特定の jar モードの引数で実行される。これにより、4つのパーツがそれぞれ独自のディレクトリに格納される。次の段階では、それら4つのディレクトリをDockerイメージの内部に別々のレイヤでコピーし、エントリポイントを指定される。
FROM adoptopenjdk:14-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:14-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イメージを構築し、その後イメージに基づいてコンテナを実行できる。
docker build . --tag infoq
docker run -p8080:8080 infoq
Liveness と Readiness のプローブは、Kubernetesのようなコンテナシステムで使用される。Kubernetesはアプリケーションを新しいバージョンにアップグレードする際に新しいポッドを起動する。このポッドは、旧バージョンが入っている旧ポッドの横で起動する。新しいポッドの準備ができたら、トラフィックを受け入れ、古いポッドは撤去される。しかし、コンテナの準備ができても、アプリケーションが完全に起動しないことが多い。Spring Bootの場合は数秒かかることがある。デフォルトでは、これは、古いアプリケーションがシャットダウンされている間に、完全に起動していない新しいアプリケーションがすでにトラフィックを受信していることを意味する。
これは、アプリケーションの起動後に利用可能となったアプリケーション内部の特定の URL をポーリングすることで解決できる。URLが利用可能になると、アプリケーションはトラフィックを受信する準備ができ、古いバージョンを削除できる。Kubernetes内では、これはいわゆるReadinessプローブによって実装できる。
Spring Bootは Readiness プローブをデフォルトでサポートするようになった。例えば、 management.health.probes.enabled=true というアプリケーションの設定を有効にした後で公開される URL http://localhost:8080/actuator/health/readinessを通して実現する。
Readiness プローブとは別に、Kubernetesには liveness プローブという概念がある。これは、事前に定義された間隔でアプリケーションが正常に機能しているかどうかを確認するために使用される。アプリケーションが応答しない場合は、ポッドを再起動する。Spring Bootは次のようなエンドポイントも提供している。 http://localhost:8080/actuator/health/liveness
これらのさまざまなエンドポイントは、すぐに動作するが、それらを設定することも可能だ。例えばデータベースの起動を待つこともできる。
グレースフルシャットダウンは、アプリケーションを停止した後、すでに受け付けてしまったリクエストを一定期間継続するために使用される。
次のスニペットは、グレースフルシャットダウンをどのように有効にし、タイムアウト値を30秒に設定する方法を示している。
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
これは、アプリケーションを停止するときに、新しいリクエストが許可されていないことを意味する。しかし、すでに受け付けたリクエストは、アプリケーションが完全に停止するまでに30秒の時間がある。