一句话结论:Ubuntu 镜像里根本没有内核。 它不是一个「完整的 Linux 系统」,只是一份 rootfs——打包好的用户态二进制和库文件。容器里的进程发起的每一个 syscall 都直接打到宿主机内核上,namespace 只是给进程一个「看起来像独立系统」的视图,不是真的给它装了一个内核。

而 Kata 和 Firecracker 之所以「有自己的内核」,是因为它们在容器外多加了一层 VM,VM 里需要一份 guest kernel 来承载上面的容器——整个链路里只发生了一次虚拟化,不是嵌套。

本文的机制描述与实测数据核对自 Docker Registry 实际镜像内容、Linux 内核文档与 man pages、glibc 与 kmod 源码、Kata Containers 与 Firecracker 官方文档。核对中发现原稿有十余处需要更正,其中一处是流传很广的机制错误,已在文中以 ⚠️ 标注。


一、把「Linux 系统」拆开看:内核和用户态是两件事

日常说「Ubuntu 22.04」这个概念,其实打包了两个完全独立的东西:

组成部分 具体内容 来源 放在哪里
Linux 内核 vmlinuz(编译好的二进制)、内核模块 .ko、设备驱动、调度器、内存管理、网络栈、syscall 实现 从 kernel.org 源码编译,由发行版打包 宿主机的 /boot/vmlinuz-xxx、/lib/modules/xxx
Ubuntu 用户态 /bin/bash、/bin/ls、/lib/x86_64-linux-gnu/libc.so.6、apt、dpkg、/etc/apt/sources.list、/var/lib/dpkg/status Ubuntu 项目自己构建的二进制 可以放在任何地方,包括一个 Docker 镜像 tar 包里

关键认知:Linux 内核是一个独立项目,跟 Ubuntu 没有从属关系。Ubuntu 只是把 kernel.org 的源码编译一遍、打上自己的补丁、配上自己的默认参数、然后随 ISO 一起发布。「Ubuntu 22.04」这个名字里,内核版本是「搭售」的,不是「Ubuntu 的一部分」——同一个内核完全可以搭载 Debian/Fedora/Arch 的用户态跑起来。


二、Ubuntu 镜像里到底有什么、没什么

做几个实验就能验证(下面是在 ubuntu:22.04 上实测的结果):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 找内核
docker run --rm ubuntu:22.04 ls -la /boot
# total 8
# drwxr-xr-x 2 root root 4096 Apr 18 2022 .
# drwxr-xr-x 1 root root 4096 Nov 6 07:45 ..

# 2. 找内核模块
docker run --rm ubuntu:22.04 ls /lib/modules
# ls: cannot access '/lib/modules': No such file or directory

# 3. 加载模块
docker run --rm ubuntu:22.04 bash -c "apt-get install -y kmod && modprobe dummy"
# modprobe: FATAL: Module dummy not found in directory /lib/modules/<宿主内核版本>

# 4. 看内核版本
docker run --rm ubuntu:22.04 uname -r
# 6.10.14-linuxkit ← 这是宿主(Docker Desktop 的 LinuxKit VM)的内核版本

⚠️ 一处措辞要精确:原稿写「没有 /boot」。实测 /boot 是存在的,只是空目录(0 字节内容)。真正的差别在于:

路径 状态
/boot 存在,但为空
/lib/modules 不存在(find -name '*.ko*' 计数为 0)
/lib/firmware 不存在(连设备固件都没有)
vmlinuz* / initrd* / System.map* / config-* 均无

镜像里共 101 个 dpkg 包,其中没有任何 linux-image-* / linux-headers-* / linux-modules-*。

uname -r 显示宿主机内核版本,这件事是本质证据。 uname 命令本身是镜像里的二进制(用户态),但它调用的 uname() syscall 被宿主内核响应,内核把自己的版本号报回去。镜像里没有任何内核可读,uname 只能问运行它的那个内核。

镜像里实际打包的是:

  • /bin、/usr/bin 下的二进制(bash、ls、apt)
  • /lib、/usr/lib 下的共享库(最核心的是 libc.so.6,即 glibc)
  • /etc 下的配置文件(/etc/passwd、/etc/apt/sources.list、/etc/resolv.conf)
  • /var/lib/dpkg 下的包数据库

