Docker 多阶段构建实战 (multi-stage builds)
欢迎访问我的 GitHub
这里分类和汇总了欣宸的全部原创(含配套源码): http://github.com/zq2599/blog_demos
本篇概览
-
在编写 Dockerfile 构建 docker 镜像时,常遇到以下问题:
-
RUN 命令会让镜像新增 layer,导致镜像变大,虽然通过 &&连接多个命令能缓解此问题,但如果命令之间用到 docker 指令例如 COPY、WORKDIR 等,依然会导致多个 layer;
-
有些工具在构建过程中会用到,但是最终的镜像是不需要的(例如用 maven 编译构建 java 工程),这要求 Dockerfile 的编写者花更多精力来清理这些工具,清理的过程又可能导致新的 layer;
-
为了解决上述问题,从 17.05 版本开始 Docker 在构建镜像时增加了新特性:多阶段构建(multi-stage builds),将构建过程分为多个阶段,每个阶段都可以指定一个基础镜像,这样在一个 Dockerfile 就能将多个镜像的特性同时用到,例如:先用 maven 镜像构建 java 工程,再把构建结果和 jre 合成,就做成了一个可以直接运行 java 工程镜像了;
-
官方描述如下图所示,地址是: http://docs.docker.com/develop/develop-images/multistage-build/
-
官方的实例是 golang 的,今天我们以 maven 构建 springboot 工程为例,实战如何使用 multi-stage 特性构建 java 微服务镜像;
环境信息
-
本次实战的环境信息如下:
-
操作系统:Ubuntu 18.04.2 LTS
-
Docker:18.06.1-ce
-
Java:1.8.0_191
-
Maven:3.6.1
实战源码
-
本次实战用到的源码是个普通 springboot 工程,功能是 SpringCloud 中的注册中心 eureka,您可以在 Github 下载,地址和链接如下所示:
-
这个 git 项目中有多个文件夹,本章源码在 springcloudscaledemo 这个文件夹下,如下图红框所示:
-
springcloudscaledemo 文件夹内有三个工程,本次实战用到的是 eureka-server,如下图:
准备材料
-
在能正常运行 docker 的电脑上新建一个目录,例如我这里是 ubuntu 系统上**/home/willzhao/temp/201906/02** ,将 maven 工程 eureka-server 复制到这个目录下;
避免每次构建镜像都下载工程所需的 jar 包
-
构建镜像过程中会用 maven 构建 springboot 工程,会下载 springboot 工程依赖的 jar 包,此过程很漫长,如果您多次构建 Dockerfile 镜像,那么每次都要经历这个过程,为了避免每次都下载,请做如下操作:
-
找一个可以运行 maven 的环境,把 eureka-server 工程复制到这个环境上;
-
在 eureka-server 目录下执行命令 mvn clean package -U -DskipTests ,开始构建此工程;
-
构建成功后,进入本地的 maven 缓存目录,通常是用户的 home 目录下的**.m2** 文件夹,里面有个名为 repository 的目录;
-
将整个 repository 目录复制到前面提到的**/home/willzhao/temp/201906/02** 目录下,和 eureka-server 放在同一目录,如下图:
-
这样,在编写 Dockerfile 的时候只要用这个 repository 覆盖镜像中的 maven 缓存,在编译时就不会去 maven 的中央仓库下载 jar 了,会节省很多时间。
编写 Dockerfile
-
在**/home/willzhao/temp/201906/02** 目录下创建文件 Dockerfile,内容如下所示:
# Docker image for multi stage build
# VERSION 0.0.1
# Author: bolingcavalry
### 第一阶段,用maven镜像进行编译
FROM maven:3.6.1 AS compile_stage
####################定义环境变量 start####################
#定义工程名称,也是源文件的文件夹名称
ENV PROJECT_NAME eureka-server
#定义工作目录
ENV WORK_PATH /usr/src/$PROJECT_NAME
####################定义环境变量 start####################
#作者
MAINTAINER BolingCavalry <[email protected]>
#将源码复制到当前目录
COPY ./$PROJECT_NAME $WORK_PATH
#如果前面您已经准备好了repository目录,就可以用来替换镜像中的repository目录了,先删除镜像中已有的repository
RUN rm -rf /root/.m2/repository
#将准备好的repository文件夹复制进来,这样相当于镜像环境中已经有了java工程所需的jar,可以避免去maven中央仓库下载
COPY ./repository /root/.m2/repository
#编译构建
RUN cd $WORK_PATH && mvn clean package -U -DskipTests
### 第二阶段,用第一阶段的jar和jre镜像合成一个小体积的镜像
FROM java:8-jre-alpine
####################定义环境变量 start####################
#定义工程名称,也是源文件的文件夹名称
ENV PROJECT_NAME eureka-server
#定义工程版本
ENV PROJECT_VERSION 0.0.1-SNAPSHOT
#定义工作目录
ENV WORK_PATH /usr/src/$PROJECT_NAME
####################定义环境变量 start####################
#安全起见不用root账号,新建用户admin
RUN adduser -Dh /home/admin admin
#工作目录是/app
WORKDIR /app
#从名为compile_stage的stage复制构建结果到工作目录
COPY --from=compile_stage $WORK_PATH/target/${PROJECT_NAME}-${PROJECT_VERSION}.jar .
#启动应用
CMD ["sh", "-c", "java -jar /app/${PROJECT_NAME}-${PROJECT_VERSION}.jar --spring.profiles.active=dev"]
复制代码
-
上面就是分成了两个阶段构建的 Dockerfile 脚本,请参考每行的注释来理解,有以下几点需要重点关注:
-
一共有两次 FROM 指令出现,而最终的镜像是基于最后一个 FROM 生成的;
-
PROJECT_NAME 这个环境变量被定义了两次(ENV PROJECT_NAME),因为前面阶段定义的环境变量在后面的阶段是用不了的;
-
COPY --from=compile_stage这个命令,可以将指定阶段的文件复制到当前阶段来,这一步很关键,第一阶段用 maven 构建出来的 jar 文件,通过该命令复制到后面的阶段来使用了;
-
最后一个 FROM 是 java:8-jre-alpine,这是精简版的 java 运行环境镜像,最终镜像的内容就是 jre 和 maven 的构建结果,而前面的 maven 镜像和最终构建结果无关;
构建镜像
-
在 Dockerfile 所在目录执行以下命令即可构建镜像:
docker build -t bolingcavalry/multi-stage-build:0.0.1-SNAPSHOT .
复制代码
-
输出的部分结果信息如下:
...
Step 13/16 : RUN adduser -Dh /home/admin admin
---> Running in 20421e52c3e6
Removing intermediate container 20421e52c3e6
---> dfb33f654436
Step 14/16 : WORKDIR /app
---> Running in d17f74e9c119
Removing intermediate container d17f74e9c119
---> bbd17f2d0777
Step 15/16 : COPY --from=compile_stage $WORK_PATH/target/${PROJECT_NAME}-${PROJECT_VERSION}.jar .
---> 5d194c2a6b17
Step 16/16 : CMD ["sh", "-c", "java -jar /app/${PROJECT_NAME}-${PROJECT_VERSION}.jar --spring.profiles.active=dev"]
---> Running in 2cb771e5af44
Removing intermediate container 2cb771e5af44
---> b05fc74903ed
Successfully built b05fc74903ed
Successfully tagged bolingcavalry/multi-stage-build:0.0.1-SNAPSHOT
复制代码
-
查看镜像的体积如下,148 兆,符合预期:
REPOSITORY TAG IMAGE ID CREATED SIZE
bolingcavalry/multi-stage-build 0.0.1-SNAPSHOT b05fc74903ed 2 minutes ago 148MB
maven 3.6.1 740262c47f21 3 days ago 614MB
java 8-jre-alpine fdc893b19a14 2 years ago 108MB
复制代码
验证镜像是否可用
-
执行以下命令可以用最新的镜像启动一个容器:
docker run -p 8080:8082 bolingcavalry/multi-stage-build:0.0.1-SNAPSHOT
复制代码
-
在前面 Dockerfile 文件的 CMD 命令中指定了 profile 参数为 dev,所以 application-dev.properties 文件会生效,这里面定义的端口号是 8082,所以 docker run 命令中通过-p 参数将容器的 8082 端口映射到宿主机 8080 端口
-
用浏览器访问宿主机的 8080 端口,看服务是否正常,如下图:
-
至此,docker 的多阶段构建实战就完成了,这是个很实用功能,在您构建镜像的过程中如果想用到多个镜像的能力,又不想自己去做相关的集成和清理工作,并且对镜像体积有要求的时候,希望本文能给您一些参考。
欢迎关注 InfoQ:程序员欣宸
- 那些 Go 语言发展历史上的重大决策
- 从趋势到挑战,一站式解读操作系统运维和可观测性
- 百万级 Topic,腾讯云的 Apache Pulsar 稳定性实践
- Apache Doris 在思必驰的应用优化实践:海量语音通话数据下,实时、离线一体的数仓架构设计实践
- 爱数正式开源认知智能开发框架 KWeaver
- 运维智能化的三大关键技术
- “抄我的还‘反捅’我一刀”,Gary Marcus 发文驳斥图灵奖得主 Yann LeCun
- 当出海成为必选项,企业如何构建全场景全生态技术底座?
- 数智底座必备能力三:快速构建创新应用
- Docker 多阶段构建实战 (multi-stage builds)
- 工作笔记之 SELECT 语句在 SAP ABAP 中的用法总结(上)
- 经久不衰的设计定律是不要让我思考的设计
- 不要指望下一个像 GPT 这样的大型语言模型会民主化
- Java 近期新闻:Helidon Níma、Spring Framework、MicroProfile、MicroStream、Kotlin 和 Piranha
- 一文入门 jQuery
- C 学习 ---__libc_open 函数的原理
- 监控系统工作原理
- 甲骨文新微服务框架 Helidon Níma:使用虚拟线程实现高性能
- 【云原生 | 从零开始学 Kubernetes】二、使用 kubeadm 搭建 K8S 集群
- Elasticsearch 聚合学习之四:结果排序