1. 序章

より多くの組織がコンテナと仮想サーバーに移行するにつれて、Dockerはソフトウェア開発ワークフローのより重要な部分になりつつあります。 そのために、 Spring Boot 2.3の優れた新機能の1つは、SpringBootアプリケーション用のDockerイメージを簡単に作成できることです。

このチュートリアルでは、Spring Bootアプリケーション用のDockerイメージを作成する方法を見ていきます。

2. 従来のDockerビルド

Spring Bootを使用してDockerイメージを構築する従来の方法は、Dockerfileを使用することです。 以下は簡単な例です。

FROM openjdk:8-jdk-alpine
EXPOSE 8080
ARG JAR_FILE=target/demo-app-1.0.0.jar
ADD ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

次に、 dockerbuildコマンドを使用してDockerイメージを作成できます。 これはほとんどのアプリケーションで正常に機能しますが、いくつかの欠点があります。

まず、SpringBootによって作成されたファットジャーを使用しています。 これは、特にコンテナ化された環境での起動時間に影響を与える可能性があります。 代わりに、jarファイルの展開された内容を追加することで、起動時間を節約できます。

次に、Dockerイメージはレイヤーに組み込まれています。 Spring Bootファットジャーの性質により、すべてのアプリケーションコードとサードパーティライブラリが単一のレイヤーに配置されます。 これは、コードが1行だけ変更された場合でも、レイヤー全体を再構築する必要があることを意味します

ビルドする前にjarを分解することにより、アプリケーションコードとサードパーティライブラリはそれぞれ独自のレイヤーを取得します。 これにより、Dockerのキャッシュメカニズムを利用できます。 これで、1行のコードが変更された場合、対応するレイヤーのみを再構築する必要があります。

これを念頭に置いて、Spring BootがDockerイメージの作成プロセスをどのように改善したかを見てみましょう。

3. ビルドパック

ビルドパックは、フレームワークとアプリケーションの依存関係を提供するツールです

たとえば、Spring Boot fat jarが与えられた場合、ビルドパックはJavaランタイムを提供します。 これにより、Dockerfileをスキップして、適切なDockerイメージを自動的に取得できます。

Spring Bootには、ビルドパックのMavenとGradleの両方のサポートが含まれています。 たとえば、Mavenを使用してビルドするには、次のコマンドを実行します。

./mvnw spring-boot:build-image

何が起こっているかを確認するために、関連する出力のいくつかを見てみましょう。

[INFO] Building jar: target/demo-0.0.1-SNAPSHOT.jar
...
[INFO] Building image 'docker.io/library/demo:0.0.1-SNAPSHOT'
...
[INFO]  > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 100%
...
[INFO]     [creator]     ===> DETECTING
[INFO]     [creator]     5 of 15 buildpacks participating
[INFO]     [creator]     paketo-buildpacks/bellsoft-liberica 2.8.1
[INFO]     [creator]     paketo-buildpacks/executable-jar    1.2.8
[INFO]     [creator]     paketo-buildpacks/apache-tomcat     1.3.1
[INFO]     [creator]     paketo-buildpacks/dist-zip          1.3.6
[INFO]     [creator]     paketo-buildpacks/spring-boot       1.9.1
...
[INFO] Successfully built image 'docker.io/library/demo:0.0.1-SNAPSHOT'
[INFO] Total time:  44.796 s

最初の行は、通常のMavenパッケージと同じように、標準のファットジャーを作成したことを示しています。

次の行は、Dockerイメージのビルドを開始します。 直後に、Packetoビルダーでビルドがプルされるのがわかります。

Packetoは、クラウドネイティブビルドパックの実装です。 プロジェクトを分析し、必要なフレームワークとライブラリを決定する作業を行います。 この場合、Spring Bootプロジェクトがあると判断し、必要なビルドパックを追加します。

最後に、生成されたDockerイメージと合計ビルド時間が表示されます。 初めてビルドするときは、ビルドパックのダウンロードとさまざまなレイヤーの作成にかなりの時間を費やしていることに注目してください。

ビルドパックの優れた機能の1つは、Dockerイメージが複数のレイヤーであることです。 したがって、アプリケーションコードのみを変更すると、後続のビルドがはるかに高速になります。

