Dockerfile中的多运行与单链运行,哪个更好?


132

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也更好。

当a 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?


1
通常无法回答,因为它取决于情况和图像的使用(针对大小,下载速度或构建速度进行优化)
亨利

Answers:


99

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


30

最佳做法中列出了官方答案(官方图片必须遵守这些规定)

减少层数

您需要在Dockerfile的可读性(以及因此的长期可维护性)与最小化其使用的层数之间找到平衡。对使用的层数保持策略性和谨慎性。

从docker 1.10开始COPYADDRUN语句向您的图像添加一个新层。使用这些语句时要小心。尝试将命令合并为一个RUN语句。仅在可读性要求时才分开使用。

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

更新:Docker> 17.05中的多阶段

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

像往常一样,Docker 在多阶段构建中具有出色的文档。这是一个简短的摘录:

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

可以在这里找到有关此问题的出色博客文章:https : //blog.alexellis.io/mutli-stage-docker-builds/

要回答您的观点:

  1. 是的,图层有点像差异。如果绝对零变化,我认为不会添加任何图层。问题在于,一旦在第二层中安装/下载了某些内容,就无法在第三层中将其删除。因此,一旦在层中写入了某些内容,就无法再通过删除该图像来减小其图像大小。

  2. 尽管可以并行拉动图层,使其潜在地更快,但即使它们要删除文件,每个图层无疑也会增加图像大小。

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


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


1
现在已经过时了-请参阅下面的答案。
Mohan

1
@Mohan感谢您的提醒!我更新了帖子以帮助用户。
Menzo Wijmenga

19

似乎上述答案已过时。文档说明:

在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/

最佳实践似乎已更改为使用多阶段构建并保持Dockerfiles的可读性。


虽然多阶段构建似乎是保持平衡的一个不错的选择,但当该docker image build --squash选择超出实验范围时,将实际解决此问题。
Yajo

2
@Yajo-我对squash过去的实验表示怀疑。它有许多头,只有在进行多阶段构建之前才有意义。使用多阶段构建,您只需要优化最终阶段即可,这非常容易。
Menzo Wijmenga

1
@Yajo要扩展这一点,只有最后阶段的图层会影响最终图像的大小。因此,如果您将所有构建器gubbins放在较早的阶段,并在最后阶段仅安装软件包并从较早的阶段跨文件复制,那么一切都会很好地工作,并且不需要压扁。
Mohan

3

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

关键是共享尽可能多的层:

错误的例子:

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

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


这两个会真正共享RUN yum install big-packagefrom缓存吗?
Yajo

是的,只要它们从相同的基础开始,它们将共享同一层。
翁德拉·
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.