本文基于对今年ASPLOS 19的一篇论文《X-Containers: Breaking Down Barriers to Improve Performance and Isolation of Cloud-Native》的理解整理而成。
PPT地址:http://www.csl.cornell.edu/~delimitrou/papers/2019.asplos.xcontainer.pdf
视频地址:https://www.youtube.com/watch?v=OxAFxS-NgyE&feature=youtu.be

背景说明

问题域

该论文针对现在云原生Single-Concerned(即一个容器只做一个事情)的普通容器平台的安全隔离问题,而提出的一种全新解决方案。X-Containers想要达到3个目标,1) 容器之间具备VM级别的安全隔离能力,2)保持对现有容器的二进制100%ABI兼容(包括binary不需要重新编译,多进程并发实现)及OCI容器标准兼容,3) 性能相对于普通runC容器来说没有太大的下降。

现有方案的问题

现有的安全沙箱方案(如Clear Containers、Kata Containers、Hyper Containers、Hyper-V Containers等)可以在兼容OCI标准、100%二进制ABI兼容的前提下,解决上述安全隔离问题。但是论文作者提出这些方案均依赖于硬件辅助虚拟化技术,无法部署在所有的云环境下(比如Amazon EC2),有些公共云或私有云(比如Google Compute Engine)通过支持嵌套虚拟化技术来运行这些安全沙箱方案,但是这会极大地影响性能。X-Containers试图提出了一个能够运行在所有云环境的一种不同的方案。

Google的gVisor方案(ptrace模式),可以在用户态实现容器间的安全隔离,但是性能和兼容性与标准runC容器有一些差距。

一些非Linux Kernel的Unikernel或LibOS也是试图通过硬件辅助虚拟化的机制来启动容器,但是依然存在云基础设施的限制,而且均存在Linux Kernel的100%兼容性问题。另外一类是user mode LibOS,比如Drawbridge或者LKL(Linux Kernel Library),他们都缺乏多进程管理的能力,因为它们的底层平台是Linux Kernel,Linux Kernel没有给用户态进程开放页表管理(多进程管理)的能力。Graphene通过Syscall fork出新的LibOS,并借助Linux Kernel来实现复杂的进程间通信功能,这样进程间通信的效率和安全性就不是最优的。另外Graphene只实现了1/3的Syscall功能,达不到二进制100%ABI兼容。兼容性(包括多进程管理)的天然缺陷目前已经成为Unikernel/LibOS推广和应用的最大阻碍。

SOPS17的LightVM《My VM is Lighter (and Safer)than your Container》以及ATC 2018的KylinX《KylinX: A Dynamic Library Operating System for Simplified and Efficient Cloud Virtualization》均是通过Xen的PV架构进行容器的隔离,可以在任意的云环境下面部署,但均没有解决Xen的PV架构(X64模式)的性能缺陷。同时两者均基于一个叫MiniOS的LibOS进行扩展,MiniOS并未达到二进制100%ABI兼容的能力。

X-Containers

架构


通过架构图可以看到,X-Containers架构,它有两个核心组件。一个是很薄的位于ring0特权级的Hypervisor(即X-Kernel),另一个组件是位于ring3特权级的X-LibOS,它和Application运行在同一个特权级,因此可以实现相互高效地函数调用。

1) X-Kernel:由Xen 4.2修改而来(近800行代码)。它位于ring0 Level。这里可以是Root模式的ring0,也可以是Non-Root模式的ring0,因此整个方案可以部署在一个标准的公共云的VM内(通过一个叫Xen-Blanket的Linux驱动)。

2) X-LibOS:由Linux Kernel 4.4.44修改而来(近1800行代码,其中1500行来自arch和半虚拟化的汇编语言),这里采用Linux作为LibOS的最主要的原因是为了达到100%的ABI兼容性。因为作为一款通用的云产品,你永远无法预料千奇百怪的应用对内核功能的依赖程度,要做到Linux 100%ABI兼容最好的方法就是直接使用Linux。其实Linux内核代码虽然庞大,但并不代表它的性能就差。相反它是一个高度可定制化(内核参数、proc参数等)、可裁剪、可调试性强、潜在高性能的内核。之前,在多租、多容器环境下,可能由于顾此失彼而导致无法做到性能最优,但是服务于Single-Concerned的云原生应用的Kernel,就有可能将系统裁剪、调优到最佳状态(比如为了单vcpu而关闭SMP,可以提升系统spinlock性能)。

