Docker镜像大小译文
Docker镜像大小重要吗?我通常听到的答案是“是的”。
接下来的讨论的问题是“为什么?”
我经常听到以下两个我不喜欢的答案:
- 较小的镜像占用更少的磁盘空间。
- 上传较大的镜像通常需要花费更多时间。
虽然这两种说法听起来都很有道理,实际却未必。
有一些其他合理的理由,为什么需要更小的Docker镜像,如减少攻击表面(因为功能少哇)。但在本文中,我想谈谈上面提到的答案。
更小的Docker镜像占用更少的磁盘空间
Docker镜像由可重用层组成,即不同的镜像可能共享一些层。由于这种架构,镜像大小和磁盘消耗不一定直接相关。
如果一个镜像中的所有层只在该镜像中使用,那么是的,镜像大小表示该镜像占用的磁盘空间量。然而,这是一种极其罕见的情况。在所有其他情况下,数学并没有那么简单。我们来看看这些数字。
我们来看两个例子。在这两种情况下,我们都会观察到一个1GB的镜像,有两层,一层是20MB,另一层是980MB,镜像的数量和大小并不具有代表性。他们被选中是为了让这一点显而易见。
如果我们的镜像是唯一使用这两个层的镜像,那么是的,该镜像占用了1GB的磁盘空间。但是,如果有10个类似的镜像—来自具有不同标记的相同仓库的镜像—会发生什么呢?嗯,这取决于……
在第一个示例中,所有这些镜像只在较小的层(20 MB)中有所不同,而共享较大的层(980 MB)。总磁盘消耗为:980 + 1020 = 980 + 200 = 1180 MB。这是每个镜像118 MB,或11.8%* docker镜像命令报告的镜像大小。
在第二个示例中,所有这些镜像只在较大的层(980 MB的层)和共享较小的层(20 MB的层)中有所不同。总磁盘消耗为:20 + 10980 = 20 + 9800 = 9820 MB。这是每个镜像982 MB,或98.2%*镜像大小。
在实际情况中,示例中的“共享”层表示很少更改的镜像层(基本镜像+顶层不可更改层)。在上面的例子中,每个镜像中不同的层表示镜像中经常变化的层——每个构建中变化的底层。
以下是我们从这些关于磁盘使用的例子中得出的结论:
- 总镜像大小并不重要。
- 基本镜像大小无关紧要。因此,通过减小基本镜像大小来减少磁盘总消耗是没有意义的(除非在磁盘小得不切实际的边界情况下)。
- 当涉及到磁盘使用时,真正重要的是频繁变化的层的大小。
附带说明:减小基本镜像大小通常是有代价的——镜像大小越小,功能就越小。(实际上,我认为这是一个优点)
一个大的Docker镜像很难上传(需要很多时间)
当上传或下载Docker镜像(推或拉)时,每个镜像层分别传输。在目标中已经存在的层根本不会被转移。
因此,需要传输的数据量与整体镜像大小或任何镜像层的大小没有直接关系。传输的数据大小等于目的地不存在的层大小之和,即已更改层的大小。
简而言之:镜像的第一次上传/下载耗时较长。其余的只传输新的(频繁变化的)层。它们的大小取决于镜像结构。
一个比较特殊的情况是公共CI/CD服务。在这样的设置中,项目通常没有专用的节点,所以通常情况下,每个构建/测试运行都是该(可能是虚拟的)节点上的第一个运行。也就是说,每次运行中的第一个Docker传输是都可能是该节点上的第一次传输。因此,每次都要传输整个镜像。幸运的是,即使是GB级的下载也不会给通常很长的构建/测试过程增加很大的开销。此外,使用层缓存还可以进一步提高Docker镜像上传/下载的整体速度。
轻松地管理镜像
人们喜欢易于管理的镜像,这通常意味着镜像上传/下载速度快,并且不占用太多磁盘空间。
为了创建这样一个镜像,我们不应该努力减少基本或整体镜像大小,而是要正确地设计Dockerfile。如上所示,重要的是频繁变化的层的大小。这是应该最小化的。
为了尽量减少这些频繁更改的层,应该根据文件的修改频率将其分组到各个层中。最频繁更改的层应该最后创建。例如,拥有这样一个Dockerfile并不是最优的:
FROM whatever:latest
COPY . ./
...
从这样一个Dockerfile创建的镜像将在每次重建时替换基础镜像之上的所有层,并且将是“重”的。相反,包含依赖项的层应该在带有项目代码的层之前添加,因为后者更改得更频繁。
结论:为什么Docker镜像大小很重要
拥有一个较小的Docker镜像通常是可取的,每个人都同意这一点。它的可取之处有很多,比如减少攻击面,而不是因为磁盘使用或上载/下载时间。
如果镜像构造正确,由Docker镜像大小引起的镜像的第一次上传/下载耗时较长将通过随后的多次上传/下载进行摊余。也就是说,如果频繁变化的层的大小很小。
Refer