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

新手上路,请多包涵

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

This is obviously true when a RUN removes something added by a previous RUN (ie yum install nano && yum clean all ), but in cases where every RUN adds东西,有几点我们需要考虑:

  1. 层应该只是在前一层之上添加一个差异,所以如果后面的层不删除在前一层中添加的东西,那么两种方法之间不应该有太多的磁盘空间节省优势。

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

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

那么,问题 来了:哪种方法更好地创建 Dockerfile?

原文由 Yajo 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 479
2 个回答

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

原文由 BMitch 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

最小化层数

您需要在 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

通过多阶段构建,您可以在 Dockerfile 中使用多个 FROM 语句。每个 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 提醒我更新这个答案。

原文由 Menzo Wijmenga 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