在Dockerfile中,多个RUN和单个链式RUN,哪个更好?

10 浏览
0 Comments

在Dockerfile中,多个RUN和单个链式RUN,哪个更好?

Dockerfile.1 使用多个 RUN 命令:\n

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

\nDockerfile.2 将它们合并:\n

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

\n每个 RUN 命令都创建一个层,因此我一直认为层数越少越好,因此 Dockerfile.2 更好。\n当一个 RUN 命令删除了前一个 RUN 命令添加的内容时,这显然是正确的(即 yum install nano && yum clean all),但是在每个 RUN 命令都添加内容的情况下,我们需要考虑以下几点:\n

    \n

  1. 层应该只在前一个层的基础上添加差异,因此如果后面的层没有删除前面层添加的内容,则两种方法之间在磁盘空间节省方面并没有太大的优势。\n
  2. \n

  3. 层从 Docker Hub 并行获取,因此理论上,Dockerfile.1 虽然可能稍大一些,但下载速度应该更快。\n
  4. \n

  5. 如果添加第四个命令(例如 echo This is the D > d)并重新构建,由于缓存的存在,Dockerfile.1 将构建更快,但是 Dockerfile.2 将不得不重新运行所有的4个命令。\n
  6. \n

\n因此,问题是:哪种方法更好用于 Dockerfile?

0
0 Comments

多个RUN与单个chained RUN在Dockerfile中的区别,哪个更好?

根据上面的内容,我们可以看到这个问题的出现原因是因为在早期版本的Docker中,需要尽量减少镜像中的层数。然而,随着Docker的更新,特别是17.05版本及以上的多阶段构建的引入,这个需求逐渐减弱了。现在可以使用多阶段构建来只复制需要的构建结果到最终的镜像中,而不必增加最终镜像的大小。

此外,将多个RUN命令合并成一个chained RUN命令也是一种解决方法,但是这种方法容易出错且难以维护。

最佳实践已经改变为使用多阶段构建并保持Dockerfile的可读性。多阶段构建可以在保持性能的同时灵活地控制最终镜像的大小。

然而,对于--squash选项是否可以正式发布,有人持怀疑态度。因为在使用多阶段构建之后,只需要优化最后一个阶段的层即可。将构建过程的内容放在前面的阶段,并在最后一个阶段中只安装软件包和从前面的阶段复制文件,可以很轻松地解决这个问题,无需使用squash选项。

另外,需要指出的是,尽管多阶段构建是很好的解决方案,但并不一定适合所有人。因此,之前的答案仍然是正确的,只是你选择性地引用了文档,并假设每个人都会切换到多阶段构建以获得性能改进。

0
0 Comments

在Dockerfile中,使用多个RUN指令和使用单个链式RUN指令会出现一个问题:哪种方式更好?这个问题的原因是Docker官方在其最佳实践中列出了以下答案:

- 尽量减少层数量:需要在Dockerfile的可读性(及长期可维护性)和使用的层数之间找到平衡。要战略性地、谨慎地使用层数。

- 自Docker 1.10版本以来,COPY、ADD和RUN语句会向您的镜像添加一个新的层。在使用这些语句时要小心。尽量将命令组合成单个RUN语句,只有在可读性需要时才进行分离。

- 使用多阶段构建(Multi-stage builds):在Dockerfile中可以使用多个FROM语句。每个FROM语句都是一个阶段,并且可以有自己的基础映像。在最后一个阶段,使用一个最小的基础映像(如alpine),从前面的阶段复制构建产物并安装运行时需求。这个阶段的最终结果就是您的镜像。

这个问题的解决方法是通过使用多阶段构建(multi-stage builds)来减少层数。每个阶段使用不同的基础映像,可以选择性地从一个阶段复制构件到另一个阶段,不需要的部分则留在之前的阶段中。Docker官方文档和一篇博客文章都提供了详细的关于多阶段构建的说明。

对于问题中的几个具体点:

1. 层类似于差异(diffs),在完全没有更改时不会添加新的层。问题在于,一旦在第二层中安装/下载了某些内容,就无法在第三层中将其删除。因此,一旦在层中写入了某些内容,就无法通过删除来减小镜像的大小。

2. 尽管层可以并行拉取,从而可能更快,但每个层无疑会增加镜像的大小,即使它们正在删除文件。

3. 是的,缓存对于更新Dockerfile是有用的。但它只能单向工作。如果有10个层,您更改了第6层,仍然需要从第6层重新构建到第10层。因此,它不会经常加快构建过程,但肯定会不必要地增加镜像的大小。

根据Docker官方的最佳实践,使用多阶段构建可以减少层数并优化镜像的大小。

0
0 Comments

多个RUN和单个chained RUN在Dockerfile中的比较,哪个更好?

在尽可能的情况下,我总是将创建文件的命令和删除相同文件的命令合并到一个RUN行中。这是因为每个RUN行都会在镜像中添加一个层,输出实际上就是文件系统的更改,可以通过在创建的临时容器上使用docker diff命令查看。如果您删除了在不同层中创建的文件,联合文件系统只会在一个新的层中注册文件系统更改,文件仍然存在于先前的层中,并在网络上传输并存储在磁盘上。因此,如果您下载源代码,提取它,将其编译为二进制文件,然后在最后删除tgz和源文件,您真的希望所有这些操作都在一个层中完成,以减小镜像大小。

接下来,我根据它们在其他镜像中的重用潜力和预期的缓存使用情况来拆分层。如果我有4个镜像,它们都使用相同的基础镜像(例如debian),我可能会将一组对于大多数这些镜像都通用的实用程序放在第一条run命令中,以便其他镜像可以受益于缓存。

Dockerfile中的顺序在查看镜像缓存重用时很重要。我会查看任何很少更新的组件,可能只在基础镜像更新时更新,并将其放在Dockerfile的前面。在Dockerfile的末尾,我包括一些快速运行且可能经常更改的命令,例如使用特定于主机的UID添加用户或创建文件夹和更改权限。如果容器包含被积极开发的解释性代码(例如JavaScript),则尽可能晚地添加,以便重新构建仅运行该单个更改。

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

多阶段构建的更新:

对于多阶段构建的非最终阶段,我对减小镜像大小的担忧要少得多。当这些阶段没有被标记和发送到其他节点时,通过将每个命令拆分为单独的RUN行,可以最大程度地增加缓存重用的可能性。

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

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

0