⚠️ 「Ubuntu 22.04 官方 ISO 里的内核是 5.15」这句话要加限定。用官方 ISO 的 .manifest 逐版本核对:

版本 Desktop ISO(HWE 滚动) Live-Server ISO(GA)
22.04 / 22.04.1 5.15.0-25 / -43 5.15.0-25 / -43
22.04.2 5.19.0-32 5.15.0-60
22.04.3 6.2.0-26 5.15.0-78
22.04.4 6.5.0-18 5.15.0-94
22.04.5 6.8.0-40 5.15.0-119

Ubuntu 官方 jammy 发布说明原文:“Ubuntu Server defaults to a non-rolling LTS kernel v5.15”、“The rolling HWE kernel is based on the v5.15 kernel for 22…”。

所以「22.04 是 5.15」只对 GA 栈和 Server ISO 成立;22.04.2 起的桌面 ISO 是 HWE 内核。这一点在拿 ISO 版本做对比时容易踩坑。


三、syscall 路径:进程为什么「借」宿主机内核用

容器里的进程看起来有自己独立的文件系统、PID 空间、网络栈,但这一切都是视图隔离,不是真的有独立内核。一次 syscall 的完整路径:

1
2
3
4
5
6
7
8
9
[容器里] 应用调用 printf()
↓
[镜像里的] glibc 把 printf 翻译成 write(1, "Hello", 5) syscall
↓ CPU 切换到内核态
[宿主机 Linux 内核] 处理 write syscall
↓ 内核看这个进程属于哪个 namespace
[内核根据 namespace 信息] 把 fd=1 映射到正确的 tty、应用正确的 quota/cgroup 限制
↓
[内核] 把数据写到真正的硬件或终端

关键在于:syscall 接口是 Linux 内核的稳定 ABI,它不关心调用方是「宿主机自己的进程」还是「容器里的进程」。内核只看到「某个 PID 发起 write syscall,这个 PID 在 pid namespace X、net namespace Y、mount namespace Z 里,cgroup 是 cgroup W」——然后按规则处理。

这部分路径分析(以及容器 / microVM / VM 三者的 syscall 代价差异)在 从冯·诺依曼到 Agent 沙箱 的第三节已经展开过,这里不重复。

namespace 的作用是让同一个内核对不同的进程组呈现不同的视图:

namespace 隔离什么 man page 原文
PID 让容器里的进程以为自己是 PID 1 “PIDs in a new PID namespace start at 1, somewhat like a standalone system”
Mount 让容器看到自己的 rootfs “will see distinct single-directory hierarchies”
Network 独立网卡、IP、路由表 “isolation of the system resources associated with networking: network devices, … IP routing tables, firewall rules, …”
User 让容器 UID 0 映射到宿主非特权 UID “a process can have a normal unprivileged user ID outside a user namespace while at the same time having a user ID of 0 inside the namespace”

⚠️ 但 user namespace 那条要加一个重要限定:Docker 默认不启用它。 实测容器内 /proc/self/uid_map 是恒等映射:

1
2
$ docker run --rm ubuntu:22.04 sh -c 'cat /proc/self/uid_map'
0 0 4294967295

0 0 4294967295 表示容器内 UID 0 = 宿主 UID 0,没有做重映射。Docker 的 userns-remap 是显式开启的 opt-in 功能,官方文档原文:“If you enable user namespaces on the daemon, all containers are started with user namespaces enabled by default”。

也就是说「容器 root 就是宿主 root」才是默认行为——这正是容器逃逸风险的根本原因,也是为什么「不可信代码要上 microVM」这个判断成立。

所有这些 namespace 的实现逻辑都在宿主机内核里。 namespaces(7) 开篇即:“One use of namespaces is to implement containers.” 创建 namespace 用的是内核系统调用 clone(2) / unshare(2)(CLONE_NEWPID / CLONE_NEWNS / CLONE_NEWNET / CLONE_NEWUSER)——容器运行时(runc 等)只是发起这些 syscall,实现全在内核。


四、为什么跨发行版能跑:syscall ABI 的稳定性

从 Docker Hub 拉 centos:7 或 rockylinux:8 跑在一个 Arch Linux 宿主机上,完全没问题。原因是 syscall ABI 极其稳定。

