基于Rust-vmm实现Kubernetes运行时

随着容器及K8s的普遍使用,愈来愈多的容器安全与隔离问题被暴露出来,如:容器逃逸、水平攻击、DDos攻击等严重威胁了办公和生产环境的安全与稳定,影响了业务的正常运行。安全容器技术孕育而生,产生了kata、gVisor、unikernel等多种安全容器方案。本文旨在介绍各类安全容器方案,分析各方案特色,结合腾讯在容器安全领域的实践,帮助读者选择适合自身特性的容器运行时。同时引入Rust-VMM项目,介绍 Rust-VMM 技术和生态,演示如何使用K8s调度和启用Rust-VMM安全容器运行时,展望以Rust语言实现的容器运行时的广阔前景。html

容器安全与隔离

一个基于K8s集群构建的基础设施中,内部存在不一样层次的隔离,从容器到Pod再到节点最后到cluster,每一层隔离都有它的特色和特性,咱们尤为关注Pod级别的隔离特性。python

相比其余层次的安全隔离,Pod及容器级别的隔离对咱们的挑战很是大。容器在运行时使用root运行进程,尽管使用namespace技术为容器空间内的pid、uts、fib等进行了隔离,但因为各个容器共享系统内核,容器与内核间缺少隔离保护,容易引起容器逃逸等安全问题,典型容器逃逸攻击如:CVE-2018-1463四、CVE-2016-519五、CVE-2019-5736 及 CVE-2019-14271等。linux

docker.vh.neargle.com:8888/?command_exec=python3 -c "import docker;client = docker.DockerClient(base_url='unix:///var/run/docker.sock');data = client.containers.run('alpine:latest', r'''sh -c \"echo 'ssh-rsa xxxxx root@620e839e9b02' >> /tmp/root/root/.ssh/authorized_keys\" ''', remove=True, volumes={'/': {'bind': '/tmp/root', 'mode': 'rw'}})"

上述脚本是一个简单的例子,这段代码会向docker.sock的端口发起请求,拉起一个alpine的容器,容器内进程会向所在主机注入一段SSH的公钥。在容器里的恶意用户或者攻击者,就能够获轻松得这个容器所在host主机的SSH的登陆权限,从而可以非法查看同主机其余容器空间的信息,篡改关键文件或记录,甚至以主机为跳板攻击整个集群。git

还有一个就是Noisy Neighbor,就是吵闹邻居问题。关于Noisy Neighbor,容器方面已经有了不少进展,好比对于CPU、memory、bandwidth甚至是buffer IO,基于Cgroup对这些资源已经有了一些隔离和限制,可是这些限制并不能彻底解决Noisy Neighbor的问题,仍是有一些吵闹的进程会影响到正常的业务进程的运行。github

# kubectl run --rm -it bb --image=busybox sh
/ #  f(){ f|f& };f                   # WARNING: Don't try this!

上面是一个简单的例子,启动一个busybox的容器,在里面执行一个嵌套循环的指令,会把这台主机上全部的file descriptor所有耗尽,形成这台主机上正常运行的业务进程工做不正常,这个是Noisy Neighbor的风险和问题。golang

对于上述问题,建议用户关注官方的漏洞报告,升级操做系统或docker的版本,根据安全指引配置容器环境,同时能够参考如下措施加强容器集群的安全防御级别。chrome

  • 在物理层面上隔离,为不一样的租户之间划分不一样的Hardware Isolation域,让不一样的租户使用不一样的硬件空间,从物理上、网络上以及存储上完全的隔离,这也是最直接最有效的方法。
  • 利用一些Security Tools,包括常常用的SElinux或者Cgroup隔离,划分不一样的资源访问和安全规则,可是这些安全规则须要编写大量的profile文件,实现起来难度颇大。
  • 入侵检测机制,主机防护的一种手段。入侵检测的软件或者进程会监控这台主机上有风险的进程或者有风险的文件,对于这些文件的读写操做都会有安全方面的记录,会即时预警,即时响应。好比对于containerd-shim/busybox/docker-runc的关键进程,好比docker-runc对于bash、init或者对于fd的访问均可以作到即时的预警和标记。对于上面提到的容器逃离漏洞,经过入侵检测的机制,就能够有一个比较有效的防护。
  • 定制Linux Kernel Patch,一些特殊的资源隔离或者安全漏洞,也能够为kernel打一些自身特有的patch来加以防范,可是这里的patch面临的问题是多种多样的,因此这里就再也不展开细讲了,若是有关注的同窗能够参考一下腾讯对于Linux Kernel方面的一些文章和报道。

容器运行时

