RunD – A Lightweight Secure Container Runtime for High-density Deployment and High-concurrency Startup in Serverless Computing
rund 是阿里提出的一种新的轻量级容器运行时技术。
不过目前从论文内容来看,更多是一些技术点的优化,而不是架构层面的创新
1. 设计目标
实现 serverless 场景下,pod的高密度部署、高频、高速启动
高密度部署:随着机型规格越来越大,比如 AMD milan 就有256核,可以部署数千个 pod
高频:faas 和 batch job 等负载,每天上百万的实例创建量,上亿次系统调用
高速:faas 场景的毫秒级启动,极致弹性
2. 问题分析
kata 容器的技术栈:
启动一个 kata 容器,首先需要通过qemu(或者其他hypervisor,比如fire cracker)拉起一个虚机,然后还需要再虚机内启动一个agent,来实现完整的oci语义
基本过程如下:
2.1 并发启动的开销
(1)在准备容器rootfs的可写层时有很长的耗时:同时启动200个kata container,准备rootfs需要耗时207ms,会产生4500iops和100MB/s的IO带宽,带来很高的cpu overhead
(2)同时启动多个kata containers时,涉及到host侧cgroup的创建及维护,在内核层面,凡涉及到cgroup 操作,需要持有全局粒度的自旋锁,导致cgroup 的创建及维护是一个串行过程
2.2 高密部署的瓶颈
(1)虚机(guest系统 + kata-agent + guest kernel)耗损
容器不是虚机,但是实现安全容器就必须依赖虚机。容器的规格一般都是很小的,比如内存100m 0.1vcpu,但是这个规格对虚机来说太小了,都起不来。所以为了能开一个100m的容器,你就得把虚机开到200m甚至更大,这就产生了 overhead
对于kata-qemu,一个内存规格为128MB的kata-containers,其内存overhead可以达到168MB;当部署密度从1提升到1500时,平均每个内存规格为128MB的kata-containers,其内存overhead 仍然会有145MB。
对于小内存规格的kata容器,guest kernel image所占内存占用了很大的比重。AWS数据:47% 的function computer的内存规格时128MB,Azure数据:90%的应用内存规格小于400MB。
(2)rootfs 内存耗损
rootfs基于块的主流解决方案在Host和Guest中生成相同的Page Cache,导致重复的内存开销。
3. rund 的解决方案
rund的设计:
3.1 读写拆分的rootfs
传统的rootfs 有两种挂载形式:filesystem sharing(virtiofs、9pfs)或者block device(virtio-blk)。这两种方式各有利弊,virtio-blk不支持pagecache sharing between host and guest kernel,此外使用virtio-blk时同时启动200个kata-containers需要耗时10s来准备rootfs,而启动一个kata-containers只需要30ms;virtiofs虽然支持dax,但io write性能差,且需要额外的virtiofsd守护进程。
如下图,在rund的设计中,将rootfs拆分成只读层和可读写层,通过overlayfs暴露给sandbox作为容器运行的rootfs。只读层使用virtiofs,可读写层使用virtio-blk,同一个node上执行相同任务的sandbox可以共享只读层。其中,准备只读层通过overlay snapshotter来完成,耗时极短;而可读写层一般需要建立逻辑卷,这个过程是很耗时的,因此,充分利用了serverless中”不需要持久化应用运行时产生的数据”这一特性,通过reflink storage image Template(是什么?pre-created in host as the base file)来作为built-in stroage Image,构建一个易失的可读写层 。将准备rootfs的耗时从207ms降低到了0.2ms,只产生了1500iops和8MB/s的IO。
3.2 共享 & 定制化的 guest kernel
(1)vm template 实现 kernel & rootfs 共享
由于每个虚机都需要加载相同的kernel以及rootfs,也就是说,虚机启动之后,大部分内存数据你可以理解为是一样的。
如果这部分相同的内存能够共享,那就节省了很大一部分内存
vm template 技术可以解决这个问题,通过一个相同的模块(虚机内存的快照)+ copy on write 的方式,极大的减少了高密度部署带来的内存开销,同时还极大的提升了速度(减少了各种引导的过程)
(2)self-modifying code 优化
基于最小化内核内存占用和内核镜像大小的原则,基于centos kernel 4.19,对guest kernel 进行裁剪定制优化,最终内存占用减少了16MB,内核镜像大小减小了4MB。
由于内核代码中存在self-modifying codes(?),其在运行时会按需修改指令,对于有10012KB的read-only kernel code,在内核启动期间,有7982KB 的code 会被修改,导致使用vm template来节省内存占用的效果不好。考虑到每个node上使用的guest kernel是一样的,并且这些self-modifying code 只在内核启动时会按需修改指令,内核初始化完成之后就变成了read-only code, 因此,制作了pre-patched guest kernel image, 提前覆盖掉self-modifying code segments,从而使用VM template技术使sandboxs能共享更多的kernel files。
3.3 池化的cgroup(lightweight cgroup)
如下图,rund在host 侧根据node的能力配置预先创建cgroup subsystems并维护在一个cgroup pool里,通过joint controller将多个cgroup subsystems组织在一起形成一个lightweight cgroup。当rund启动一个sandbox时,从lightweight cgroup list中分配一个空闲的cgroup,并进行rename操作,将rename 后的cgroup 与当前的sandbox attach到一起。cgroup rename操作不需要持有cgroup 相关全局锁,从而在高并发启动sandbox时,减少cgroup锁的争强。按照这种方式,采用200 threads同时创建cgroups,平均耗时只有0.14s。
4 性能评价
新能测试环境参数如下:使用smem命令来查看内存占用情况;测试时使用crictl工具只创建出sandbox,没有在sandobox中创建容器;
(1)高并发性:启动一个sandbox耗时88ms,每秒可以启动200个sandbox,且cpu overhead占用很低。
(2)高密部署:在384GB的内存机器上,可以部署2500个内存规格为128MB的sandbox。
(3)在高密部署的基础上启动sandbox的并发性和延迟都有很好的表现
5 一些经验
(1)CRI Specification的设计与serverless system不太匹配。在CRI中,创建一个sandbox后会先在sandbox中启动一个pause container,来准备cgroups等其它工作,但是在serverless system中,一个sandbox中只运行一个container,没有必要存在pause container
(2)Functions倾向于使用相同的标准guest environment,因此可以将language environment(JVM)集成到vm template中。但是这种language-level的template需要预先分配内存,需要在内存占用和启动耗时之间做出权衡。
(3)内存规格大小是制约高密部署的主要因素。因为当部署2000个sandobox时,其cpu 利用率还没有达到50%,CPU利用率低的一个原因是,在无服务器的 场景中,许多沙箱实际上是空闲的,而在其函数调用完成后“保持活动”。