⚠️ Linus 那句名言的原文要引准。流传的版本是「we don’t break user space」,原文是全大写——LKML,2012-12-23,主题 Re: [Regression w/ patch] Media commit causes user space to misbahave:

If a change results in user programs breaking, it’s a bug in the kernel. We never EVER blame the user programs.
WE DO NOT BREAK USERSPACE!

内核官方文档 stable-api-nonsense.rst 也把这写成正式规则:

The kernel to userspace interface is the one that application programs use, the syscall interface. That interface is very stable over time, and will not break.

注意这里有个容易混淆的地方:稳定的只是 kernel→userspace 的 syscall 接口,内核内部的 API 明确不保证稳定(这也是「stable-api-nonsense」这个名字的由来)。

实际的兼容矩阵是:容器里的 glibc 版本 ↔ 宿主机内核版本。

  • 老镜像跑在新内核上 → 通常没问题(内核向前兼容旧 syscall);
  • 新镜像跑在老内核上 → 取决于新 glibc 用了哪些新 syscall。

4.1 关于 clone3:一个流传很广的错误归因

⚠️ 原稿在这里搞错了机制。它写「新 glibc 用了 clone3 syscall,老内核(CentOS 7 的 3.10)不认识,就失败」。前半段对,后半段的因果错了。

先把事实摆清楚:

事实 值
clone3 引入版本 Linux 5.3(man page HISTORY 段)
CentOS 7 内核 3.10(仓库实包 kernel-3.10.0-1160.el7)
glibc 开始用 clone3 2.34(2021-08 发布,新增 clone-internal.c)

但 glibc 对「老内核不认识」这件事是有回退的。 glibc 2.34 的 clone-internal.c:

1
2
3
4
5
/* Try clone3 first.  */
ret = __clone3 (cl_args, sizeof (*cl_args), func, arg);
if (ret != -1 || errno != ENOSYS)
return ret;
/* ... 回退到 clone ... */

即 glibc 只在拿到 ENOSYS(和后来的 EINVAL)时才回退。而老内核(< 5.3)对 clone3 返回的正是 ENOSYS——所以老内核不会导致失败,会正常回退到 clone()。

真正的事故是 seccomp 返回 EPERM。 Docker 的默认 seccomp 策略把 clone3 拦掉、返回 EPERM,而 glibc 不把 EPERM 当作可回退错误 → 直接致命失败。

最权威的记录是 moby/moby issue #42680(2021-07-27):

The latest glibc now attempts to use ‘clone3’ by default. For backwards compatibility it will look for ENOSYS errno and fallback to ‘clone’. The EPERM errno meanwhile is treated as a fatal error. The default seccomp filter installed by docker is causing EPERM and so this breaks the glibc fallback.

报错现场:

1
2
clone3({flags=CLONE_VM|CLONE_FS|...|CLONE_THREAD|...}, 88) = -1 EPERM (Operation not permitted)
curl: (6) getaddrinfo() thread failed to start

而且这些事故恰恰发生在新内核上(Fedora Rawhide、openSUSE Tumbleweed、RHEL 9 Beta)——这本身就证明问题不在「老内核」。

修复在 Docker 侧:PR #42681 把默认 seccomp 策略改成「当无 CAP_SYS_ADMIN 时让 clone3 返回 ENOSYS 而不是 EPERM」,从而让 glibc 的回退生效:

1
{ "names": ["clone3"], "action": "SCMP_ACT_ERRNO", "excludes": {"caps": ["CAP_SYS_ADMIN"]} }

结论:跨发行版的兼容性是 syscall 兼容性,不是发行版兼容性。但要注意——syscall 之外的中介层(seccomp 过滤器、能力集)会人为改变可见的 syscall 集合,从而破坏可移植性。clone3 这个案例的教训正在于此。

4.2 兼容性的边界:镜像不能「自带」任何需要内核侧配合的东西

命令 能跑吗 为什么
ip ✅ 用的是宿主机内核的网络栈
iptables ✅ 操作的是宿主机的 netfilter
systemd ✅ 但需要专门配置(以 systemd 作 PID 1、处理 cgroup 挂载)
systemctl poweroff ❌ 见下
modprobe ❌ 见下

modprobe 为什么失败:报错是

1
modprobe: FATAL: Module dummy not found in directory /lib/modules/<宿主内核版本>

这个字符串来自 kmod 源码 tools/modprobe.c(LOG("Module %s not found in directory %s\n", ...),log_priority = LOG_CRIT 渲染成 FATAL:),目录名来自 kmod_get_dirname = /lib/modules/$(uname -r)。

在容器里先命中「找不到模块文件」——/lib/modules 目录压根不存在,modprobe 在「查模块文件」阶段就失败了,走不到「加载」那一步。

权限是下一道关:即便把模块文件塞进去,加载模块需要 CAP_SYS_MODULE(实测默认容器 CapEff 不含),且 Docker 默认 seccomp 对 init_module/finit_module 只在有该能力时才放行——那时会变成 EPERM。两个原因都会导致失败,但原稿给的这条报错对应的是「找不到文件」而非「没有权限」。

systemctl poweroff 为什么关不掉机器:两道机制都拦着。

  1. 权限:reboot(2) man page:“EPERM — The calling process has insufficient privilege to call reboot(); the caller must have the CAP_SYS_BOOT inside its user namespace.” 默认容器没有 CAP_SYS_BOOT。
  2. 语义(更根本):“Since Linux 3.4, if reboot() is called from a PID namespace other than the initial PID namespace … it performs a ‘reboot’ of that namespace: the ‘init’ process of the PID namespace is immediately terminated.”

也就是说,容器里调 LINUX_REBOOT_CMD_POWER_OFF 只会终止本 namespace 的 init(即停掉这个容器),不会关掉宿主机。容器不是一台机器,poweroff 最多把自己干掉。


五、容器 vs Kata vs VM:栈的真正差异

组成层 Docker 容器 Kata 容器 虚拟机
应用 ✅(来自镜像) ✅(来自镜像) ✅
用户态二进制 + 库 ✅(来自镜像) ✅(来自镜像) ✅(来自 guest OS)
init / systemd ⚠️ 可选,通常无(PID 1 是 entrypoint) ✅(kata-agent 或 systemd) ✅
内核 ❌ 没有,借用宿主机的 ✅ 有,Kata 自己的精简 Linux ✅ 有,guest kernel
内核模块 ❌ 无 ✅ 有 ✅ 有
隔离机制 namespace + cgroups VM 硬件虚拟化 + namespace VM 硬件虚拟化
启动对象 一个进程 一个 VM(含 guest kernel)+ 容器 一个 VM
启动时间 毫秒级 见下方更正 秒级
内存 overhead 数 MB 量级(无权威精确值) 数十~数百 MiB,取决于 VMM 几十 MB 起
镜像大小 ≈28.4 MiB(ubuntu:22.04 压缩后) 更大(含 guest 内核与 mini-OS rootfs) GB 级

⚠️ Kata 的内存开销那格,原稿写错了。它写「<5 MiB(VMM)+ guest kernel」——<= 5 MiB 是 Firecracker 的数字,被误用到了 Kata 上。

Firecracker SPECIFICATION.md 原文:“Firecracker’s virtual machine manager threads have a memory overhead <= 5 MiB. The memory overhead is dependent on the workload … and on the VMM configuration”(连 Firecracker 自己都强调这数字依赖负载与配置)。

Kata 官方从未给过这个数。 Kata 的默认 VMM 是 QEMU / Cloud Hypervisor / Dragonball,官方 docs/design/virtualization.md 只给定性排序:QEMU = Medium、Cloud Hypervisor = Low、Firecracker = Lowest、Dragonball = Lowest——QEMU 与 CLH 的 footprint 远大于 5 MiB。

官方给出的实测量级是百 MiB 级(模板方案每 VM 约 178.2 MiB vs 223.2 MiB),而且 how-to-size-sandbox-overhead-runtime-rs.md 明确说:overhead 取决于 “the hypervisor in use (each has a different memory/CPU footprint)”,且 “Kata cannot ship a single correct value for this function”,需实测调优。

⚠️ Kata 的启动时间也没有官方数字。原稿写「~150-300ms」——Kata 官方仓库、文档站、4.0 release blog 都没有这个数字,它来自一家第三方厂商博客。Kata 4.0 的 release blog 只定性写 “Lower latency and faster startup times”,无数字。

