Docker 多阶段构建打包Java应用

在Dockerfile中使用多阶段构建打包 Java应用

多阶段构建指在Dockerfile中使用多个FROM语句,每个FROM指令都可以使用不同的基础镜像,并且是一个独立的子构建阶段。使用多阶段构建打包Java应用具有构建安全、构建速度快、镜像文件体积小等优点

镜像构建的通用问题

  • 镜像容易臃肿
    构建镜像时,开发者会将项目的编译、测试、打包构建流程编写在一个Dockerfile中。每条Dockerfile指令都会为镜像添加一个新的镜像层,从而导致镜像层次深,镜像文件体积特别大。

  • 存在源码泄露风险
    打包镜像时,源代码容易被打包到镜像中,从而产生源代码泄漏的风险。

多阶段构建优势

  • 保证构建镜像的安全性
    使用Dockerfile多阶段构建镜像时,需要在第一阶段选择合适的编译时基础镜像,进行代码拷贝、项目依赖下载、编译、测试、打包流程。在第二阶段选择合适的运行时基础镜像,拷贝基础阶段生成的运行时依赖文件。最终构建的镜像将不包含任何源代码信息。

  • 优化镜像的层数和体积
    构建的镜像仅包含基础镜像和编译制品,镜像层数少,镜像文件体积小。

  • 提升构建速度
    使用构建工具(Docker、Buildkit等),可以并发执行多个构建流程,缩短构建耗时。

以 Java Maven项目为例,在 Java Maven项目的根目录下新建Dockerfile文件,并在Dockerfile文件添加以下内容(参考最终版本)。

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

简介: 利用镜像构建缓存机制来加速 Java 镜像构建过程,免重复下载依赖包。

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

参考地址:如何在 Java 镜像构建过程中免重复下载依赖包-阿里云开发者社区 (aliyun.com)

原始 Dockerfile

以一个demo工程为例,在项目的根目录下,创建如下的Dockerfile文件,Dockerfile 内定义了一个两阶段构建,首次构建耗时100s左右,且后续构建也无法利用前次已经下载依赖包缓存。

# 阶段一: 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

# 阶段二: inimal runtime environment
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"]

说明

该Dockerfile文件使用了二阶段构建。

  • 第一阶段:选择Maven基础镜像(Gradle类型也可以选择相应Gradle基础镜像)完成项目编译,拷贝源代码到基础镜像并运行RUN命令,从而构建Jar包。
  • 第二阶段:拷贝第一阶段生成的Jar包到OpenJDK镜像中,设置CMD运行命令。

优化和遇到的问题

优化思路是将项目包下载、打包过程划分开,先拷贝工程 pom.xml 并下载所有的依赖包,再拷贝工程源代码并打包项目

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

# 阶段一: 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

# 阶段二: inimal runtime environment
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 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 的说明可以知道在构建过程中,所有被写入卷目录的内容在后续构建过程中都会被清理,这也就是缓存无法被利用到的原因

最终版本

为了避开默认 /root/.m2 目录,使用 -Dmaven.repo.local 来显示指定本地 maven 仓库目录。

# 阶段一: 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

# 阶段二: inimal runtime environment
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打包的时候出现:

[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.7.13:repackage (repackage) on project dataScreen: Execution repackage of goal org.springframework.boot:spring-boot-maven-plugin:2.7.13:repackage failed: Unable to find main class

二、分析问题原因

项目里面包含了某些工具类的模块,而工具类模块是不需要我们启动的,只是提供给其他微服务引用而已,不需要启动就意味着 没有main启动类,但是父工程的pom文件却引用了 springboot打包插件 spring-boot-maven-plugin, 即:

<!--springboot 打包插件-->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

在打包的时候mvn会扫描所有的依赖模块,如果发现某个模块下面没有main启动类,就会报错。

解决问题

将父工程中pom文件的打包插件 spring-boot-maven-plugin 注释掉,然后 Reload Maven Projects

在这里插入图片描述

接下来就可以正常打包了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值