泊坞窗映像实际上是文件系统层的链接列表。Dockerfile中的每个指令都会创建一个文件系统层,该层描述执行相应指令之前和之后文件系统中的差异。该docker inspect
子命令可以在docker映像上使用,以揭示其作为文件系统层链接列表的性质。
图像中使用的层数很重要
- 推或拉图像时,它会影响并发上载或下载的次数。
- 启动容器时,由于将各层组合在一起以产生容器中使用的文件系统;涉及的层越多,性能越差,但是不同的文件系统后端受到的影响也不同。
这对应该如何构建图像有几个影响。我可以提供的第一个也是最重要的建议是:
建议#1确保涉及源代码的构建步骤尽可能早地出现在Dockerfile中,并且不要与使用a &&
或a的先前命令绑定在一起;
。
这样做的原因是,所有先前的步骤都将被缓存,并且不需要一遍又一遍地下载相应的层。这意味着您可能需要更快的构建和更快的发行。有趣的是,很难充分利用docker缓存。
我的第二条建议不太重要,但从维护角度来看,它非常有用:
建议2:请勿在Dockerfile中编写复杂的命令,而应使用要复制和执行的脚本。
遵循此建议的Dockerfile看起来像
COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
COPY install_pacakges.sh /root/
RUN sh -x /root/install_packages.sh
等等。绑定多个命令的建议&&
范围有限。使用脚本编写脚本要容易得多,在脚本中可以使用函数等来避免冗余或出于文档目的。
人们对预处理器感兴趣,并愿意避免这些COPY
步骤带来的小开销,并且实际上是在动态生成Dockerfile,其中
COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
序列被替换为
RUN base64 --decode … | sh -x
其中…
是的base64编码版本apt_setup.sh
。
我的第三条建议是给那些想要以更长的构建成本为代价来限制层的大小和数量的人的。
建议#3使用with
-idiom避免文件存在于中间层中,但不存在于生成的文件系统中。
在结果文件系统中不存在由某些docker指令添加并由后续指令删除的文件,但在构成docker镜像的docker层中有两次提到该文件。一次,在添加指令的结果层中添加名称和完整内容,一次在删除该指令的结果中,作为删除通知。
例如,假设我们暂时需要一个C编译器和一些映像,并考虑
# !!! THIS DISPLAYS SOME PROBLEM --- DO NOT USE !!!
RUN apt-get install -y gcc
RUN gcc --version
RUN apt-get --purge autoremove -y gcc
(一个更现实的示例将使用编译器构建一些软件,而不是仅使用--version
标志声明其存在。)
Dockerfile片段创建了三层,第一层包含完整的gcc套件,因此,即使最终文件系统中不存在该套件,相应的数据也仍然以相同的方式成为映像的一部分,并且需要在每次下载,上传和解压缩时最终的图像是。
该with
-idiom是在功能编程到分离资源所有权和资源使用它从逻辑释放常见形式。这是很容易转这个成语的shell脚本,我们可以改写先前的命令,下面的脚本,与被用来COPY & RUN
作为建议#2。
# with_c_compiler SIMPLE-COMMAND
# Execute SIMPLE-COMMAND in a sub-shell with gcc being available.
with_c_compiler()
(
set -e
apt-get install -y gcc
"$@"
trap 'apt-get --purge autoremove -y gcc' EXIT
)
with_c_compiler\
gcc --version
复杂的命令可以转换为功能,以便将其提供给with_c_compiler
。也可以链接几个with_whatever
函数的调用,但可能不是很理想。(使用外壳程序更深奥的功能,当然可以使with_c_compiler
接受复杂的命令成为可能,但是在所有方面,最好将这些复杂的命令包装成函数。)
如果我们想忽略建议2,则生成的Dockerfile代码段将是
RUN apt-get install -y gcc\
&& gcc --version\
&& apt-get --purge autoremove -y gcc
由于混淆,它不太容易阅读和维护。了解shell脚本变体如何强调重要部分,gcc --version
而链式&&
变体则将其掩埋在噪声中间。