上述安全实践方案和措施可以很大程度的减小对外提供服务时受攻击的范围,提升容器服务的安全能力。但咱们仍然想要寻找一种简单的、有效的、统一的runtime解决方法,咱们把眼光投入到CNCF runtime landscape,能够看到有多种解决方案。docker

简单梳理一下这些解决方案,都是按照两大标准划分的。一个就是CRI的标准,偏向于kubelet或者K8s这一侧的标准。还有一侧的标准就是OCI,就是偏向于容器底层基础实现。浏览器

能够看到OCI这边有不少种实现方案,简单梳理一下,划分了一个表格:安全

OCI Solution OCI Compatible Dedicated Docker Image Implementation Language Open source Hot plug Direct access to HW Required Hypervisors Backed by
Runc yes yes golang yes no yes None Docker
gVisor + runsc yes yes golang yes no no none or kvm Google
Kata + qemu yes yes golang,C yes yes yes kvm Hyper
Firecracker + Firecracker containerd no yes Rust,golang yes no no kvm Amazon
Nabla + runnc yes no C,golang yes no no None IBM

简单介绍一下,好比有runc的方案,就是基于原生namespace的容器方案,docker使用的,containerd直接对接的runc的方案。GVisor是谷歌开源出来的一种基于用户态的内核进程的安全沙箱技术的方案。Kata和qemu的基于虚拟化实现安全容器,这个如今也是比较热门,使用比较普遍的一种方案。Firecracker,由AWS开源出来的,Firecracker Containerd是对接OCI标准的组件,但如今尚未彻底对接OCI标准,因此这里是有所欠缺的。最后是Nabla, runnc是Nabla对接OCI实现的一个组件,Nabla是IBM开源的一套安全容器的方案,可是它跟上述的一些方案有所区别,它是一个基于unikernel的方案,把业务应用和内核彻底编译在了一块儿,直接运行在host上面,因此能够看到它跟其余的方案不同的就是它不能直接使用容器的镜像,须要本身编译一个带着内核的特殊镜像,因此用起来不太符合容器的标准。

比较下来,典型的三种安全容器方案就是runc,kata-runtime,以及 gvisor 方案。

三种方案各有优缺点,从架构来讲,runc的方案最简单,直接用原生的namespace的方案。kata是基于hypervisor的方案,就是基于虚拟化的方案,因此它的每个容器或者它的容器是运行在一个一个guest kernel上面,就是guest的虚拟机里,而后会有一个kata agent负责runtime跟底层真正跑的container的进行通讯。GVisor是介于runc和kata runtime之间,它不像Kata有一个虚拟化的Guest Kernel的存在,它是把Guest Kernel变成了一个运行于用户态的一个内核的进程叫作Sentry,Gofer是它IO的组件,核心的sentry负责的就是拦截和劫持全部运行在它的沙箱里面容器的全部的Syscall,对系统调用进行过滤和保护。

Solution Typical Software Bullet Point
Rule-based Sandbox RunC Needs to work with SELinux, AppArmor, Seccomp, cgroup
VM-based Sandbox Kata-container BareMetal Only, Heavy control logic
Application kernel based Sandbox gVisor Compatibility problem, Bottleneck in Sentry

最后汇总比较一下,runc的方案实现最简单,并且它的性能也是最好的。可是一大缺点就是安全隔离的特性不够安全,因此须要大量的外部Security Tools的保护。 基于kata的虚拟化的沙箱方案,它只能运行在支持KVM虚拟化的环境上面,由于他的虚拟层是基于KVM实现的。Kata的逻辑的链路很长,调用的路径也很是长,会形成一些性能上的损耗,也会有一些问题。 GVisor介于二者之间,可是GVisor如今的问题就是它的兼容性不够,300多个系统调用只能兼容里面的一部分,而且Sentry的做为核心会有一些性能瓶颈。

腾讯云在安全容器上融合和上述方案的优势,结合腾讯云在虚拟化,存储和网络方面的优点, 选择使用mVMd + QEMU + EKLET的方案,实现一个弹性的Kubernetes的服务,即EKS,你们能够访问一下腾讯云的官网,看一下EKS的介绍。

EKS是基于Hypervisor的虚拟化的解决方案,不一样于Kata,EKS使用的containerd + mVMd组件更加轻量,调用路径更短。经过containrtd + mVMd来实现对于上层K8s调用的CRI的解析,而且把它转化成真正对于底层一个一个Guest VM或者QEMU的控制指令,在guest VM里会启动相应的containers容器。

