如何在 Java 镜像构建过程中免重复下载依赖包-阿里云开发者社区

开发者社区> 阿里云容器服务 ACK> 正文
登录阅读全文

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

简介: 利用镜像构建缓存机制来加速 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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
阿里云容器服务 ACK
使用钉钉扫一扫加入圈子
+ 订阅

云端最佳容器应用运行环境,安全、稳定、极致弹性

官方博客
官网链接