在 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. 层应该只是添加一个差异以上的前一个,所以如果后一个层没有删除添加到前一个,应该没有太多的磁盘空间节省优势之间的两种方法。

  2. 层是从 Docker Hub 并行提取的,因此 Dockerfile.1尽管可能稍微大一些,但理论上下载速度会更快。

  3. 如果添加第4个句子(即 echo This is the D > d)并在本地重新构建,那么由于缓存的存在,Dockerfile.1会构建得更快,但是 Dockerfile.2将不得不再次运行所有4个命令。

那么,问题来了: 处理 Dockerfile 的更好方法是什么?

137672 次浏览

这取决于你在图像层中包含了什么。关键在于共享尽可能多的层。

不好的例子
  1. < p > Dockerfile

    RUN yum install big-package && yum install package1
    
  2. < p > Dockerfile

    RUN yum install big-package && yum install package2
    
很好的例子
  1. < p > Dockerfile

    RUN yum install big-package
    RUN yum install package1
    
  2. < p > Dockerfile

    RUN yum install big-package
    RUN yum install package2
    

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

官方答案列出了他们的最佳做法(官方形象必须遵守这些)

尽量减少层数

你需要找到两者之间的平衡 Dockerfile的可读性(以及长期可维护性) 尽量减少它使用的层数。有策略,谨慎 关于你使用的层数。

从docker 1.10开始,COPYADDRUN语句为你的图像添加了一个新层。使用这些语句时要小心。尝试将命令组合成一个RUN语句。仅在为了可读性需要时才将其分离。

更多信息:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#minimize-the-number-of-layers

更新:docker >17.05多阶段

在多阶段构建中,你可以在Dockerfile中使用多个FROM语句。每个FROM语句都是一个阶段,可以有自己的基映像。在最后一个阶段,您使用一个最小的基本映像(如alpine),从前面的阶段复制构建工件并安装运行时需求。这一阶段的最终结果就是你的形象。这就是前面描述的图层的问题。

通常,docker在多阶段构建中有伟大的医生。以下是一个简短的节选:

对于多阶段构建,您将在您的数据库中使用多个FROM语句 Dockerfile。每个FROM指令可以使用不同的基,并且每个FROM指令可以使用不同的基 其中一个开始了构建的新阶段。你可以选择性地复制 从一个阶段到另一个阶段,留下你的一切

关于这方面的一篇很棒的博客文章可以在这里找到:https://blog.alexellis.io/mutli-stage-docker-builds/

回答你的问题:

  1. 是的,层次有点像差异。如果完全没有变化,我不认为会增加层次。问题是,一旦你在第2层安装/下载了一些东西,你就不能在第3层删除它。因此,一旦在一个层中写入了一些东西,图像大小就不能再通过删除它来减小了。

  2. 虽然图层可以并行拉取,这可能会更快,但毫无疑问,每一层都会增加图像大小,即使它们正在删除文件。

  3. 是的,如果你正在更新docker文件,缓存是有用的。但这是单向的。如果你有10层,你改变了第6层,你仍然需要重建从第6层到第10层的所有东西。所以它不会经常加速构建过程,但它肯定会不必要地增加映像的大小。


感谢@Mohan提醒我更新这个答案。

如果可能的话,我总是将创建文件的命令与删除相同文件的命令合并到一个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和任何其他编译工具。

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

在Docker 17.05之前,甚至在Docker 1.10之前,最小化图像中的层数是很重要的。的 以下改进减轻了这一需求:

[…]

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

:

注意,这个例子还人为地压缩了两个RUN命令 一起使用Bash &&操作符,以避免创建附加的 图层在图像中。

.

.

最佳实践似乎已经改变为使用多级构建并保持__abc0可读。