容器技术发展史

容器技术20年:容器引擎与江湖门派 - 知乎 (zhihu.com)

简介

相对较为正式的术语定义,可以把容器管理系统分为三层:

  1. High-level Container Management:容器管控的 UI 层。直接实现容器的管控和使用界面,也是用户最熟悉的子系统。
  2. High-level Container Runtime:容器状态及资源供给。包括镜像管理、网络接入、容器状态、调用 Low Level Runtime 执行容器等功能。习惯上这层称之为容器引擎(Container Engine)。
  3. Low-level Container Runtime:容器执行层。负责具体构建容器运行环境并执行容器进程。习惯上这层直接简称为容器运行时(Container Runtime)。

High-level Container Management 和 Container Engine 之间的接口规范是 CRI,Container Engine 和Container Runtime 之间的接口规范是 OCI。

下文将分别从容器引擎(High-level container runtime)和容器运行时(Low-level container runtime)两个纬度讨论容器执行引擎相关技术。

容器引擎

容器引擎的核心是运行容器所需要的 资源以及管理容器生命周期。在K8S生态圈中,容器编排系统通过CRI接口来调用容器引擎。支持CRI接口的容器引擎主要有docker、rkt、pouch、containerd和cri-o等,其中活跃度比较高的是containerd和CRI-O。

containerd

Containerd是从dockerd中抽离出来的容器管理的核心功能,是在社区影响下dockerd模块化的结果,也是现在最热梦的容器引擎。由于containerd是从dockerd演化出来的,使用接口是针对容器管理而设计,内置管理对象是Image和Container。K8S CRI的管控对象是Image、Pod、Container,为了让containerd支持Pod对象以及实现CRI接口,引入了CRI-containerd组件来粘合Kubelet和containerd俩个子系统。现在cri-containerd组件已经作为一个功能模块内置到containerd。containerd依赖后端container runtime来具体管理容器,最常见的container runtime是runC。

CRI-O

而 CRI-O 则是专门支持 K8S CRI 接口而设计实现的容器引擎,它抛弃了对 docker 的支持而专注于支持 K8S CRI,所以架构相对 containerd 要简洁不少。但是,虽然架构简洁优雅,但是从实际使用体验看 CRI-O 在成熟度、可扩展性方面相对 cantainerd 还是有一定差距。今年 CRI-O 也增加了对 shimv2 接口的支持,从而可以支持 runC 之外其它容器运行时,但是实际测试发现功能还不成熟。

进一步看 CRI-O 的架构,一个 CRI 实现需要实现的核心功能包括:镜像管理(Image service)、运行管理(runtime service、管理 Pod、container 生命周期)、容器网络(CNI)和对接 OCI 兼容的容器运行时。这儿有一个非常重要的细节,虽然 cri-containerd 和 CRI-O 都是同时x实现 CRI 的 image service 和 runtime service,但是 CRI 规范其实允许用不同的组件来分别实现 image service 和 runtime service。

容器运行时

runc

runC 是目前使用最广泛的容器运行时,但是 runC 也不是完美的。业界对 runC 的担心主要集中在 runC 的隔离能力,众所周知 runC 容器共享一个 host 内核,利用 cgroup 和 namespace 机制来构建相互隔离的容器执行环境。Docker、LXC、RKT 和 runC 这类原生容器都是基于共享主机操作系统的,这些技术的优点是资源利用率高、弹性能力强,缺点则是受攻击面大和攻击后果严重。特别是在多租户的云环境中,不同客户的容器会被编排部署到同一个服务器系统,这种威胁就变得尤其明显了。

其实,容器引擎技术领域也存在类似 CAP 理论的三边关系:资源效率、安全隔离和标准通用。目前所有的容器运行时技术最多都只能满足资源效率、安全隔离和标准通用中的两项,还没有一个技术能做到同时满足三项。比如:runC 在资源效率和标准通用方面最强,但是在安全隔离方面却最弱。Kata containers/firecracker-containerd/runD 在安全隔离和标准通用上有优势,但是在资源效率方面却有不足。

