Dockerfile.1执行多个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 加入他们:
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添加了内容的情况下,我们需要考虑以下几点:
yum install nano && yum clean all
层应该只是在前一层之上添加差异,因此如果后一层没有删除在前一层中添加的内容,那么这两种方法之间就没有太多的磁盘空间节省优势…
层是从Docker Hub并行拉出的,因此Dockerfile.1,尽管可能稍大一些,但理论上可以更快地下载。
如果添加第4句(即echo This is the D > d)并在本地重建,Dockerfile.1由于使用了缓存,构建起来会更快,但是Dockerfile.2必须再次运行所有4条命令。
echo This is the D > d
所以,问题是: 哪种更好的方式来制作Dockerfile?
如果可能,我总是将创建文件的命令与将相同文件删除到同一RUN行的命令合并在一起。这是因为每一RUN行都在图像上添加了一层,输出实际上是文件系统更改,您可以docker diff在其创建的临时容器上查看该文件系统更改。如果删除在不同层中创建的文件,则联合文件系统所做的全部工作就是在新层中注册文件系统更改,该文件仍存在于上一层中,并通过网络运送并存储在磁盘上。因此,如果您下载源代码,将其解压缩,将其编译为二进制文件,然后最后删除tgz和源文件,则您确实希望所有这些操作都在单个层中完成以减小图像大小。
docker diff
接下来,我根据图层在其他图像中可重复使用的潜力以及预期的缓存使用情况来将其拆分。如果我有4个映像,所有映像都具有相同的基本映像(例如debian),则可以将大多数这些映像的通用实用程序集合拉入第一个运行命令,以便其他映像可以从缓存中受益。
在查看映像缓存重用时,Dockerfile中的顺序很重要。我查看的是很少更新的任何组件,可能只有在基本映像更新并将其放在Dockerfile中时才会更新。在Dockerfile的末尾,我包含了所有将快速运行并且可能会经常更改的命令,例如,添加具有主机特定UID的用户或创建文件夹并更改权限。如果容器包含正在积极开发的解释代码(例如JavaScript),则将其添加到尽可能晚的位置,以便重建仅运行该单个更改。
在所有这些更改组中,我都尽力整合以最小化层。因此,如果有4个不同的源代码文件夹,则将它们放在单个文件夹中,以便可以使用单个命令将其添加。如果可能,从apt- get之类的任何软件包安装都将合并到一个RUN中,以最大程度地减少软件包管理器的开销(更新和清理)。
多阶段构建的更新:
对于在多阶段构建的非最终阶段减小图像大小,我不必担心太多。如果没有标记这些阶段并将其传送到其他节点,则可以通过将每个命令拆分到单独的RUN行来最大程度地提高缓存重用的可能性。
但是,这不是挤压层的完美解决方案,因为您在阶段之间复制的全部是文件,而不是其余的图像元数据,例如环境变量设置,入口点和命令。而且,当您在Linux发行版中安装软件包时,库和其他依赖项可能分散在整个文件系统中,从而很难复制所有依赖项。
因此,我使用多阶段构建来代替在CI / CD服务器上构建二进制文件,因此我的CI / CD服务器仅需要具有运行的工具docker build,而没有jdk,nodejs,go和安装的任何其他编译工具。
docker build