在整个的架构中,在Runtime方面最大的瓶颈是QEMU,由于QEMU有几十年的历史了,因此存在着它比较臃肿,反应慢,占用的资源多等等问题。因此让QEMU做为底层Runtime还不够快,不够安全。为了加强QEMU的性能和安全特性,咱们很天然把眼光投向了Rust-Vmm。

Rust-Vmm 介绍

Rust-Vmm是一个开源工程,是一个能够自由定制的VMM(virtual machine monitor)虚拟机管理器,用户能够按照本身的方式订制它。它是基于Rust语言实现的VMM,有着Rust语言带来的优势和特性。

首先,Rust语言一个内存安全的语言,相比于用C或者C++会频繁遇到的各类内存的问题,好比内存的溢出、空指针、野指针、越界访问等等,更进一步会形成安全的问题、性能的问题,以及各类崩溃的问题。Rust语言很好地解决了这一点,从它的语法、编译规则等杜绝了内存级别访问的漏洞以及风险,因此用Rust写的Rust-Vmm自然的就是内存安全的。

第二,Rust-Vmm是不易被攻击的,Rust-VMM是从零开始的,它是从最小的硬件虚拟化出发的,最小的硬件虚拟化意味着它有着最小的攻击面,被攻击的面就很是少,因此它会很安全。

第三,Rust-Vmm可以很灵活的定制。Rust-VMM能够灵活定制它的每个组件,全部的对于设备的模拟或者关键特性的处理都是封装成了一个一个的Rust-Vmm crates包,好比有VCPU,有linuxloader,vm-virtIO等等。其中crates是Rust语言中的包管理工具,能够理解JAVA或golang里面的package,它是以发行不一样的包或者库的形式对外发布它的feature。

第四,Rust-Vmm有很是高的性能,基于Rust语言的without garbage collection特性,它是没有GC回收检查机制的,不像JAVA或者其余更高级的语言会有一个runtime,Rust-Vmm的性能上会更好,同时基于KVM实现的虚拟化方案也是性能的保证。

简单介绍一下Rust-Vmm的一个历史,它是由谷歌首先实现的,谷歌首先实现一个Rust based的轻量级的VMM,它叫作crosVM,你们也能够从连接里面看到,它是一个为chrome浏览器作的一个微内核。而后AWS,亚马逊基于谷歌开源出来的crosVM,实现了本身的基于rust的VMM叫Firecracker。两个项目的开发人员会发现作这两个项目的时候,会有不少重复的重叠的通用的代码,很天然的把能够开源的、通用的部分结合到一块,就有了Rust-Vmm的项目。

从这个全景图里面能够看到,Rust-Vmm应用在多个项目和产品中。从最开始的谷歌开源的crosVM里面会有Rust-Vmm,好比AWS的Firecracker、以及其它CSP云服务器厂商,好比QEMU里面会把一些耗时的或者内存访问的部分也用Rust-Vmm实现,还有开源的Cloud Hypervisor项目。

Cloud Hypervisor是Intel开源出来的一套框架,把Rust-Vmm的组件组合在一块儿,可以对外提供一个VMM的完整服务。用户能够配置使用已公开发布的crates包,或集成本身的开发的crates包,经过将各类crates包组合在一块儿,就能够生成一个订制的安全的高性能的VMM。

基于 Rust-Vmm 实现 K8s Runtime

讲到这里,基于Rust-VMM的一个实现,组件、原料和全部的知识都已经具有了,接下来就是如何对接K8s,基于Rust-VMM实现Kubernetes runtime,运行一个Kubernetes的Pod。

测试环境如图所示,用到了包括K8s, containerd, Kata, Rust-Vmm, Cloud Hypervisor等开源项目,首先咱们配置Kubernetes节点上的kubelet使用的runtime为containerd, 在containerd上配置了Kata-runtime做为容器的运行时,再配置了Kata-runtime使用的vmm为基于Rust-Vmm crates构建的Cloud Hypervisor,同时为了提高性能,这里也是用了virtos-fs技术来承载物理机上的容器镜像到子机内容器的imagefs的共享,因此在物理机上须要额外开启virtiofsd的进程,在guest os的内核上也是使用最新的包含virtio-fs支持的内核版本。

在K8s上建立一个Pod后,调度器调度这个Pod到咱们这个环境上的一个worker节点,节点上的kubelet进程根据咱们的配置调用containerd,containerd的runtime插件kata来建立Cloud Hypervisor的vmm(virtual machine monitor),在子机内预置的Guest OS操做系统启动后,kata-agent也随之启动,成功创建母机与子机的通道,后续cri协议中的createcontainer,startcontainer以及中止容器等接口也由这个通道传递到子机内的kata-agent,由其来调用子机内真正的容器运行时,从而完成整个pod的生命周期。

