docker build with maven - 如何防止重新下载依赖项

kos*_*tja 5 java maven docker

我希望基础映像mavenDeps仅在依赖项更改时下载依赖项并重建,而第二个映像mavenBuild在代码更改时重建。但是,在docker build .两个 Maven 命令上都下载所有依赖项。我可能误解了堆叠的工作原理或复制的内容。

我尝试过的:将所有内容从第一个容器显式复制到第二个:COPY / /以及各种更具体的COPY目标,例如.m2从 maven 基础映像构建第二个容器,例如第一个容器,然后从第一个容器复制所有内容。

Dockerfile:

FROM maven:3.5-jdk-8 as mavenDeps
COPY pom.xml pom.xml
RUN mvn dependency:resolve

FROM mavenDeps as mavenBuild
RUN mvn install

FROM java:8
COPY --from=mavenBuild ./target/*.jar ./
ENV JAVA_OPTS ""
CMD [ "bash", "-c", "java ${JAVA_OPTS} -jar *.jar -v"]
Run Code Online (Sandbox Code Playgroud)

我正在MacOS 上使用 Docker 桌面2.2.2.0(引擎19.03.5)进行构建。

编辑2020.03.04:

@gcallea 的回答有效地防止了重新下载pom文件中列出的依赖项+1。但是,该install步骤仍然会在每次由代码更改触发的构建中提取 100 多个工件。这些都是短暂的依赖关系maven-resources-pluginmaven-compiler-plugin并没有列出任何地方明确的几个其他插件。

我有时需要离线工作,并希望预加载所有依赖项,因此在代码更改后不会拉取任何依赖项。

dav*_*xxx 9

在告诉您我将如何处理之前,我将解释您遇到的问题。

您的 Dockerfile 依赖于构建多阶段功能。
此处阶段被视为中间层,在最终图像中不保留为层。要在层之间保留文件/文件夹,您必须在完成后显式复制它们。

所以具体来说,这意味着在以下说明中:maven 解析 pom.xml 中指定的所有依赖项,并将它们存储在位于该阶段层的本地存储库中:

FROM maven:3.5-jdk-8 as mavenDeps
COPY pom.xml pom.xml
RUN mvn dependency:resolve
Run Code Online (Sandbox Code Playgroud)

但如前所述,舞台内容默认不保留。因此,本地 Maven 存储库中所有下载的依赖项都将丢失,因为您在下一阶段永远不会复制它:

FROM mavenDeps as mavenBuild
RUN mvn install
Run Code Online (Sandbox Code Playgroud)

由于该映像的本地存储库为空:mvn install重新下载所有依赖项。


如何处理?

你真的有很多很多方法。
最佳选择取决于您的要求。
但无论如何,docker 层的构建策略如下所示:

构建阶段(Maven 镜像):

  • pom 复制到图像
  • 依赖项插件下载。
    关于这一点,mvn dependency:resolve-plugins链接到mvn dependency:resolve可能会完成这项工作,但并非总是如此。
    为什么 ?因为这些插件和package执行可能依赖于不同的工件/插件,甚至对于相同的工件/插件,它们仍然可能会提取不同的版本。因此,一种更安全但可能较慢的方法是通过准确执行mvn package命令(这将准确提取您需要的依赖项)来解决依赖项,但跳过源编译并删除目标文件夹以加快处理速度并防止任何不需要的层更改该步骤的检测。
  • 源代码复制到图像
  • 打包应用程序

运行阶段(JDK 或 JRE 映像):

  • 复制上一阶段的 jar

1) 没有显式缓存 Maven 依赖项:当 pom 频繁更改时,直接但很烦人

如果在每次 pom.xml 更改时重新下载所有依赖项是可以接受的。

从您的脚本开始的示例:

########build stage########
FROM maven:3.5-jdk-8 as maven_build
WORKDIR /app

COPY pom.xml .
# To resolve dependencies in a safe way (no re-download when the source code changes)
RUN mvn clean package -Dmaven.main.skip -Dmaven.test.skip && rm -r target

# To package the application
COPY src ./src
RUN mvn clean package -Dmaven.test.skip

########run stage########
FROM java:8
WORKDIR /app

COPY --from=maven_build /app/target/*.jar

#run the app
ENV JAVA_OPTS ""
CMD [ "bash", "-c", "java ${JAVA_OPTS} -jar *.jar -v"]
Run Code Online (Sandbox Code Playgroud)

该解决方案的缺点?pom.xml 中的任何更改都意味着重新创建下载和存储 maven 依赖项的整个层。
对于具有许多依赖项的应用程序来说,这通常是不可接受的,总体而言,如果您在映像构建期间不使用 Maven 存储库管理器。

2) Maven 依赖项的显式缓存:需要更多配置和使用 buildkit 但效率更高,因为只下载所需的依赖项

这里唯一改变的是 Maven 依赖项下载缓存在 docker builder 缓存中:

# syntax=docker/dockerfile:experimental
########build stage########
FROM maven:3.5-jdk-8 as maven_build
WORKDIR /app

COPY pom.xml .    
COPY src ./src

RUN --mount=type=cache,target=/root/.m2 mvn clean package  -Dmaven.test.skip

########run stage########
FROM java:8
WORKDIR /app

COPY --from=maven_build /app/target/*.jar

#run the app
ENV JAVA_OPTS ""
CMD [ "bash", "-c", "java ${JAVA_OPTS} -jar *.jar -v"]
Run Code Online (Sandbox Code Playgroud)

要启用 buildkit,DOCKER_BUILDKIT=1必须设置env 变量(您可以在需要的地方执行此操作:bashrc、命令行、docker daemon json 文件...)


gre*_*lea 5

您不需要将构建阶段分为 2 个不同的阶段mavenDepsmavenBuild。您可以包含一个利用 Docker 层实现相同目的的构建阶段。

您可以根据您的目的构建 Dockerfile,如下所示:

#----
# Build stage
#----
FROM maven:3.5-jdk-8 as buildstage
# Copy only pom.xml of your projects and download dependencies
COPY pom.xml .
RUN mvn -B -f pom.xml dependency:go-offline
# Copy all other project files and build project
COPY . .
RUN mvn -B install

#----
# Final stage
#----
FROM java:8
COPY --from=buildstage ./target/*.jar ./
ENV JAVA_OPTS ""
CMD [ "bash", "-c", "java ${JAVA_OPTS} -jar *.jar -v"]
Run Code Online (Sandbox Code Playgroud)

仅当对 pom.xml 进行更改时才执行此操作,依赖项才会重新加载。否则与命令相关的 Docker 层RUN mvn -B -f pom.xml dependency:go-offline将被重用为缓存。