我们在github上,或者在一些应用官方提供的docker镜像的Dockerfile中,经常会看到很多难以琢磨的操作,这篇文章主要说明使用Dockerfile的RUN命令为什么要在后面使用&&链接,以及在实际镜像中的影响和区别。阅读本篇文章对Dockerfile的RUN命令和层有更深的认识,同时学会更好的缩减容器的镜像。
为了更好的演示RUN命令与层带来的差异,我们分为几个步骤来做!
- 1,测试多条命令的大小与差异
- 2,减少多条命令的大小与差异
多条RUN
首先,我们进行多条命令RUN命令测试,如下:
安装nginx,php,curl三个软件包
[root@linuxea.com ~]# cat Dockerfile
FROM alpine:3.9 as default
MAINTAINER www.linuxea.com
LABEL maintainer="www.linuxea.com"
RUN apk add nginx
RUN apk add php
RUN apk add curl
在Build的同时,依次进行
[root@linuxea.com ~]# docker build -t linuxea:26 .
Sending build context to Docker daemon 427kB
Step 1/6 : FROM alpine:3.9 as default
---> caf27325b298
Step 2/6 : MAINTAINER www.linuxea.com
---> Using cache
---> 5ee27f5e579a
Step 3/6 : LABEL maintainer="www.linuxea.com"
---> Using cache
---> a9b2f826f6c9
Step 4/6 : RUN apk add nginx
---> Running in c2b048d78656
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz
(1/2) Installing pcre (8.42-r1)
(2/2) Installing nginx (1.14.2-r0)
Executing nginx-1.14.2-r0.pre-install
Executing busybox-1.29.3-r10.trigger
OK: 7 MiB in 16 packages
Removing intermediate container c2b048d78656
---> 116c307055ed
Step 5/6 : RUN apk add php
---> Running in 29c651203043
(1/7) Installing php7-common (7.2.14-r0)
(2/7) Installing ncurses-terminfo-base (6.1_p20190105-r0)
(3/7) Installing ncurses-terminfo (6.1_p20190105-r0)
(4/7) Installing ncurses-libs (6.1_p20190105-r0)
(5/7) Installing libedit (20181209.3.1-r0)
(6/7) Installing libxml2 (2.9.9-r1)
(7/7) Installing php7 (7.2.14-r0)
Executing busybox-1.29.3-r10.trigger
OK: 21 MiB in 23 packages
Removing intermediate container 29c651203043
---> ec1cbef7f89e
Step 6/6 : RUN apk add curl
---> Running in d96aef6e09f8
(1/5) Installing ca-certificates (20190108-r0)
(2/5) Installing nghttp2-libs (1.35.1-r0)
(3/5) Installing libssh2 (1.8.0-r4)
(4/5) Installing libcurl (7.63.0-r0)
(5/5) Installing curl (7.63.0-r0)
Executing busybox-1.29.3-r10.trigger
Executing ca-certificates-20190108-r0.trigger
OK: 22 MiB in 28 packages
Removing intermediate container d96aef6e09f8
---> f4000a9883f1
Successfully built f4000a9883f1
Successfully tagged linuxea:26
此时构建的镜像大小为18.8M
[root@linuxea.com ~]# docker images linuxea:26
REPOSITORY TAG IMAGE ID CREATED SIZE
linuxea 26 f4000a9883f1 12 seconds ago 18.8MB
分为七层,每一条命令都分为一层,加起来的总大小是18.8M
[root@linuxea.com ~]# docker history linuxea:26
IMAGE CREATED CREATED BY SIZE COMMENT
f4000a9883f1 18 seconds ago /bin/sh -c apk add curl 1.68MB
ec1cbef7f89e 21 seconds ago /bin/sh -c apk add php 8.89MB
116c307055ed 54 seconds ago /bin/sh -c apk add nginx 2.74MB
a9b2f826f6c9 9 days ago /bin/sh -c #(nop) LABEL maintainer=www.linu… 0B
5ee27f5e579a 9 days ago /bin/sh -c #(nop) MAINTAINER www.linuxea.com 0B
caf27325b298 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:2a1fc9351afe35698… 5.53MB
单条RUN
此刻,我们尝试将安装的nginx,php,curl放在一条RUN,使用&&链接起来执行
[root@linuxea.com ~]# cat Dockerfile
FROM alpine:3.9 as default
MAINTAINER www.linuxea.com
LABEL maintainer="www.linuxea.com"
RUN apk add nginx
&& apk add php
&& apk add curl
[root@linuxea.com ~]# docker build -t linuxea:26-1 .
Sending build context to Docker daemon 427kB
Step 1/4 : FROM alpine:3.9 as default
---> caf27325b298
Step 2/4 : MAINTAINER www.linuxea.com
---> Using cache
---> 5ee27f5e579a
Step 3/4 : LABEL maintainer="www.linuxea.com"
---> Using cache
---> a9b2f826f6c9
Step 4/4 : RUN apk add nginx && apk add php && apk add curl
---> Running in 19fb009c38d1
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz
(1/2) Installing pcre (8.42-r1)
(2/2) Installing nginx (1.14.2-r0)
Executing nginx-1.14.2-r0.pre-install
Executing busybox-1.29.3-r10.trigger
OK: 7 MiB in 16 packages
(1/7) Installing php7-common (7.2.14-r0)
(2/7) Installing ncurses-terminfo-base (6.1_p20190105-r0)
(3/7) Installing ncurses-terminfo (6.1_p20190105-r0)
(4/7) Installing ncurses-libs (6.1_p20190105-r0)
(5/7) Installing libedit (20181209.3.1-r0)
(6/7) Installing libxml2 (2.9.9-r1)
(7/7) Installing php7 (7.2.14-r0)
Executing busybox-1.29.3-r10.trigger
OK: 21 MiB in 23 packages
(1/5) Installing ca-certificates (20190108-r0)
(2/5) Installing nghttp2-libs (1.35.1-r0)
(3/5) Installing libssh2 (1.8.0-r4)
(4/5) Installing libcurl (7.63.0-r0)
(5/5) Installing curl (7.63.0-r0)
Executing busybox-1.29.3-r10.trigger
Executing ca-certificates-20190108-r0.trigger
OK: 22 MiB in 28 packages
Removing intermediate container 19fb009c38d1
---> 6d1be09ddac1
Successfully built 6d1be09ddac1
Successfully tagged linuxea:26-1
得到的大小是18.7M
[root@linuxea.com ~]# docker images linuxea:26-1
REPOSITORY TAG IMAGE ID CREATED SIZE
linuxea 26-1 6d1be09ddac1 9 seconds ago 18.7MB
而层也随机变少了,只有5层,18.7M
[root@linuxea.com ~]# docker history linuxea:26-1
IMAGE CREATED CREATED BY SIZE COMMENT
6d1be09ddac1 23 seconds ago /bin/sh -c apk add nginx && apk add php &… 13.1MB
a9b2f826f6c9 9 days ago /bin/sh -c #(nop) LABEL maintainer=www.linu… 0B
5ee27f5e579a 9 days ago /bin/sh -c #(nop) MAINTAINER www.linuxea.com 0B
caf27325b298 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:2a1fc9351afe35698… 5.53MB
思考?
现在我们经过对比,多条RUN似乎会导致多层,并且比一条RUN,也就是同一个层的镜像构建的要小一些。那现在肯定有人会迷惑,这说明了什么?
现在,我们不妨在看看,我们换个方式。
[root@linuxea.com ~]# cat Dockerfile
FROM alpine:3.9 as default
MAINTAINER www.linuxea.com
LABEL maintainer="www.linuxea.com"
RUN apk add nginx
RUN apk add php
RUN apk add curl
RUN apk del nginx php curl
如上,假设,我此刻在Dockerfile中已经使用了在这三个软件,在构建的最后步骤,我希望删掉->依赖安装最小化原则。而后我在进行build
[root@linuxea.com ~]# docker build -t linuxea:26-2 .
Sending build context to Docker daemon 427kB
Step 1/7 : FROM alpine:3.9 as default
---> caf27325b298
Step 2/7 : AINTAINER www.linuxea.com
---> Using cache
---> 5ee27f5e579a
Step 3/7 : LABEL maintainer="www.linuxea.com"
---> Using cache
---> a9b2f826f6c9
Step 4/7 : RUN apk add nginx
---> Using cache
---> 116c307055ed
Step 5/7 : RUN apk add php
---> Using cache
---> ec1cbef7f89e
Step 6/7 : RUN apk add curl
---> Using cache
---> f4000a9883f1
Step 7/7 : RUN apk del nginx php curl
---> Running in 124fea0865fb
(1/14) Purging nginx (1.14.2-r0)
(2/14) Purging php7 (7.2.14-r0)
(3/14) Purging php7-common (7.2.14-r0)
(4/14) Purging curl (7.63.0-r0)
(5/14) Purging pcre (8.42-r1)
(6/14) Purging libedit (20181209.3.1-r0)
(7/14) Purging ncurses-libs (6.1_p20190105-r0)
(8/14) Purging ncurses-terminfo (6.1_p20190105-r0)
(9/14) Purging ncurses-terminfo-base (6.1_p20190105-r0)
(10/14) Purging libxml2 (2.9.9-r1)
(11/14) Purging libcurl (7.63.0-r0)
(12/14) Purging ca-certificates (20190108-r0)
Executing ca-certificates-20190108-r0.post-deinstall
(13/14) Purging nghttp2-libs (1.35.1-r0)
(14/14) Purging libssh2 (1.8.0-r4)
Executing busybox-1.29.3-r10.trigger
OK: 6 MiB in 14 packages
Removing intermediate container 124fea0865fb
---> 3a74abf45025
Successfully built 3a74abf45025
Successfully tagged linuxea:26-2
构建完成。镜像的大小仍然是18.9M。
[root@linuxea.com ~]# docker images linuxea:26-2
REPOSITORY TAG IMAGE ID CREATED SIZE
linuxea 26-2 3a74abf45025 16 seconds ago 18.9MB
我们使用docker history命令查看镜像,可见我们的确执行了apk del nginx php curl命令
[root@linuxea.com ~]# docker history linuxea:26-2
IMAGE CREATED CREATED BY SIZE COMMENT
3a74abf45025 28 seconds ago /bin/sh -c apk del nginx php curl 21.2kB
f4000a9883f1 6 minutes ago /bin/sh -c apk add curl 1.68MB
ec1cbef7f89e 6 minutes ago /bin/sh -c apk add php 8.89MB
116c307055ed 6 minutes ago /bin/sh -c apk add nginx 2.74MB
a9b2f826f6c9 9 days ago /bin/sh -c #(nop) LABEL maintainer=www.linu… 0B
5ee27f5e579a 9 days ago /bin/sh -c #(nop) MAINTAINER www.linuxea.com 0B
caf27325b298 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:2a1fc9351afe35698… 5.53MB
我们进入容器确认是否被卸载
[root@linuxea.com ~]# docker run --rm -it linuxea:26-2 sh
/ # curl
sh: curl: not found
/ #
发生了什么?
为什么在Dockerfile删除的软件已经消失,而层的大小并没有因为删除而缩减?
要回答这一点,我们要返回上面的docker history linuxea:26-2中查看。我们将SIZE用加法运算,得到的大小与docker image查看的几乎接近,如下
[root@linuxea.com ~]# docker history linuxea:26-2|awk '{print $NF}'|grep -v "COMMENT"
21.2kB
1.68MB
8.89MB
2.74MB
0B
0B
0B
5.53MB
这说明docker会将所有的层相加。尽管我们在删除了这三个包,但是在上一层中,这三个包仍然是存在的,因为从下到上叠加的层最终构成了镜像。所以,要从层中有效删除,就需要在一个层中有效的组织。
这些层是否就没有了作用?
当在测试环境中,编写Dockerfile的时候可以使用多层,也就是多条RUN的命令来缓存每一个层做测试或者调试,当相同的指令就会复用缓存中间层。这可以有效的缩减build时间,原因是利用了docker的构建缓存。而构建缓存在CI/CD中是很有用的。
RUN正确的使用方式
我们应该将命令使用RUN和&&链接起来,这样在中间的层就表现为一个层,这样的情况并不利于调试,如上所述。每次构建都会认为是一条指令,当发生一条命令的变化,就无法使用缓存加速构建。
- 我们重新build,并且删除缓存
FROM alpine:3.9 as default
MAINTAINER www.linuxea.com
LABEL maintainer="www.linuxea.com"
RUN apk add nginx
&& apk add php
&& apk add curl
&& apk del nginx php curl
&& rm /var/cache/apk/*
重新build后的大小仅为5.56M
[root@linuxea.com ~]# docker images linuxea:26-3
REPOSITORY TAG IMAGE ID CREATED SIZE
linuxea 26-3 f300b5797d81 33 seconds ago 5.56MB
[root@linuxea.com ~]# docker history linuxea:26-3
IMAGE CREATED CREATED BY SIZE COMMENT
f300b5797d81 2 seconds ago /bin/sh -c apk add nginx && apk add php &&… 26kB
a9b2f826f6c9 10 days ago /bin/sh -c #(nop) LABEL maintainer=www.linu… 0B
5ee27f5e579a 10 days ago /bin/sh -c #(nop) MAINTAINER www.linuxea.com 0B
caf27325b298 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:2a1fc9351afe35698… 5.53MB
其他的办法
我们在使用RUN命令的时候,除了尽可能的减少层,我们还可以使用docker多阶段构建Multi-Stage。
- docker多阶段构建将"上一次的构建用在下一个Dockerfile中",这很实用
FROM golang:1.7.3 as builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
延伸阅读
linuxea:如何利用docker构建缓存快速迭代?
linuxea:docker多阶段构建Multi-Stage与Builder对比总结
学习更多
学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。
- docker目录
- 白话容器
- docker-compose