Java 如何在Docker中缓存maven依赖项

Java 如何在Docker中缓存maven依赖项,java,maven,docker,Java,Maven,Docker,我正在从事一个有大约200MB依赖项的项目,由于带宽有限,我希望避免无用的上传 当我推我的Dockerfile(我会马上附加它)时,我总是有一个~200MB的上传,即使我没有触摸pom.xml: FROM maven:3.6.0-jdk-8-slim WORKDIR /app ADD pom.xml /app RUN mvn verify clean --fail-never COPY ./src /app/src RUN mvn package ENV CONFIG_FOLDER=

我正在从事一个有大约200MB依赖项的项目,由于带宽有限,我希望避免无用的上传

当我推我的Dockerfile(我会马上附加它)时,我总是有一个~200MB的上传,即使我没有触摸pom.xml:

FROM maven:3.6.0-jdk-8-slim

WORKDIR /app

ADD pom.xml /app

RUN mvn verify clean --fail-never

COPY ./src /app/src

RUN mvn package

ENV CONFIG_FOLDER=/app/config
ENV DATA_FOLDER=/app/data
ENV GOLDENS_FOLDER=/app/goldens
ENV DEBUG_FOLDER=/app/debug

WORKDIR target

CMD ["java","-jar","-Dlogs=/app/logs", "myProject.jar"]
这个Dockerfile应该制作一个包含所有依赖项的200MB fatJAR,这就是为什么每次都会发生~200MB的上传。我想要实现的是构建一个包含所有依赖项的层,并“告诉”打包阶段不要将依赖项jar包含到fatJAR中,而是在给定目录中搜索它们

我想构建一个脚本,执行
mvn依赖项:在构建过程之前复制依赖项
,然后将目录复制到容器中;然后构建一个“非胖”JAR,该JAR只链接所有这些依赖项,而不实际复制到其中

这可能吗

编辑: 我发现容器的Maven本地存储库位于
/root/.m2
下。因此,我最终制作了一个非常简单的脚本,如下所示:

BuildDocker.sh

和编辑的Dockerfile类似:

在构建过程之后,我声明
/root/.m2
拥有我所拥有的所有目录,但一旦启动JAR,我就会得到:

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/Priority
    at myProject.ThreeMeans.calculate(ThreeMeans.java:17)
    at myProject.ClusteringStartup.main(ClusteringStartup.java:7)
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Priority
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 2 more

也许我不应该通过
java-jar

来运行它,在一般的Dockerfile容器构建中,它是在层中工作的,每次构建时,这些层都可以在catch中使用,并且在没有更改的情况下使用。 理想情况下,它应该以同样的方式工作

Maven通常默认情况下会在Ubuntu的用户主页目录中的
.m2
文件夹中查找依赖项

如果依赖JAR不可用,那么它会将这些JAR下载到.m2并使用它

现在,您可以在1次成功构建后压缩并复制此
.m2
文件夹,并将其移动到Docker容器用户的主目录中

在运行build命令之前执行此操作

注意:您可能需要替换docker中现有的
.m2
文件夹

所以你的Docker文件应该是这样的

FROM maven:3.6.0-jdk-8-slim

WORKDIR /app

COPY .m2.zip /home/testuser/

ADD pom.xml /app

RUN mvn verify clean --fail-never

COPY ./src /app/src

RUN mvn package
...

如果我正确理解了您想要实现的目标,那么问题是避免在每个Docker构建中创建一个包含所有Maven依赖项的胖jar(以减轻重建后要推送的Docker层的大小)

如果是,您可能对的感兴趣,它也适用于非Spring启动项目。一些综合文档可在相应GitHub repo的
README.md
中找到:

总之,在
pom.xml
中添加以下插件声明就足够了:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <!--<version>${spring-boot.version}</version>-->
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot.experimental</groupId>
                    <artifactId>spring-boot-thin-layout</artifactId>
                    <version>1.0.19.RELEASE</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
