Kata Containers: 两年而立
这是我在上海峰会之后的 Kata 系列博客文章中的第一篇,这个系列包括:
Kata Containers: 两年而立
Kata Containers: 面向云原生的虚拟化
Kata Containers: 2.0的蓝图
这个系列是首先完成整个系列的英文版,再在修订英文版的同时,由我本人重新用中文写下的,两个版本的内容基本是一致的,英文版同步在 Kata 的Medium 官方博客发表。除了这段广告:如果你希望来蚂蚁金服或阿里巴巴,参与 Kata 的上游社区开发和落地实践,请联系我,私信或者直接邮件联系我(xu@hyper.sh)即可。
2017年12月,我们创立了 Kata Containers 项目,在此之后的诸如 gVisor 和 Firecracker 的开源等连我本人在当初也未曾预见到的社区变化,确实让我们相信,两年来的这项工作的确对云原生世界里的沙箱技术具有相当的推动作用。两年来,非常荣幸地看到越来越多的用户开始采纳安全容器技术并开始回馈给我们的上游社区。
这里我将会总结过去两年中我们所达到的技术成绩和遗留下来的痛点。而在接下来的两篇文章中,我会谈到我们对未来开发的想法,这些想法不仅来自我本人和蚂蚁金服的团队,也包括来其他的上海峰会PTG研讨参与者以及其他的上游社区成员。
成就1: 无缝的 Kubernetes 集成
从我们开始筹划这个项目的时候开始,和 Kubernetes 生态的集成就是 Kata Containers 的最高优先级目标之一。在这两年里,我们一直致力于与相关项目合作、保证并增强与 Kubernetes 社区的兼容性。这里我会列举其中的一些工作:
谈到兼容性,首先我们逐步引入了很多 Kubernetes 相关的集成测试项目,比如 node-e2e conformance tests, cri-contaienrd tests, k8s test for http and tcp liveness probes, heptio/sonobuoy e2e conformance tests 等等。所有这些测试都在保证我们在开发中可以始终保持与 Kubernetes 的兼容性。
网络支持方面就是 Kata Containers 对 Kubernetes 生态支持的一个最显著的例子:在一开始,我们就实现了 bridge
和 macvtap
两种网络模式来对接一般的 Kubernetes CNI 网络。而从 1.6 版本开始,我们引入了 tcfilter
网络模式,把它设为缺省网络让我们几乎可以无缝地接入所有的 Kubernetes CNI 网络。另一方面,对于希望更高效地网络接入的用户,我们也开发了 enlightened
网络模式,提供原生的为 Kata 优化的 CNI。
除了改进 Kata Containers 自身以外,相关社区的开发也帮我们改善了和 Kubernetes 的集成。
这里面最重要的变动就是 Kubernetes SIG-Node 引入的 RuntimeClass
,这个资源的引入让用户可以有一个标准的方式为每个 Pod 指定自己的运行时引擎(比如 Kata 或 runC),乃至为运行时指定某一个配置。更进一步,我们也为 RuntimeClass
做了一些增强,比如将 Pod Sandbox 的资源开销加入 RuntimeClass 描述内容中去,这样就可以帮助用户,也帮助调度器更好地使用 Kata Containers 了。
简而言之,在所有安全容器开发者以及其他相关社区的开发者的共同推动下,云原生生态已经变得对安全容器越来越友好了。
成就2: 更少的间接层
我最喜欢的名言之一就是 David Wheeler 的这句
计算机科学中的所有问题都可以通过增加一个间接层来解决,当然,除了间接层过多的问题。
在过去,为了能无缝地集成到 Kubernetes 生态中去,我们不得不引入了很多的间接层,老实说,这既不简洁,同样也对生产集群的运维不够友好。
在过去两年中我们一直推动在时机成熟的时候去掉不必要的间接层,第一个被去掉的组件就是 kata-proxy
。在我们提供了可以替代虚拟串口的 vsock
的支持以后,我们就可以在使用 vsock
的时候去掉这个代理组件了,因为 vsock
作为一个 socket,本身可以提供多路复用,所以也就不用加一个做多路复用的组件了,同样,在 Agent 一端的解复用功能也就可以被跳过了。
更为重要的架构变动是 shim-v2
的引入,shim-v2
是由 Kubernetes SIG-Node 的开发者在2018年首先在 containerd 社区发起的,并且随后就在 containerd 和 Kata Containers 中实现了这个接口。在此后,CRI-O 也支持了这一接口,从而成为了一个事实上的容器调用标准。这个接口背后的逻辑是,不再把每个容器都看作是一组有 PID 的进程,不再使用 Signal 来操作 PID 了,代之以一个标准的 RPC 协议。这样,我们就可以为每个 Pod 实现一个 shim,而不是每个容器都实现一个,这样做的直接好处是,对于一个有 N 个容器的 Pod,现在系统中只需要跑一个 shim-v2 进程,而不想过去,要跑 2N+2 个 containerd-shim
和 kata-shim
进程。下图是这个变化的示意图(图自官方 GitHub,也是我画的)。
成就3: 降低开销
我常常说,差不多每个用户都喜欢更强的 Pod 隔离性,如果他们不需要为此付出那些开销的话。我们项目自创立之日起,信条就是“虚机的安全性,容器的快速性”。当然,我们也一直在尽力来降低开销,兑现我们对速度的承诺。
在项目开始的时候,我们就带来了两种加速技术来提升启动速度、降低内存开销——模版和 DAX。这两种技术都致力于在 Pod 之间共享部分只读内存,来减少启动时的消耗。
模版技术是来自于 hyper.sh 的 runV 的,最早由赖江山设计并为 runV 开发,彭涛和茶水把它搬到了 Kata 上面。这是一个很巧妙的思路——一个模版就是一个启动好内核和 Agent 并被暂停住的空的虚拟机,之后,再要启动新虚机的时候,就做一次到本机的“live-migration”,因为是到本机,那些一样的内存是不需要复制一次的,所以,这样你就没有开销地得到了一个启动好的沙盒,之后再继续运行这个新虚机,并通过一些热插拔魔术变成你要启动的Pod就好了。
而 DAX 技术本身是为 NVM 设备开发的技术,Intel Clear Containers 的开发者们巧妙地把它用在了容器上。DAX 背后的思路是——如果文件系统是在一块内存上的,即使 NVM 也是内存,那么它的访问应该是很快的,做 mmap
的时候就没必要把它读到物理内存里,再消耗一份 Page Cache 了。在 Kata 背景下,我们告诉虚机里的内核,Pod 虚机的 rootfs 是一块 NVM,让它使用 DAX,那么所有 Pod 里都不会为这些数据分配物理内存了,它们可以共享同一块主机上的 Page Cache。
在过去的两年中,template 的相关 patch 已经被并入 Qemu 上游了,DAX 也取得了更广泛的应用。
另一方面,我们也一直在努力降低 VMM 的开销,Qemu-lite 和 Nemu 先后被引入进来,降低 Qemu 的开销,它们中的很多改动也回到了 Qemu 上游。
在2018年年底,AWS 发布了它们的轻量级虚拟机 Firecracker,几个星期之后,我们也支持了 Firecracker 作为我们的一种 VMM。之后,我们推动建立了 Rust-VMM 项目,并将很快接入基于 Rust-VMM 的 cloudhypervisor,让它成为一个轻量级、安全并可以为 Kata Containers 定制的 VMM。
此外,在2019年我们新引入的一个重大的改变是,我们用 Rust 重写了一个沙箱内的 agent,它在上海峰会之前一个星期刚刚并入上游,并将包含在 1.10 Release 中。从初始版本看,rust-agent 需要的匿名页大约是 1.1MB,作为对比,之前的 Go 版本的 agent 大约需要 11MB,这里比较匿名页的原因是,这部分内存(也就是堆内存)是无法通过 DAX 或模版技术节省掉的。
目前,仍然有很多进行中的降低开销的工作,我们一直以来追逐的目标就是零开销——向用户提供“免费”的沙箱技术。
成就4: 开启了“面向云原生的虚拟化”
和之前的虚拟机领域并不相同,容器的世界是以应用为中心的,这意味着我们对虚拟化的需求并不是完全不变的。上面提到的 vsock 就是一个例子,这项源于 VMWare 的技术并不是最近才被开发出来的,但在弹性计算服务中,这项技术似乎并没有什么重要性,然而,在 Kata Containers 中就完全不同了,因为主机和沙箱之间有更多的通信场景和语义,vsock 的引入带来了很大不同。
一个更好的例子是最近引入进来的 virtio-fs。和之前 Qemu 里的 VirtFS 不同,后者实际上是一种网络文件系统,而 virtio-fs 就是为本机上的文件系统共享来量身定做的,这种用法只在容器领域中才尤为常见,弹性计算中几乎看不到它的应用场景。Virtio-fs 使用了 fuse、vhost-user 和 DAX等技术,这让它可以具备更好的 POSIX 兼容性和比 9p 强不知道哪里去了的性能,对于容器镜像这样的文件共享场景来说,正如上文提到过的,virtio-fs 使用的 DAX 还可以在容器之间共享 Page Cache。Virtio-fs 的另一个优势是,它的存储后端是完全用户态的,这样我们可以在里面加入很多精巧的逻辑,做很多为应用的优化处理。
在 virtio-vsock 和 virtio-fs 之外,更灵活的内存弹性技术 virtio-mem 也在开发中,如果顺利的话,将来也会进入到 Kata Containers 中来,让我们的虚拟化更像容器一些,未来没准还会有 virtio-kata 这样的东西出现,我现在把这些为容器化场景而做的虚拟化定义为面向云原生的虚拟化。
不足1: 尚未做到全链路的隔离
虽然我们在过去的两年中取得了不少成绩,但还是有很多工作要做。最重要的事情是加强隔离性。
正如我们前面提到的,相比于传统虚机,Kata Containers 对里面对应用内容更有感知。但这就好像是硬币的两个面一样,对内容对感知常常意味着对隔离性对破坏,我们需要很小心地去保证沙箱之间对隔离性。在上海峰会中对一个论坛上,我们讨论了 Kata Containers 对安全威胁模型:
首先,第一个问题是 containerd 和 CRI-O 会在主机上创建管道、存储这些东西,然后再把它们共享给容器,这就意味着用户镜像和IO流对处理并没有被沙箱隔离住,这对安全性和QoS都是个威胁。
而在图中的右下角,(Qemu使用的)vsock-vhost 和 MACVTap 是主机内核的功能。理论上说,用户可能会构造一些有问题的包来从此攻击内核。在这一点上,Firecracker 所使用的用户态的 vsock 倒是会更安全一些。
论坛的参与者们认为,主机的内核是应该受到最高级保护的,其次是 VMM 和容器的内核。另一方面,沙箱中的 agent 和容器内核是面临最大安全风险的组件。
不足2: 运维便利性和可调试性
另一个关于隔离和内容感知的折衷点是运维便利性和可调试性。与传统的容器不同,我们加强了隔离性,于是,用户变得很难使用主机系统上的工具来调试他们的应用了,这也让获取各种数据指标信息变得困难起来。
在上海PTG上,开发者们都认为,我们需要在运维便利性和可调试性上更进一步,一个可能的思路是提供一个事件接口来方便各种工具收集事件和指标。
总结
回首过去两年,我们在付出一些开销的代价下,增强来容器的隔离性。我们始终相信容器世界是需要更好的隔离性的,但不希望因为隔离性而丧失云原生的那些“以应用为中心”的特性。我们的愿景是使用沙箱技术,以最小的代价来透明地隔离云原生应用。在后面的几篇博客里,我会解释我们对于未来开发的设想。