Cube轻量虚拟化如何做到100ms交付一个安全容器
从 Serverless 技术最有代表性的 FaaS 产品现网运营数据来看,Serverless 场景有以下特点:
1)单实例生命周期短, 平均执行时间 50ms, P95 小于 100ms
2)并发创建能力要求高, 资源流转快, 每天近千万次的资源流转(创建 / 销毁)
3) 冷启动速度要求高, 要求百毫秒级的冷启动时延
在通用虚拟机环境下, 无法满足 Serverless 的以上特点, 根本原因是通用虚拟机的实例创建过程会涉及到和装箱、 VPC、CBS、安全组等多个子系统的交互,实例创建往往需要等待数十秒的时间, 无法满足 Serverless 对冷启动和高并发的诉求。而实例本身的创建销毁时间远远大于实例本身的执行时间, 会导致大量的算力浪费。
在实际的运营中,通用虚拟机架构下,为了满足 Serverless 的运营指标诉求, 通常采取储备资源池的方案, 也就是提前创建好一批虚拟机实例, 直接用提前创建好并已运行的虚拟机实例来满足客户高并发高资源流转的诉求。这个方案通过储备资源池来平衡客户高并发高资源流转的需求与平台低并发资源流转慢的矛盾, 但储备资源池会给平台带来比较大的呆滞成本以及很大的算力浪费。
为了让底层基础设施更加匹配 Serverless 的诉求, 腾讯云技术团队对管控流程、KVM、主机 OS、VMM、虚拟机 OS 进行全方位的裁剪和优化, 并针对快照技术进行了深度改进,打造了 Cube 安全容器产品。Cube 提供了高并发,高密度运行环境,使 Serverless 场景下的安全容器的交付更加迅速,并在有限空间内,提供高性能、低开销的解决方案。
通用虚拟机的创建流程会涉及与虚拟化,网络,存储等诸多 IaaS 基础设施的交互, 这是制约通用虚拟化高并发高资源流转能力的关键瓶颈。
Cube 在整体架构设计上完全与 IaaS 基础设施解耦, 并把所需基础资源在主机层面进行池化管理,将整个虚拟机的创建变成一个单台物理机内可以闭环的操作。
虚拟机交付管控流程的具体实现上, Cube 在主机初始化时与 IaaS 基础设施进行交互,获取所有必要的 基础资源,如网络资源(VPC IP)与存储资源(CBS), 并在内部闭环管理网络和存储资源的分配。
网络上, Cube 在 VPC 网络之上, 实现了一个单机维度的 Cube 网络, 这个网络可以通过 CubeGW 实现和 VPC 网络的互通;
存储上, Cube 将本地盘和 CBS 云盘资源进行了切割管理,将只读存储通过 virtiofs 的方式暴露给虚拟机, 可写存储结合写时复制的技术以 virtio-blk 的方式暴露给虚拟机。
通过上述存储,网络等虚拟机配套资源全部在主机范围内的闭环管理,从管控流程上完全消除了外部基础设施的影响因素。
KVM 作为虚拟化的底座,在通用虚拟机场景中已经经过千锤百炼,然而在高并发场景下却暴露出来了很多性能和时延相关问题,典型的如高并发操作下 lock contention 导致的时延。
首先是 irqfd 操作引入接近 100ms 时延。irqfd 是 KVM 提供给 VMM 的将 eventfd 绑定到虚拟机中断的一个 feature,用户态的 VMM 通过写入 irqfd,KVM 在内核层面将对应中断注入到虚拟机中去,这个 feature 通常被 virtio 设备使用,比如 virtio 设备的队列事件,在 CUBE 产品中因为 irqfd 注册引入了 60ms 以上时延。
经过对 KVM 中 irqfd 注册流程分析,腾讯云技术团队针对非直通设备场景彻底消除了这部分时延影响,而针对更为棘手的直通设备场景,积极在社区讨论终极解决方案。
另外一个典型时延问题是,nx-lpage-recovery 内核线程创建会导致创建虚拟机引入 100ms 的抖动,是因为高并发场景下这个内核线程启动引入了同步等待,放大了虚拟机创建的时延。
经过调查,腾讯云技术团队发现 nx-lpage 是 KVM 为了缓解 ITLB multihit CPU 漏洞的软件缓解方案,而 nx-lpage-recovery 内核线程则是针对这组缓解方案造成性能损失的补偿机制。且不说这个补偿机制的效果,CUBE 产品部署环境中并不会触发这个问题,因为漏洞触发依赖于特定 CPU 和 KVM 使用大页。看起来很普通的一个内核线程启动,在场景中却引入这么大副作用,对 KVM 的优化需要深入到每一个细节。
社区近些年兴起基于 Rust 的轻量级 VMM,相比 QEMU 简化了设备模型,更小的内存和运行开销,腾讯云技术团队基于 cloud-hypervisor 构建 VMM 组件(cube-hypervisor),是因为其在保持了轻量化的同时,有较为完备的现代 VMM 具备的基础功能,如热插拔 /TDX 等。
在产品架构上为了适配容器场景,腾讯云技术团队对虚拟机存储进行读写分离。
虚拟机镜像通过 pmem 设备方式在主机层面在所有虚拟机之间进行只读共享, 通过 DAX 方式直接访问主机内存。
使用更为灵活的 virtiofs 协议为容器提供 rootfs 目录,为了达到极致的性能我们对 virtiofs 协议的服务端进程 (virtiofsd) 进行了 native 改造,改造后的 virtiofsd 从独立进程变成了 VMM 的一组线程,处理上优化了 vhost 逻辑,可以像普通 virtio 设备一样直接操作后端文件系统。
容器的可写块存储通过 virtio-blk 暴露给虚拟机,使用空洞文件减少实际磁盘使用量的同时, 通过 reflink 和写时复制技术实现磁盘块的按需使用。
在技术演进方面,腾讯云技术团队对虚拟机冷启动做了多项优化,如通过并发内核加载加速虚拟机启动,以及调优启动过程中的阻塞行为,但仅仅通过调优只能将安全容器交付时间缩减到 200ms 左右,无法达到我们对启动时延的极致要求,而我们场景中,不同虚拟机的内核和 rootfs 都是一致的,针对这个高度重复的动作,是否有办法做到一次性优化么?答案是有的,这里,腾讯云技术团队选择了虚拟机快照技术。
快照技术原本是 VMM 提供的对虚拟机快照的保存和恢复,快照保存时,将虚拟机暂停下来,将虚拟机所有状态保存到快照文件中,快照恢复时,基于快照文件恢复虚拟机,恢复完成之后继续虚拟机运行,快照技术语义上支持 1 to 1 的虚拟机状态恢复,且对虚拟机后端设备有一致性要求(磁盘 / 网卡)。
首先,需要对快照技术进行 1 to n 的虚拟机状态恢复改造,实现虚拟机内部状态和后端设备无关,并可以将保存的旧设备状态恢复到新设备。实际实现中我们通过替换后端设备,以及基于快照中设备状态对设备进行重新初始化,实现了快照恢复过程中后端设备无感替换。
经过对快照技术持续优化,我们最终做到将安全容器的交付时间从 200ms+ 降低到 100ms 以内,同时为单个虚拟机减少了 20MB+ 内存(1000 个虚拟机部署密度下节省 20G 内存)。
Cube 在网络方面更是结合 ebpf 实现快速转发路径,已经将网络层面的转发开销降到了最低,然而高并发测试总是能够发现彩蛋,这一次是 TAP 设备,我们发现虚拟机在 TAP 设备初始化上耗时 50ms 以上,MAX 甚至超过 100ms。
虚拟机创建过程需要对 TAP 设备做初始化,然而这个高并发的操作在主机内核触发了全局锁冲突,造成大量时延。很显然解决这个问题需要深入主机内核 TAP 做深度优化,然而有没有跟简单的办法呢,是否可以避免重复的 TAP 设备初始化动作呢?腾讯云技术团队对 TAP 网络方案进行了重新设计,将 TAP 设备的初始化放在了 TAP 池化初始化中,而虚拟机的网络初始化只需要从池子中申请一个对应的资源就可以了,经过对方案的不断调优,成功将虚拟机启动过程中 TAP 设备初始化降低到了 1ms 级别。
高并发场景下, 高频的创建销毁会带来主机内核全局资源的锁竞争, 导致性能的波动和不可预期性。
Cube 为了保证高并发场景下冷启动时延的可预期性, 通过以下技术手段来优化
计算,网络,存储资源的池化
对于无法池化的资源, 尽量减小对全局资源的冲击
Mount/umount
在 mount/umount 过程中都需要全局的命名空间锁来保证关键路径的串行操作,在安全容器环境的构建中不但需要通过 overlayfs mount 创建容器的 rootfs,还需要各种形式的 bind mount 来实现 hosts,dns 等个性化配置。Cube 通过把这种个性化的 bind mount 转换成 overlayfs 的一个层次(layer)的方式使 mount/umount 次数减少到最低。
进程创建 / 销毁
在进程的创建,销毁过程中也需要全局的 task 锁来确保关键路径的串行操作, 为了确保进程创建的时延有保证,cube 技术团队将进程销毁的优先级调低, 并引入排队机制串行执行进程的销毁操作。同时通过进程合并等手段减少虚拟机创建需要创建的进程数目。
通用虚拟机的设计目标是提供一个通用的计算环境,要求虚拟机 OS(内核与操作系统)的功能足够丰富与灵活,但在 Serverless 场景中,过多的功能反而变成了一种负担,牺牲了极速的启动速度,也额外耗费了太多的资源。Cube 为了满足 Serverless 极致的用户体验,对虚拟机 OS 的所有功能模块进行了深度的裁剪和优化。
虚拟机镜像(虚拟机 rootfs)层面:
裁剪掉所有不需要软件包,只保留极少数关键库和命令,镜像大小控制在 128MB 以下。
使用 Cube-agent 作为 init 替换庞大的 systemd 体系,系统启动时只进行少量必须的初始化操作。
虚拟机内核层面:
关闭所有不需要的模块和功能,减少内核代码段和数据段的大小。
调整内核各个子系统的 hash 表大小,减少额外内存消耗。
调整预留内存的默认计算算法,提供更多资源给业务进程。
经过裁剪优化,1C128MB 的规格下,相比通用虚拟机秒级启动速度,Cube 虚拟机启动时间降至 80ms 以下,启动时内存消耗也降至 20MB 左右。
目前在 FaaS 实际业务场景下 Cube 交付安全容器的时延控制在 100 毫秒以下的水平。
值得一提的是,腾讯云技术团队在优化 Cube 在高密高并发场景下性能的同时也发现并修复了其他开源组件长期以来没有被发现的问题。
golang runtime 相关:
在高并发操作 tap 设备时会触发了 go runtime epoll 对象复用的 bug。
这是一个 go net epoll 模型的重大缺陷,已提交社区并推动修复。
kata container 相关:
容器控制流程 hang 住的问题反馈与修复
https://github.com/kata-containers/kata-containers/issues/6059
https://github.com/kata-containers/kata-containers/pull/6060
unix domain 资源泄露的问题
https://github.com/kata-containers/kata-containers/issues/6171
cloud-hypervisor 相关:
cloud-hypervisor 清除 virtiofs dax mapping 时 offset 设置错误。
https://github.com/cloud-hypervisor/cloud-hypervisor/pull/5235
设备初始化时 Breadth First Traversal 设备树实现不当,存在多余的内存拷贝开销。
https://github.com/cloud-hypervisor/cloud-hypervisor/pull/5428
支持网络设备配置 offloading 能力 feature
https://github.com/cloud-hypervisor/cloud-hypervisor/pull/5029
支持 block 设备 latency 统计
https://github.com/cloud-hypervisor/cloud-hypervisor/pull/5044
解决 vcpu 异常退出造成死循环问题
https://github.com/cloud-hypervisor/cloud-hypervisor/pull/5211
优化 virtio console 信号线程
https://github.com/cloud-hypervisor/cloud-hypervisor/pull/5240
virtiofs 相关:
在 cache=never 模式下,virtiofs 前端驱动的 private mmap 的 page cache 存在一致性问题。
https://lore.kernel.org/linux-fsdevel/[email protected]/
在 cache=never 模式下,对 shared_mmap 的缺乏支持。
https://lore.kernel.org/linux-fsdevel/[email protected]/
在 cache=always 模式下目录 page cache 失效,导致 readdir 性能变差。
https://gitlab.com/virtio-fs/virtiofsd/-/merge_requests/172
没必要的 open(/proc/pid/mountinfo) 操作导致的 mount 操作时延抖动。
https://gitlab.com/virtio-fs/virtiofsd/-/merge_requests/167
无效的后端读写 size 限制。
https://gitlab.com/virtio-fs/virtiofsd/-/merge_requests/161
virtiofs 设备增加了 ratelimiter 功能以支持 QoS。
https://gitlab.com/virtio-fs/virtiofsd/-/merge_requests/147
virtiofsd 新增加了 cache=part 模式,使得节省内存的同时不影响 readdir 操作的效率。
https://gitlab.com/virtio-fs/virtiofsd/-/merge_requests/173
rust-vmm 相关:
解决 linux-loader 没有正确处理 ELF align 的问题
https://github.com/rust-vmm/linux-loader/pull/128
解决 virtio-queue 没有正确处理 descriptor align 问题
https://github.com/rust-vmm/vm-virtio/pull/220
目前为止,Cube 对底层支撑功能以及各相关组件进行了大量深入的分析,并有针对性进行优化, 真正的做到了用户按量付费,平台按需生产,并且可以满足用户的性能诉求。
技术的道路没有尽头,后续随着技术方案的不断创新和迭代,腾讯云技术团队将不遗余力地持续提高 Cube 的底层性能,与此同时,在用户体验,易用性等方面做更多的探索和增强。
点击底部阅读原文访问 InfoQ 官网,获取更多精彩内容!
微信扫码关注该文公众号作者