Dockerfile多阶段构建原理和使用场景
导语
Docker 17.05版本以后,新增了Dockerfile多阶段构建。所谓多阶段构建,实际上是允许一个Dockerfile 中出现多个 FROM
指令。这样做有什么意义呢?
老版本Docker中为什么不支持多个 FROM 指令
在17.05版本之前的Docker,只允许Dockerfile中出现一个FROM
指令,这得从镜像的本质说起。
在《Docker概念简介》 中我们提到,你可以简单理解Docker的镜像是一个压缩文件,其中包含了你需要的程序和一个文件系统。其实这样说是不严谨的,Docker镜像并非只是一个文件,而是由一堆文件组成,最主要的文件是 层。
Dockerfile 中,大多数指令会生成一个层,比如下方的两个例子:
# 示例一,foo 镜像的Dockerfile
# 基础镜像中已经存在若干个层了
FROM ubuntu:16.04
# RUN指令会增加一层,在这一层中,安装了 git 软件
RUN apt-get update \
&& apt-get install -y --no-install-recommends git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# 示例二,bar 镜像的Dockerfile
FROM foo
# RUN指令会增加一层,在这一层中,安装了 nginx
RUN apt-get update \
&& apt-get install -y --no-install-recommends nginx \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
假设基础镜像ubuntu:16.04
已经存在5层,使用第一个Dockerfile打包成镜像 foo,则foo有6层,又使用第二个Dockerfile打包成镜像bar,则bar中有7层。
如果ubuntu:16.04
等其他镜像不算,如果系统中只存在 foo 和 bar 两个镜像,那么系统中一共保存了多少层呢?
是7层,并非13层,这是因为,foo和bar共享了6层。层的共享机制可以节约大量的磁盘空间和传输带宽,比如你本地已经有了foo镜像,又从镜像仓库中拉取bar镜像时,只拉取本地所没有的最后一层就可以了,不需要把整个bar镜像连根拉一遍。但是层共享是怎样实现的呢?
原来,Docker镜像的每一层只记录文件变更,在容器启动时,Docker会将镜像的各个层进行计算,最后生成一个文件系统,这个被称为 联合挂载。对此感兴趣的话可以进入了解一下 AUFS。
Docker的各个层是有相关性的,在联合挂载的过程中,系统需要知道在什么样的基础上再增加新的文件。那么这就要求一个Docker镜像只能有一个起始层,只能有一个根。所以,Dockerfile中,就只允许一个FROM
指令。因为多个FROM
指令会造成多根,则是无法实现的。但为什么 Docker 17.05 版本以后允许 Dockerfile支持多个 FROM
指令了呢,莫非已经支持了多根?
多个 FROM 指令的意义
多个 FROM 指令并不是为了生成多根的层关系,最后生成的镜像,仍以最后一条 FROM 为准,之前的 FROM 会被抛弃,那么之前的FROM 又有什么意义呢?
每一条 FROM 指令都是一个构建阶段,多条 FROM 就是多阶段构建,虽然最后生成的镜像只能是最后一个阶段的结果,但是,能够将前置阶段中的文件拷贝到后边的阶段中,这就是多阶段构建的最大意义。
最大的使用场景是将编译环境和运行环境分离,比如,之前我们需要构建一个Go语言程序,那么就需要用到go命令等编译环境,我们的Dockerfile可能是这样的:
# Go语言环境基础镜像
FROM golang:1.10.3
# 将源码拷贝到镜像中
COPY server.go /build/
# 指定工作目录
WORKDIR /build
# 编译镜像时,运行 go build 编译生成 server 程序
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server
# 指定容器运行时入口程序 server
ENTRYPOINT ["/build/server"]
基础镜像golang:1.10.3
是非常庞大的,因为其中包含了所有的Go语言编译工具和库,而运行时候我们仅仅需要编译后的server
程序就行了,不需要编译时的编译工具,最后生成的大体积镜像就是一种浪费。
使用脉冲云的解决办法是将程序编译和镜像打包分开,使用脉冲云的编译构建服务,选择增加构Go语言构建工具,然后在构建步骤中编译。
最后将编译接口拷贝到镜像中就行了,那么Dockerfile的基础镜像并不需要包含Go编译环境:
# 不需要Go语言编译环境
FROM scratch
# 将编译结果拷贝到容器中
COPY server /server
# 指定容器运行时入口程序 server
ENTRYPOINT ["/server"]
提示:
scratch
是内置关键词,并不是一个真实存在的镜像。FROM scratch
会使用一个完全干净的文件系统,不包含任何文件。 因为Go语言编译后不需要运行时,也就不需要安装任何的运行库。FROM scratch
可以使得最后生成的镜像最小化,其中只包含了server
程序。
在 Docker 17.05版本以后,就有了新的解决方案,直接一个Dockerfile就可以解决:
# 编译阶段
FROM golang:1.10.3
COPY server.go /build/
WORKDIR /build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server
# 运行阶段
FROM scratch
# 从编译阶段的中拷贝编译结果到当前镜像中
COPY --from=0 /build/server /
ENTRYPOINT ["/server"]
这个 Dockerfile 的玄妙之处就在于 COPY 指令的--from=0
参数,从前边的阶段中拷贝文件到当前阶段中,多个FROM语句时,0代表第一个阶段。除了使用数字,我们还可以给阶段命名,比如:
# 编译阶段 命名为 builder
FROM golang:1.10.3 as builder
# ... 省略
# 运行阶段
FROM scratch
# 从编译阶段的中拷贝编译结果到当前镜像中
COPY --from=builder /build/server /
更为强大的是,COPY --from
不但可以从前置阶段中拷贝,还可以直接从一个已经存在的镜像中拷贝。比如,
FROM ubuntu:16.04
COPY --from=quay.io/coreos/etcd:v3.3.9 /usr/local/bin/etcd /usr/local/bin/
我们直接将etcd镜像中的程序拷贝到了我们的镜像中,这样,在生成我们的程序镜像时,就不需要源码编译etcd了,直接将官方编译好的程序文件拿过来就行了。
有些程序要么没有apt源,要么apt源中的版本太老,要么干脆只提供源码需要自己编译,使用这些程序时,我们可以方便地使用已经存在的Docker镜像作为我们的基础镜像。但是我们的软件有时候可能需要依赖多个这种文件,我们并不能同时将 nginx 和 etcd 的镜像同时作为我们的基础镜像(不支持多根),这种情况下,使用 COPY --from
就非常方便实用了。
Dockerfile多阶段构建原理和使用场景的更多相关文章
- Dockerfile 多阶段构建实践
写在前面 在Docker Engine 17.05 中引入了多阶段构建,以此降低构建复杂度,同时使缩小镜像尺寸更为简单.这篇小作文我们来学习一下如何编写实现多阶段构建的Dockerfile 关于doc ...
- Dockerfile多阶段构建
多阶段构建 之前的做法: 在Docker17.05版本之前,构建Docker镜像,通常采用两种方式: 1.全部放入一个Dockerfile 一种方式是将所有的构建过程全都包含在一个Dockerfile ...
- 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?
在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...
- 第36讲 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景
在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...
- 使用 Docker 开发 - 使用多阶段构建镜像
多阶段构建是一个新特性,需要 Docker 17.05 或更高版本的守护进程和客户端.对于那些努力优化 Dockerfiles 并使其易于阅读和维护的人来说,多阶段构建非常有用. 在多阶段构建之前 构 ...
- Java进阶(七)正确理解Thread Local的原理与适用场景
原创文章,始自发作者个人博客,转载请务必将下面这段话置于文章开头处(保留超链接). 本文转发自技术世界,原文链接 http://www.jasongj.com/java/threadlocal/ Th ...
- 多阶段构建Docker镜像
在Docker 17.05及更高的版本中支持支持一种全新的构建镜像模式:多阶段构建: 多阶段构建Docker镜像的最大好处是使构建出来的镜像变得更小: 目前常见的两个构建镜像的方式为: 1.直接使用某 ...
- [Docker] 使用 Dockerfile 的多级构建 (multi-stage builds)
Multi-stage build 即在一个 Dockerfile 中使用多个 FROM 指令. 每个 FROM 指令可以使用不同的基础镜像,并且每一个都开启新的构建阶段. 你可以有选择地 ...
- docker 多阶段构建
构建镜像最具挑战性的一点是使镜像大小尽可能的小.Dockerfile中的每条指令都为图像添加了一个图层,您需要记住在移动到下一层之前清理任何不需要的工件.对于多阶段构建,您可以在Dockerfile中 ...
随机推荐
- Java——继承,重载,重写三剑客
About-继承 所有Java的类均是由java.lang.Object类继承而来的,所以Object是所有类的祖先类,而除了Object外,所有类必须有一个父类. 继承可以理解为一个对象从另一个对象 ...
- SANGFOR AC配置AD域单点登录(二)----AD域侧配置及单点登录认证、注销测试
1.AD域侧配置 1)新建组策略并配置logon登录脚本,以实现用户开机登录域时,自动通过AC认证 AD域服务器"运行"输入gpmc.msc,打开组策略编辑器,如下图. 右建需要 ...
- UI中的事件系统EventSystem
一.EventSystem简介 用于处理事件的分发和相应的系统,创建画布的同时会创建事件系统 二.UGUI实现事件系统的3种方式 1.使用组件eventTrigger(不推荐),拖动赋值 2.代码添加 ...
- Codeforces Round #635 (Div. 2)
Contest Info Practice Link Solved A B C D E F 4/6 O O Ø Ø O 在比赛中通过 Ø 赛后通过 ! 尝试了但是失败了 - 没有尝试 Sol ...
- HDU 6264 (深搜,数论)
题目链接 题意 求\(\sum_{d|n}\phi (d) \times {n\over d}\),其中\(\phi(n) = n\prod_{p|n}({1-{1\over p}})\) 分析 将\ ...
- 2019牛客多校 Round4
Solved:3 Rank:331 B xor 题意:5e4个集合 每个集合最多32个数 5e4个询问 询问l到r个集合是不是都有一个子集的xor和等于x 题解:在牛客多校第一场学了线性基 然后这个题 ...
- 牛客算法周周练20 F.紫魔法师 (二分图染色)
题意:给你一张图,对其染色,使得相连的点的颜色两两不同求,最少使用多少种颜色. 题解:首先,若\(n=1\),只需要一种.然后我们再去判断是否是二分图,对于二分图,两种颜色就够了,若不是二分图,也就是 ...
- Codeforces Round #654 (Div. 2) C. A Cookie for You (思维)
题意:有\(a\)个蛋糕,\(b\)个巧克力,第一类人有\(n\)个,总是吃多的东西(若\(a>b\),吃蛋糕,否则吃巧克力),第二类人有\(m\)个,总是吃少的,可以随便调整这两类人吃的顺序, ...
- Spring web之restTemplate超时问题处理
问题 项目中有个远程服务因为某些原因会访问不通,于是就在调用的那一步挂起无法结束了. 查看代码 代码大概如下 CloseableHttpClient closeableHttpClient = Htt ...
- OPENSOURCE - libcurl
本文仅做备份存档,原文地址如下,请点链接进入 https://www.cnblogs.com/moodlxs/archive/2012/10/15/2724318.html https://www.c ...