1. 序章

Dockerは、自己完結型アプリケーションを作成するための事実上の標準です。 バージョン2.3.0以降、 Spring Boot には、効率的なDockerイメージの作成に役立ついくつかの拡張機能が含まれています。 したがって、は、アプリケーションを異なるレイヤーに分解することを可能にします

つまり、ソースコードは独自のレイヤーに存在します。 したがって、独立して再構築でき、効率と起動時間が向上します。 このチュートリアルでは、Spring Bootの新機能を活用してDockerレイヤーを再利用する方法を説明します。

2. Dockerのレイヤードジャー

Dockerコンテナーは、ベースイメージと追加のレイヤーで構成されます。 レイヤーが構築されると、それらはキャッシュされたままになります。 したがって、後続の世代ははるかに高速になります。

下位レベルのレイヤーを変更すると、上位レベルのレイヤーも再構築されます。 したがって、頻繁に変更されないレイヤーは下部に残し、頻繁に変更されるレイヤーは上部に配置する必要があります。

同様に、Spring Bootを使用すると、アーティファクトのコンテンツをレイヤーにマッピングできます。 レイヤーのデフォルトのマッピングを見てみましょう。

ご覧のとおり、アプリケーションには独自のレイヤーがあります。 ソースコードを変更すると、独立したレイヤーのみが再構築されます。 ローダーと依存関係はキャッシュされたままになり、Dockerイメージの作成と起動時間が短縮されます。 Spring Bootでそれを行う方法を見てみましょう!

3. SpringBootを使用した効率的なDockerイメージの作成

Dockerイメージを構築する従来の方法では、SpringBootはfatjarアプローチを使用します。 その結果、単一のアーティファクトにすべての依存関係とアプリケーションのソースコードが埋め込まれます。 したがって、ソースコードを変更すると、レイヤー全体が再構築されます。

3.1. SpringBootを使用したレイヤー構成

Spring Bootバージョン2.3.0では、Dockerイメージの生成を改善するための2つの新機能が導入されています。

  • ビルドパックのサポートはアプリケーションのJavaランタイムを提供するため、DockerfileをスキップしてDockerイメージを自動的にビルドできるようになりました
  • レイヤードjarは、Dockerレイヤー生成を最大限に活用するのに役立ちます

このチュートリアルでは、階層化されたjarアプローチを拡張します。

最初に、Mavenレイヤードjarをセットアップします。 アーティファクトをパッケージ化するときに、レイヤーを生成します。 jarファイルを調べてみましょう。

jar tf target/spring-boot-docker-0.0.1-SNAPSHOT.jar

ご覧のとおり、fatjar内のBOOT-INFフォルダーに新しいlayers.idxファイルが作成されます。 確かに、依存関係、リソース、およびアプリケーションのソースコードを独立したレイヤーにマップします。

BOOT-INF/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/"

3.2. レイヤーとの相互作用

アーティファクト内のレイヤーをリストしてみましょう。

java -Djarmode=layertools -jar target/docker-spring-boot-0.0.1.jar list

結果は、layers.idxファイルのコンテンツの単純なビューを提供します。

dependencies
spring-boot-loader
snapshot-dependencies
application

レイヤーをフォルダーに抽出することもできます。

java -Djarmode=layertools -jar target/docker-spring-boot-0.0.1.jar extract

次に、次のセクションで説明するように、Dockerfile内のフォルダーを再利用できます。

$ ls
application/
snapshot-dependencies/
dependencies/
spring-boot-loader/

3.3. Dockerfileの構成

Dockerの機能を最大限に活用するには、イメージにレイヤーを追加する必要があります。

まず、fatjarファイルをベースイメージに追加しましょう。

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

最後に、抽出したフォルダーをコピーして、対応するDockerレイヤーを追加しましょう。

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"]

この構成では、ソースコードを変更すると、アプリケーション層のみが再構築されます。 残りはキャッシュされたままになります。

4. カスタムレイヤー