基于共享内核的 OS 虚拟化技术总是让人不放心,“安全容器”便应运而生了。安全容器的核心思路是摒弃共享内核,为每个 Pod/Container 实例配置一个专用的容器 OS,再辅以硬件虚拟化、应用内核等技术以获得更好的隔离性。我们可以按是否使用共享内核、是否使用硬件虚拟化、是否使用 Linux Kernel 作为容器内核几个纬度来对容器运行时进行分类。从下面的分类表可以看出,基于硬件虚拟化+Linux 分离内核是目前行业的主流技术路线。

KataContainers

对于传统容器安全性的担忧。Intel在2015年启动了它们虚拟机为基础的容器技术:Clear Container。Clear Container依赖Intel VT硬件虚拟化技术以及高度定制的QEMU-KVM(qemu-lite)来提供高性能的基于虚拟机的容器。在2017年,Clear container项目加入了Hyper RunV,这是一个基于hypervisor的OCI运行时,从而启动了Kata容器项目Home Clear Linux Project | Clear Linux Project。Kata containers 的核心思路是:

  1. 操作系统本身的容器机制没法解决安全性问题,需要一个隔离层
  2. 虚拟机是一个现成的隔离层,云服务已经让全世界相信,对用户来说secure of vm 是可以满足需求的
  3. 虚拟机里面只要有个内核,就可以支持OCI规范的语义,在内核酸跑个Linux应用者并不太难实现
  4. 虚拟机可能不够快,阻碍了它在容器环境的应用,那么可不可以有容器一般的速度呢?

所以Kata containers核心之一是把VM变得轻快稳,使之能满足更高弹性的需求。同时作为一个容器运行时,必须深度融入生态,支持相关规范。

Kata container最大的特点是它专注于实现一个开放的符合OCI标准的安全容器runtime实现,对于对接什么虚拟化方案,它抽象了一套Hypervisor接口,如今已经对接了多种许你计划实现,比如qemu、nemu、firecarcker、cloud-hypervisor等。

2019 年,Kata containers 有个非常重要的技术进步,和 containerd 社区共同制定了 shimv2 接口规范,并率先在 Kata containers 支持了该规范。通过 containerd-shim-v2 和 vsock 技术,kata 精简了大量的组件,配合轻量级 hypervisor 和精简内核,kata 可以大幅降低内存开销和容器启动时间。更关键的是,降低系统部署复杂度还大幅提高了稳定性,特别是在系统重载情况下的稳定性。从实际使用体感看,阿里云沙箱容器 1.0 从 shimv1 升级到 shimv2 后,稳定性得到大幅提升,缺陷数量大幅下降。一个技术,同时服务于“轻”、“快”、“稳”三个目标,当之无愧重要的技术进步!

第二个热点则是 Kata containers 2.0 架构。从 2019 年 8 月开始,阿里云、蚂蚁和 intel 三方共同推动 Kata containers 2.0 架构定义及设计,核心是进一步提升多租隔离能力及可观测性。现在蚂蚁可信原生团队、阿里云容器团队和阿里云操作系统团队三方正在合力推进 Kata containers 2.0 架构。

回头再聊聊 Kata Containers 和 runC 的关系吧!

Kata Containers和RunC不是替代与被替代的关系,而是青出于蓝胜于蓝。Kata containers把runC一层基于OS虚拟化的隔离机制扩展为三层隔离:

  • Guest OS 虚拟化(等效于runC)
  • 硬件虚拟化
  • Host OS虚拟化

所以Kata Containers通过引入更多的简介层来提升系统的隔离能力,但是要付出更多简介层的代价。

Firecracker-containerd