从上图可以看到X-Containers 架构和LightVM一样,也是利用了成熟Xen的PV架构(包括成熟VCPU管理、中断管理及页表管理等体系),再结合Linux Kernel自身的进程管理,就可以在Guest实现一套完整的SMP多进程、多线程并发执行环境。存储和网络能力也还是原先的Guest VM视角看到的虚拟设备,因此管理各个Linux VM的方法,还是与之前的接口类似,但可以说是基于原先Xen的PV虚拟化架构和接口,为Single-Concerned云原生容器做的一个架构升级。

特权级与地址空间

X-Kernel位于ring0,不同于标准Xen下的Linux Kernel,X-LibOS与Application同时运行于ring3的Guest环境。X-Kernel修改了Xen的ABI,消除Kernel(X-LibOS)和Application之间的隔离。Kernel(X-LibOS)映射到了Application的地址空间,所以就算直接访问内核数据也不会引起一般情况下的内存访问异常。和正常的Linux一样,X-LibOS被映射进程了每一个进程地址空间的高地址部分(所有进程共享),低地址部分是进程的部分。

一般情况下,为了进程间的安全隔离,Xen下的Guest Kernel的页表的Global bit不会置上,进程切换会全部刷新。但是X-Containers内会置G bit,只有在切容器时才会刷G的TLB entry,性能有所提升。另外,标准Xen的PV架构为了支持X64模式,只能在ring3同时运行Kernel和Application(因为X64模式取消了段保护),为了保护Kernel和App的隔离,分别用不同的页表(类似于gVisor KVM模式的sentry),那么普通的Syscall都会通过Xen来中转并且有页表的切换,性能不是太好,这也是为什么Xen后来用硬件辅助全虚拟化来支持X64平台的原因。而X-Container为了增强性能,牺牲了Kernel与Application之间的安全隔离能力,采用同一个页表。这两点变化弥补了Xen的标准PV架构(X64模式)的性能缺陷。

从Syscall到Function Call

X-Containers提供3种syscall调用方式。

方式1:Trap模式。应用程序通过Syscall陷入ring0(X-Kernel),X-Kernel判断syscall num以及参数,然后让应用程序返回到同一地址空间的Kernel(X-LibOS)中的系统调用入口(从syscall entry table中获取)去执行真正的系统调用。但是这种情况不是LibOS的函数调用模式,性能和一般的Guest Kernel内核访问没有太大差别,存在着两次特权级地切换。

方式2:Function call模式。X-Containers在每个应用的地址空间中透明的映射了一个vsyscall page,里面包含了syscall entry table,一些可以修改代码的应用程序可以直接通过这个table找到syscall entry来加速syscall的调用。

方式3:透明Function call模式。X-Containers 实现了一个运行于X-Kernel中的叫做ABOM(Automatic Binary Optimization Module)的组件。这个模块可以在X-Kernel识别到syscall指令之后,根据RIP的上下文指令识别该次syscall指令的模式(包括syscall指令的前后指令,包括syscall num和参数),然后将进程地址空间中(仅内存)的该RIP及上下文替换为function call(即使是只读映射)的指令(有点类似热补丁技术)。这样,下次再次从该RIP地址触发syscall指令时就总是一次高效的function call了。



从图中可以看到,两条指令(mov和syscall)直接替换为一条callq指令,rax这个寄存器也不需要赋值了,因为该RIP已经替换为了syscall entey table中function call的绝对地址的callq指令调用。目前只支持跟随着mov指令的syscall指令,同时X-Containers也提供工具离线修改application或library的二进制,以注入代码去将更为复杂的syscall指令修改为function call。

这个地方笔者认为是项目的画龙点睛之笔。既要实现LibOS所带来的function call的性能好处,又实现了二进制(binary不用重新编译)兼容,还很有可能巧妙地避免了GPL Licence的感染。

安全隔离

再回过头来看,X-Containers的确是通过直接利用Xen的PV架构继承了Guest VM间的安全隔离能力。虽然X-Containers的Kernel与Application的隔离很弱,但是Xen作为Hypervisor与Guest的原语非常简单、攻击面比较小,再加上Xen作为TCB代码非常少,因此Hypervisor与Guest之间的安全性也可以得到保障。这里通过一个简单地ring3就可以做到隔离,与平时见到的其它的依赖于Linux Kernel的user mode LibOS的区别在于,其它user mode LibOS是基于Linux Kernel,而Linux Kernel没有类似VCPU、页表管理等VM的原语定义。Linux Kernel只有进程原语,所以user mode LibOS只能依赖进程的线程管理、内存管理等Syscall原语来模拟一个VM的行为,这个接口与攻击面是比Xen的VM原语要大一些的。因此可以看到像gVisor、Graphene等user mode LibOS基本上都是需要通过seccomp来做一些syscall上面的限制。

