使用Dockerfile进行镜像构建,自然离不开RUN命令,相较于docker run的run命令,Dockerfile中的RUN是镜像创建阶段使用的命令,而docker run则是使用镜像启动容器阶段使用的命令。这篇文章主要介绍一下使用RUN命令常见的技巧和注意事项。

基本原则1: 尽量减少一个Dockerfile中的RUN命令的个数

RUN命令在构建时会创建一个新层,如非特殊的需要,建议一个Dockerfile在需要使用RUN命令的时候尽可能的只用一个RUN命令,将多条RUN命令进行合并可以有效降低构建的镜像的层数。


基本原则2: 使用&&连接多条命令

出错之后就地返回是Dockerfile中进行错误控制的一条经验,这样不但能快速定位到错误的地方,还能出错时减少镜像构建的时间。在RUN命令中如果需要使用多条语句,建议使用&&进行连接,单条语句结束时使用 \进行连接避免可读性太差。使用全部使用&&符的好处在于如果出错后续的内容就会不再执行,在镜像构建失败的时候根据出错的第一现场能更加容易地快速确认问题的原因。


基本原则3: 版本等变化信息以变量方式提前定义

考虑到那些内容会经常变化,比如版本的信息,此类信息往往会多处出现,后续版本升级的时候也需要进行修改,所以尽可能在整个Dockerfile中仅有一处进行定义,方便于调试和后续升级。


基本原则4: 避免复杂的逻辑实现

虽然使用基本原则1和基本原则2之后,RUN命令看起来似乎更加规范,可读性更强了,但是仍然改变不了的它是将多行语句顺次连在一起形成的一个命令集合,唯一的逻辑关联就是&&符连接的部分一旦出错,后续的语句不再执行。复杂的逻辑判断和循环尽可能的避免出现在Dockerfile之中,如果一定需要,可以考虑以脚本文件的方式存在,结合COPY命令拷贝之后再执行,这样虽然增加了一个文件进行维护,但是Dockerfile本身的可读性得到了很好的增强。


基本原则5: 使用set -o pipefail来设定管道出错的返回值

本着出错时就地返回的原则,在RUN命令开头通过set -o pipefail来设定管道出错时的返回值,缺省状况下,使用管道符号连接起来的多条命令,命令的返回值是最后一条命令的结果,只要最后一条命令出错,结果一定是成功。如果希望管道符号连接起来的任何一条命令失败时都返回错误码,则需要在RUN命令开头处设定set -o pipefail。


基本原则6: 设定set -evx用于进行错误控制和调试信息显示

如果不需要对结果进行规范化显示,实际上显示处调试信息反而更加有效。考虑到出现的局部使用分号隔开的语句,使用-e选项保证在出错处立即返回。使用vx选项显示代码和执行时的结果,调试时更加有效。


示例Dockerfile

比如如下Dockerfile用于创建一个Apache Bench的Alpine镜像。

liumiaocn:ab liumiao$ cat Dockerfile 
FROM alpine:3.10.2

RUN set -evx -o pipefail                \
    && apk update                       \
    && apk add --no-cache apache2-utils \
    && rm -rf /var/cache/apk/*          \
    && ab -V |grep Version
liumiaocn:ab liumiao$ 

执行结果如下所示

liumiaocn:ab liumiao$ docker build -t ab:2.3 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM alpine:3.10.2
 ---> 961769676411
Step 2/2 : RUN set -evx -o pipefail                    && apk update                           && apk add --no-cache apache2-utils     && rm -rf /var/cache/apk/*              && ab -V |grep Version
 ---> Running in 94f82eca8213
+ apk update
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
v3.10.3-46-gd7d7d0f8fd [http://dl-cdn.alpinelinux.org/alpine/v3.10/main]
v3.10.3-43-gcc200417b2 [http://dl-cdn.alpinelinux.org/alpine/v3.10/community]
OK: 10341 distinct packages available
+ apk add --no-cache apache2-utils
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/5) Installing libuuid (2.33.2-r0)
(2/5) Installing apr (1.6.5-r0)
(3/5) Installing expat (2.2.8-r0)
(4/5) Installing apr-util (1.6.1-r6)
(5/5) Installing apache2-utils (2.4.41-r0)
Executing busybox-1.30.1-r2.trigger
OK: 6 MiB in 19 packages
+ rm -rf /var/cache/apk/APKINDEX.00740ba1.tar.gz /var/cache/apk/APKINDEX.d8b2a6f4.tar.gz
+ ab -V
+ grep Version
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Removing intermediate container 94f82eca8213
 ---> b64179fd3874
Successfully built b64179fd3874
Successfully tagged ab:2.3
liumiaocn:ab liumiao$ docker images |grep ab |grep -w 2.3
ab                                              2.3                             b64179fd3874        48 seconds ago      6.41MB
liumiaocn:ab liumiao$