1. 序章

このチュートリアルでは、DockerでMavenプロジェクトをビルドする方法を示します。 まず、シンプルな単一モジュールJavaプロジェクトから始めて、Dockerのマルチステージビルドを活用してビルドプロセスをドッキングする方法を示します。 次に、Buildkitを使用して複数のビルド間の依存関係をキャッシュする方法を示します。 最後に、マルチモジュールアプリケーションでレイヤーキャッシュを活用する方法について説明します。

2. マルチステージレイヤービルド

この記事では、Guavaを依存関係として使用する単純なJavaアプリケーションを作成します。 maven-assemblyプラグインを使用してファットJARを作成します。 コードとMaven構成はメイントピックではないため、この記事から省略されます。

マルチステージビルドは、Dockerビルドプロセスを最適化するための優れた方法です。 これらにより、プロセス全体を1つのファイルに保持でき、Dockerイメージを可能な限り小さく保つことができます。 。 最初の段階では、Mavenビルドを実行してファットJARを作成し、2番目の段階では、JARをコピーして、エントリポイントを定義します。

FROM maven:alpine as build
ENV HOME=/usr/app
RUN mkdir -p $HOME
WORKDIR $HOME
ADD . $HOME
RUN mvn package

FROM openjdk:8-jdk-alpine 
COPY --from=build /usr/app/target/single-module-caching-1.0-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar
ENTRYPOINT java -jar /app/runner.jar

このアプローチでは、Maven実行可能ファイルやソースコードが含まれないため、最終的なDockerイメージを小さく保つことができます。

Dockerイメージを作成しましょう:

docker build -t maven-caching .

次に、画像からコンテナを開始しましょう。

docker run maven-caching

コード内の何かを変更してビルドを再実行すると、Maven packageタスクの前のすべてのコマンドがキャッシュされてすぐに実行されることがわかります。 コードはプロジェクトの依存関係よりも頻繁に変更されるため、依存関係のダウンロードとコードのコンパイルを分離して、Dockerレイヤーキャッシュを使用してビルド時間を改善できます

FROM maven:alpine as build
ENV HOME=/usr/app
RUN mkdir -p $HOME
WORKDIR $HOME
ADD pom.xml $HOME
RUN mvn verify --fail-never
ADD . $HOME
RUN mvn package

FROM openjdk:8-jdk-alpine 
COPY --from=build /usr/app/target/single-module-caching-1.0-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar
ENTRYPOINT java -jar /app/runner.jar

Dockerがキャッシュからレイヤーをフェッチするため、コードのみを変更したときに後続のビルドを実行すると、はるかに高速になります。

3. BuildKitを使用したキャッシング

Dockerバージョン18.09では、既存のビルドシステムのオーバーホールとしてBuildKitが導入されています。 オーバーホールの背後にある考え方は、パフォーマンス、ストレージ管理、およびセキュリティを改善することです。 BuildKitを活用して、複数のビルド間の状態を維持できます。 このように、永続的なストレージがあるため、Mavenは毎回依存関係をダウンロードしません。 DockerインストールでBuildKitを有効にするには、daemon.jsonファイルを編集する必要があります。

...
{
"features": {
    "buildkit": true
}}
...

BuildKitを有効にした後、Dockerfileを次のように変更できます。

FROM maven:alpine as build
ENV HOME=/usr/app
RUN mkdir -p $HOME
WORKDIR $HOME
ADD . $HOME
RUN --mount=type=cache,target=/root/.m2 mvn -f $HOME/pom.xml clean package

FROM openjdk:8-jdk-alpine
COPY --from=build /usr/app/target/single-module-caching-1.0-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar
ENTRYPOINT java -jar /app/runner.jar

コードまたはpom.xmlファイルを変更すると、Dockerは常にADDを実行してMavenコマンドを実行します。 Mavenは依存関係をダウンロードする必要があるため、ビルド時間は最初の実行で最長になります。 後続の実行ではローカルの依存関係が使用され、実行速度が大幅に向上します。

このアプローチでは、依存関係のストレージとしてDockerボリュームを維持する必要があります。 場合によっては、Dockerfileの-Uフラグを使用して依存関係を更新するようにMavenに強制する必要があります。

4. マルチモジュールMavenプロジェクトのキャッシング

前のセクションでは、さまざまな方法を活用して、単一モジュールのMavenプロジェクトのDockerイメージのビルド時間を短縮する方法を示しました。 より複雑なアプリケーションの場合、これらの方法は最適ではありません。 マルチモジュールMavenプロジェクトには通常、アプリケーションのエントリポイントである1つのモジュールがあります。 1つ以上のモジュールにロジックが含まれており、依存関係としてリストされています。

サブモジュールは依存関係としてリストされているため、Dockerがレイヤーキャッシングを実行できなくなり、Mavenがすべての依存関係を再度ダウンロードするようにトリガーされます。 BuildKitを使用したこのソリューションは、ほとんどの場合に適していますが、前述のように、更新されたサブモジュールをフェッチするには、時々強制的に更新する必要があります。このような状況を回避するために、レイヤーに投影し、Mavenインクリメンタルビルドを使用します。

FROM maven:alpine as build
ENV HOME=/usr/app
RUN mkdir -p $HOME
WORKDIR $HOME

ADD pom.xml $HOME
ADD core/pom.xml $HOME/core/pom.xml
ADD runner/pom.xml $HOME/runner/pom.xml

RUN mvn -pl core verify --fail-never
ADD core $HOME/core
RUN mvn -pl core install
RUN mvn -pl runner verify --fail-never
ADD runner $HOME/runner
RUN mvn -pl core,runner package

FROM openjdk:8-jdk-alpine
COPY --from=build /usr/app/runner/target/runner-0.0.1-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar
ENTRYPOINT java -jar /app/runner.jar

このDockerfileでは、すべての pom.xml ファイルをコピーし、各サブモジュールを段階的にビルドしてから、最後にアプリケーション全体をパッケージ化します。 経験則では、チェーンの後半でより頻繁に変更されるサブモジュールを作成します。

5. 結論

この記事では、Dockerを使用してMavenプロジェクトをビルドする方法について説明しました。 最初に、頻繁に変更されない部分をキャッシュするためにレイヤーを活用する方法について説明しました。 次に、BuildKitを使用してビルド間の状態を維持する方法について説明しました。 最後に、インクリメンタルビルドを使用してマルチモジュールMavenプロジェクトをビルドする方法を示しました。 いつものように、完全なコードはGitHubにあります。