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

lf5gs5x2  于 2022-11-22  发布在  Docker
关注(0)|答案(4)|浏览(376)

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.层应该只是在前一层上添加一个diff,所以如果后一层没有删除前一层中添加的内容,这两种方法之间应该没有太多的磁盘空间节省优势。
1.层是从Docker Hub并行提取的,因此Dockerfile.1虽然可能稍大,但理论上下载速度会更快。
1.如果添加第4个语句(即echo This is the D > d)并在本地重建,Dockerfile.1将由于缓存而更快地构建,但Dockerfile.2将不得不再次运行所有4个命令。
于是,问题来了:哪种方法更好?

o2rvlv0m

o2rvlv0m1#

如果可能的话,我总是把创建文件的命令和删除相同文件的命令合并到一个RUN行中。这是因为每一个RUN行都向映像添加一个层,输出就是文件系统的变化,你可以在docker diff创建的临时容器上查看这些变化。如果你删除一个在不同层创建的文件,union filesystem所做的就是在新的层中注册文件系统的改变,文件仍然存在于前一层中,并通过网络传输和存储在磁盘上。因此,如果你下载源代码,提取它,编译成二进制文件,然后在最后删除tgz和源文件,你真的希望这一切都在一个层中完成,以减少映像的大小。
接下来,我个人根据它们在其他图像中的重用潜力和预期的缓存使用情况来划分层。如果我有4个图像,都有相同的基础图像(例如debian),我可能会把这些图像的公共工具集合拉到第一个运行命令中,这样其他图像就可以从缓存中受益。
在查看图像缓存重用时,Dockerfile中的顺序很重要。我查看了很少更新的组件,可能只有在基础图像更新时才会更新,并将这些组件放在Dockerfile中的较高位置。在Dockerfile的末尾,我包括了运行速度快且可能频繁更改的命令。例如,添加具有主机特定UID的用户或创建文件夹并更改权限。如果容器包含解释的代码,(例如JavaScript),它是正在积极开发的,尽可能晚地添加,以便重建只运行该单个更改。
在每一组更改中,我都尽可能地进行合并以最小化层次。因此,如果有4个不同的源代码文件夹,这些文件夹会被放在一个文件夹中,这样就可以用一个命令添加它。任何从apt-get之类的地方安装的包都会尽可能地合并到一个RUN中,以最小化包管理器的开销(更新和清理)。

针对多阶段生成的更新:

在多阶段构建的非最后阶段,我对减少映像大小的担心要少得多。当这些阶段没有被标记并传送到其他节点时,您可以通过将每个命令拆分到单独的RUN行来最大限度地提高缓存重用的可能性。
然而,这并不是一个完美的解决方案,因为你在不同的阶段之间复制的都是文件,而不是映像元数据的其余部分,比如环境变量设置,入口点和命令。当你在linux发行版中安装包时,库和其他依赖项可能分散在整个文件系统中,使得复制所有依赖项变得困难。
因此,我使用多阶段构建来替代在CI/CD服务器上构建二进制文件,这样我的CI/CD服务器只需要有运行docker build的工具,而不需要安装jdk、nodejs、go和任何其他编译工具。

eqoofvh9

eqoofvh92#

在最佳实践中列出官方答案(官方图片必须遵守这些)
最小化层数
您需要在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在多阶段构建中使用了great docs
对于多阶段构建,您可以在Dockerfile中使用多个FROM语句。每个FROM指令可以使用不同的基础,并且每个FROM指令都开始构建的新阶段。您可以选择性地将工件从一个阶段复制到另一个阶段,在最终图像中留下您不想要的所有内容。
关于这一点的一篇很棒的博客文章可以在这里找到:https://blog.alexellis.io/mutli-stage-docker-builds/
回答你的问题:
1.是的,层有点像差异。我不认为有层添加,如果有绝对零变化。问题是,一旦你安装/下载的东西在层#2,你不能删除它在层#3。所以,一旦东西写在一个层,图像大小不能再减少删除。
1.虽然图层可以并行提取,使其可能更快,每个图层无疑增加了图像大小,即使他们正在删除文件。
1.是的,如果你正在更新你的docker文件,缓存是很有用的。但是它是单向的。如果你有10个图层,并且你改变了图层#6,你仍然需要重建从图层#6到#10的所有东西。所以它不会经常加速构建过程,但是它肯定会不必要地增加你的图像的大小。

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

niwlg2el3#

上面的答案似乎已经过时了。文件指出:
在Docker 17.05之前,甚至更早,在Docker 1.10之前,最大限度地减少图像中的图层数量是非常重要的。
[...]
Docker 17.05和更高版本增加了对多阶段构建的支持,这允许您只将所需的工件复制到最终映像中。这允许您在中间构建阶段包含工具和调试信息,而不会增加最终映像的大小。
this
请注意,这个例子还使用Bash &&操作符人为地将两个RUN命令压缩在一起,以避免在图像中创建额外的层。
最佳实践似乎已经改为使用多级构建并保持Dockerfile的可读性。

y3bcpkx1

y3bcpkx14#

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

错误示例

1.停靠文件

RUN yum install big-package && yum install package1

1.停靠文件

RUN yum install big-package && yum install package2
好例子

1.停靠文件

RUN yum install big-package
RUN yum install package1

1.停靠文件

RUN yum install big-package
RUN yum install package2

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

相关问题