Firecracker-containerd是Firecracker和Containerd的合体。Firecracker 是AWS基于Google crosvm开发的、配合KVM使用的一个轻量级安全VMM,用以支持实现MicroVM。Firecracker MicroVM同时具备传统虚拟机的安全性和工作负载能力以及容器的速度和资源利用率。Firecracker 具有如下特色:

gpt4生成
KVM

  • KVM 是一种基于 Linux 内核的虚拟化技术。它将 Linux 内核转换成一个类型 2 的虚拟机监控器(Hypervisor)。
  • KVM 利用了 CPU 的硬件虚拟化扩展(如 Intel VT 或 AMD-V),允许直接在硬件级别运行虚拟机,提供了高性能的虚拟化解决方案。
  • 它主要负责 CPU 资源的调度和隔离,以及硬件级别的虚拟化支持。

QEMU

  • QEMU 是一个功能丰富的通用硬件仿真和虚拟化解决方案。它可以用来模拟各种硬件组件,包括 CPU、磁盘、网络接口等。
  • 在不使用 KVM 的情况下,QEMU 能够通过软件仿真来提供虚拟化,但性能较低。
  • 当与 KVM 结合使用时,QEMU 负责模拟其他虚拟硬件设备,如磁盘、网络接口等,而 CPU 虚拟化则由 KVM 在硬件级别处理。

crosvm

crosvm 是一个虚拟机监控器(hypervisor),它用于在 Chrome OS 上运行虚拟机。crosvm 的设计目的是为了提供轻量级的虚拟化支持,特别是为了在 Chromebook 上安全地运行 Linux 应用程序。

VMM

VMM(Virtual Machine Monitor)是虚拟化技术中的一个核心概念,通常也被称为 Hypervisor。它是一种软件层,用于创建和运行虚拟机(VMs),允许多个操作系统同时在单个物理硬件上运行。VMM 提供了对硬件资源的抽象,管理这些资源并在虚拟环境中分配给各个虚拟机。

VMM的类型

  1. Type-1 Hypervisor(裸机 Hypervisor):直接运行在物理硬件上的 VMM。由于没有操作系统层的开销,这类 Hypervisor 通常提供更好的性能和安全性。例如:VMware ESXi,Microsoft Hyper-V,Xen。
  2. Type-2 Hypervisor(宿主型 Hypervisor):运行在传统操作系统之上的 VMM。这种类型的 Hypervisor 适合于开发和测试环境。例如:VMware Workstation,Oracle VirtualBox。
  1. 抛弃QEMU使用的C语言,选择内存安全的rust作为开发语言
  2. 基于crosvm使用极简设备模型,模拟尽可能少的必要设备,减小暴露的攻击面。
  3. 高性能和低开销:得益于极简的设备模型, Firecracker 取消了 SeaBIOS (开源的 X86 BIOS),移除了 PCI 总线,取消了 VGA 显示等等硬件模拟,严格的说它甚至不是一台完整的虚拟计算机。而 Firecracker 运行的 GuestOS 使用的也是 AWS 定制过的精简 Linux 内核,同样裁剪掉了对应的设备驱动程序、子系统等等。因此叫它 MicroVM,其启动步骤和加载项要远远少于传统虚拟机。因此 Firecracker 目前已经能提供小于 125ms 的 MircroVM 启动速度,每秒 150 台的启动能力,小于 5MiB 的内存开销,并发运行 4000 台的极限承载容量(AWS i3.metal EC2 作为宿主机),以及热升级能力等。这些都是传统虚拟机所遥不可及,但现代化弹性工作负载又有强烈需求的性能指标。

但是,Firecracker 毕竟只是一个 VMM,还必须配合上 containerd 才能支持容器生态。所以,AWS 又开源了 firecracker-containerd 项目,用于对接 K8S 生态。本质上 Firecracker-containerd 是另外一个私有化、定制化的 Kata containers,整体架构和 Kata containers 类似,只是放弃了一些兼容性换取更简化的实现。

gVisor