请注意,它依赖于所谓的of Docker(存在来自指令的两个
),这意味着最终映像将比
maven
基本映像本身小得多。
(如果您在开发阶段对该特性不感兴趣,可以从openjdk:8u171 jre alpine
COPY--FROM=maven/app/app.jar./app.jar
中删除行

在这种方法中,Maven依赖项是在
RUN-mvn-dependency:go offline-B
COPY./src./src
之前获取的(以从Docker缓存中获益)

但是请注意,
dependency:go offline
标准目标并不是“完美的”,因为一些动态依赖项/插件仍可能在
mvn包
步骤中触发一些重新下载。 如果这对你来说是个问题(例如,如果在某个时候你真的想离线工作),你可以看看另一个建议使用专用插件,该插件提供了
de.qaware.maven:go offline maven plugin:resolve dependencies
目标。

还指出了实现更好的依赖缓存的不同方法


基本上,他们建议要么将本地maven存储库作为卷装载并跨Docker映像使用它,要么使用一个特殊的本地存储库(/usr/share/maven/ref/),其内容将在容器启动时复制

我个人并没有使用过这种方法,但Jib可能会有所帮助:看看它是针对Spring Boot的,但您可以对每个maven项目使用相同的方法。基本上,您必须创建具有多个层的Docker文件,以便在构建期间Docker可以缓存未更改的层。在编辑的问题中,您引入了一个命令
COPY./.m2/root/.m2
,类似于@mytwoons的建议(因此可以将其视为执行
运行mvn依赖关系的标准解决方案的替代方案:脱机-B
,即使
复制。/.m2/root/.m2
的可移植性较差,因为它需要在主机上安装maven),但我不确定这是否能解决您关于~200MB上传与推送胖jar相关的主要问题…cf.另一位感谢您的回答,我会尝试一下,让您知道!因此基本上您的方法是不向jar提供任何类型的库绑定知识,而是直接替换
.m2
文件夹在容器中?我没有尝试你关于
~/.m2.jar
文件的建议,所以我不确定它是否会工作…但是它是否有文档记录?我在dockerfile的.m2.zip中没有发现这样的提及。当你第一次运行mvn build/mvn install.Fol时,将生成一个.m2文件夹,其中所有jar都以某种方式缓存der结构是这样的~/.m2/repository/com/oracle/ojdbc7/12.1.0.1/ojdbc7-12.1.0.1.jar。这里有更多详细信息:您是否在/home/testuser中解压了docker中的.m2文件夹。在运行build comnandI之前,我删除了以前的注释,因为我错了,现在它似乎满足了我的要求,好吧。@mytwoons,当我现在有了cContainer
.m2
文件夹填充了所有依赖项,我的应用程序仍然无法找到它们,导致出现
NoClassDefFoundError
。我将编辑我的
FROM maven:3.6.0-jdk-8-slim

WORKDIR /app

COPY .m2.zip /home/testuser/

ADD pom.xml /app

RUN mvn verify clean --fail-never

COPY ./src /app/src

RUN mvn package
...
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <!--<version>${spring-boot.version}</version>-->
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot.experimental</groupId>
                    <artifactId>spring-boot-thin-layout</artifactId>
                    <version>1.0.19.RELEASE</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
# our base build image
FROM maven:3.5-jdk-8 as maven

WORKDIR /app

# copy the Project Object Model file
COPY ./pom.xml ./pom.xml

# fetch all dependencies
RUN mvn dependency:go-offline -B

# copy your other files
COPY ./src ./src

# build for release
# NOTE: my-project-* should be replaced with the proper prefix
RUN mvn package && cp target/my-project-*.jar app.jar


# smaller, final base image
FROM openjdk:8u171-jre-alpine
# OPTIONAL: copy dependencies so the thin jar won't need to re-download them
# COPY --from=maven /root/.m2 /root/.m2

# set deployment directory
WORKDIR /app

# copy over the built artifact from the maven image
COPY --from=maven /app/app.jar ./app.jar

# set the startup command to run your binary
CMD ["java", "-jar", "/app/app.jar"]