すべてが魅力のように機能しているようです。 しかし、注意深く見ると、依存関係レイヤーはビルド間で共有されていません。 つまり、内部のものも含めて、すべてが単一のレイヤーになります。 したがって、内部ライブラリのクラスを変更すると、すべての依存関係レイヤーが再構築されます。

4.1. SpringBootを使用したカスタムレイヤー構成

Spring Bootでは、別の構成ファイルを使用してカスタムレイヤーを調整できます。

<layers xmlns="http://www.springframework.org/schema/boot/layers"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
                     https://www.springframework.org/schema/boot/layers/layers-2.3.xsd">
    <application>
        <into layer="spring-boot-loader">
            <include>org/springframework/boot/loader/**</include>
        </into>
        <into layer="application" />
    </application>
    <dependencies>
        <into layer="snapshot-dependencies">
            <include>*:*:*SNAPSHOT</include>
        </into>
        <into layer="dependencies" />
    </dependencies>
    <layerOrder>
        <layer>dependencies</layer>
        <layer>spring-boot-loader</layer>
        <layer>snapshot-dependencies</layer>
        <layer>application</layer>
    </layerOrder>
</layers>

ご覧のとおり、依存関係とリソースをレイヤーにマッピングして順序付けしています。 さらに、必要な数のカスタムレイヤーを追加できます。

ファイルにlayers.xmlという名前を付けましょう。 次に、Mavenで、レイヤーをカスタマイズするようにこのファイルを構成できます。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <layers>
            <enabled>true</enabled>
            <configuration>${project.basedir}/src/layers.xml</configuration>
        </layers>
    </configuration>
</plugin>

アーティファクトをパッケージ化すると、結果はデフォルトの動作と同様になります。

4.2. 新しいレイヤーの追加

アプリケーションクラスを追加して、内部依存関係を作成しましょう。

<into layer="internal-dependencies">
    <include>com.baeldung.docker:*:*</include>
</into>

さらに、新しいレイヤーを注文します。

<layerOrder>
    <layer>internal-dependencies</layer>
</layerOrder>

その結果、fat jar内のレイヤーを一覧表示すると、新しい内部依存関係が表示されます。

dependencies
spring-boot-loader
internal-dependencies
snapshot-dependencies
application

4.3. Dockerfileの構成

抽出したら、Dockerイメージに新しい内部レイヤーを追加できます。

COPY --from=builder internal-dependencies/ ./

したがって、イメージを生成すると、Dockerが内部依存関係を新しいレイヤーとして構築する方法がわかります。

$ mvn package
$ docker build -f src/main/docker/Dockerfile . --tag spring-docker-demo
....
Step 8/11 : COPY --from=builder internal-dependencies/ ./
 ---> 0e138e074118
.....

その後、Dockerイメージのレイヤーの構成を履歴で確認できます。

$ docker history --format "{{.ID}} {{.CreatedBy}} {{.Size}}" spring-docker-demo
c0d77f6af917 /bin/sh -c #(nop)  ENTRYPOINT ["java" "org.s… 0B
762598a32eb7 /bin/sh -c #(nop) COPY dir:a87b8823d5125bcc4… 7.42kB
80a00930350f /bin/sh -c #(nop) COPY dir:3875f37b8a0ed7494… 0B
0e138e074118 /bin/sh -c #(nop) COPY dir:db6f791338cb4f209… 2.35kB
e079ad66e67b /bin/sh -c #(nop) COPY dir:92a8a991992e9a488… 235kB
77a9401bd813 /bin/sh -c #(nop) COPY dir:f0bcb2a510eef53a7… 16.4MB
2eb37d403188 /bin/sh -c #(nop)  ENV JAVA_HOME=/opt/java/o… 0B

ご覧のとおり、レイヤーにはプロジェクトの内部依存関係が含まれています。

5. 結論

このチュートリアルでは、効率的なDockerイメージを生成する方法を示しました。 つまり、新しいSpringBoot機能を使用してレイヤードjarを作成しました。 単純なプロジェクトの場合、デフォルト構成を使用できます。 また、レイヤーを再利用するためのより高度な構成についても説明しました。

いつものように、コードはGitHubから入手できます。