【问题标题】:Docker incremental build does not reuse cacheDocker增量构建不重用缓存
【发布时间】:2021-08-04 19:59:07
【问题描述】:

我正在尝试通过应用此处解释的增量构建原则https://www.docker.com/blog/intro-guide-to-dockerfile-best-practices/ 以优化的方式构建一个巨大的 docker 映像。

不幸的是,每次我运行构建命令 docker 都会从头开始构建映像,因此我必须再次下载所有 maven 依赖项。

这是构建命令:

docker build \
    --build-arg MAVEN_SETTINGS_FILE="${HOME}/.m2/settings.xml" \
    --build-arg PROJECT_PATH="." \
    --file "${DOCKER_FILE}" \
    --tag "${IMAGE_TAG}" \
    . 

这里是Dockerfile:

# Global vars (for passing between stages)

ARG MAVEN_SETTINGS_FILE
ARG PROJECT_PATH

ARG APP_FOLDER=/app

# 1st stage

FROM maven:3.6-jdk-11 as build

# Build artifact

ARG APP_FOLDER
ENV APP_FOLDER ${APP_FOLDER}

ARG MAVEN_SETTINGS_FILE
ENV MAVEN_SETTINGS_FILE ${MAVEN_SETTINGS_FILE}

ARG PROJECT_PATH
ENV PROJECT_PATH ${PROJECT_PATH}

WORKDIR ${APP_FOLDER}

ADD ${MAVEN_SETTINGS_FILE} .

ADD ${PROJECT_PATH}/pom.xml .
RUN mvn package -s ${MAVEN_SETTINGS_FILE} -DskipTests

ADD ${PROJECT_PATH}/src src
RUN mvn package -s ${MAVEN_SETTINGS_FILE} -DskipTests

# 2nd stage: build image

FROM openjdk:11-jre-slim

ARG APP_FOLDER
ENV APP_FOLDER ${APP_FOLDER}

ENV TARGET_FOLDER ${APP_FOLDER}/target

WORKDIR ${APP_FOLDER}

# Copy the binary built in the 1st stage

COPY --from=build ${TARGET_FOLDER}/myapp-1.0.0-SNAPSHOT.jar .

EXPOSE 8080

CMD ["java", "-jar", "myapp-1.0.0-SNAPSHOT.jar"]

非常感谢您的任何建议!


编辑 1(为@sai)

嗨@sai,我通过docker history 命令检查了缓存层,发现缺少MVN 步骤!

IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
b78b3d09d314   16 minutes ago   /bin/sh -c #(nop)  CMD ["java" "-Doracle.jdb…   0B        
a2db7da187f0   16 minutes ago   /bin/sh -c #(nop)  EXPOSE 8080                  0B        
ed8f3dc45017   16 minutes ago   /bin/sh -c #(nop)  EXPOSE 8081                  0B        
715f12eba281   16 minutes ago   /bin/sh -c #(nop) COPY dir:33e2303bf32b392fc…   58.3MB    
4edb35a1b6f6   16 minutes ago   /bin/sh -c #(nop) COPY file:0b42aca8bb0ad316…   234kB     
56ecbfb34e74   16 minutes ago   /bin/sh -c #(nop) COPY dir:8497884af419f408f…   4.59kB    
255913b0fc25   16 minutes ago   /bin/sh -c #(nop) WORKDIR /app                  0B        
87c5b4ca34df   16 minutes ago   /bin/sh -c #(nop)  ENV TARGET_FOLDER=/app/ta…   0B        
5f95ab5b8d19   16 minutes ago   /bin/sh -c #(nop)  ENV APP_FOLDER=/app          0B        
da6d51b1d3b8   16 minutes ago   /bin/sh -c #(nop)  ARG APP_FOLDER               0B        
940f48bb6c92   3 days ago       /bin/sh -c set -eux;   arch="$(dpkg --print-…   142MB     
<missing>      3 days ago       /bin/sh -c #(nop)  ENV JAVA_VERSION=11.0.11+9   0B        
<missing>      3 days ago       /bin/sh -c #(nop)  ENV LANG=C.UTF-8             0B        
<missing>      3 days ago       /bin/sh -c #(nop)  ENV PATH=/usr/local/openj…   0B        
<missing>      3 days ago       /bin/sh -c { echo '#/bin/sh'; echo 'echo "$J…   27B       
<missing>      3 days ago       /bin/sh -c #(nop)  ENV JAVA_HOME=/usr/local/…   0B        
<missing>      3 days ago       /bin/sh -c set -eux;  apt-get update;  apt-g…   8.82MB    
<missing>      3 days ago       /bin/sh -c #(nop)  CMD ["bash"]                 0B        
<missing>      3 days ago       /bin/sh -c #(nop) ADD file:7362e0e50f30ff454…   69.3MB

我的观点是所有使用变量扩展的指令都不会生成缓存层。你也适合吗?


编辑 2(Helidon 演示)

我尝试使用 Helidon 演示进行挖掘,我观察到它没有在历史记录中显示 MVN 层。相反,它使用缓存层。

查看docker文件和第二次构建执行日志:

# 1st stage, build the app
FROM maven:3.6-jdk-11 as build

WORKDIR /helidon

# Create a first layer to cache the "Maven World" in the local repository.
# Incremental docker builds will always resume after that, unless you update
# the pom
ADD pom.xml .
RUN mvn package -Dmaven.test.skip -Declipselink.weave.skip

# Do the Maven build!
# Incremental docker builds will resume here when you change sources
ADD src src
RUN mvn package -DskipTests
RUN echo "done!"

# 2nd stage, build the runtime image
FROM openjdk:11-jre-slim
WORKDIR /helidon

# Copy the binary built in the 1st stage
COPY --from=build /helidon/target/helidon-quickstart-mp.jar ./
COPY --from=build /helidon/target/libs ./libs

CMD ["java", "-jar", "helidon-quickstart-mp.jar"]

EXPOSE 8080
Sending build context to Docker daemon  43.01kB
Step 1/13 : FROM maven:3.6-jdk-11 as build
 ---> e23b595c92ad
Step 2/13 : WORKDIR /helidon
 ---> Using cache
 ---> 25e45ff1f01d
Step 3/13 : ADD pom.xml .
 ---> Using cache
 ---> ec5c0a3ecd2c
Step 4/13 : RUN mvn package -Dmaven.test.skip -Declipselink.weave.skip
 ---> Using cache
 ---> a21083c406a0
Step 5/13 : ADD src src
 ---> Using cache
 ---> d718f90a4c6d
Step 6/13 : RUN mvn package -DskipTests
 ---> Using cache
 ---> ebdb2ff847fd
Step 7/13 : RUN echo "done!"
 ---> Using cache
 ---> d9c9f46d0af2
Step 8/13 : FROM openjdk:11-jre-slim
 ---> 940f48bb6c92
Step 9/13 : WORKDIR /helidon
 ---> Using cache
 ---> e0aa150de7c3
Step 10/13 : COPY --from=build /helidon/target/helidon-quickstart-mp.jar ./
 ---> Using cache
 ---> 0f64c021b20f
Step 11/13 : COPY --from=build /helidon/target/libs ./libs
 ---> Using cache
 ---> d2c6fad54ac1
Step 12/13 : CMD ["java", "-jar", "helidon-quickstart-mp.jar"]
 ---> Using cache
 ---> 7de63ada236c
Step 13/13 : EXPOSE 8080
 ---> Using cache
 ---> d6a11cd62373
Successfully built d6a11cd62373
Successfully tagged helidon-quickstart-mp:latest

**编辑 3(找到问题原因)

我在第二个构建执行日志中看起来更好,我认为 ENV 变量分配会破坏缓存:

Sending build context to Docker daemon  2.294MB

Step 1/36 : ARG CI_CONTAINER_BUILD_FULL_VERSION
Step 2/36 : ARG CI_CONTAINER_BUILD_PROFILE
Step 3/36 : ARG CI_CONTAINER_BUILD_VERSION
Step 4/36 : ARG CI_CONTAINER_MAVEN_BUILD_OPTIONS
Step 5/36 : ARG CI_CONTAINER_MAVEN_POM_ARTIFACT_ID
Step 6/36 : ARG CI_CONTAINER_MAVEN_POM_GROUP_ID
Step 7/36 : ARG CI_CONTAINER_MAVEN_POM_VERSION
Step 8/36 : ARG CI_CONTAINER_MAVEN_SETTINGS_FILE
Step 9/36 : ARG CI_CONTAINER_PROJECT_PATH
Step 10/36 : ARG APP_FOLDER=/app
Step 11/36 : FROM maven:3.6-jdk-11 as build
 ---> e23b595c92ad
Step 12/36 : ARG APP_FOLDER
 ---> Using cache
 ---> 7418c1e78088
Step 13/36 : ENV APP_FOLDER ${APP_FOLDER}
 ---> Using cache
 ---> 9fb55b74b57b
Step 14/36 : ARG CI_CONTAINER_MAVEN_SETTINGS_FILE
 ---> Using cache
 ---> 9de7642ea7ae
Step 15/36 : ENV CI_CONTAINER_MAVEN_SETTINGS_FILE ${CI_CONTAINER_MAVEN_SETTINGS_FILE}
 ---> Running in 0d5655b4dd2b
Removing intermediate container 0d5655b4dd2b
 ---> d590a3bc4167
Step 16/36 : ARG CI_CONTAINER_MAVEN_BUILD_OPTIONS
 ---> Running in 528c5d6c82f2
Removing intermediate container 528c5d6c82f2
 ---> 5ec69a0c4629
Step 17/36 : ENV CI_CONTAINER_MAVEN_BUILD_OPTIONS ${CI_CONTAINER_MAVEN_BUILD_OPTIONS}
 ---> Running in eefe59ddc94b
Removing intermediate container eefe59ddc94b
 ---> beb77c7c67e5
Step 18/36 : ARG CI_CONTAINER_PROJECT_PATH
 ---> Running in da389f2e0824
Removing intermediate container da389f2e0824
 ---> 6835d33be70e
Step 19/36 : ENV CI_CONTAINER_PROJECT_PATH ${CI_CONTAINER_PROJECT_PATH}
 ---> Running in 89420b67a110
Removing intermediate container 89420b67a110
 ---> fd1b05ed1dfd
Step 20/36 : WORKDIR ${APP_FOLDER}
 ---> Running in 39135509f1d7
Removing intermediate container 39135509f1d7
 ---> 833258753a45
Step 21/36 : COPY ${CI_CONTAINER_MAVEN_SETTINGS_FILE} .
 ---> 6f1b6a7176de
Step 22/36 : COPY ${CI_CONTAINER_PROJECT_PATH}/pom.xml .
 ---> 92f34751c641
Step 23/36 : RUN mvn -e -B -s ${CI_CONTAINER_MAVEN_SETTINGS_FILE} -DskipTests -Dmaven.openapi-generator.skip ${CI_CONTAINER_MAVEN_BUILD_OPTIONS} package
 ---> Running in 38fcf8af4523
[INFO] Error stacktraces are turned on.
[INFO] Scanning for projects...
[INFO] Downloading from public: http://ci.betting.sisal.it/nexus/repository/maven-public/it/sisal/betting/root-pom/2.0.2/root-pom-2.0.2.pom
[INFO] Downloaded from public: http://ci.betting.sisal.it/nexus/repository/maven-public/it/sisal/betting/root-pom/2.0.2/root-pom-2.0.2.pom (12 kB at 41 kB/s)
[INFO] Downloading from public: http://ci.betting.sisal.it/nexus/repository/maven-public/io/helidon/helidon-dependencies/2.3.0/helidon-dependencies-2.3.0.pom

... and so on...

现在我应该知道如何使变量赋值可缓存!!!

【问题讨论】:

  • 哪个步骤没有被缓存? (第一个 RUN mvn package?)所有 ARG 值和 Maven 设置文件的内容在重新构建时是否相同?
  • 嗨@DavidMaze!是的,未缓存的步骤是第一个 MVN 调用,正如您在构建命令中看到的那样,参数不会改变。
  • @AntonioPetricca 日志与提供的构建命令或 Dockerfile 不匹配,输出表明 args 正在更改。

标签: docker maven


【解决方案1】:

documentation 中提到

每条 FROM 指令都可以使用不同的基础,它们中的每一个都开始构建的新阶段

要检查缓存了哪些步骤,请运行以下命令

docker history <IMAGE_NAME>

您只会看到最终构建的说明。

【讨论】:

  • 感谢您的回答@sai,您的解释不正确。我正在开发一个 Helidon 应用程序,如果您按照helidon.io/docs/latest/#/mp/guides/10_mp-tutorial 的教程进行操作,您会看到 maven 工件模板生成的 Dockerfile 与我的相似,但是如果我们在源代码中更改某些内容,则只有第 2 条 MVN 指令被调用,而第一个下载了所有依赖项的,永远不会像预期的那样被调用。
  • 嗨@antonio-petricca,在多阶段中,最终构建将使用您提供的 docker 映像名称进行标记。但是,中间构建可以作为悬空图像(名称为 的图像)存在于系统中,可以用作缓存,而不是重新构建整个图像。只需检查在构建 Dockerfile 时是否创建了悬空图像。要检查系统中存在的悬空图像,请使用以下命令 docker images -f dangling=true
  • 我编辑了主帖,添加了 Edit 2 (for @sai)
  • Edit 3中我演示了变量赋值会破坏层缓存! :(
  • 是的,这是错误的。好吧,从构建一个新阶段是对的,但在问题的上下文中它是错误的——每个单独的层仍然被缓存。
【解决方案2】:

我找到了解决办法!

第一个问题与我在原始帖子中未显示的构建命令的细节有关:MAVEN_SETTINGS_FILE 是由mktemp 生成的,因此它在任何执行时都会更改,从而使以下层无效.

第二个问题是我并不真正需要 ENV 变量,但 ARG 就足够了。

所以,我将它们重写如下,并且所有层都按预期从缓存中重用。

M2_SETTINGS_FILE=.settings.xml
cp "${HOME}/.m2/settings.xml" "${M2_SETTINGS_FILE}"

docker build \
    --build-arg MAVEN_SETTINGS_FILE="${M2_SETTINGS_FILE}" \
    --build-arg PROJECT_PATH="." \
    --file "${DOCKER_FILE}" \
    --tag "${IMAGE_TAG}" \
    . 
# Global vars (for passing between stages)

ARG MAVEN_SETTINGS_FILE
ARG PROJECT_PATH

ARG APP_FOLDER=/app

# 1st stage

FROM maven:3.6-jdk-11 as build

# Build artifact

ARG APP_FOLDER
ARG MAVEN_SETTINGS_FILE
ARG PROJECT_PATH

WORKDIR ${APP_FOLDER}

ADD ${MAVEN_SETTINGS_FILE} .
ADD ${PROJECT_PATH}/pom.xml .
RUN mvn package -s ${MAVEN_SETTINGS_FILE} -DskipTests

ADD ${PROJECT_PATH}/src src
RUN mvn package -s ${MAVEN_SETTINGS_FILE} -DskipTests

# 2nd stage: build image

FROM openjdk:11-jre-slim

ARG APP_FOLDER
ARG TARGET_FOLDER ${APP_FOLDER}/target

WORKDIR ${APP_FOLDER}

# Copy the binary built in the 1st stage

COPY --from=build ${TARGET_FOLDER}/myapp-1.0.0-SNAPSHOT.jar .

EXPOSE 8080

CMD ["java", "-jar", "myapp-1.0.0-SNAPSHOT.jar"]

【讨论】:

    猜你喜欢
    • 2018-06-27
    • 2018-02-06
    • 2020-02-23
    • 2021-09-24
    • 2020-07-05
    • 2022-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多