在Docker 17.05之后,Docker在构建中支持了多阶段构建,简单来说,Dockerfile中可以有多个FROM,这篇文章通过一个简单的示例来说明多阶段构建的使用场景和方式。


目录

  • 基本语法
  • 场景说明
    • 代码示例
    • 构建go语言应用
    • 构建go应用镜像
    • 运行go应用容器
  • 上述场景的其他解法
    • 解法示例Dockerfile
    • 分阶段方式的构建
    • 运行go应用容器
  • 总结

基本语法

FROM的三种语法格式如下所示:

语法格式1: FROM [--platform=<platform>] <image> [AS <name>]

语法格式2: FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

语法格式3: FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]


场景说明

比如有如下go语言的类似spring boot的web应用,提供在8080端口提供信息显示服务。此应用构建时需要go语言的编译环境,而一旦运行的时候则只需要一个Alpine容器即可,一般来说可以分两步进行,先编译,编译完生成的二进制文件再进行构建生成可执行的容器。

代码示例

liumiaocn:go liumiao$ cat basic-web-hello.go
package mainimport "fmt"
import "net/http"func Hello(response http.ResponseWriter, request *http.Request) {fmt.Fprintf(response, "Hello, LiuMiao, Welcome to go web programming...\n")
}func main() {http.HandleFunc("/", Hello)http.ListenAndServe(":8080", nil)
}
liumiaocn:go liumiao$

构建go语言应用

执行命令:docker pull golang:1.15.3-alpine3.12

可以看到此编译环境即使是alpine方式也是不小的,如果运行时使用此镜像作为Base镜像大小比较浪费,如果用在边缘设备上基本上不太可行。

liumiaocn:go liumiao$ docker images |grep 1.15.3-alpine3.12
golang                                                               1.15.3-alpine3.12               d099254f5fc3        7 days ago          299MB
liumiaocn:go liumiao$

使用此镜像进行构建

liumiaocn:go liumiao$ docker run --rm -it -v `pwd`:/build golang:1.15.3-alpine3.12 sh
/go # cd /build
/build # ls
basic-web-hello.go
/build # go build -o http-server basic-web-hello.go
/build # ls
basic-web-hello.go  http-server
/build #

可以看到已经成功构建出来所需要的二进制文件http-server了

构建go应用镜像

此处使用一个Alpine镜像即可运行此go语言应用了,我们这里使用如下简单的Dockerfile来实现

liumiaocn:go liumiao$ cat Dockerfile
FROM alpine:3.12.1 WORKDIR /target
COPY    http-server /target/EXPOSE  8080
ENTRYPOINT ["/target/http-server"]
liumiaocn:go liumiao$

然后即可进行镜像的构建了

liumiaocn:go liumiao$ docker build -t test-go:latest .
Sending build context to Docker daemon  6.453MB
Step 1/5 : FROM alpine:3.12.1---> d6e46aa2470d
Step 2/5 : WORKDIR /target---> Running in da7d4cae785c
Removing intermediate container da7d4cae785c---> ae974a6c826c
Step 3/5 : COPY    http-server /target/---> 3fce2c8c9c1c
Step 4/5 : EXPOSE  8080---> Running in dc98c1a16510
Removing intermediate container dc98c1a16510---> 395a0c569fb1
Step 5/5 : ENTRYPOINT ["/target/http-server"]---> Running in e30b500061ea
Removing intermediate container e30b500061ea---> da9281ce2a5f
Successfully built da9281ce2a5f
Successfully tagged test-go:latest
liumiaocn:go liumiao$

可以看到构建的镜像大小也比较适中

liumiaocn:go liumiao$ docker images |grep test-go
test-go                                                              latest                          da9281ce2a5f        2 minutes ago       12MB
liumiaocn:go liumiao$

运行go应用容器

这里使用docker run命令运行此容器,也可以使用其他各种方式,比如docker-compose或者在kubernetes中

liumiaocn:go liumiao$ docker run --rm -p 8080:8080 test-go

在另外的终端或者浏览器中进行确认此8080端口启动的go的web应用

liumiaocn:go liumiao$ curl http://localhost:8080
Hello, LiuMiao, Welcome to go web programming...
liumiaocn:go liumiao$

可以看到已经可以正常运行了

上述场景的其他解法

上述场景是一个非常常见的开发过程的编译、构建和运行阶段的操作,比较典型的就是构建阶段所用到的很多依赖和环境在最终的结果中可能只是部分需要,所以导致的问题就是要分开来做,构建产生二进制文件,然后将此二进制文件拿出来然后再进行构建,而在Docker 17.03之后的这个版本,现在有了新的解法了。

解法示例Dockerfile

首先来看一个示例解法,使用如下Dockerfile可以直接解决此问题