Google gVisor 是 GCP App Engine、Cloud Functions Cloud Run 和 CloudML 中使用的沙箱技术,正式商用名称是 Google Sandbox。Google 意识到在公有云基础设施中运行不受信容器的风险,以及虚拟机沙箱的低效,因此开发了用户空间的内核作为沙箱来运行不受信应用。gVisor 是沿着 libdune 的系统调用拦截思路发展而来的用户态内核或进程虚拟化技术。gVisor 通过拦截所有从应用到主机内核的系统调用,并使用用户空间中 gVisor 的内核实现来处理这些调用。

本质上来说,gVisor 是 VMM 和客户内核的组合,或者说 gVisor 是 syscall 虚拟化。基于 lock-in-pop 理论,假设 Linux 内核被高频访问的syscall是安全的,这部分可以开放给容器进程直接使用而不会导致严重的安全风险;对于冷僻 Linux syscall 则需单独处理,要不就是sentry模拟实现,要不就是在一个专用受限环境中执行(gofer)。sentry 实现了多数的 Linux 系统调用,尤其是内核功能,例如信号分发、内存管理、网络栈以及线程模型。gVisor 和 Nabla 有很相似的策略:保护主机。它们都使用了不到 10%的系统调用来和主机内核通信。gVisor 创建通用内核,而 Nabla 依赖的是 Unikernel,它们都是在用户空间运行特定的客户内核来支持沙箱应用的运行。

gVisor 还在婴儿期,也一样有一些限制。gVisor 要拦截和处理沙箱应用中的系统调用,总要有一定开销,因此不适合系统调用繁重的应用。gVisor 没有直接的硬件访问(透传),所以如果应用需要硬件(例如 GPU)访问,就无法在 gVisor 上运行。最后,gVisor 没有实现所有的系统调用,因此使用了未实现系统调用的应用是无法在 gVisor 上运行的。

出于对使用 C 语言开发系统软件导致的安全风险和软件缺陷的担忧,Google 开创了使用安全语言开发系统软件的先河。2013 年开发的 gVisor 选择了 Golang,2017 年开源的 crosvm 则使用 Rust。从目前状态看,Rust 更适合开发底层系统软件,Golang 则更适合开发上层便应用管理的系统软件。也许 Rust 早成熟几年 gVisor 就会有不同的选择啦!

Nabla Containers

Nabla Containers 的核心思想是用 libOS 作为容器运行时的隔离机制。通过增加一个隔离层,libOS kernel,把容器应用和 host OS给隔离开来。nabla 是继承于 unikernel 的隔离方式,应用采用 rumprun 打包成一个 unikernel 镜像,直接运行在一个专为运行 unikernel 定制虚拟机(ukvm)中。应用直接打包首先可以降低很多内核态和用户态转换的开销,另外通过 ukvm 暴露非常有限的主机上的 syscall(只剩 7 个),可以大大缩小主机的攻击面。它是这些安全容器实现中,最安全的。

这条技术路径从学术理论角度看是很好的路线,但是 libOS 一二十年发展不起来是有原因的。它要求应用打包成 unikernel 镜像,因此和当前 docker 的镜像标准是不兼容的。另外,unikernel 应用在诸如支持创建子进程等一些常规操作上都有很难解决的问题。核心原因是应用侵入性没法解决,需要修改应用来适配容器运行时,和 K8S 部分语义也是冲突。

最近 Nabla containers 项目发生了一个有意思的转变,从采用 rumpkernel 切换到 user model linux。估计背后的原因是 rumpkernel 已经有 6 年没有更新了,背后的 NetBSD 活跃度也不高,Linux compatibility layer 的质量有限。但是,即使切换到 UML 解决部分 syscall ABI 兼容兼容问题,这条路前途也很渺茫。