官方唯一带数字的地方反而更慢:how-to-use-template-in-runtime-rs.md 给的是「0.6s vs 0.82s」(模板方案带来的约 73.2% 降低),即官方口径的 VM 启动在 600—820ms 量级。

Docker 容器省掉的就是「内核」这一层。 这也是为什么它启动是毫秒级(没有内核 boot 流程)、内存 overhead 只有数 MB(没有内核常驻内存)、镜像只有几十 MB(没有 vmlinuz 和模块)。


六、「内核套内核」的误解:栈长 ≠ 嵌套

这是最容易被搞混的一点。看完整栈就清楚了:

1
2
3
4
5
6
7
[宿主机 Linux 内核](启用 KVM 模块)
↓ 用户态调用 KVM ioctl
[Firecracker / Cloud Hypervisor / QEMU](只是一个用户态进程)
↓ 通过 KVM 创建 VM
[guest Linux 内核](第一次也是唯一一次虚拟化)
↓ guest 内核上跑 namespace/cgroups
[容器 workload]

整个过程只发生了一次虚拟化:宿主机内核的 KVM 模块把 CPU/内存虚拟化给 guest 内核用。guest 内核起来之后跑容器,走的是 namespace/cgroups——这是容器,不是又一次虚拟化。

真正的「内核套内核」(nested virtualization)长这样:

1
2
3
4
Host kernel (KVM)
→ VM A (第一层 guest kernel + KVM)
→ VM B (第二层 guest kernel)
→ 应用

这才需要嵌套虚拟化支持,性能损耗也大。

栈长 ≠ 虚拟化嵌套。 Kata 看起来栈很长(containerd → runtime → VMM → guest kernel → agent → container),但其中只有 VMM → guest kernel 这一段是虚拟化,前面是编排逻辑,后面是容器机制。

Kata 官方文档也印证了这一点:“Requirements: Kata requires nested virtualization or bare metal.”——宿主是裸机时不需要嵌套虚拟化,因为 Kata 的 VM 就是唯一一层硬件虚拟化。

唯一的例外:如果宿主机本身就是一个云 VM(比如在 AWS EC2 上跑 Kata),那才会出现真正的嵌套,需要云厂商在该实例类型上开启 nested virtualization。AWS 官方给了具体清单(M7i / C7i / R7i / I7i 等系列,实例级 CpuOptions NestedVirtualization=enabled)。注意 .metal 实例是裸机,本身不需要嵌套虚拟化。


七、Kata 与 Firecracker 的内核来源:谁自带、谁不自带

主体 是否「自带」guest 内核 内核来源
Kata Containers ✅ 自带,随包分发 基于 Linux LTS 精简编译,配套 mini-OS rootfs
Firecracker ❌ 二进制本身不含 但官方分发预编译 CI 内核(见下方更正)
QEMU / Cloud Hypervisor ❌ 不自带 用户提供

Kata 确实自带内核。 官方原文:

Kata Containers is Apache 2 licensed software consisting of two main components: the Kata agent, and the Kata Containerd shim v2 runtime. It also packages a Linux kernel and versions of QEMU, Cloud Hypervisor and Firecracker hypervisors.

The default kernel provided in Kata Containers is highly optimized for kernel boot time and minimal memory footprint, providing only those services required by a container workload. It is based on the latest Linux LTS kernel.

打包后位于 /opt/kata/share/kata-containers/,内核文件形如 vmlinuz.container / vmlinux.container,rootfs 镜像为 kata-containers.img。

而且**「mini-OS rootfs」就是 Kata 自己的叫法**:“The default packaged rootfs image, sometimes referred to as the mini O/S, is a highly optimized container bootstrap system.”

⚠️ Firecracker 那条只说了一半,而且末句不属实。

原稿写「Firecracker 不自带内核,官方文档教你 git clone torvalds/linux 自己编译……它不维护、不分发内核镜像」。

  • 自编译那条路径确实存在,但不在 getting-started 主文档里——它在 docs/rootfs-and-kernel-setup.md。
  • getting-started 主文档实际是让你下载官方预编译内核:“This guide uses the latest kernel image and Ubuntu rootfs available in our CI… S3="https://s3.amazonaws.com/spec.ccfc.min"”
  • 「不分发内核镜像」不属实:官方 S3 桶 firecracker-ci/ 里长期存放官方构建的 vmlinux(最新一批含 vmlinux-5.10.271、6.1.188、6.18.56),kernel-policy.md 还明确对特定内核版本提供至少 2 年的支持周期,并通过 Amazon Linux 提供 microVM 专用内核。

准确说法是:Firecracker 二进制本身不含内核,但项目维护内核支持策略、并分发官方预编译的 CI 内核。

另外「Firecracker 只做四件事」也是过度简化——README 列出的 API 能力远不止四件(vCPU/内存配置、网络接口、块设备、限速器、vsock、entropy、pmem、内存热插拔、快照、MMDS……)。


八、回到最初的问题

为什么 Ubuntu 镜像可以「看起来像 Ubuntu」却不需要内核?

因为「像 Ubuntu」只取决于用户态二进制——apt 能用、/etc/os-release 写着 Ubuntu、ls 输出长得像 Ubuntu。而 syscall 永远打到宿主机内核上,宿主机内核不关心上面跑的是 Ubuntu、Debian、Alpine 还是裸 bash,它只认 syscall 号和进程的 namespace 属性。

这也解释了一个非常实际的现象:容器镜像无法「自带内核更新」。实测在 ubuntu:22.04 里跑 apt-get -s upgrade,只会列出 3 个包(apt / libapt-pkg / libpcre2),没有任何 linux-image-*——因为镜像里根本没装内核包,也没有任何包把内核列为依赖,apt 根本不会去碰内核。

(补充一句:用户当然可以手动 apt install linux-image-generic,apt 会把内核文件装进 /boot 和 /lib/modules——但装了也不会被使用,因为容器跑的是宿主内核,uname -r 不会变。所以更准确的说法是「装了也不用」。)

⚠️ 但「宿主机一升级所有容器立刻用上新内核」这句不严谨。常规内核升级要重启宿主机才生效——新内核在下次启动时才加载;不重启的话旧内核仍在内存里跑,所有容器继续用旧内核。

唯一能「不重启就打内核补丁」的是 livepatch(内核官方文档:“fixing critical functions without a system reboot”),但它是特例、只覆盖部分函数。

所以准确的说法是:宿主升级并重启后,所有容器才用上新内核(或走 livepatch 免重启,但覆盖有限)。这个性质既是容器部署的一大优势(内核安全修复一次到位),也是一大坑(内核行为变了,所有容器可能同时受影响)。

一句话总结:

镜像 ≠ 系统。镜像 = rootfs = 一份用户态。系统 = rootfs + 内核。容器只有前一半,所以它必须跟宿主机共享后一半——这不是「巧妙的设计」,这是 Linux namespace 机制天然的工作方式,Docker 只是把这个机制用到了极致。

而当你需要把「不可信代码」和宿主机内核彻底隔开时,Kata / Firecracker 这类 VM-based 方案的意义就浮现了——用一份精简 guest kernel 重新划分边界,把 Linux 内核那 4000 万行代码的攻击面挡在 VM 层之外。

✅ 「4000 万行」这个量级是对的:Linux 内核源码在 6.14-rc1(2025-01)突破 4000 万行,7.3-rc1(2026-08)约 4100 万行,其中约 60% 是 drivers。


附录 A:Kata 的隔离模型——微内核与 namespace 是分层协作,不是二选一

一个常见的误解是「Kata 有微内核了,namespace 就不重要了」。实际上两者是纵深防御的两层。

第一层:微内核构建的「硬」边界

Kata 引入独立内核的核心目的,是解决传统容器(runc)共享宿主内核带来的安全风险——在共享内核模型下,任何一个容器中的内核提权漏洞都可能危及整个宿主机。

Kata 官方对此的定位非常明确,三处文档都用了同一表述:

…provide stronger workload isolation using hardware virtualization technology as a second layer of defence.

Kata Containers creates a second layer of isolation on top of traditional namespace-based containers using hardware virtualization.

Kata Containers provides a second layer of isolation on top of those provided by traditional-containers.

第二层:namespace 实现的「软」隔离与容器语义

微内核解决了「谁和谁之间需要一堵墙」,但在墙的内部,要让应用感觉自己运行在一个独立的 Linux 环境中,依然需要 namespace。Kata 的架构是 Hypervisor → 客户机内核 → 容器进程,容器进程运行在客户机内核之上。