liumiaocn:go liumiao$ cat Dockerfile
ARG  GOLANG_VER=1.15.3
ARG  ALPINE_VER=3.12
FROM golang:${GOLANG_VER}-alpine${ALPINE_VER} as go-builder-1.15.3WORKDIR /buildCOPY basic-web-hello.go /build
RUN  cd /build && go build -o http-server basic-web-hello.goFROM alpine:${ALPINE_VER}WORKDIR /targetCOPY --from=go-builder-1.15.3 /build/http-server /target/EXPOSE  8080
ENTRYPOINT ["/target/http-server"]
liumiaocn:go liumiao$

可以看到这个Dockerfile和普通的Dockerfile略有区别,就是感觉是两个Dockerfile拼接在一起的,后续的Dockerfile中对于前面的关联仅仅在于所生成的二进制文件,它的好处?IAAC算不算一个回答?算。原本需要手工操作才能完成的连接工作,将此二进制文件和其相应的Dockerfile进行关联,但是考虑到比如此二进制文件的版本、支持的操作系统的体系架构、权限设定、传输产生的不完整性风险等,以及相关的Dockerfile的版本变化以及人为的误操作因素等,这么小的一个点也可能产生很多问题。而在一个Dockerfile中就没有这个问题了,因为输入的go语言应用,输出的是最终的应用镜像,没有中间的其他结果,那我们来看一下这种方式下的构建过程。

分阶段方式的构建

liumiaocn:go liumiao$ docker build -t go-app .
Sending build context to Docker daemon  3.072kB
Step 1/11 : ARG  GOLANG_VER=1.15.3
Step 2/11 : ARG  ALPINE_VER=3.12
Step 3/11 : FROM golang:${GOLANG_VER}-alpine${ALPINE_VER} as go-builder-1.15.3---> d099254f5fc3
Step 4/11 : WORKDIR /build---> Running in 64c975a9affe
Removing intermediate container 64c975a9affe---> 11f7f0fed685
Step 5/11 : COPY basic-web-hello.go /build---> e0fae581e05b
Step 6/11 : RUN  cd /build && go build -o http-server basic-web-hello.go---> Running in 025158d79879
Removing intermediate container 025158d79879---> 1cb815aa110f
Step 7/11 : FROM alpine:${ALPINE_VER}---> d6e46aa2470d
Step 8/11 : WORKDIR /target---> Using cache---> ae974a6c826c
Step 9/11 : COPY --from=go-builder-1.15.3 /build/http-server /target/---> 7671328fc17e
Step 10/11 : EXPOSE  8080---> Running in 9db998af6571
Removing intermediate container 9db998af6571---> cf5362c2063a
Step 11/11 : ENTRYPOINT ["/target/http-server"]---> Running in d51adf74a6e2
Removing intermediate container d51adf74a6e2---> 0dfeb1ca46df
Successfully built 0dfeb1ca46df
Successfully tagged go-app:latest
liumiaocn:go liumiao$

可以看到构建的过程包括了编译和最终结果的生成,而我们所关心的是这个镜像是否像我们期待的那样没有包括go的编译环境里面几百兆的内容呢?执行docker images可以看到

liumiaocn:go liumiao$ docker images |grep go-app
go-app                                                               latest                          0dfeb1ca46df        7 seconds ago       12MB
liumiaocn:go liumiao$

正是我们所期待的结果,接下来我们运行一下此go应用,看看是否能够正常动作。

运行go应用容器

同样这里也使用docker run命令运行此容器

liumiaocn:go liumiao$ docker run --rm  -d -p 8080:8080 go-app
3f3b951caab2c91f00157966571aae6c90c08029b65e80dbb8d91f1b49888228
liumiaocn:go liumiao$ docker ps |grep go-app
3f3b951caab2        go-app                          "/target/http-server"   6 seconds ago       Up 4 seconds          0.0.0.0:8080->8080/tcp                                               trusting_shamir
liumiaocn:go liumiao$

在另外的终端或者浏览器中进行确认此8080端口启动的go的web应用

liumiaocn:go liumiao$ curl http://localhost:8080
Hello, LiuMiao, Welcome to go web programming...
liumiaocn:go liumiao$

可以看到已经可以正常运行了

总结

实际上分阶段构建主要应用了Docker 17.03之后的FROM as语法,如果as省略的话from参数可以直接使用0这样的需要进行引用,结合起来还是能够实现很多有用的功能的。从本质上来说Dockerfile是单根模式的,但是在实际使用中,有时会出现可能会需要两个不同的Base镜像的内容的可能,但是在本质上并没有改变其单根的特点,比如本文中最终所生成的go语言应用,它的根是Alpine镜像,它继承了Alpine镜像的所有内容,但是同时又需要另外一个镜像中的某个文件,这实际上是一个非常常见的场景,也可以看出Docker也是一直根据用户需求在不断地进行功能演化。

