在 Docker 中构建缓慢的 gradle。缓存gradle构建

PAw*_*l_Z 13 java caching gradle docker gradlew

我正在做大学项目,我们需要一次运行多个 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"]
Run Code Online (Sandbox Code Playgroud)

我正在使用 docker-compose 构建和运行所有内容。docker-compose 的一部分:

 website_server:
    build: website-server
    image: website-server:latest
    container_name: "website-server"
    ports:
      - "81:8080"
Run Code Online (Sandbox Code Playgroud)

当然,第一次构建需要很长时间。Docker 正在拉取它的所有依赖项。我对此没有意见。

目前一切正常,但代码中的每一个小变化都会导致一个应用程序的构建时间大约为 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
Run Code Online (Sandbox Code Playgroud)

每次它冻结后 Starting a Gradle Daemon (subsequent builds will be faster)

我正在考虑使用缓存的 gradle 依赖项添加卷,但我不知道这是否是问题的核心。我也找不到很好的例子。

有没有办法加快构建速度?

Evg*_*yst 25

构建需要很多时间,因为每次构建 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"]
Run Code Online (Sandbox Code Playgroud)

Gradle 插件和依赖缓存位于$GRADLE_USER_HOME/caches. GRADLE_USER_HOME必须设置为不同于/home/gradle/.gradle. /home/gradle/.gradle在父 Gradle Docker 镜像中定义为卷并在每个镜像层之后被擦除。

在示例代码GRADLE_USER_HOME中设置为/home/gradle/cache_home.

builder舞台摇篮缓存复制,以避免重新下载的依赖关系:COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle

cache只有在build.gradle更改时才会重建舞台。当 Java 类发生更改时,会重用所有依赖项的缓存图像层。

这种修改可以减少构建时间,但是使用 Java 应用程序构建 Docker 镜像的更干净的方法是Google 的Jib。有一个Jib Gradle 插件,它允许为 Java 应用程序构建容器镜像,而无需手动创建 Dockerfile。使用应用程序构建映像并运行容器类似于:

gradle clean build jib
docker-compose up
Run Code Online (Sandbox Code Playgroud)

  • @Saris我遇到了同样的问题,并且能够使用“RUN gradle clean build -i --stacktrace -x bootJar”在缓存阶段排除“bootJar”,而不必担心指定“mainClassName”。 (3认同)
  • 多阶段构建,其中一个阶段仅包含上下文中的“build.gradle”,这绝对是正确的选择。通过仅复制“cache”中的“build.gradle”,您可以确保在 Gradle 构建文件未更改的情况下仅下载一次依赖项(Docker 将重新使用缓存) (2认同)

ast*_*asr 5

Docker 将其图像缓存在“层”中。您运行的每个命令都是一个层。在给定层中检测到的每个更改都会使其后面的层失效。如果缓存失效,那么失效的层必须从头开始构建,包括依赖项

我建议分开你的构建步骤。有一个前一层,仅将依赖项规范复制到映像中,然后运行一个命令,该命令将导致 Gradle 下载依赖项。完成后,将源代码复制到刚刚执行此操作的同一位置,然后运行真正的构建

这样,只有当gradle文件发生变化时,前面的层才会失效。

我还没有使用 Java/Gradle 完成此操作,但在这篇博文的指导下,我在 Rust 项目中遵循了相同的模式。


Vet*_*ras -2

我对 docker 内部了解不多,但我认为问题是每个新docker build命令都会复制所有文件并构建它们(如果它检测到至少一个文件中的更改)。那么这很可能会改变几个罐子,并且第二步也需要运行。

我的建议是在终端(docker 外部)上构建,并且仅 docker 构建应用程序映像。

这甚至可以通过 gradle 插件自动化: