java - 如何在 Docker 中缓存 Maven 依赖项

标签 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 Local Repository 位于 /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 运行它?

最佳答案

如果我正确理解你想要实现的目标,问题是避免在每次 Docker 构建时创建一个包含所有 Maven 依赖项的 fat jar(以减轻 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 的原型(prototype)可以避免在每次构建时重新下载所有 Maven 依赖项,如果只触及源代码文件 (src/*),请参见以下引用:
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"]

注意它依赖于所谓的multi-stage build feature Docker(存在两个 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 目标的专用插件。

关于java - 如何在 Docker 中缓存 Maven 依赖项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53691781/

相关文章:

java - 我可以在 IntelliJ IDEA 或 Atom IDE 中使用 Google Mobile Vision API 吗?

java - 异常解析器和调度器不转发

java - MapActivity 上的 AutoCompleteTextView 不起作用

java - 如何教 checkstyle 忽略我的自定义 javadoc 标签?

docker - ASP .NET Core 在 Heroku 上给出 System.Net.Sockets.SocketException 错误

java - 馆藏副本

java - BIM服务器错误

maven - 在 NetBeans 中获取意外异常

macos - 如何在 MacO 上轻松安装和卸载 docker

django - 在 docker 中运行 django migrate