微信截图_20190226154718

我们在github上,或者在一些应用官方提供的docker镜像的Dockerfile中,经常会看到很多难以琢磨的操作,这篇文章主要说明使用Dockerfile的RUN命令为什么要在后面使用&&链接,以及在实际镜像中的影响和区别。阅读本篇文章对Dockerfile的RUN命令和层有更深的认识,同时学会更好的缩减容器的镜像。

为了更好的演示RUN命令与层带来的差异,我们分为几个步骤来做!

多条RUN#

首先,我们进行多条命令RUN命令测试,如下:

安装nginx,php,curl三个软件包

[marksugar@www.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的同时,依次进行

[marksugar@www.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

[marksugar@www.linuxea.com ~]# docker images linuxea:26
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
linuxea             26                  f4000a9883f1        12 seconds ago      18.8MB

分为七层,每一条命令都分为一层,加起来的总大小是18.8M

[marksugar@www.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,使用&&链接起来执行

[marksugar@www.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 
[marksugar@www.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

[marksugar@www.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

[marksugar@www.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,也就是同一个层的镜像构建的要小一些。那现在肯定有人会迷惑,这说明了什么?

现在,我们不妨在看看,我们换个方式。

[marksugar@www.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

[marksugar@www.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。

[marksugar@www.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命令

[marksugar@www.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  

我们进入容器确认是否被卸载

[marksugar@www.linuxea.com ~]# docker run --rm -it linuxea:26-2 sh
/ # curl
sh: curl: not found
/ # 

发生了什么?#

为什么在Dockerfile删除的软件已经消失,而层的大小并没有因为删除而缩减?

要回答这一点,我们要返回上面的docker history linuxea:26-2中查看。我们将SIZE用加法运算,得到的大小与docker image查看的几乎接近,如下

[marksugar@www.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和&&链接起来,这样在中间的层就表现为一个层,这样的情况并不利于调试,如上所述。每次构建都会认为是一条指令,当发生一条命令的变化,就无法使用缓存加速构建。

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

[marksugar@www.linuxea.com ~]# docker images linuxea:26-3
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
linuxea             26-3                f300b5797d81        33 seconds ago      5.56MB
[marksugar@www.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

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文档和我的其他帖子以了解更多信息。