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

Yaj*_*ajo 101 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
Run Code Online (Sandbox Code Playgroud)

Dockerfile.2 加入他们:

FROM busybox
RUN echo This is the A > a &&\
    echo This is the B > b &&\
    echo This is the C > c
Run Code Online (Sandbox Code Playgroud)

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

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

  1. 图层都应该只是添加高于前一个差异,所以如果以后层不会删除的东西在前面的一个补充,不存在应节省多少磁盘空间这两种方法之间的优势...

  2. 从Docker Hub并行拉取层,因此Dockerfile.1,虽然可能稍大,但理论上可以更快地下载.

  3. 如果添加第4个句子(即echo This is the D > d)和本地重建,Dockerfile.1由于缓存会更快地构建,但是Dockerfile.2必须再次运行所有4个命令.

那么,问题是:哪个是更好的方法来做Dockerfile?

BMi*_*tch 76

在可能的情况下,我总是将创建文件的命令与将删除这些文件的命令合并为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和安装的任何其他编译工具.


Men*_*nga 25

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

最小化层数

您需要在Dockerfile的可读性(以及长期可维护性)与最小化它使用的层数之间找到平衡点.对您使用的图层数量保持战略性和谨慎性.

由于码头工人1.10 COPY,ADDRUN语句添加一个新图层,你的形象.使用这些语句时要小心.尝试将命令组合到单个RUN语句中.只有在可读性要求时才将其分开.

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

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

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

像往常一样,docker 在多阶段构建方面拥有出色的文档.这是一个快速摘录:

对于多阶段构建,您在Dockerfile中使用多个FROM语句.每个FROM指令可以使用不同的基础,并且每个指令都开始构建的新阶段.您可以选择性地将工件从一个阶段复制到另一个阶段,从而在最终图像中留下您不想要的所有内容.

有关这方面的精彩博文可以在这里找到:https://blog.alexellis.io/mutli-stage-docker-builds/

回答你的观点:

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

  2. 虽然可以并行拉动图层,使其更快,但每个图层无疑会增加图像大小,即使它们正在删除文件.

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


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

  • 这现在已经过时了 - 请参阅下面的答案。 (2认同)
  • @Mohan 谢谢提醒!我更新了帖子以帮助用户。 (2认同)

Moh*_*han 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

请注意,此示例还使用Bash &&运算符人为压缩两个RUN命令,以避免在图像中创建其他图层.这很容易出错并且难以维护.

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

最佳实践似乎已经改为使用多级构建并保持Dockerfile可读性.

  • 与您的开场白“上述答案已过时”相反,其他答案仍然是正确的。您有选择地引用文档,并假设每个人都会切换到多阶段构建以获得性能改进。虽然多阶段构建很棒,但它们可能不是适合每个人的最佳解决方案。 (10认同)
  • @Yajo - 我对"壁球"过去实验持怀疑态度.它有许多噱头,只有在多阶段构建之前才有意义.使用多阶段构建,您只需要优化最终阶段,这非常容易. (2认同)
  • @Yajo 对此进行扩展,只有最后阶段的图层会对最终图像的大小产生影响。因此,如果您将所有构建器 gubbin 放在早期阶段,并且最后阶段只需安装软件包并复制早期阶段的文件,那么一切都会运行良好,并且不需要挤压。 (2认同)

xda*_*ays 12

这取决于您在图像图层中包含的内容。关键点是共享尽可能多的层。

坏例子
  1. Dockerfile.1

    RUN yum install big-package && yum install package1
    
    Run Code Online (Sandbox Code Playgroud)
  2. Dockerfile.2

    RUN yum install big-package && yum install package2
    
    Run Code Online (Sandbox Code Playgroud)
好例子
  1. Dockerfile.1

    RUN yum install big-package
    RUN yum install package1
    
    Run Code Online (Sandbox Code Playgroud)
  2. Dockerfile.2

    RUN yum install big-package
    RUN yum install package2
    
    Run Code Online (Sandbox Code Playgroud)

另一个建议是,仅当删除与添加/安装操作发生在同一层时,删除才没有那么有用。

  • 是的,只要它们从相同的基础开始,它们就会共享同一层。 (3认同)
  • 这两个真的会共享缓存中的“RUN yum install big-package”吗? (2认同)