如何在Docker中缓存Maven依赖项

时间:2018-12-09 11:21:04

标签: java maven docker

我正在处理一个约200MB依赖项的项目,由于带宽有限,我想避免无用的上传。

当我推送Dockerfile时(稍后会附加它),即使我不碰pom.xml,我也总是上传〜200MB:

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 dependency:copy-dependencies,然后将目录复制到容器中;然后构建一个“非胖” JAR,将所有那些依赖项链接在一起,而实际上不将其复制到其中。

这可能吗?

编辑: 我发现容器的Maven本地存储库位于/root/.m2下。所以我结束了一个非常简单的脚本,如下所示:

BuildDocker.sh

mvn verify -clean --fail-never
mv ~/.m2 ~/git/myProjectRepo/.m2

sudo docker build -t myName/myProject:"$1"

并像这样编辑 Dockerfile

# Use an official Python runtime as a parent image
FROM maven:3.6.0-jdk-8-slim

# Copy my Mavne Local Repository into the container thus creating a new layer
COPY ./.m2 /root/.m2

# Set the working directory to /app
WORKDIR /app

# Copy the pom.xml
ADD pom.xml /app

# Resolve and Download all dependencies: this will be done only if the pom.xml has any changes
RUN mvn verify clean --fail-never

# Copy source code and configs 
COPY ./src /app/src

# create a ThinJAR
RUN mvn package


# Run the jar
...

在构建过程之后,我说/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运行它?

3 个答案:

答案 0 :(得分:2)

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

如果是,那么您可能对Spring Boot Thin Launcher感兴趣,它也适用于非Spring-Boot项目。相应的GitHub存储库的README.md中提供了一些全面的文档: https://github.com/dsyer/spring-boot-thin-launcher#readme

总而言之,在您的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>

理想情况下,此解决方案应与标准Dockerfile设置结合使用,以受益于Docker的缓存(请参阅下面的典型示例)。

利用Docker的Java / Maven项目缓存机制

以下参考文献中给出了Dockerfile的原型,该原型避免了在仅触及源代码文件(src/*)的情况下在每个构建中重新下载所有Maven依赖项的情况:
https://whitfin.io/speeding-up-maven-docker-builds/

确切地说,拟议的Dockerfile如下:

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

请注意,它依赖于所谓的Docker multi-stage build feature(存在两个FROM指令),这意味着最终映像将比maven基本映像本身小得多。
(如果在开发阶段对该功能不感兴趣,则可以删除行FROM openjdk:8u171-jre-alpineCOPY --from=maven /app/app.jar ./app.jar。)

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

但是请注意,dependency:go-offline标准目标不是“完美的”,因为一些动态依赖项/插件仍可能在mvn package步骤中触发一些重新下载。 如果这对您来说是个问题(例如,如果您确实想离线工作),则可以看看其他SO answer,它建议使用提供de.qaware.maven:go-offline-maven-plugin:resolve-dependencies的专用插件目标。

答案 1 :(得分:1)

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

Maven通常默认情况下会在Ubuntu .m2的User主目录中的/home/username/文件夹中查找依赖项

如果不存在依赖的jar,则将这些jar下载到.m2并使用。

现在,您可以在成功构建1次后压缩并复制此.m2文件夹,并将其移动到Docker Container User's Home目录中。

在运行构建命令之前执行此操作

注意:您可能需要替换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
...

答案 2 :(得分:0)

documentation of the official Maven Docker images还指出了实现更好的依赖项缓存的不同方法。

基本上,他们建议将本地Maven存储库作为卷挂载并在Docker映像中使用,或使用特殊的本地存储库(/ usr / share / maven / ref /),其内容将在容器启动时复制。