容器化是最近几年 DevOps 界流行的趋势,通过业务的容器化我们将创建一个完全打包、自包含的计算环境,让软件开发人员能够更加快速地创建和部署自己的应用程序。然而长期以来,由于镜像格式的限制,容器启动镜像的加载是很慢的(相关背景细节可以参考“容器技术之容器镜像篇”)。
为了加速容器的启动,我们可以将优化后的容器镜像搭配 P2P 网络等技术,从而有效降低容器部署启动的时间,并可保障容器持续稳定运行。(“让容器应用管理更快更安全,Dragonfly 发布 Nydus 容器镜像加速服务”。)
除了启动速度,镜像分层、去重、压缩、按需加载等核心特性在容器镜像领域也尤为重要。
但是由于没有原生的文件系统支持,大多数都选择了用户态方案,Nydus 最初亦如此。随着方案和需求的不断演进,用户态方案遇到了越来越多的挑战,如性能与原生文件系统相比有较大差距、高密场景下资源开销较大等等。究其原因,主要是在用户态实现镜像格式的解析、按需加载等操作,将带来大量内核态/用户态通信开销。所以看起来解决了启动速度,这些核心功能的研发就会有不小的挑战,有点顾此失彼。
那么有没有可能做到鱼和熊掌兼得呢?为此,龙蜥社区做了一个大胆的尝试,我们设计并实现了兼容内核原生 EROFS 文件系统的 RAFS v6 格式,希望将容器镜像方案下沉到内核态。同时为了一劳永逸,我们也尝试将这个方案推进到内核主线以让更多的人收益。最终,随着 Linux 内核各位大佬的挑战和我们的不断完善,erofs over fscache 按需加载技术终于被合入 5.19 内核(链接见文末),至此 Nydus 镜像服务的下一代容器镜像分发方案逐渐清晰起来。
这也是 Linux 主线内核首个原生支持、开箱即用的容器镜像分发方案,容器镜像的高密、高性能、高可用和易用性从此不再是个问题。
本文将从 Nydus 架构回顾、RAFS v6 镜像格式和 EROFS over Fscache 按需加载技术三个角度来分别介绍这一技术的演变历程,并通过对比数据展示了当前方案的卓越性能,希望大家能够尽早享受到容器启动飞一样的体验!
一、Nydus 架构回顾
一句话总结一下,Nydus 镜像加速服务是一种优化了现有的 OCIv1 容器镜像架构,设计了 RAFS (Registry Acceleration File System) 磁盘格式,最终呈现为一种文件系统的容器镜像格式的镜像加速实现。
容器镜像的根本需求,本质上是为了提供容器的根目录 (rootfs),这可以通过文件系统 (file system) 或者是归档格式 (archive format) 来承载,当然也可以在文件系统的基础上二次套娃 (例如通过自定义的块格式来承载),但本质载体是一个目录树,体现为文件接口。
先看一下 OCIv1 标准镜像,OCIv1 格式是一种基于 Docker Image Manifest Version 2 Schema 2 格式的镜像格式规范,由 manifest、镜像索引 (optional)、一系列容器镜像层及配置文件组成,细节可以参见相关文档,本文不再赘述。本质上说 OCI 镜像是一个以层为基本单位的镜像格式,每个层存储了文件级别的 diff data,以 tgz 归档格式存储,如下所示:
由于 tgz 的限制,OCIv1 存在一些固有问题,例如无法按需加载、较粗的层级的去重粒度、每层 hash 值易变等等。
而一些“二次套娃”方案 (例如基于自定义块格式的容器镜像方案),也存在一些原理性的设计缺陷。例如:
- 容器镜像最终要体现为一棵目录树,那么就需要相应的文件系统来承载 (例如 ext4),这样整个链路为“自定义块格式 + 用户态块设备 + 文件系统”,相对于文件系统方案其链路更长更复杂,端到端稳定性不可控;
- 由于块格式对上层的文件系统不感知,无法区分文件系统的元数据和数据并分别处理 (例如压缩);<