使用 maven 构建 docker - 如何防止重新下载依赖项

2022-09-04 08:40:39

我希望基础映像下载依赖项并仅在依赖项更改时才重新生成,并希望第二个映像在代码更改时重新生成。但是,在这两个 maven 命令上,请下载所有依赖项。我可能误解了堆叠的工作原理或复制什么。mavenDepsmavenBuilddocker build .

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

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"]

我正在MacOS上使用Docker Desktop(引擎)进行构建。2.2.2.019.03.5

编辑2020.03.04:

来自@gcallea的应答有效地防止重新下载文件中列出的依赖项。但是,该步骤仍会在由代码更改触发的每个生成上拉取 100 多个项目。这些是 的瞬态依赖关系,以及其他几个插件没有在任何地方明确列出。pom+1installmaven-resources-pluginmaven-compiler-plugin

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


答案 1

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

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

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

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

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

FROM mavenDeps as mavenBuild
RUN mvn install

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


如何加工?

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

构建阶段(Maven 图像):

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

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

  • 从上一阶段复制罐子

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"]

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

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

这里唯一变化的是maven依赖项下载缓存在docker构建器缓存中:

# 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"]

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


答案 2

您不需要将构建阶段分为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"]

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


推荐