我们在Dockerfile中包含以下代码块:
RUN yum -y update
RUN yum -y install epel-release
RUN yum -y groupinstall "Development Tools"
RUN yum -y install python-pip git mysql-devel libxml2-devel libxslt-devel python-devel openldap-devel libffi-devel openssl-devel


我是docker的新手,不确定我完全了解指定多个RUN命令的这两个版本之间的区别。什么时候将一个RUN命令组合成一个命令,何时才有意义使用多个RUN命令?

评论

如何每次都创建最小的工作docker映像的可能重复项?

#1 楼

泊坞窗映像实际上是文件系统层的链接列表。 Dockerfile中的每个指令都会创建一个文件系统层,该层描述执行相应指令之前和之后文件系统中的差异。可以在docker映像上使用docker inspect子命令,以显示其作为文件系统层的链接列表的性质。 br />推动或拉动图像时,这会影响并发上载或下载的次数。涉及的层越多,性能越差,但是不同的文件系统后端受此影响的程度也不同。我可以提供的第一个也是最重要的建议是:建议#1确保涉及您的源代码的构建步骤尽可能晚地出现在Dockerfile中,并且不要捆绑在一起使用&&;以前的命令。 。这意味着您可能需要更快的构建和更快的发行。有趣的是,很难充分利用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


序列被替换为/>其中apt_setup.sh的base64编码版本。 >

建议#3使用with -idiom避免文件出现在中间层中,而不出现在结果文件系统中。并在以后的指令中删除并没有出现在结果文件系统中,但是在构成docker镜像的docker层中两次被提及。一次,通过添加指令在层中添加名称和完整内容,一次在通过删除指令的层中作为删除通知。例如,假设我们暂时需要一个C编译器和一些映像,并考虑
RUN base64 --decode … | sh -x


(一个更实际的示例将使用编译器来构建某些软件,而不是仅使用--version标志来声明其存在。) />
Dockerfile片段创建了三层,第一层包含完整的gcc套件,因此即使最终文件系统中不存在该套件,相应的数据也仍然以相同的方式成为映像的一部分,需要下载,只要有最终图片,就可以上传和解压缩。

with -idiom是函数式编程中的一种常见形式,用于将资源所有权和资源释放与使用它的逻辑隔离开来。将此成语转换为shell脚本很容易,我们可以将前面的命令改写为下面的脚本,与建议43#2中的COPY & RUN一起使用。 />可以将复杂的命令转换为功能,以便将其馈送到with_c_compiler。也可以链接几个with_whatever函数的调用,但可能不是很理想。 (使用Shell的更多深奥功能,当然可以使with_c_compiler接受复杂的命令,但是在所有方面将这些复杂的命令包装到函数中都是可取的。)

如果要忽略建议#2,生成的Dockerfile片段应为

# !!! THIS DISPLAYS SOME PROBLEM --- DO NOT USE !!!
RUN apt-get install -y gcc
RUN gcc --version
RUN apt-get --purge autoremove -y gcc


,由于混淆不易阅读和维护。了解shell脚本变体如何强调重要部分gcc --version,而链式&&变体则将其掩埋在噪声中间。

评论


您可以在使用一个脚本并在一个RUN语句中使用多个命令来生成框大小的结果吗?

– 030
17年8月14日在16:04

对我来说,将映像库的配置(即OS内容)甚至libs与您编写的源代码的设置混合在一起似乎是一个坏主意。您说:“确保涉及源代码的构建步骤尽可能晚。”使该零件成为完全独立的工件有任何问题吗?

– JimmyJames
17年8月14日在18:23

@ 030“盒子”大小是什么意思?我不知道您指的是哪个盒子。

–MichaëlLe Barbier
17年8月14日在18:26



我的意思是泊坞窗图片大小

– 030
17年8月14日在18:27

@JimmyJames这在很大程度上取决于您的部署方案。如果我们假设已编译程序,则“正确的做法”是将其打包并安装该软件包依赖项和软件包本身,作为两个不同的即将完成的步骤。这样可以最大程度地利用docker缓存,并避免在具有相同文件的各层之间进行下载。我发现共享构建食谱来构建docker镜像比构建图像的长依赖链要容易得多,因为后者会使重建更加困难。

–MichaëlLe Barbier
17年8月14日在18:34

#2 楼

您在Dockerfile中创建的每条指令都会创建一个新的图像层。每层都带来了附加数据,这些数据并非始终是结果图像的一部分。例如,如果您在一个层中添加了一个文件,但随后又在另一层中将其删除,则尽管您删除了该文件,但最终图像的大小将以特殊的“变白”文件的形式包括所添加的文件大小。

假设您有以下Dockerfile:

FROM centos:6

RUN yum -y update 
RUN yum -y install epel-release


生成的图像大小将为
>相反,使用“相似的” Dockerfile:

bigimage     latest        3c5cbfbb4116        2 minutes ago    407MB


结果图像大小将为

FROM centos:6

RUN yum -y update  && yum -y install epel-release


如果您在单个RUN语句中清理yum缓存,则大小会变得更小。 >

#3 楼

RUN语句代表每一层。想象一下,有人下载了一个软件包,然后安装并想要删除它。如果使用三个RUN语句,则图像大小将不会缩小,因为存在单独的图层。如果使用一条RUN语句运行所有命令,则可以减小磁盘映像的大小。