...
[INFO]     [creator]     Reusing layer 'paketo-buildpacks/executable-jar:class-path'
[INFO]     [creator]     Reusing layer 'paketo-buildpacks/spring-boot:web-application-type'
...
[INFO] Successfully built image 'docker.io/library/demo:0.0.1-SNAPSHOT'
...
[INFO] Total time:  10.591 s

4. レイヤードジャー

場合によっては、ビルドパックを使用したくない場合があります。インフラストラクチャがすでに別のツールに関連付けられているか、再利用したいカスタムDockerfileがすでにある可能性があります。

これらの理由により、SpringBootはレイヤー化されたjarを使用したDockerイメージの構築もサポートしています。 それがどのように機能するかを理解するために、典型的なSpring Bootファットジャーのレイアウトを見てみましょう。

org/
  springframework/
    boot/
  loader/
...
BOOT-INF/
  classes/
...
lib/
...

ファットジャーは、3つの主要な領域で構成されています。

  • Springアプリケーションを起動するために必要なブートストラップクラス
  • アプリケーションコード
  • サードパーティのライブラリ

階層化されたjarの場合、構造は似ていますが、fatjar内の各ディレクトリをレイヤーにマップする新しいlayers.idxファイルを取得します。

- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"

すぐに使用できるSpringBootは、次の4つのレイヤーを提供します。

  • 依存関係:サードパーティからの一般的な依存関係
  • snapshot-dependencies :サードパーティからのスナップショットの依存関係
  • リソース:静的リソース
  • アプリケーション:アプリケーションコードとリソース

目標は、アプリケーションコードとサードパーティライブラリを、それらが変更される頻度を反映するレイヤーに配置することです

たとえば、アプリケーションコードは最も頻繁に変更される可能性が高いため、独自のレイヤーを取得します。 さらに、各レイヤーは独自に進化することができ、レイヤーが変更された場合にのみ、Dockerイメージ用に再構築されます。

新しい階層化されたjar構造を理解したので、それを利用してDockerイメージを作成する方法を見てみましょう。

4.1. レイヤードジャーの作成

まず、レイヤードjarを作成するようにプロジェクトを設定する必要があります。 Mavenの場合、これは、POMのSpringBootプラグインセクションに新しい構成を追加することを意味します。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <layers>
            <enabled>true</enabled>
        </layers>
    </configuration>
</plugin>

この構成では、Maven package コマンド(およびその依存コマンドのいずれか)は、前述の4つのデフォルトレイヤーを使用して新しいレイヤードjarを生成します。

4.2. レイヤーの表示と抽出

次に、Dockerイメージに適切なレイヤーが含まれるように、jarからレイヤーを抽出する必要があります。

階層化されたjarのレイヤーを調べるには、次のコマンドを実行します。

java -Djarmode=layertools -jar demo-0.0.1.jar list

次に、それらを抽出するために、次を実行します。

java -Djarmode=layertools -jar demo-0.0.1.jar extract

4.3. Dockerイメージの作成

これらのレイヤーをDockerイメージに組み込む最も簡単な方法は、Dockerfileを使用することです。

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

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

このDockerfileは、ファットjarからレイヤーを抽出し、各レイヤーをDockerイメージにコピーします。 各COPYディレクティブは、最終的なDockerイメージに新しいレイヤーを作成します。

このDockerfileをビルドすると、レイヤー化されたjarの各レイヤーが独自のレイヤーとしてDockerイメージに追加されることがわかります。

...
Step 6/10 : COPY --from=builder dependencies/ ./
 ---> 2c631b8f9993
Step 7/10 : COPY --from=builder snapshot-dependencies/ ./
 ---> 26e8ceb86b7d
Step 8/10 : COPY --from=builder spring-boot-loader/ ./
 ---> 6dd9eaddad7f
Step 9/10 : COPY --from=builder application/ ./
 ---> dc80cc00a655
...

5. 結論

このチュートリアルでは、SpringBootを使用してDockerイメージを構築するさまざまな方法を見てきました。 ビルドパックを使用すると、ボイラープレートやカスタム構成なしで適切なDockerイメージを取得できます。 または、もう少し努力すれば、階層化されたjarを使用して、より調整されたDockerイメージを取得できます。

このチュートリアルのすべての例は、GitHubにあります。

JavaとDockerの使用の詳細については、jibチュートリアルを確認してください。