咱们来看一下最终咱们达到的一个效果,如图中第一条指令,咱们执行kubectl get pod命令能够看到pod的状态为运行中,一样的,咱们也能够经过crictl pods命令查询到这个Pod的状态。经过ps查询进程列表,能够看到包含这个pod id的kata shim进程,virtiofsd进程以及cloud-hypervisor这个vmm的进程。最后咱们也能够经过kubectl exec -it交互式的进入pod的container内,经过uname返回此容器使用的内核版本为5.6,物理机的内核版本为5.4。

总结与展望

最后是对将来的展望,基于Rust-Vmm咱们想要作得更多。好比但愿扩展一下Rust-Vmm在网络以及存储方面的一些特性,尤为是在腾讯云的环境里面,经过结合CBS和VPC,提高Runtime的性能。

第二,会结合入侵检测、主机安全、安全工具等,把它们融合在一块儿,构建起一个多维的、多重防御的一套全维度安全的容器服务平台,Rust-VMM会实现里面的一部分。

第三,扩展Rust Runtime,尤为在边缘计算领域,在边缘端可以实现K8s如下彻底由Rust接管,由Rust语言所有重写,好比底层的Containerd、 shim等组件,变成一个Full Stack Rust的实现。

最后,由于Rust-VMM也是基于hypervisor的一个实现,因此基于hypervisor的一些特性,好比热迁移、备份、回滚这些机制,将来均可以在Rust-Vmm上有所实现,这个也是咱们的将来的一个设想。

最后你们若是想要了解更多关于我刚才介绍的K8s Runtime,Rust-Vmm以及EKS方面的知识,你们能够扫描下方的二维码加入腾讯云容器团队的公众号。

或者点击下方的网址了解TKE的公有云产品。腾讯云对外也有一个开源出来的项目叫作TKEstack,你们能够从下方的github网址访问获得,也但愿你们多多支持。

最后若是你们有兴趣,立志于开源事业,立志于CNCF的云原生事业,也欢迎你们加入腾讯的团队。连接就在上方,欢迎你们加入咱们。

Q&A

  1. 如今的Rust-VMM项目开源了,哪里能够看到项目相关的信息?

    Rust-VMM是有谷歌,也有亚马逊、英特尔,也有业内的厂商共同开源出来的一个项目,相关的信息你们能够从github(https://github.com/rust-vmm)上面找到它们开源的项目,另外google一下就能够找到这个项目更多的信息。

  2. 基于Rust-Vmm成熟度如何,基于Rust-Vmm构建的安全容器主要解决了哪些难题。

    你们能够对比一下AWS开源的Firecracker,它就是基于Rust-Vmm实现的一套亚马逊本身的方案,并且这套方案已经应用在了亚马逊fargate以及lambda的项目,serverless的解决方案里面。因此基于Rust-Vmm这种项目的成熟度,亚马逊已经把它作到商用了,这一点是有所保证的。

    可是Rust-Vmm自己又是开源的项目,它是跟AWS的firecracker有不太同样的地方,成熟度方面确定是有所欠缺的,须要厂商本身维护和保证质量问题或者安全问题。

    所以我想说基于Rust-Vmm实现的方案,亚马逊已经帮咱们作了前驱的工做,已经帮咱们作了一个商业上的试水,因此这个是没有问题的。

  3. 是否如今的公有云容器运行时都不倾向于使用runc。

    不是不使用runc,使用runc有一些隔离的问题。好比两个租户共用一个物理资源池,或者两个租户的容器运行在一个物理节点上,这个时候就带来了容器逃离以及吵闹邻居的问题,多租户的场景下或者serverless的场景下就会有这种问题。

    因此大部分的公有云厂商的解决方案其实就是物理隔离。一个租户所在的物理主机都是属于本身的,属于同一个租户的,因此你会发现某一个用户假如从一个容器里面逃离出去以后,逃离出去的这台主机仍是本身的,因此这样逃出去,从物理上面还有安全防御的。

    可是这种面对于serverless的场景,serverless自然的就是要脱离主机的管理、脱离主机的隔离,因此serverless场景下,跟公有云的容器服务就有所区别,它就不能简单靠物理隔离的方式实现的,它就要考虑沙箱(安全容器)的技术,好比像刚才我也介绍了多种多样的沙箱隔离的技术,咱们可能选择的就是这种基于hypervisor的实现方案。

    若是感兴趣,您能够访问一下腾讯云的官网,看一下里面EKS相关的介绍,这个就是腾讯对于这一领域的解决方案和贡献。 【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!

相关文章
相关标签/搜索