web-dev-qa-db-ja.com

DockerでのGradleビルドが遅い。 Gradleビルドのキャッシュ

複数のSpring Bootアプリケーションを一度に実行する必要がある大学のプロジェクトを行っています。

Gradle Dockerイメージでマルチステージビルドを構成してから、openjdk:jreイメージでアプリを実行しました。

ここに私のDockerfileがあります:

_FROM gradle:5.3.0-jdk11-slim as builder
USER root
WORKDIR /usr/src/Java-code
COPY . /usr/src/Java-code/

RUN gradle bootJar

FROM openjdk:11-jre-slim
EXPOSE 8080
WORKDIR /usr/src/Java-app
COPY --from=builder /usr/src/Java-code/build/libs/*.jar ./app.jar
ENTRYPOINT ["Java", "-jar", "app.jar"]
_

私はすべてをdocker-composeでビルドして実行しています。 docker-composeの一部:

_ website_server:
    build: website-server
    image: website-server:latest
    container_name: "website-server"
    ports:
      - "81:8080"
_

もちろん、最初のビルドには時間がかかります。 Dockerは依存関係をすべて引き出しています。そして、私はそれで大丈夫です。

今のところすべてが問題なく動作していますが、コードの小さな変更ごとに、1つのアプリのビルド時間は約1分です。

ビルドログの一部:_docker-compose up --build_

_Step 1/10 : FROM gradle:5.3.0-jdk11-slim as builder
 ---> 668e92a5b906
Step 2/10 : USER root
 ---> Using cache
 ---> dac9a962d8b6
Step 3/10 : WORKDIR /usr/src/Java-code
 ---> Using cache
 ---> e3f4528347f1
Step 4/10 : COPY . /usr/src/Java-code/
 ---> Using cache
 ---> 52b136a280a2
Step 5/10 : RUN gradle bootJar
 ---> Running in 88a5ac812ac8

Welcome to Gradle 5.3!

Here are the highlights of this release:
 - Feature variants AKA "optional dependencies"
 - Type-safe accessors in Kotlin precompiled script plugins
 - Gradle Module Metadata 1.0

For more details see https://docs.gradle.org/5.3/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)
> Task :compileJava
> Task :processResources
> Task :classes
> Task :bootJar

BUILD SUCCESSFUL in 48s
3 actionable tasks: 3 executed
Removing intermediate container 88a5ac812ac8
 ---> 4f9beba838ed
Step 6/10 : FROM openjdk:11-jre-slim
 ---> 0e452dba629c
Step 7/10 : EXPOSE 8080
 ---> Using cache
 ---> d5519e55d690
Step 8/10 : WORKDIR /usr/src/Java-app
 ---> Using cache
 ---> 196f1321db2c
Step 9/10 : COPY --from=builder /usr/src/Java-code/build/libs/*.jar ./app.jar
 ---> d101eefa2487
Step 10/10 : ENTRYPOINT ["Java", "-jar", "app.jar"]
 ---> Running in ad02f0497c8f
Removing intermediate container ad02f0497c8f
 ---> 0c63eeef8c8e
Successfully built 0c63eeef8c8e
Successfully tagged website-server:latest
_

Starting a Gradle Daemon (subsequent builds will be faster)の後でフリーズするたび

Gradleの依存関係がキャッシュされたボリュームを追加することを考えていましたが、それが問題の核心かどうかはわかりません。また、その良い例を見つけることができませんでした。

ビルドをスピードアップする方法はありますか?

8
PAwel_Z

DockerイメージがビルドされるたびにGradleがすべてのプラグインと依存関係をダウンロードするため、ビルドには時間がかかります。

イメージのビルド時にボリュームをマウントする方法はありません。ただし、すべての依存関係をダウンロードし、Dockerイメージレイヤーとしてキャッシュされる新しいステージを導入することは可能です。

FROM gradle:5.6.4-jdk11 as cache
RUN mkdir -p /home/gradle/cache_home
ENV GRADLE_USER_HOME /home/gradle/cache_home
COPY build.gradle /home/gradle/Java-code/
WORKDIR /home/gradle/Java-code
RUN gradle clean build -i --stacktrace

FROM gradle:5.6.4-jdk11 as builder
COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle
COPY . /usr/src/Java-code/
WORKDIR /usr/src/Java-code
RUN gradle bootJar -i --stacktrace

FROM openjdk:11-jre-slim
EXPOSE 8080
USER root
WORKDIR /usr/src/Java-app
COPY --from=builder /usr/src/Java-code/build/libs/*.jar ./app.jar
ENTRYPOINT ["Java", "-jar", "app.jar"]

Gradleプラグインと依存関係キャッシュは$GRADLE_USER_HOME/cachesにあります。 GRADLE_USER_HOME/home/gradle/.gradleとは異なる値に設定する必要があります。親Gradle Dockerイメージの/home/gradle/.gradleはボリュームとして定義され、各イメージレイヤーの後に消去されます。

サンプルコードでは、GRADLE_USER_HOME/home/gradle/cache_homeに設定されています。

builderステージでは、依存関係の再ダウンロードを回避するためにGradleキャッシュがコピーされます:COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle

ステージcacheは、build.gradleが変更された場合にのみ再構築されます。 Javaクラスが変更されると、すべての依存関係を持つキャッシュされた画像レイヤーが再利用されます。

この変更によりビルド時間を短縮できますが、Javaアプリケーションは Jib by Googleを使用してDockerイメージをビルドするよりクリーンな方法です。 Jib Gradleプラグイン)があります これにより、手動でDockerfileを作成せずにJavaアプリケーションのコンテナイメージを構築できます。アプリケーションを使用してイメージを構築し、コンテナを実行することは、次のようになります。

gradle clean build jib
docker-compose up
11
Evgeniy Khyst

Dockerはそのイメージを「レイヤー」にキャッシュします。実行する各コマンドはレイヤーです。特定のレイヤーで検出された各変更は、それ以降のレイヤーを無効にします。キャッシュが無効化されている場合は、無効化されたレイヤーを最初から構築する必要があります依存関係を含みます

ビルド手順を分割することをお勧めします。依存関係の仕様のみをイメージにコピーする前のレイヤーを用意し、Gradleが依存関係をダウンロードするコマンドを実行します。 これが完了したら、ソースをコピーした場所と同じ場所にソースをコピーし、実際のビルドを実行します。

このように、gradleファイルが変更された場合にのみ、以前のレイヤーが無効になります。

私はこれをJava/Gradleで実行していませんが、Rustプロジェクト、 this ブログ記事でガイド)を使用して同じパターンに従っています。

3
asthasr

BuildKit を試して使用できます(現在、デフォルトで latest docker-compose 1.25 でアクティブ化されています)

Speed up your Java application Docker images build with BuildKit! "from Aboullaite Med 」を参照してください。

(これはMaven用でしたが、同じ考え方がGradleにも適用されます)

次のDockerfileについて考えてみましょう。

FROM maven:3.6.1-jdk-11-slim AS build  
USER MYUSER  
RUN mvn clean package  

2行目を変更すると、誤った依存関係のために常にMavenキャッシュが無効になり、非効率的なキャッシュの問題が発生します。

BuildKitは、並行ビルドグラフソルバーを導入することでこの制限を解決します。これにより、ビルドステップを並列に実行し、最終結果に影響を与えないコマンドを最適化できます。

さらに、Buildkitは、ローカルソースファイルへのアクセスを最適化する、繰り返されるビルド呼び出しの間にファイルに対して行われた更新のみを追跡します。したがって、作業を開始する前にローカルファイルが読み取られるかアップロードされるのを待つ必要はありません。

1
VonC

他の人への追加が答えるように、インターネット接続が遅い場合、依存関係を毎回ダウンロードするため、すでにダウンロードされている依存関係を維持するためにsonatypeネクサスを設定することができます。

0

私はドッカーの内部についてはあまり知りませんが、問題は新しいdocker buildコマンドは、すべてのファイルをコピーしてビルドします(少なくとも1つのファイルで変更を検出した場合)。その後、これはいくつかのjarを変更する可能性が高く、2番目のステップも実行する必要があります。

私の提案は、(Dockerの外の)ターミナルでビルドし、Dockerのみがアプリイメージをビルドすることです。

これはgradleプラグインで自動化することもできます:

0
Vetras

他の答えが述べたように、ドッカーはレイヤーの各ステップをキャッシュします。ダウンロードされた依存関係のみを何らかの方法でレイヤーに取得できる場合、依存関係が変更されていないと想定して、毎回再ダウンロードする必要はありません。

残念ながら、gradleにはこれを行うための組み込みタスクがありません。しかし、それでも回避できます。これが私がしたことです:

# Only copy dependency-related files
COPY build.gradle gradle.properties settings.gradle /app/

# Only download dependencies
# Eat the expected build failure since no source code has been copied yet
RUN gradle clean build --no-daemon > /dev/null 2>&1 || true

# Copy all files
COPY ./ /app/

# Do the actual build
RUN gradle clean build --no-daemon

また、.dockerignoreファイルには少なくとも次のアイテムが含まれているため、イメージのビルド時にdockerビルドコンテキストで送信されません。

.gradle/
bin/
build/
gradle/
0
zwbetz