多阶段构建指在Dockerfile中使用多个FROM语句,每个FROM指令都可以使用不同的基础镜像,并且是一个独立的子构建阶段。使用多阶段构建打包Java应用具有构建安全、构建速度快、镜像文件体积小等优点,本文以Github上的Java Maven项目为例,结合阿里云容器镜像服务(ACR)的镜像构建服务,介绍如何进行多阶段构建。
前提条件
背景信息
镜像构建的通用问题
镜像构建服务使用Dockerfile来帮助用户构建最终镜像,但在具体实践中,存在一些问题:
Dockerfile编写有门槛
开发者(尤其是Java)习惯了语言框架的编译便利性,不知道如何使用Dockerfile构建应用镜像。
镜像容易臃肿
构建镜像时,开发者会将项目的编译、测试、打包构建流程编写在一个Dockerfile中。每条Dockerfile指令都会为镜像添加一个新的镜像层,从而导致镜像层次深,镜像文件体积特别大。
存在源码泄露风险
打包镜像时,源代码容易被打包到镜像中,从而产生源代码泄漏的风险。
多阶段构建优势
针对Java这类的编译型语言,使用Dockerfile多阶段构建,具有以下优势:
保证构建镜像的安全性
当您使用Dockerfile多阶段构建镜像时,需要在第一阶段选择合适的编译时基础镜像,进行代码拷贝、项目依赖下载、编译、测试、打包流程。在第二阶段选择合适的运行时基础镜像,拷贝基础阶段生成的运行时依赖文件。最终构建的镜像将不包含任何源代码信息。
优化镜像的层数和体积
构建的镜像仅包含基础镜像和编译制品,镜像层数少,镜像文件体积小。
提升构建速度
使用构建工具(Docker、Buildkit等),可以并发执行多个构建流程,缩短构建耗时。
步骤一:使用多阶段构建Dockerfile
以Java Maven项目为例,在Java Maven项目中新建Dockerfile文件,并在Dockerfile文件添加以下内容。
该Dockerfile文件使用了二阶段构建。
第一阶段:选择Maven基础镜像(Gradle类型也可以选择相应Gradle基础镜像)完成项目编译,拷贝源代码到基础镜像并运行RUN命令,从而构建Jar包。
第二阶段:拷贝第一阶段生成的Jar包到OpenJDK镜像中,设置CMD运行命令。
# 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 clean package
# Second stage: minimal runtime environment
From openjdk:8-jre-alpine
# 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"]
步骤二:绑定源代码仓库
在容器镜像服务控制台,绑定您托管的代码仓库,以下内容以GitHub为例。
- 登录容器镜像服务控制台。
- 在顶部菜单栏,选择所需地域。
- 在实例列表页面单击个人版实例。
在个人版实例管理页面左侧导航栏中选择 。
在GitHub栏单击绑定账号,在弹出的对话框中单击点击前往源代码仓库登录,跳转到GitHub。
在授权界面,单击Authorize AliyunDeveloper。
返回至代码源界面,GitHub栏中显示已绑定,表示绑定成功。
步骤三:新建镜像仓库
- 登录容器镜像服务控制台。
- 在顶部菜单栏,选择所需地域。
- 在实例列表页面单击个人版实例。
在个人版实例管理页面左侧导航栏中选择 ,然后单击创建镜像仓库。
设置镜像仓库信息。
配置项
描述
地域
镜像仓库所在区域,目前阿里云容器镜像服务已经全球23个区域开服。
命名空间
仓库所属命名空间。一个镜像仓库必须且仅属于一个命名空间。一个命名空间下可以包含多个镜像仓库。
仓库名称
输入仓库名称。
仓库类型
仓库类型分为公开和私有。无论公开还是私有类型仓库,推送镜像都需要先进行登录。
公开:拉取镜像时可以免登录,直接通过网络拉取。
私有:必须要通过Docker客户端进行登录,才能拉取镜像。
摘要
简要描述信息。
描述信息
完整描述信息。
代码源:将代码源设为GitHub,选择步骤二:绑定源代码仓库中绑定的源代码仓库。
构建设置 :
代码变更时自动构建镜像:当分支有代码提交后会自动触发构建规则。
海外机器构建:构建时会在海外机房构建,构建成功后推送到指定地域。
不使用缓存:每次构建时会强制重新拉取基础依赖镜像,可能会增加构建时间。
单击创建镜像仓库。
选择
,在镜像仓库界面查找到创建的仓库,表示创建镜像仓库成功。
步骤四:执行构建
在左侧导航栏中选择 ,单击仓库名称或目标仓库操作列的管理,进入仓库详情页面。
单击左侧导航栏中的构建,在构建规则设置区域的单击添加规则。
设置构建规则。
配置项
描述
类型
定义了推送代码到托管仓库,触发构建规则的事件。目前有Branch和Tag两种类型的推送。
Branch/Tag
设置构建的代码分支。
构建上下文目录
设置Dockerfile文件所在的目录。这里的目录指的是相对目录,以代码分支的根目录为父目录。
Dockerfile文件名
设置Dockerfile文件名,默认为Dockerfile。
镜像版本
设置镜像版本。
单击确认,返回至构建页面。
在构建规则设置区域中,找到创建的规则,单击目标规则对应操作列的立即构建。
在构建日志区域中找到构建记录,当构建状态显示成功,表示构建成功。
执行结果
查看构建的镜像
在个人版实例管理页面左侧导航栏中选择
,单击仓库名称或目标仓库操作列的管理,进入仓库详情页面。单击镜像版本,查看构建的镜像。查看镜像层数
使用Docker客户端登录镜像仓库,执行以下命令,拉取构建的镜像并检查镜像层数。可以发现仅包含了第一阶段编译Jar包,大小增加有限,镜像层数控制在四层。
docker pull registry.cn-hangzhou.aliyuncs.com/docker-builder/java-multi-stage:master docker inspect registry.cn-hangzhou.aliyuncs.com/docker-builder/java-multi-stage:master | jq -s .[0][0].RootFS { "Type": "layers", "Layers": [ "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81", "sha256:9b9b7f3d56a01e3d9076874990c62e7a516cc4032f784f421574d06b18ef9aa4", "sha256:edd61588d12669e2d71a0de2aab96add3304bf565730e1e6144ec3c3fac339e4", "sha256:2be89a7ccd49878fb5f09d6dfa5811ce09fc1972f0a987bbb5a006992aa3dff3" ] }
运行镜像,查看运行结果
使用Docker客户端登录镜像仓库,执行以下命令,运行镜像,查看运行结果。
docker run -ti registry.cn-hangzhou.aliyuncs.com/docker-builder/java-multi-stage:master Hello World!