本文与实习生@河上共同撰写。
问题背景
在容器生态的生产实践中,有一个不起眼但令人头痛的问题是,节点(baremetal or ECS vm)上用于存放容器相关内容的目录常常用满了整个 disk,导致了对业务的影响。
问题分析
通常情况下,容器相关的目录主要指 /var/lib/docker 或者/var/lib/containerd,其中最为消耗 disk空间的是容器镜像的targz 格式layer文件以及targz解压后的snapshots目录。 我们知道容器 layer 的设计初衷之一是为了复用公共的 layer ,在不同镜像之间共享相同的数据,那么在用满disk 空间的情况下,我们首先需要确认,是不是所有公共的数据都被共享了? 实践出真知,下面是干货。 为了方便说明,这里用常用的应用版本迭代场景,以AI模型训练应用为基础,设计了以下两个镜像来观察现象:
镜像 |
介绍 |
tensorflow-linux:2.90-5.17 |
OCI 镜像,tensorflow:2.90 加 Linux-5.17.3 源码用于模拟数据集,size 为619.3 MiB |
tensorflow-linux:2.91-5.17 |
OCI 镜像,tensorflow:2.91 加 Linux-5.17.3 源码用于模拟数据集,size 为619.8 MiB |
-
OCIv1 镜像
After pulling 2.90-5.17: 3.3GB After pulling 2.90-5.17 and 2.91-5.17: 6.2GB
-
容器镜像layer 分析
通过分析容器的 config中的 build history,可以发现,虽然image manifest 的history 中的操作除了 tensorflow 版本号其它全部相同,但从第 2 层layer开始,2.90 和 2.91 的 layer hash 有了差异,所以结果显示disk 空间是线性增长的。 通常我们认为相邻的小版本差距会较小,所以在这种情况下,对于layer共享的预期是很高的,但实际上,docker build 会因为build环境,timestamp 不同等原因产生“相同数据,layer hash不同”的情况,给用户反直觉的结果。 这背后的原因是 OCIv1 镜像选择了 layer 的设计,但只要有1 个 bit 不同,整个 layer 的 hash 就会改变,导致不能复用。 所以用户在构建镜像时需要特别注意地去避免环境等各种因素对整个build 产生影响,显然这是负担,那么有没有办法去掉这个负担呢?
解决方法
这里给出的答案是使用Nydus。
Nydus 将容器镜像从文件系统层面分成一个元数据bootstrap对象文件和若干个数据blob 对象文件。
Nydus在数据 blob部分的设计中引入了chunk,实现了 chunk级别的镜像数据去重。即,容器的每一个文件都由一个或若干chunks组成(chunk size <= 1MB), 而每一个chunk在镜像中只会保存一次。
回到上面我们遇到的问题,镜像之间的相同文件本来应该复用,但因为layer hash 不同,做不到复用。如果将文件分为 chunk,去重对比的粒度从 layer变为了 chunk,那么镜像之间的相同文件就可以做到复用。
综上,Nydus既提供了分层的设计,保持着镜像间层级去重能力,也基于Chunk level deduplication提供了在不同镜像之间低成本的data去重能力。
Nydus测试结果
测试准备
为了对比,这里使用与上面相同的 tensorflow-linux 镜像,将它们转换为 Nydus 镜像之后,进行相同的实验。
注:因为 Nydus 是按需加载,我们需要运行一个“遍历全读”的workload 来加载所有数据,用来演示disk空间的变化情况。
镜像 |
介绍 |
tensorflow-linux:2.90-5.17-nydus |
Ndyus 镜像,tensorflow:2.90 加 Linux-5.17.3 源码用于模拟数据集。 作为 base image, 用作其他 Nydus image 的 chunk dictionary。 |
tensorflow-linux:2.91-5.17-nydus-without-dict |
Ndyus 镜像,tensorflow:2.91 加 Linux-5.17.3 源码用于模拟数据集。 转换过程中不指定任何 chunk dictionary。 |
tensorflow-linux:2.91-5.17-nydus |
Ndyus 镜像,tensorflow:2.91 加 Linux-5.17.3 源码用于模拟数据集。 转换过程中指定 tensorflow-linux:2.90-5.17-nydus 作为 chunk dictionary。 |
测试结果
-
Nydus镜像间不做 chunk 去重
After running 2.90-5.17-nydus: 1310656KB
After running 2.90-5.17-nydus and 2.91-5.17-nydus-without-dict: 2568884KB
-
Nydus镜像间做 chunk去重
After running 2.90-5.17-nydus: 1310656KB
After running 2.90-5.17-nydus and 2.91-5.17-nydus: 1339596KB
结果说明与分析
-
现象
-
当disable chunk 去重时,disk 空间占用翻倍
-
当 enable chunk 去重时,disk 空间占用基本没变化
-
结论
-
tensorflow-linux:2.90 和 tensorflow-linux:2.91 的内容基本相同。
-
相比 layer 去重效果,chunk 去重效率好很多,达到了近似block 级别的去重极限。
-
在数据全部加载的情况下,在占用disk空间方面,Nydus 镜像是OCIv1 镜像的40%。
-
分析
-
tensorflow 的 2 个版本实际内容差别不大,所以当以chunk 粒度对比时,镜像 tensorflow-2.91 可以复用镜像 tensorflow-2.90 的内容。
-
OCIv1 镜像下载之后,占用 disk 空间的部分不仅有 targz 的 blob 文件,还有解压后的 snapshots。而Nydus 的 targz 部分只包含元数据bootstrap,数据的 blob没有对应的 targz,全部是通过网络按需拉取的。
Nydus相关
Nydus 已经开源在 github Nydus上,文中所有实验与测试均使用 Nydus master branch制作。 https://github.com/dragonflyoss/image-service
Nydus 的最新进展是
-
袋鼠安全沙箱的容器rootfs存储方案从 virtiofs 升级到了 Nydus,解决了之前线上遇到的文件描述符相关的稳定性问题,同时提升读I/O性能75%,cpu消耗降低 55%,内存节省 30%。
-
基于Linux 5.19 erofs + fscache提供数据加速能力,其特点之一是最大程度上避免了用户态和内核态之间的数据copy带来的性能损耗。
-
字节跳动加入Nydus社区共建开源生态。
近期,我们会在将龙蜥社区中集成Nydus 能力,提供开箱即用的数据加速服务。
Reference
-
tar's problem: Lack of de-duplication https://www.cyphar.com/blog/post/20190121-ociv2-images-i-tar
-
Nydus 镜像加速之内核演进之路