我的观点是用 libOS 做容器运行时是条死路,我们在这个上面有血的教训!2018 年 5 月加入阿里云就开始 unikernel,基本思路和软件架构和 Nabla 基本一摸一样,rumpkernel + miniOS + ukvm + OCI 接口实现。搞了四五个月之后 rumpkernel + miniOS + ukvm 的核心架构基本 ready,但是当我们计划支持 OCI 时,发现模拟 Linux namespace、支持多进程难度太大,如果一步一步做下去最后就会做成另外一个简化版的 KVM + linux 实现。进一步评估发现解决 OS ABI 兼容或应用改造的代价都很大,所以这条技术路线不具有广泛落地生产的可能性。所以,当 IBM 宣布 Nabla Containers 项目时,我们已经基本放弃这条路线了。

其他容器

Windows容器

受 Windows 操作系统的限制导致 Windows 容器天然有臃肿及生态的问题,所以 Windows 容器一直相对比较小众。但是从软件架构上看,Windows 容器架构反而是一个非常优秀的案例,值得探讨一番。Windows 2016 开始支持 Windows Containers,具有两种形态:Windows Server Containers 和 Hyper-v Containers,其具体架构如下图。

直观的理解,Windows Server Container 相当于 runC,而 Hypver-V Containers 则相当于 Kata Containers。也就是从诞生的一天开始,Windows 就提供了两种不同的 Container Runtime,以提供不同的特性来满足不同场景的需求。

具体到 Windows Server Containers 的架构细节,基本上和 Linux 容器技术类似,没有太多的惊喜。内核支持容器技术的关键子系统名称都是一致的:Control Groups、Namespaces、Layer Capabilities。比较特殊的是 Windows 内核子系统Host Compute Service 实现了 containerd + runc 的能力,也就是说 Windows 内核内置支持容器对象。

为了更好地与 K8S 生态融合,Windows 2019 在 docker 之外增加了对 Containerd/CRI 的支持,以及相关组件 runhcs(run Host Computer Service,对应 Linux runC)。Windows 容器技术整体架构升级为下图的架构。至此,Windows 容器通过支持 OCI 规范基本无缝融入了 K8S 生态。

Windows 容器技术架构中有两个很有意思的组件。一是 Host Compute Service(hcs),这个是专门为容器服务的 service。相对Linux直接暴露支撑容器的底层技术(cgroup、namespace、seccomp 等),Windows 选择了对底层基础技术进行一层封装由内核直接容器暴露容器兑现而不暴露底层技术。阿里云操作系统团队内部也进行过类似的技术探讨,Linux kernel 是否应该内置支持 container object,目前还没结论。二是轻量化的 Hyper-V 虚拟机,Microsoft 基于轻量化的 Hyper-V 虚拟机长出了 Hyper-V container,Windows Subsystem for Linux 2 和 Windows Sandbox 三个技术。最近 Redhat 基于 rust-vmm 搞的 runK 类似 Windows Sandbox 的思路,值得关注。

VMware project Pacific

相对其它家的技术路线,VMware Project Pacific 走了一条与众不同的路线来融入 K8S 生态。Project Pacific 没有采用常规路线实现一个 runP 之类的容器运行时,而是选择了全链路技术改造。Project Pacific 通过 pherelet、CRX 等组件完全重构 kubelet、containerd、runc 等组件,直接对接到 K8S API server。从下图的架构可以看出,Project Pacific 核心是在节点上实现容器和虚拟机的混合部署,同时支持虚拟机管理系统 vCenter 和容器编排系统 K8S。

进一步展开 Project Pacific 节点级的架构细节。为了支持 K8S 容器生态,ESXi 节点上增加了 Spherelet、Image Service、Spherelet Agent 等组件。ESXi 上同时部署了 hostd(相当于 pync + libvirt)和 Spherelet(相当于 Kubelet)两种管控系统以支持容器虚拟机混合部署,同时还为容器专门部署了 Image Service 用以管理容器镜像。

VMware Project Pacific 和 ECI 使用场景和部署形态有很多共通之处,部分设计理念值得借鉴。

Last modification:January 18, 2024
如果觉得我的文章对你有用,请随意赞赏