(转)多阶段构建---多个FROM指令
⽼版本Docker中为什么不⽀持多个 FROM 指令
Docker 17.05版本以后,新增了Dockerfile多阶段构建。所谓多阶段构建,实际上是允许⼀个Dockerfile 中出现多个FROM指令。这样做有什么意义呢?
⽼版本Docker中为什么不⽀持多个 FROM 指令
在17.05版本之前的Docker,只允许Dockerfile中出现⼀个FROM指令,这得从镜像的本质说起。
在《Docker概念简介》中我们提到,你可以简单理解Docker的镜像是⼀个压缩⽂件,其中包含了你需要的程序和⼀个⽂件系统。其实这样说是
层。
不严谨的,Docker镜像并⾮只是⼀个⽂件,⽽是由⼀堆⽂件组成,最主要的⽂件是层
Dockerfile 中,⼤多数指令会⽣成⼀个层,⽐如下⽅的两个例⼦:
# ⽰例⼀,foo 镜像的Dockerfile
# 基础镜像中已经存在若⼲个层了
FROMubuntu:16.04
# RUN指令会增加⼀层,在这⼀层中,安装了 git 软件
RUN apt-getupdate \
&& apt-getinstall -y --no-install-recommends git \
&& apt-getclean \
&& rm -rf /var/lib/apt/lists/*
# ⽰例⼆,bar 镜像的Dockerfile
FROMfoo
# RUN指令会增加⼀层,在这⼀层中,安装了 nginx
RUN apt-getupdate \
&& apt-getinstall -y --no-install-recommends nginx \
&& apt-getclean \
&& 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语⾔环境基础镜像
FROMgolang:1.10.3
# 将源码拷贝到镜像中
/build/
# 指定⼯作⽬录
WORKDIR /build
# 编译镜像时,运⾏ go build 编译⽣成 server 程序
RUN CGO_ENABLED=0GOOS=linux GOARCH=amd64 GOARM=6go build -ldflags'-w -s' -o server
# 指定容器运⾏时⼊⼝程序 server
ENTRYPOINT ["/build/server"]
基础镜像golang:1.10.3是⾮常庞⼤的,因为其中包含了所有的Go语⾔编译⼯具和库,⽽运⾏时候我们仅仅需要编译后的server程序就⾏了,不需要编译时的编译⼯具,最后⽣成的⼤体积镜像就是⼀种浪费。
使⽤脉冲云的解决办法是将程序编译和镜像打包分开,使⽤脉冲云的编译构建服务,选择增加构Go语⾔构建⼯具,然后在构建步骤中编译。
最后将编译接⼝拷贝到镜像中就⾏了,那么Dockerfile的基础镜像并不需要包含Go编译环境:
# 不需要Go语⾔编译环境
FROMscratch
# 将编译结果拷贝到容器中
COPY server /server
# 指定容器运⾏时⼊⼝程序 server
ENTRYPOINT ["/server"]
提⽰:scratch是内置关键词,并不是⼀个真实存在的镜像。FROM scratch会使⽤⼀个完全⼲净的⽂件
系统,不包含任何⽂件。 因为Go语⾔编译后不需要运⾏时,也就不需要安装任何的运⾏库。FROM scratch可以使得最后⽣成的镜像最⼩化,其中只包含了 server 程序。
在 Docker 17.05版本以后,就有了新的解决⽅案,直接⼀个Dockerfile就可以解决:
# 编译阶段
FROMgolang:1.10.3
/build/
WORKDIR /build
RUN CGO_ENABLED=0GOOS=linux GOARCH=amd64 GOARM=6go build -ldflags'-w -s' -o server
# 运⾏阶段
FROMscratch
# 从编译阶段的中拷贝编译结果到当前镜像中
COPY --from=0/build/server /
ENTRYPOINT ["/server"]
这个 Dockerfile 的⽞妙之处就在于 COPY 指令的--from=0参数,从前边的阶段中拷贝⽂件到当前阶段中,多个FROM语句时,0代表第⼀个阶段。除了使⽤数字,我们还可以给阶段命名,⽐如:
# 编译阶段 命名为 builder
FROMgolang:1.10.3asbuilder
# ... 省略
# 运⾏阶段docker打包镜像
FROMscratch
# 从编译阶段的中拷贝编译结果到当前镜像中
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多阶段构建

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。