性能对比 

X-Containers的性能是docker容器syscall 吞吐的27倍。同时针对Google’s gVisor and Intel’s Clear Containers,甚至和Unikernel and Graphene等,在不同的benchmark的数据也有较大的提升(具体参考论文数据)。与标准Docker的比较,在不同场景各有优劣势。

架构约束

1) 安全模型:X-Containers的安全模型重点是强调容器间、以及容器与Hypervisor的隔离,为了降低性能损失,X-Containers修改了标准的PV架构API,打破了容器内进程间、以及User与Kernel间的隔离性,Application与Guest Linux采用同一个页表,同一个特权级且通过function call进行调用。因此不适合那种通过进程来做隔离的应用(比如OpenSSH server通过不同进程来隔离用户,或者区分root进程和普通用户worker的Nginx服务),另外也不适合容器内进程间强容错的应用。

2) GPL感染:Linux作为LibOS与Application进行函数调用,是否会让Application受到GPL感染的风险,这个问题值得再次深入探讨。因为只是几条指令地替换,并未有链接过程,标准的binary(在普通Linux环境是不受感染的)是未经重新编译就是直接运行的,这个可能会降低GPL感染的风险。但是论文作者的确并没有证明100%没有问题。也姑且把它作为一个架构约束留作读者的家庭作业吧。

整体效果

在整个环境中,Docker引擎运行在HostOS的Domain-0上,通过runV的runtime来启动X-Container容器。在启动过程中,通过一个简单的”boot loader”来启动X-LibOS,附带个X-LibOS传递一些参数,如虚拟设备,ip地址等。这样X-Containers容器就无缝地融入了现在标准的Docker容器平台中。到目前位置,到底是通过Linux as LibOS来弥补Xen PV架构(X64模式)的性能缺陷,亦或是先确定了Linux as LibOS,然后再选择了不少Unikernel/LibOS(比如LightVM、KylinX、ClickOS的MiniOS或者是MirageOS)均喜爱的VMM试验田Xen,我们不得而知。X-Containers整体效果上来看是非常好的,首先不依赖于硬件辅助虚拟化就让容器间、容器与Hypervisor间具备了VM级别的安全隔离能力;第二与原生Guest Linux Kernel的对接,可以做到应用的二进制以及100%ABI兼容(也意味着多进程支持),可以无缝对接现在绝大多数容器;最后一点,通过LibOS function call的方式对Guest 环境的地址空间与函数访问做了优化,性能也做到了架构上的最优。基本上达到了项目最初的3个目标。而且即便是作为声称为约束的容器内的弱安全模型,也“幸运”地符合Single-Concerned云原生容器的特点。或许,这里的约束是一种不得已而为之的无奈。下图是X-Containers与各个安全容器方案的综合比较。


借鉴与影响

从X-Containers 论文的情况来看(源码暂时未开放),目前某些细节以及功能只是一个原型,仍然很不完善,同时Xen的社区热度和技术生态也不如KVM那么丰富和完整。但它的技术特点还是有非常值得借鉴的地方,一是巧妙地“疑似”解决了使用Linux Kernel来作为LibOS所面临地GPL感染问题。这个idea的提出,就算目前ABOM的方案解决不了GPL问题,相信也会有人朝着这个方向继续探索。因为我们知道,现在绝大部分的LibOS的基础都不是Linux,原因之一就是GPL问题。Linux as LibOS的魔力得到释放,就像打开了潘多拉魔盒一样,或许就在若干个月之后,我们就会看到一些基于该idea的Linux或KVM平台下的新的安全沙箱的创新方案。二是通过尝试PV虚拟化机制去除基础设施约束,打开了一条不同的容器安全之路。这有助于打破现有的多租云原生PaaS服务的市场格局,催生出不同的更丰富的PaaS产品形态。目前该论文的3位作者已经根据这篇论文的idea在NSF(National Science Foundation)”的资助下成立了一个初创公司(Exotanium Inc,一作沈之明任CTO),足以看出作者对于该项目、方案前景的信心。