Dockerfile中有多个RUN与单链RUN,哪个更好?

时间:2016-08-30 09:09:53

标签: docker dockerfile

Dockerfile.1执行多个RUN

FROM busybox
RUN echo This is the A > a
RUN echo This is the B > b
RUN echo This is the C > c

Dockerfile.2加入他们:

FROM busybox
RUN echo This is the A > a &&\
    echo This is the B > b &&\
    echo This is the C > c

每个RUN都会创建一个图层,因此我总是认为较少的图层更好,因此Dockerfile.2更好。

RUN删除之前RUN添加的内容(即yum install nano && yum clean all)时,这显然是正确的,但在每个RUN添加某些内容的情况下,有一个我们需要考虑的几点:

  1. 图层应该只是在前一个图层之上添加一个差异,所以如果后一个图层没有删除在前一个图层中添加的内容,那么两个方法之间应该没有太多的磁盘空间节省优势...... / p>

  2. 从Docker Hub并行拉取层,因此Dockerfile.1虽然可能略大,但理论上下载速度会更快。

  3. 如果添加第4个句子(即echo This is the D > d)并进行本地重建,Dockerfile.1可以通过缓存更快地构建,但Dockerfile.2必须再次运行所有4个命令。

  4. 所以,问题是:哪个是更好的方法来做Dockerfile?

4 个答案:

答案 0 :(得分:53)

如果可能,我总是将创建文件的命令与将删除这些文件的命令合并到一个RUN行中。这是因为每个RUN行都为图像添加了一个图层,输出完全是您可以在其创建的临时容器上使用docker diff查看的文件系统更改。如果删除在不同层中创建的文件,则所有联合文件系统都会在新层中注册文件系统更改,该文件仍存在于上一层中,并通过网络传输并存储在磁盘上。因此,如果您下载源代码,将其解压缩,编译成二进制文件,然后在最后删除tgz和源文件,您真的希望这一切都在一个层中完成,以减少图像大小。

接下来,我个人根据其在其他图像中重用的可能性以及预期的缓存使用情况来拆分图层。如果我有4个图像,所有图像都具有相同的基本图像(例如debian),我可以将大多数图像中的常用实用程序集合拉入第一个运行命令,以便其他图像从缓存中受益。

在查看图像缓存重用时,Dockerfile中的顺序非常重要。我看看很少会更新的任何组件,可能只有在基本映像更新并将这些组件放入Dockerfile时才会更新。在Dockerfile的末尾,我包含了任何可以快速运行并且可能经常更改的命令,例如:添加具有主机特定UID的用户或创建文件夹并更改权限。如果容器包含正在积极开发的解释代码(例如JavaScript),则会尽可能晚地添加该代码,以便重建仅运行该单个更改。

在这些变化的每一组中,我尽可能地合并以最小化层。因此,如果有4个不同的源代码文件夹,那么这些文件夹将被放置在单个文件夹中,因此可以使用单个命令添加它们。任何类似apt-get的软件包安装都会尽可能合并到一个RUN中,以最大限度地减少软件包管理器开销(更新和清理)的数量。

多阶段构建的更新:

我更担心的是在多阶段构建的非最后阶段减少图像大小。当这些阶段没有被标记并运送到其他节点时,您可以通过将每个命令拆分为单独的RUN行来最大化缓存重用的可能性。

然而,这并不是压缩图层的完美解决方案,因为在各个阶段之间复制的都是文件,而不是图像元数据的其余部分,如环境变量设置,入口点和命令。当您在Linux发行版中安装软件包时,库和其他依赖项可能会分散在整个文件系统中,从而使所有依赖项的副本变得困难。

因此,我使用多阶段构建来替代在CI / CD服务器上构建二进制文件,因此我的CI / CD服务器只需要运行工具docker build,而不是安装了jdk,nodejs,go和任何其他编译工具。

答案 1 :(得分:22)

他们的最佳实践中列出的官方答案(官方图片必须遵守这些)

  

最小化图层数

     

你需要在两者之间找到平衡点   Dockerfile和的可读性(以及长期可维护性)   最小化它使用的层数。要有战略性和谨慎性   关于你使用的层数。

自docker 1.10起,COPYADDRUN语句会为您的图片添加一个新图层。使用这些语句时要小心。尝试将命令组合到单个RUN语句中。只有在可读性要求的情况下才将其分开。

更多信息:https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize-the-number-of-layers

更新:泊坞广告中的多阶段> 17.05

使用多阶段构建,您可以在Dockerfile中使用多个FROM语句。每个FROM语句都是一个阶段,可以拥有自己的基本映像。在最后阶段,您使用像alpine这样的最小基本图像,复制先前阶段的构建工件并安装运行时要求。这个阶段的最终结果是你的形象。因此,您可以在此担心前面所述的图层。

像往常一样,docker在多阶段构建上有great docs。这是一个快速摘录:

  

使用多阶段构建,您可以使用多个FROM语句   Dockerfile。每个FROM指令可以使用不同的基础,每个   他们开始了构建的新阶段。您可以选择性地复制   从一个阶段到另一个阶段的文物,留下你的一切   不要在最终的图像中。

有关此内容的精彩博文可在此处找到:https://blog.alexellis.io/mutli-stage-docker-builds/

回答你的观点:

  1. 是的,图层有点像差异。如果没有绝对零的变化,我认为没有添加图层。问题是,一旦您在第2层安装/下载某些内容,就无法在第3层中删除它。因此,一旦在图层中写入某些内容,就不能再删除图像大小了。

  2. 虽然可以并行拉动图层,使其速度更快,但每个图层无疑会增加图像尺寸,即使他们正在删除文件。

  3. 是的,如果您要更新docker文件,缓存很有用。但它在一个方向上起作用。如果您有10层,并且更改了第6层,那么您仍然需要重建层#6-#10中的所有内容。因此,它不会经常加速构建过程,但可以保证不必要地增加图像的大小。

  4. 感谢@Mohan提醒我更新此答案。

答案 2 :(得分:13)

上面的答案似乎已经过时了。文档说明:

  

在Docker 17.05之前,甚至更多,在Docker 1.10之前,最大限度地减少图像中的图层数量非常重要。该   以下改进减轻了这种需求:

     

[...]

     

Docker 17.05及更高版本增加了对多阶段构建的支持   允许您仅将所需的工件复制到最终图像中。   这允许您在工具中包含工具和调试信息   中间构建阶段而不增加最终的大小   图像。

https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers

  

请注意,此示例还会人为压缩两个RUN命令   一起使用Bash&&运营商,以避免创建额外的   图像中的图层。这很容易出错并且难以维护。

https://docs.docker.com/engine/userguide/eng-image/multistage-build/

最佳做法似乎已经改为使用多阶段构建并保持Dockerfile可读。

答案 3 :(得分:2)

这取决于您在图像图层中包含的内容。

关键是分享尽可能多的图层:

错误示例:

Dockerfile.1

RUN yum install big-package && yum install package1

Dockerfile.2

RUN yum install big-package && yum install package2

好例子:

Dockerfile.1

RUN yum install big-package
RUN yum install package1

Dockerfile.2

RUN yum install big-package
RUN yum install package2

另一个建议是删除只有在与添加/安装操作在同一层上时才会有用。