如何在 Java 镜像构建过程中免重复下载依赖包

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 利用镜像构建缓存机制来加速 Java 镜像构建过程,免重复下载依赖包。

近来收到一些反馈:使用 maven 编译 Java 工程,如何保留本地 repository 缓存,避免每次构建都重新下载所有依赖包,毕竟这很耗时。

实际上,构建工具(docker/buildkit 等)在构建过程中是没办法直接挂载本地目录到系统的,所以构建系统也没办法通过为用户创建缓存来复用依赖包。但是,可以利用容器镜像构建缓存机制来复用 Java 依赖包缓存。

原始 Dockerfile

以一个 Java Hello World 工程为例,Dockerfile 内定义了一个两阶段构建,首次构建耗时 110s,且后续构建也无法利用前次已经下载依赖包缓存。

# First stage: complete build environment
FROM maven:3.5.0-jdk-8-alpine AS builder

# add pom.xml and source code
ADD ./pom.xml pom.xml
ADD ./src src/

# package jar
RUN mvn install -Dmaven.test.skip=true

From openjdk:8

# copy jar from the first stage
COPY --from=builder target/my-app-1.0-SNAPSHOT.jar my-app-1.0-SNAPSHOT.jar
EXPOSE 8080
CMD ["java", "-jar", "my-app-1.0-SNAPSHOT.jar"]

优化和遇到的问题

优化思路是将项目包下载、打包过程划分开,先拷贝工程 pom.xml 并下载所有的依赖包,再拷贝工程源代码并打包项目,下文给了一个改写方案。

使用此 Dockerfile 首次构建耗时在 240s,且惊奇的发现两次 mvn install 过程中,第二次依然需要下载所有依赖包,无法复用第一次的结果。更改项目代码,再次构建镜像也没办法利用到前次构建的缓存。

# First stage: complete build environment
FROM maven:3.5.0-jdk-8-alpine AS builder

# download dependencies (no re-download when the source code changes)
ADD ./pom.xml pom.xml
RUN  mvn install

ADD ./src src/
# package jar
RUN mvn install -Dmaven.test.skip=true

From openjdk:8

# copy jar from the first stage
COPY --from=builder target/my-app-1.0-SNAPSHOT.jar my-app-1.0-SNAPSHOT.jar
EXPOSE 8080
CMD ["java", "-jar", "my-app-1.0-SNAPSHOT.jar"]
使用 mvn 下载依赖包的命令有很多,例如:mvn install、dependency:go-offline 等。

起初怀疑是 maven 的问题,但是直接在本地运行并进入基础镜像 maven:3.5.0-jdk-8-alpine,手动执行 Dockerfile 内的所有命令,发现第二次执行 mvn install 是可以利用到第一次的依赖包缓存的。

mvn install 命令默认将依赖包下载到 ~/.m2 目录(即镜像内的 /root/.m2)下,而对于 Dockerfile 内的每个 RUN ,构建工具都会启动新容器来执行命令,生成新的镜像层。猜测是启动容器时 /root/.m2 目录被清理了,所以才导致缓存失效,这应该与基础镜像 maven:3.5.0-jdk-8-alpine 有关。

查看 maven:3.5.0-jdk-8-alpine 的镜像配置,发现 /root/.m2 目录被定义成 Volume 了。

截屏2021-02-26 下午5.57.47.png

查看官方文档中对 Volume 的说明可以知道在构建过程中,所有被写入卷目录的内容在后续构建过程中都会被清理,这也就是缓存无法被利用到的原因。

截屏2021-02-26 下午5.52.01.png

最终版本

为了避开默认 /root/.m2 目录,使用 -Dmaven.repo.local 来显示指定本地 maven 仓库目录。首次构建耗时 115s,后续构建耗时在 10s 左右,复用了依赖包缓存,耗时降低了 91%。

# First stage: complete build environment
FROM maven:3.5.0-jdk-8-alpine AS builder

# To resolve dependencies in a safe way (no re-download when the source code changes)
ADD ./pom.xml pom.xml
RUN  mvn install -Dmaven.repo.local=./.m2

ADD ./src src/
# package jar
RUN mvn -Dmaven.repo.local=./.m2 install -Dmaven.test.skip=true

From openjdk:8

# copy jar from the first stage
COPY --from=builder target/my-app-1.0-SNAPSHOT.jar my-app-1.0-SNAPSHOT.jar
EXPOSE 8080
CMD ["java", "-jar", "my-app-1.0-SNAPSHOT.jar"]

附录

  1. https://stackoverflow.com/questions/60522767/docker-build-with-maven-how-to-prevent-re-downloading-dependencies
  2. https://docs.docker.com/engine/reference/builder/#volume
目录
相关文章
|
2月前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
72 11
|
13天前
|
监控 Java API
【潜意识Java】使用SpringBoot构建高效的RESTfulAPI
本文介绍了使用Spring Boot构建RESTful API的完整流程,涵盖从项目创建到API测试的各个步骤。
34 1
|
2月前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
107 34
|
3月前
|
XML Java 测试技术
从零开始学 Maven:简化 Java 项目的构建与管理
Maven 是一个由 Apache 软件基金会开发的项目管理和构建自动化工具。它主要用在 Java 项目中,但也可以用于其他类型的项目。
92 1
从零开始学 Maven:简化 Java 项目的构建与管理
|
3月前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
291 12
基于开源框架Spring AI Alibaba快速构建Java应用
|
3月前
|
Java Android开发
Eclipse Java 构建路径
Eclipse Java 构建路径
51 3
|
3月前
|
Java Android开发
Eclipse 创建 Java 包
Eclipse 创建 Java 包
44 1
|
3月前
|
存储 Java API
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
142 4
|
3月前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
79 2
|
3月前
|
存储 Java 开发者
成功优化!Java 基础 Docker 镜像从 674MB 缩减到 58MB 的经验分享
本文分享了如何通过 jlink 和 jdeps 工具将 Java 基础 Docker 镜像从 674MB 优化至 58MB 的经验。首先介绍了选择合适的基础镜像的重要性,然后详细讲解了使用 jlink 构建自定义 JRE 镜像的方法,并通过 jdeps 自动化模块依赖分析,最终实现了镜像的大幅缩减。此外,文章还提供了实用的 .dockerignore 文件技巧和选择安全、兼容的基础镜像的建议,帮助开发者提升镜像优化的效果。