尽管大多数IT行业都基于容器和云解决方案实施基础架构解决方案,但有必要了解这些技术的局限性。 传统上,Docker,Linux容器(LXC)和Rocket(rkt)并不是真正隔离的,因为它们在工作中共享父操作系统的核心。 是的,它们在资源方面很有效,但是估计的攻击媒介总数和黑客入侵造成的潜在损失仍然很大,尤其是在容器位于多租户云环境中的情况下。

我们问题的根源在于,当主机操作系统为每个容器创建虚拟用户区域时,容器的界定不明确。 是的,已经进行了旨在创建具有完整沙箱的真正“容器”的研究和开发。 并且大多数最终解决方案导致了容器之间边界的重组,以增强其隔离性。 在本文中,我们将分别研究来自IBM,Google,Amazon和OpenStack的四个独特项目,它们使用不同的方法来实现相同的目标:创建可靠的隔离。 因此,IBM Nabla在Unikernel之上部署了容器,Google gVisor创建了专门的来宾内核,Amazon Firecracker对沙盒应用程序使用了非常轻量级的虚拟机管理程序,而OpenStack将容器放置在针对编排工具进行了优化的专门虚拟机中。
现代集装箱技术概述
容器是打包,共享和部署应用程序的现代方法。 与将所有功能打包到一个程序中的整体应用程序不同,容器应用程序或微服务仅用于目标狭窄用途,并且仅专注于一项任务。
容器包含应用程序完成其特定任务所需的所有依赖项(例如,程序包,库和二进制文件)。 结果,容器化的应用程序是独立于平台的,并且可以在任何操作系统上运行,而不管版本或安装的软件包如何。 这种便利使开发人员免去了为适应不同平台或客户端而改编不同版本软件的大量工作。 尽管从概念上讲并不完全准确,但许多人还是喜欢将容器视为“轻量级虚拟机”。
将容器部署在主机上时,每个容器的资源(例如其文件系统,进程和网络堆栈)将置于虚拟隔离的环境中,其他容器无法访问该环境。 这种体系结构允许成千上万个容器在单个群集中同时运行,然后可以通过复制大量实例轻松扩展每个应用程序(或微服务)。
在这种情况下,容器布局基于两个关键的“构建块”:Linux名称空间和Linux控制组(cgroups)。
命名空间创建了一个虚拟的隔离用户空间,并为应用程序提供了专用的系统资源,例如文件系统,网络堆栈,进程ID和用户ID。 在这个隔离的用户空间中,应用程序控制文件系统的根目录,并且可以以root身份运行。 这个抽象空间允许每个应用程序独立运行,而不会干扰位于同一主机上的其他应用程序。 当前有六个名称空间可用:装载,进程间通信(ipc),UNIX分时系统(uts),进程ID(pid),网络和用户。 建议为该列表添加两个额外的命名空间:time和syslog,但是Linux社区尚未决定最终的规范。
Cgroup提供硬件资源限制,优先级划分,应用程序监视和控制。 他们可以控制的硬件资源的一个示例是处理器,内存,设备和网络。 当将命名空间和cgroups组合在一起时,我们可以在同一主机上安全地运行多个应用程序,每个应用程序都在其自己的隔离环境中-这是容器的基本属性。
虚拟机(VM)和容器之间的主要区别在于,虚拟机是硬件级别的虚拟化,而容器是操作系统级别的虚拟化。 VM虚拟机管理程序为每台计算机模拟硬件环境,容器运行时已在其中依次为每个对象模拟操作系统。 虚拟机共享主机的物理硬件,而容器共享硬件和OS核心。 由于容器通常与主机共享更多资源,因此它们在存储,内存和CPU周期方面的工作要比虚拟机高效得多。 但是,这种共享访问的缺点是信息安全方面存在问题,因为在容器和主机之间建立了太多的信任。 图1说明了容器和虚拟机之间的体系结构差异。

