嘉宾 | 李福攀 整理 | 王谟仕
出品 | CSDN云原生
2022年6月7日,在CSDN云原生系列在线峰会第7期“安全技术峰会”上,蚂蚁集团高级技术专家李福攀分享了Kata安全容器及其在蚂蚁集团的落地实践。
2013年左右,Docker容器横空出世,极大地方便了业务部署。在Docker容器出现之前,业务的部署需要根据不同的环境做不同适配,会出现在开发和测试环境下,业务运行得很好,但到了生产环境上就出问题,原因就是研发、测试和生产的环境不一致导致的。
Docker容器的出现就是为了解决环境不一致的问题,Docker image会把开发、测试和生产的环境进行有机统一,实现了开发、测试、生产都是同一个环境,保证开发测试和生产部署都是统一的环境,不会因为环境出现问题。
传统Docker的隔离机制
Docker runtime容器运行时,主要是利用Linux系统或者其他系统的一些资源隔离和限制机制,把业务进程限制在沙箱中。传统容器隔离机制就是利用Liunx系统的PID namespace、Cgroup namespace以及其他一些namespace,把资源进行划分从而形成隔离,但它有一个致命的缺点——共享内核。简单来说,在一个host上部署了很多业务,如果一个容器里面有恶意代码或者被外面攻击了,它可能会利用系统的一些漏洞,逃逸出namespace的限制,攻击系统内核。
内核受到攻击可能会对整个系统上的其他业务产生影响,甚至窃取其他业务的数据。传统的容器运行时有这样的痛点,那么能不能既利用虚拟机的隔离又利用容器的快速部署能力,结合起来形成一个新的运行时?答案就是Kata容器。
Kata容器的隔离机制
Kata容器在host之上新建了一层虚拟机,在虚拟机里面有自己的guest内核。guest内核利用普通容器的资源和线程限制机制,又加了一层namespace和Cgroup的限制等。如果Kata容器被攻击,攻击会被限制在整个guest内核里面,很难再逃逸到host主机上。
runC容器和Kata容器隔离性的主要区别
runC容器安全隔离主要是两层隔离机制:namespace和root权限。一般容器运行时是以第三方用户权限运行的,正常没有root权限,所以即使逃离了namespace限制的话,也很难去对整个系统产生严重的破坏。万一恰好主机的内核又有一定的安全漏洞或CV漏洞的话,有可能会利用这个漏洞做一些欺权的操作,提权到root权限。此时runC容器对整个系统的安全影响就非常大了。
Kata容器在两层限制之外,又加了一层虚拟机(vm)限制。即使是它逃逸了namespace的限制,同时又利用一些CV的漏洞做了提权拥有了root权限,但它还是只能在这个虚拟机里,很难对host上的其他业务再造成一定的破坏或者干扰。
1.0:融入容器生态
Kata架构的第一次升级是为了融入容器生态。Docker架构中每起一个容器,需要一个containerd-shim来管理里面进程的生命周期。因为Kata容器进程是在guest系统里面的,普通的containerd-shim不能完成对guest系统内进程的控制,达不到对Kata容器生命周期的控制。需要在containerd-shim和容器之间加上一层Kata-shim作为中转,把containerd-shim的一些请求,通过Kata-shim中转到guest进程上,从而实现管理guest里面的容器进程。
由于Kata容器的I/O从guest里面不能直接和containerd-shim做对接,所以还需要一个额外的中间进程——Kata-proxy,把Kata容器进程的I/O和containerd-shim进行中转。对于普通的容器来说,一个Pod里面包含容器进程和对应的一个containerd-shim,Kata容器不仅仅包含容器进程、containerd-shim,还需要Kata-shim以及Kata-proxy,无论在进程的管理上还是运维操作上都带来了极大负担。为了减少兼容性开销,Kata将containerd-shim、Kata-shim、Kata-proxy融合为一个containerd-shim-Kata-v2。自此,Kata容器不再是模仿runC,而是融入了容器生态成为了云原生世界的一等公民。
2.0:精简瘦身
经过第一次架构升级后,Kata删减了一些不必要的进程,但本质是包了一层虚拟机。加一层虚拟机之后,相对于runC来说需要额外的内存开销。Kata为了降低容器的内存开销,使用NVDIMM DAX技术在Pod之间共享guest image。Kata guest里面的内存开销取决于agent的匿名页内存的大小,使用Rust重写agent降低了Kata-agent的匿名页开销,极大地提高了Kata容器的单机部署密度。
容器的生态基本上都用了Prometheus加上容器的Metrics或status的接口来获取整个容器运行的指标数据。对于Kata来说,status接口提供的指标有限,同时Kata的监控需要通过host进入guest,再进入容器的namespace里才能采集指标数据。
因此,Kata在host和guest里面之间加了一层Metrics的数据采集工作对接Prometheus,把Kata容器的数据传入到Prometheus。同时可以利用一些Dashboard来查看整个业务容器的Metrics数据,或者对这些数据进行报警(Alert)设置。
由于Kata容器需要在host和guest之间进行文件共享,原本的9pfs兼容性存在一定缺陷。于是和RedHat共同开发了virtio-fs取代9pfs。virtio-fs主要特点是兼容性好、扩展性强,相比9pfs在性能上也有很大提升。整体架构演变如下图所示。
3.0:日臻完美
Rust重写
Kata 3.0对比2.0来说,最主要的变化是使用Rust语言进行全面重构。整体实现基于同一种语言,将guest vm管理进程和Kata容器的runtime进程,合并成一个进程。
引入Nydus
除了用Rust重写之外,Kata 3.0引入Nydus镜像加速系统。镜像加速是指:在最早的OCI镜像里,镜像是按分层来存储的,每层是一个压缩包。每次加载时需要把整个镜像里所有的层下载下来,然后组装成一个容器的rootfs才能够把容器启起来。
在容器启动过程中,rootfs里的很多进程是用不到的,只用到了其中一小部分。可以让容器的rootfs的加载,不需要把所有的文件都拉下来之后再去启动容器,而只是按需加载就把容器启起来。Nydus就做到了按需下载,容器的启动时间缩小很多。同时在镜像服务中,还可以实时监控到整个容器运行过程当中,加载了哪些文件,加载了文件的哪些部分,实现了对容器文件访问模式的监控,来提前感知到容器是否受到攻击,因为容器在受到攻击之后,对文件的访问模式有可能会发生变化。
精密计算
对一些具体业务来说,例如银行的数据是敏感的,银行不信任容器的服务提供商,认为提供商有可能会窃取容器里面的一些数据。所以他们会希望在容器服务整个过程当中,提供商是探听不到容器里面的内存的。
runC容器运行时的rootfs是在host上去加载,然后通过共享机制共享到guest里面的。而Kata机密容器不是把容器的镜像在host里面去加载,而是加载在guest里面,整个guest对于云服务提供商来说是加密的。容器服务商是进入不到guest里面的,即使进入到guest也获取不到业务方的数据。同时,整个guest的所有内存的访问都是经过加密的,即使host内核可以访问到guest的内存,也不能解密内存数据。
这就是加密容器提供的两大加密措施:
把镜像放在guest里去加载,对整个虚拟机的内存做加密保护,只有guest里面才有权访问,在host上面是访问不到的;
整个guest的加载需要通过加密认证。
性能隔离
蚂蚁的在线和离线业务一开始都是用runC容器部署的,存在一些离线业务在关键时候影响在线业务的情况。runC是共享内核的,在一些关键的路径上,离线业务调用过程中有可能会阻塞在线业务的调用,从而导致离线业务干扰到在线业务。用Kata容器把离线业务封装之后,离线业务里边有自己的guest内核,即使离线业务在关键路径上,它也只是在guest内核上去调用不会影响到整个host上的在线业务。
存储
Kata容器平时是用virtio-fs在host和guest之间进行文件共享,对于重I/O型业务通过文件共享的模式,可能满足不了I/O带宽需求。如果说某个业务是纯CPU这样的计算类型的,对I/O要求比较少,就可以用virtio-fs做共享;如果是重I/O型业务,需要把存储设备或者一些下载设备,通过直通的方式通到guest里边给Kata容器使用。
网络
有些业务对网络开销需求不大,网络带宽要求也不高,可以用传统的bridge或者其他rpc方式在guest和host之间通信。如果是对网络或者网络带宽需求比较高的业务,就需要把网卡从host上面直通到guest里边去。
总体来说,Kata安全容器在1.0砍掉了一些不必要的进程,2.0进行了内存消耗的精简瘦身,3.0将两个进程统一成一个进程,同时引入一些安全机制,增加精密计算、镜像加速等功能。
而Kata安全容器在蚂蚁的应用主要是涉及两个方面:
关键业务需要安全隔离,用安全容器把这个业务进行封装起来,和其他runC容器混合部署;对一些离线业务需要性能隔离的也用Kata封装起来,和其他一些在线业务进行混合部署;
针对不同业务的网络需求和I/O需求,采用不同的网络模式;对不同的存储需求,采用存储设备的直通或者其他一些块设备的直通,用直插的方式来满足不同的业务需求。