黑客松Demo: Kata 的下一代镜像系统
在12月15日的第十届中国开源黑客松上,蚂蚁金服和阿里巴巴的开发者彭涛、关尔昱 Demo 了他们为未来 Kata 2.0 设计的一套他们命名为 Nydus 的镜像系统。Nydus 利用 OCI artifacts, virtio-fs 这些社区新进展,结合 OCI 镜像社区对未来演进方向的共识,对隔离性、拉取速度、内存效率等方面综合考虑,为计划中的 Kata 2.0 的镜像的设计提供参考。
背景
OCI 镜像格式的演进方向
今年(2019年)年初,OCI 社区中开始有人讨论下一代镜像格式的问题,这个热烈的讨论中,集中讨论了 OCIv1(实际也是Docker的)镜像格式的一些问题,Aleksa Sarai 也专门写了一篇博客来讨论这个话题,具体说,除了 tar
格式本身的标准化问题外,大家对当前的镜像的主要不满集中在:
内容冗余:不同层之间相同信息在传输和存储时都是冗余内容,在不读取内容的时候无法判断到这些冗余的存在;
无法并行:单一层是一个整体,对同一个层既无法并行传输,也不能并行提取;
无法进行小块数据的校验,只有完整的层下载完成之后,才能对整个层的数据做完整性校验;
其他一些问题:比如,跨层数据删除难以完美处理。
上述这些问题用一句话来总结就是“层是镜像的基本单位”,然而,镜像的数据的实际使用率是很低的,比如 Cern 的这篇论文中就提到,一般镜像只有6%的内容会被实际用到,这就产生了实质性的升级镜像数据结构,不再以层为基本单位的动力。
Kata 的镜像隔离性
话分两头,上边是镜像格式本身的问题,另一方面,Kata 社区里也对镜像的服务方式不能完全满意,在前面的Kata生日总结文章《Kata Containers: 两年而立》中就提到,在上海峰会的PTG中,阿里云和IBM共同提到的一个问题就是——镜像在宿主机挂载,再共享给容器的方式,对宿主机侵入过多,破坏了隔离性,希望有个更直接的方式,让容器镜像不需要挂载在主机上,而是可以直接送给沙箱使用。
但是,如果在Pod沙箱建立之后再拉取镜像带来的效果必然是拉取镜像的全部时间都要串行叠加在容器启动链路之上,这对用户是非常不友好的,我们必须有方法可以在保证隔离性的同时,压缩掉不必要的镜像加载时间。
已有的容器镜像优化改进
针对目前的镜像问题,不止是刚才提到的 Aleksa 的思路,社区里还有一些改进工作:
这些改进中的一部分是直接通过P2P分发替代标准 HTTP 传输来直接加速的,比如来自阿里巴巴的 CNCF 项目 Dragonfly 以及 Uber 的 Karken。这些方案都可以很好地加速镜像的传输,但是仍然需要在镜像传输完成后才可以启动容器,没有充分利用容器镜像低利用率的特点,它们应该可以作为理想方案中的一部分但不是全部。
还有一些方案,维持了镜像的分层结构,但可以做到按需加载,元数据/数据分开,然后基于存储层快照能力构建镜像。这些方案包括了 CERN 的 cernvm-fs,Google golang build team 的 CRFS,以及威斯康星大学的 slacker,它们可以利用镜像的很多特征,只是在跨层处理上不太完美。
另一些方案则直接打破镜像分层结构,使用元数据/数据两层结构,比如 NTT 的 bootfs,filegrain,SuSE 的 umoci。这些方案抛弃了分层结构来优化效率。
这些已有方案都有很好的启示性和一些集成价值,随着 OCI artifacts 规范的制定,我们已经可以使用更标准化的方案来为 kata 实现一个更高效也同时具有隔离性的镜像格式。
目标
面对这些问题,我们在考虑未来版本的 Kata 的时候,就在考虑,相应的镜像存储问题,它应该顺应社区发展的潮流,具有内容感知、内容优化的能力,并达到 Kata 的隔离性要求:
按需加载,启动时无需完全下载镜像,同时对加载的不完全镜像内容可以进行完整性校验;
镜像数据直接送给Pod沙箱内部使用,不加载在宿主机上;
最好可以同时支持runC容器;
尽量节约沙箱内的资源;
尽量做用户态解决方案,不依赖宿主机内核变动。
针对这些需求,结合刚刚完成的 OCI artifacts 和专门为 Kata 场景开发的 virtio-fs,就可以设计一个针对 Kata Containers 的优化的镜像方案。
架构
在黑客松上演示的 Nydus 就是这样一个系统,它以文件chunk为基本内容单位,大文件会划分为多个chunk,单独增加一个元数据索引,可以在 layer 数据中找到每个 chunk,目录树结构使用一个 MerkleTree 来表示,保证每个chunk都可以独立地去重和计算完整性。在节点侧,通过 virtio-fs 支持 Pod 沙箱访问文件系统,无需在宿主机上加载文件系统:
这里,因为 virtio-fs 是支持 DAX 的,所以当有多个镜像有同样的 chunk 的时候,这些只读 chunk 是可以共享宿主机上的 page cache 来节省内存、加速访问的。这里,因为 chunk 是基本单位,所以,只要两个镜像里有共同的 chunk 就可以共享,也无需重复传输。
此外,这个系统里的传输是通过标准的 RESTful 访问镜像 Registry,也可以很容易地加入 P2P 的传输层加速。这次黑客松的演示中并不包括 P2P 加速,但是,和 CNCF DragonFly 的集成工作正在进行中。
Demo 测试和项目当前的状态
在这次黑客松中,Nydus 开发者们紧张进行了这些工作:
开始前准备好的:containerd nydus snapshotter 和支持 nydus 格式的 virtiofsd;
现场开发的:需要修改 kata 支持解析 nydus rootfs mount 格式,从而做到 kata 容器的极速启动。
在两天的开发中,他们做了一些常见大小镜像的拉取速度测试:
注意这里使用的是对数坐标系,其中,端到端时间是指,从 Kubelet 接到 SYNC ADD 消息,得到镜像开始,到 Kubelet 日志中标记容器启动为止。可以看到,对不同尺寸的容器,拉取速度时间几乎是常数,只有少量的元数据尺寸的影响。
下一步开发
可以看到,Nydus 通过把负责拉取镜像的 fuse 终端归到 pod 沙箱内部,从而支持端到端资源隔离。从部署角度看,这个方案在宿主机上是纯用户空间的,并且元数据和数据均符合 OCI artifacts 规范,非常易于部署和调试。在后面的开发中,Nydus 还会进一步统一 runC 和 Kata 的支持,进行和 cloud-hypervisor、CRI-O 等上下游组件的集成/支持工作。