Docker 中的 Slow gradle build。缓存渐变构建

2022-09-02 19:47:32

我正在做大学项目,我们需要一次运行多个Spring Boot应用程序。

我已经使用gradle docker image配置了多阶段构建,然后在openjdk:jre image中运行应用程序。

这是我的 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分钟。

构建日志的一部分: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依赖项的卷,但我不知道这是否是问题的核心。我也找不到很好的例子。

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


答案 1

构建需要花费大量时间,因为每次构建 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 插件和依赖项缓存位于 . 必须设置为 与 不同的内容。 在父级中,Docker 映像被定义为卷,并在每个映像层之后被擦除。$GRADLE_USER_HOME/cachesGRADLE_USER_HOME/home/gradle/.gradle/home/gradle/.gradle

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

在阶段中,复制 Gradle 缓存以避免再次下载依赖项:。builderCOPY --from=cache /home/gradle/cache_home /home/gradle/.gradle

仅当更改时,才会重建舞台。当 Java 类发生更改时,将重用具有所有依赖项的高速缓存映像层。cachebuild.gradle

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

gradle clean build jib
docker-compose up

答案 2

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

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

这样,仅当 gradle 文件发生更改时,以前的图层才会失效。

我还没有用Java / Gradle做到这一点,但我在这篇博客文章的指导下,在Rust项目中遵循了相同的模式。