namespace 在 Kata 内部承担三个不可或缺的功能:

  1. 实现容器的视图隔离 —— PID namespace 让容器内进程认为自己拥有独立 PID 空间;Mount namespace 提供独立 rootfs 视图;Network namespace 赋予独立网络栈。没有这些,微虚拟机内部跑的只是一个普通进程,而不是符合 OCI 标准的「容器」。
  2. Pod 内容器的隔离 —— Kata 以 Pod 为边界创建微虚拟机,官方原文:“the sandbox is carried out at the pod level”、“The shimv2 architecture allows running several containers per VM”。当一个 Pod 内运行多个容器(如 Sidecar 模式)时,它们共享同一个客户机内核,必须依靠 namespace 和 cgroups 隔离:“Linux cgroups and namespaces are created inside the VM by the guest kernel to isolate the workload from the VM environment.”
  3. 维持生态兼容性 —— 使 Kata 容器能无缝接入 Kubernetes 与 containerd 生态,满足 CRI 和 OCI 标准。

一句话概括:

微内核负责「主机 vs 租户」的强安全边界,namespace 负责「租户内部」的容器资源视图隔离。 微内核取代的是主机内核在隔离中的角色,但并没有、也无法取代客户机内核内部 namespace 的作用。

一个补充细节

kata-agent 在 VM 内,官方原文:“written in the Rust programming language, is a long running process that runs inside the VM. It acts as the supervisor for managing the containers and the workload running within those containers. Only a single agent process is run for each VM.”,与宿主机通过 ttRPC over VSOCK 通信。


附录 B:内核相关术语速查

术语 一句话定义
rootfs 文件系统根目录的内容,即一份用户态,不含内核
vmlinuz 编译好的 Linux 内核二进制镜像
syscall 用户态进程请求内核服务的唯一入口,Linux 的稳定 ABI
glibc GNU C 库,用户态进程与内核之间的标准 syscall 包装层
namespace 内核提供的资源视图隔离机制(PID / Mount / Net / User 等)
cgroups 内核提供的资源限额与统计机制
KVM Linux 内核内置的虚拟化模块,让宿主内核能创建 VM
VMM Virtual Machine Monitor,用 KVM 创建 VM 的用户态程序(QEMU / Firecracker 等)
guest kernel VM 里运行的内核,与宿主机内核独立
nested virtualization VM 里再跑 VM,需要两层 KVM
kata-agent 运行在 Kata VM 内的进程,负责在 guest 里创建容器并与宿主机通信

相关阅读

核实说明:本文的镜像内容与实测命令在 ubuntu:22.04(Docker 27.4.0)上真实执行;内核机制核对自 Linux man pages(namespaces / pid_namespaces / mount_namespaces / network_namespaces / user_namespaces / clone / reboot / modprobe)与内核文档(stable-api-nonsense、handling-regressions、livepatch);clone3 事故核对自 moby/moby issue #42680、PR #42681、issue #42876、Red Hat Bug 1985525 与 glibc 2.34 源码 clone-internal.c;Ubuntu 内核版本核对自 releases.ubuntu.com 的各版本 .manifest;Kata 与 Firecracker 核对自各自官方仓库文档。

原稿更正汇总:clone3 的归因错了(glibc 对 ENOSYS 有回退,老内核不会失败;真实事故是 seccomp 返回 EPERM,且发生在新内核上)、/boot 是存在的空目录(不是「没有」)、「Ubuntu 22.04 ISO 是 5.15」对 22.04.2+ 桌面 ISO 不成立(HWE 内核)、Linus 原话是全大写 WE DO NOT BREAK USERSPACE!(LKML 2012-12-23)、默认 Docker 不启用 user namespace(uid_map 是恒等映射)、Kata 的内存开销 <5 MiB 是把 Firecracker 的数字用错了(Kata 官方量级是数十~数百 MiB,且官方明说「cannot ship a single correct value」)、Kata 启动 150-300ms 无官方来源(官方口径约 0.6—0.82s)、Firecracker 确实分发预编译内核(S3 桶 + 至少 2 年支持周期)、「宿主升级后容器立刻用新内核」需重启才生效(livepatch 例外)、modprobe 失败的报错对应「找不到文件」而非「没有权限」(权限是下一道关)。