Dockerfile实践之多阶段构建相关推荐

  1. 2021年 最新 多阶段构建dockerfile实现java源码编译打jar包并做成镜像

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

  2. Dockerfile多阶段构建

    多阶段构建 之前的做法:在Docker17.05版本之前,构建Docker镜像,通常采用两种方式:1.全部放入一个Dockerfile一种方式是将所有的构建过程全都包含在一个Dockerfile中,包 ...

  3. Dockerfile构建Nginx镜像、镜像优化(多阶段构建,最小化镜像构建)

    Dockerfile创建镜像 Dockerfile 有以下指令选项: FROM MAINTAINER RUN CMD EXPOSE ENV ADD COPY ENTRYPOINT VOLUME USE ...

  4. dockerfile 中的 multi-stage 多阶段构建

    在应用了容器技术的软件开发过程中,控制容器镜像的大小可是一件费时费力的事情.如果我们构建的镜像既是编译软件的环境,又是软件最终的运行环境,这是很难控制镜像大小的.所以常见的配置模式为:分别为软件的编译 ...

  5. 从Docker镜像构建演化史来了解多阶段构建的影响

    现在很多开发者都会慢慢习惯在开发环境通过Docker来构建开发环境,有时候可能会有环境移植的问题,所以需要我们写好一套Dockerfile来构建相关的开发镜像,既然说到镜像,那我想问问大家了解Dock ...

  6. Docker 镜像多阶段构建实战总结

    文章目录 Docker 镜像多阶段构建实战总结 一 背景 二 实践步骤 2.1 只通过一个 Dockerfile 来构建[方案一] 2.2 多个 Dockerfile 实现多阶段构建[方案二] 2.3 ...

  7. 【前端部署第四篇】使用 Docker 构建缓存及多阶段构建优化单页应用

    大家好,我是山月,这是我最近新开的专栏:前端部署系列.包括 Docker.CICD 等内容,大纲图示如下: 示例代码开源,置于 Github 中,演示如何对真实项目进行部署上线. simple-dep ...

  8. Dockerfile实践优化建议

    本文讲的是Dockerfile实践优化建议[编者的话]Dockerfile是一种被Docker程序解释的脚本,Dockerfile由一条一条的指令组成,每条指令对应Linux下面的一条命令.Docke ...

  9. docker 多阶段构建

    多阶段构建 之前的做法 在 Docker 17.05 版本之前,我们构建 Docker 镜像时,通常会采用两种方式: 全部放入一个 Dockerfile 一种方式是将所有的构建过程编包含在一个 Doc ...

最新文章

  1. pandas使用to_datetime函数把dataframe的字符串日期数据列转化为日期格式日期数据列( strings to datetime in dataframe column)
  2. linux进程--进程调度算法(十三)
  3. 动态规划——最长公共子序列(LCS)
  4. Qt与FFmpeg联合开发指南(二)——解码(2):封装和界面设计
  5. 解决报错:java.util.UnknownFormatConversionException: Conversion = ‘p‘
  6. containerd与安全沙箱的Kubernetes初体验
  7. 「拨云见日」英特尔揭秘短视频背后的二三事
  8. python windows自动化 爬虫_使用Python实现自动化截取Windows系统屏幕
  9. 服务器云采购,从发展角度看小企业需要上云还是采购服务器
  10. LabelImg 影像標註工具使用教學,製作深度學習用的資料集
  11. 金士顿服务器内存条型号解读,教你如何解读金士顿台式机内存标签的含义
  12. 火星人学习第二周——虚幻引擎蓝图应用与开发
  13. python全系列官方中文文档
  14. 打印机显示正在未连接服务器,打印机状态未联机是怎么回事
  15. 最新互联网架构师视频教程+源码20G
  16. wpf给模板控件添加事件一
  17. Summarization 文本摘要进展
  18. Hadoop HA架构
  19. Android应用内展示office文件--腾讯浏览服务(TBS)
  20. filter()函数使用

热门文章

  1. js语法基础入门(4)
  2. 什么TDD,让它见鬼去吧!
  3. 高等代数 线性映射(第9章)6 线性函数与对偶空间
  4. MongoDB与亚马逊云科技扩大全球合作
  5. CodeForces 1169B、Pairs
  6. 苹果手机屏幕尺寸和弹出键盘高度总结
  7. python判断图片类型_Python使用filetype精确判断文件类型
  8. 【系统分析师之路】第一章 计算机组成与体系结构
  9. 拦截器HandlerInterceptor+方法参数解析器HandlerMethodArgumentResolver用于统一获取当前登录用户信息
  10. BlackHoleSwap智能合约已通过PeckShield安全审计服务