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东西,有几点我们需要考虑:
层应该只是在前一层之上添加一个差异,所以如果后面的层不删除在前一层中添加的东西,那么两种方法之间不应该有太多的磁盘空间节省优势。
图层是从 Docker Hub 中并行提取的,因此
Dockerfile.1
虽然可能稍大一些,但理论上下载速度会更快。如果添加第 4 句(即
echo This is the D > d
)并在本地重建,Dockerfile.1
会由于缓存而更快地构建,但Dockerfile.2
将不得不再次运行所有 4 个命令。
那么,问题 来了:哪种方法更好地创建 Dockerfile?
原文由 Yajo 发布,翻译遵循 CC BY-SA 4.0 许可协议
如果可能,我总是将创建文件的命令与删除相同文件的命令合并到一个
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 和任何其他编译工具。