通常,与仅隔离名称空间相比,虚拟化设备的隔离创建了更强大的安全边界。 攻击者成功离开隔离进程的风险远高于成功离开虚拟机的机会。 超出受限容器环境的较高风险的原因是名称空间和cgroup创建的隔离差。 Linux通过将新的属性字段与每个进程相关联来实现它们。
/proc
文件系统中的这些字段向主机操作系统指示一个进程是否可以看到另一个进程,或一个特定进程可以使用多少处理器/内存资源。 从父操作系统查看正在运行的进程和线程时(例如top或ps命令),容器进程看起来与其他进程一样。 通常,由于LXC或Docker等传统解决方案使用同一主机内的同一内核,因此不认为它们是完全隔离的。 因此,容器具有足够数量的漏洞也就不足为奇了。 例如,CVE-2014-3519,CVE-2016-5195,CVE-2016-9962,CVE-2017-5123和CVE-2019-5736可能导致攻击者获得对容器外部数据的访问权限。
大多数内核漏洞利用都会为成功的攻击创建一个向量,因为它们通常会导致特权升级,并允许受到破坏的进程在其预期名称空间之外获得控制权。 除了软件漏洞方面的攻击媒介以外,不正确的配置也可能起一定作用。 例如,以过多特权(CAP_SYS_ADMIN,特权访问)或关键挂载点(
/var/run/docker.sock
)部署映像可能会导致泄漏。 考虑到这些潜在的灾难性后果,您应该了解在多租户空间中部署系统或使用容器存储敏感数据时所承担的风险。
这些问题促使研究人员创建更强大的安全边界。 想法是创建一个与主操作系统尽可能隔离的真实沙箱容器。 这些解决方案中的大多数都包括混合体系结构的开发,该混合体系结构在应用程序和虚拟机之间进行了严格区分,并致力于提高容器解决方案的效率。
在撰写本文时,还没有一个项目可以足够成熟以被接受为标准,但是将来,开发人员无疑会接受其中一些概念作为主要概念。
我们从Unikernel开始审查,Unikernel是最古老的高度专业化的系统,它使用最少的OS库集将应用程序打包到一个映像中。 事实证明,Unikernel本身的概念对于许多旨在创建安全,紧凑和优化图像的项目都是至关重要的。 之后,我们将继续考虑IBM Nabla,这是一个启动Unikernel应用程序(包括容器)的项目。 此外,我们还有Google gVisor,这是一个用于在用户内核空间中启动容器的项目。 接下来,我们将切换到基于虚拟机的容器解决方案-Amazon Firecracker和OpenStack Kata。 通过比较以上所有解决方案来总结本文。
Unikernel
虚拟化技术的发展使我们能够转向云计算。 Xen和KVM之类的管理程序为我们现在称为Amazon Web Services(AWS)和Google Cloud Platform(GCP)奠定了基础。 而且,尽管现代虚拟机管理程序能够将数百个虚拟机组合到一个集群中,但传统通用操作系统并不太适应和优化,无法在这种环境下工作。 通用操作系统首先旨在支持和使用尽可能多的不同应用程序,因此它们的内核包括各种驱动程序,库,协议,调度程序等。 但是,现在部署在云中某处的大多数虚拟机都用于运行单个应用程序,例如,提供DNS,代理或某种数据库。 由于这样的单个应用程序仅依赖于OS内核的特定部分,因此其所有其他“裙子”都只是浪费系统资源,事实上,它的存在增加了潜在攻击的向量数量。 确实,代码库越大,消除所有缺点的难度就越大,潜在的漏洞,错误和其他弱点也就越多。 这个问题鼓励专家开发具有最少内核功能集的高度专业化的操作系统,即创建支持一个特定应用程序的工具。
Unikernel的想法第一次在90年代诞生。 然后,他成为了具有单个地址空间且可以直接在虚拟机管理程序上运行的机器的专用映像。 它将核心和内核相关的应用程序和功能打包到一个映像中。 Nemesis和Exokernel是Unikernel项目的两个最早的研究版本。 打包和部署过程如图2所示。
图2.设计用于支持所有类型的应用程序的多用途操作系统,因此需要预先加载许多库和驱动程序。 Unikernel是高度专业的操作系统,旨在支持一种特定的应用程序。
Unikernel将内核分为几个库,并且仅将必要的组件放入映像中。 与常规虚拟机一样,Unikernel在VM虚拟机管理程序上部署并运行。 由于其体积小,因此可以快速加载并快速扩展。 Unikernel的最重要功能是增强的安全性,较小的占用空间,高度的优化和快速的加载。 由于这些映像仅包含依赖于应用程序的库,并且如果未有目的地连接,则无法使用OS Shell,因此攻击者可以在其上使用的攻击媒介数量很少。
也就是说,攻击者不仅难以在这些独特的核心中立足,而且其影响力也仅限于一个核心实例。 由于Unikernel映像的大小仅为几兆字节,因此下载它们的时间为数十毫秒,实际上,单个主机上可以运行数百个实例。 与大多数现代操作系统一样,在一个地址空间中使用内存分配而不是多级页表,与常规虚拟机上运行的同一个应用程序相比,单内核应用程序具有较低的内存访问延迟。 因为在构建映像时应用程序与内核一起提供,所以编译器只需执行静态类型检查即可优化二进制文件。
Unikernel.org维护一个单内核项目列表。 但是,unikernel具有其所有鲜明的特征和特性,并未得到广泛使用。 Docker在2016年收购Unikernel Systems时,社区决定该公司现在将容器包装在其中。 但是三年过去了,仍然没有整合的迹象。 实施缓慢的主要原因之一是,尚无成熟的工具来创建Unikernel应用程序,并且这些应用程序中的大多数只能在某些虚拟机管理程序上运行。 另外,将应用程序移植到unikernel可能需要手动重写其他语言的代码,包括重写依赖的内核库。 同样重要的是,不可能在Unikernel中进行监视或调试,或者对性能产生重大影响。
所有这些限制使开发人员无法使用该技术。 应该注意的是,unikernel和容器具有许多相似的属性。 第一和第二个都是高度聚焦的不可变映像,这意味着它们中的组件无法更新或固定,也就是说,您始终必须为应用程序补丁创建一个新映像。 今天,Unikernel与Docker的祖先相似:然后容器运行时不可用,开发人员不得不使用基本工具来构建隔离的应用程序环境(chroot,unshare和cgroups)。
伊本·纳布拉
曾经,IBM的研究人员提出了“将Unikernel作为流程”的概念-也就是说,将在专用管理程序上作为流程运行的unikernel应用程序。 IBM项目“ Nabla容器”增强了单内核的安全范围,并用其自己的开发名为Nabla Tender的通用设备取代了通用管理程序(例如QEMU)。 这种方法的基本原理是,在unikernel和虚拟机管理程序之间的调用仍然提供最多的攻击媒介。 这就是为什么使用专用于unikernel的虚拟机管理程序并允许较少的系统调用可以显着增强安全性的原因。 Nabla Tender拦截了将唯一内核路由到管理程序的调用,并将其转换为系统请求。 同时,seccomp Linux策略会阻止Tender正常工作所需的所有其他系统调用。 因此,Unikernel与Nabla Tender一起在主机的用户空间中作为进程运行。 下面的图3中显示了Nabla如何在unikernel与主机之间创建瘦接口。
图3.为了将Nabla与现有容器运行时平台链接,Nabla使用了OCI兼容环境,该环境又可以连接到Docker或Kubernetes。开发人员声称,Nabla Tender在其工作中使用少于七个系统调用来与主机进行交互。 由于系统调用充当用户空间中的进程与操作系统内核之间的一种桥梁,因此对我们可用的系统调用越少,用于攻击内核的可用向量越少。 将unikernel作为进程运行的另一个优点是,您可以使用大量工具(例如,使用gdb)调试此类应用程序。
为了与容器编排平台一起使用,Nabla提供了专用的
runnc
,该
runnc
是使用开放容器计划(OCI)标准实现的。 后者定义了客户端(例如Docker,Kubectl)和运行时环境(例如runc)之间的API。 Nabla还附带了一个图像构造函数,
runnc
以后可以运行它。 但是,由于unikernel和传统容器之间文件系统的差异,Nabla映像不符合OCI映像规范,因此Docker映像与
runnc
不兼容。 在撰写本文时,该项目仍处于开发的早期阶段。 还有其他限制,例如,缺少对挂载/访问主机文件系统的支持,添加多个网络接口(Kubernetes必需)或使用其他Unikernel映像(例如MirageOS)中的映像。
Google gVisor
Google gVisor是使用Google Cloud Platform应用程序引擎(GCP),云功能和CloudML的沙盒技术。 在某个时候,Google意识到了在公共云基础架构中运行不受信任的应用程序的风险,以及使用虚拟机的沙盒应用程序效率低下的风险。 结果,为这种不可靠应用程序的隔离环境开发了用户空间内核。 gVisor将这些应用程序放置在沙箱中,拦截从它们到主机内核的所有系统调用,并使用gVisor Sentry内核在用户环境中对其进行处理。 本质上,它充当来宾核心和管理程序的组合。 图4显示了gVisor架构。
图4. gVisor内核的实现// Sentry和gVisor Gofer文件系统使用少量系统调用与主机进行交互gVisor在应用程序及其主机之间创建了强大的安全范围。 它限制了应用程序可以在用户空间中使用的系统调用。 在不依赖虚拟化的情况下,gVisor充当在独立应用程序和主机之间进行交互的主机进程。 Sentry支持大多数Linux系统调用和核心内核功能,例如信号传递,内存管理,网络堆栈和流模型。 Sentry实现了319 Linux系统调用中的70%以上,以支持沙盒应用程序。 但是,Sentry使用少于20个Linux系统调用来与主机内核进行交互。 值得注意的是,gVisor和Nabla具有非常相似的策略:保护主机操作系统,并且这两种解决方案都使用少于10%的Linux系统调用来与内核进行交互。 但是您需要了解gVisor创建了一个多用途内核,例如,Nabla依赖于唯一的内核。 同时,这两种解决方案都在用户空间中启动了专门的来宾内核,以支持它们信任的隔离应用程序。
有人可能会奇怪,当Linux内核已经是开放源代码且易于访问时,为什么gVisor需要自己的内核。 , gVisor, Golang, , Linux, C. Golang. gVisor — Docker, Kubernetes OCI. Docker gVisor, gVisor runsc. Kubernetes «» gVisor «»-.
gVisor , . gVisor , , , . ( , Nabla , unikernel . Nabla hypercall). gVisor (passthrough), , , , GPU, . , gVisor 70% Linux, , , gVisor.
Amazon Firecracker
Amazon Firecracker — , AWS Lambda AWS Fargate. , « » (MicroVM) multi-tenant . Firecracker Lambda Fargate EC2 , . , , . Firecracker , , . Firecracker , . Linux ext4 . Amazon Firecracker 2017 , 2018 .
unikernel, Firecracker . micro-VM , . , micro-VM Firecracker 5 ~125 2 CPU + 256 RAM. 5 Firecracker .
5. FirecrackerFirecracker KVM, . Firecracker seccomp, cgroups namespaces, , , . Firecracker . , API microVM. virtIO ( ). Firecracker microVM: virtio-block, virtio-net, serial console 1-button , microVM. . , , microVM File Block Devices, . , cgroups. , .
Firecracker Docker Kubernetes. Firecracker , , , . . , , OCI .
OpenStack Kata
, 2015 Intel Clear Containers. Clear Containers Intel VT QEMU-KVM
qemu-lite
. 2017 Clear Containers Hyper RunV, OCI, Kata. Clear Containers, Kata .
Kata OCI, (CRI) (CNI). (, passthrough, MacVTap, bridge, tc mirroring) , , . 6 , Kata .
6. Kata Docker KubernetesKata . Kata Kata Shim, API (, docker kubectl) VSock. Kata . NEMU — QEMU ~80% . VM-Templating Kata VM . , , , CVE-2015-2877. « » (, , , virtio), .
Kata Firecracker — «» , . , . Firecracker — , , Kata — , . Kata Firecracker. , .
结论
, — .
IBM Nabla — unikernel, .
Google gVisor — , .
Amazon Firecracker — , .
OpenStack Kata — , .
, , . . Nabla , , unikernel-, MirageOS IncludeOS. gVisor Docker Kubernetes, - . Firecracker , . Kata OCI KVM, Xen. .

, , , , .