基于 containerd 与 Firecracker 的 MicroVM 容器技术实践

目录

  1. 引言与背景
  2. 技术架构全景
  3. 前置条件与环境搭建
  4. 核心组件深度剖析
  5. 工作流程与数据流详解
  6. 通信机制:vsock 技术内幕
  7. 总结与展望

1. 引言与背景

1.1 容器安全隔离的演进

传统的 Linux 容器(如 runc)依赖 Namespace 和 Cgroups 实现进程级隔离,共享宿主机内核。这种模型在追求轻量和高密度的同时,也带来了潜在的安全风险——恶意容器可能通过内核漏洞影响宿主机或其他容器。

MicroVM 技术的出现为容器安全提供了硬件级别的隔离方案。Firecracker 作为 AWS 开源的高性能 VMM(Virtual Machine Monitor),专为轻量级 MicroVM 设计,可在毫秒级启动时间下提供完整的硬件虚拟化隔离。

1.2 项目背景

firecracker-containerd 项目由 AWS 主导,旨在将 Firecracker 的强大隔离能力与 containerd 的成熟容器管理生态相结合,实现"像管理容器一样管理 MicroVM"的愿景。

1.3 关键问题

本报告要回答的核心问题:使用 nerdctl --runtime aws.firecracker 启动 MicroVM 的背后机制是什么?整个技术栈如何协同工作?


2. 技术架构全景

2.1 总体拓扑结构

下图展示了标准容器路径与 MicroVM 容器路径的完整调用链对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ 用户操作层 (User CLI) │
│ │
│ 标准容器路径: nerdctl run ... │
│ MicroVM路径: nerdctl run --runtime aws.firecracker ... │
└──────────────────────────────┬──────────────────────────────────────────────────────┘
│ (通过 Containerd API 通信)

┌─────────────────────────────────────────────────────────────────────────────────────┐
│ 容器运行时管理层 (Containerd Daemon) │
│ │
│ ┌─────────────────────────────┐ ┌────────────────────────────────────┐ │
│ │ 标准容器控制插件 │ │ Firecracker 控制插件 (Plugin) │ │
│ │ (Container Plugin) │ │ (管理 VM 生命周期、快照) │ │
│ └──────────┬──────────────────┘ └──────────────┬─────────────────────┘ │
└───────────────┼──────────────────────────────────────────┼──────────────────────────┘
│ │
│ (创建 Shim 进程) │ (创建 Shim 进程)
▼ ▼
┌───────────────────────────────┐ ┌─────────────────────────────────────────────┐
│ 标准 Shim │ │ Firecracker Shim │
│ containerd-shim-runc-v2 │ │ containerd-shim-aws-firecracker-v1 │
│ (每个容器一个进程) │ │ (每个 MicroVM 一个进程) │
└───────────────┬───────────────┘ └──────────────┬──────────────────────────────┘
│ │
│ (execve 调用) │ (通过 REST API / CLI 参数启动)
▼ ▼
┌───────────────────────────────┐ ┌─────────────────────────────────────────────┐
│ OCI 运行时 (runc) │ │ 虚拟机监视器 (VMM) │
│ 负责创建 Namespace/Cgroups │ │ Firecracker 进程 │
│ │ │ (作为普通用户进程运行在宿主机上) │
└───────────────┬───────────────┘ └──────────────┬──────────────────────────────┘
│ │
│ (通过 clone/unshare) │ (通过 KVM API 创建 VM)
▼ ▼
┌───────────────────────────────┐ ┌─────────────────────────────────────────────┐
│ 宿主机内核空间 │ │ 硬件虚拟化层 (KVM) │
│ (Linux Kernel) │ │ /dev/kvm 设备接口 │
│ Namespaces: │ │ 负责分配内存、VCPUs、硬件中断 │
│ - Mount, Net, PID, UTS... │ └──────────────┬──────────────────────────────┘
│ Cgroups: 资源限制 │ │
└───────────────┬───────────────┘ │ (VM Enter/Exit 硬件切换)
│ ▼
│ ┌─────────────────────────────────────────┐
│ │ 客户机内核空间 (Guest Kernel) │
│ │ 独立的 Linux Kernel (与宿主机隔离) │
│ │ 加载 vmlinux 镜像启动 │
│ └──────────────┬──────────────────────────┘
│ │
│ │ (启动 init 进程)
│ ▼
│ ┌─────────────────────────────────────┐
│ │ Guest 内部 Agent 进程 │
│ │ (firecracker-agent) │
│ │ 通过 vsock 与宿主机 Shim 通信 │
│ └──────────────┬──────────────────────┘
│ │
│ │ (调用 Guest 内的 OCI 运行时)
│ ▼
│ ┌─────────────────────────────────────┐
│ │ Guest 内部的 runc (或 youki) │
│ │ 在 Guest 内创建 Namespace/Cgroups │
└───────────────┬───────────────┴─────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────────────────────┐
│ 最终业务容器进程 (User Container Process) │
│ │
│ 标准路径: PID 在宿主机进程空间,直接使用宿主机内核。 │
│ MicroVM 路径: PID 在 Guest 进程空间,使用 Guest 内核,宿主机看到的是 Firecracker │
│ 进程 (VMM) 和 kvm 线程。 │
└─────────────────────────────────────────────────────────────────────────────────────┘

2.2 路径对比总结

层级 标准容器 (runc) MicroVM (Firecracker)
客户端 nerdctl run nerdctl run --runtime aws.firecracker
管理平面 containerd + 默认插件 containerd + Firecracker Control Plugin
中介进程 containerd-shim-runc-v2 containerd-shim-aws-firecracker-v1
执行引擎 runc (二进制) Firecracker VMM (调用 KVM)
隔离边界 宿主机内核 (命名空间) 硬件虚拟化 (KVM) + 独立 Guest 内核
内部代理 不需要 firecracker-agent (监听 vsock)
内部执行器 (无,直接由宿主机 runc 执行) Guest 内部的 runc
最终进程 宿主机上的进程 (PID 可见) Guest 内部的进程 (宿主机不可见)

2.3 核心本质

用户的理解完全正确:

  • 标准路径:containerd → runc
  • MicroVM 路径:containerd → microVM (硬件层) → guest kernel → agent → runc

3. 前置条件与环境搭建

3.1 硬件与操作系统要求

条件 说明
操作系统 Linux 系统(推荐 Ubuntu 20.04+ / Amazon Linux 2)
CPU 架构 x86_64 或 ARM64
虚拟化支持 CPU 必须支持硬件虚拟化(Intel VT-x / AMD-V)
KVM 模块 Linux 内核需加载 KVM 模块,并确保用户对 /dev/kvm 有读写权限

验证命令:

1
2
3
4
5
6
7
8
9
# 检查 KVM 是否加载
lsmod | grep kvm

# 检查 /dev/kvm 权限
ls -la /dev/kvm
# 应显示 crw-rw---- 1 root kvm 10, 232

# 将当前用户加入 kvm 组
sudo usermod -aG kvm $USER

3.2 软件组件清单

组件 角色 来源
Firecracker VMM 二进制文件 GitHub Releases / 自行编译
containerd 容器运行时(含 Firecracker 控制插件) firecracker-containerd 构建版本
firecracker-containerd runtime Shim 运行时 firecracker-containerd 项目
nerdctl containerd CLI 工具 GitHub Releases
vmlinux Guest 内核镜像 预编译或自行构建
rootfs.ext4 根文件系统 使用 rootfs-builder 构建
CNI 插件 网络配置 containernetworking/plugins

3.3 关键安装步骤

3.3.1 下载 Firecracker 二进制文件

1
2
3
4
5
6
7
8
9
# 获取最新版本
RELEASE="v1.4.0"
ARCH="x86_64" # 或 aarch64
wget "https://github.com/firecracker-microvm/firecracker/releases/download/${RELEASE}/firecracker-${RELEASE}-${ARCH}"
wget "https://github.com/firecracker-microvm/firecracker/releases/download/${RELEASE}/firecracker-${RELEASE}-${ARCH}.tgz.sha512.txt"

# 安装到系统路径
sudo mv firecracker-${RELEASE}-${ARCH} /usr/local/bin/firecracker
sudo chmod +x /usr/local/bin/firecracker

3.3.2 安装 containerd (带 Firecracker 支持)

1
2
3
4
5
6
7
# 推荐使用 firecracker-containerd 项目提供的构建版本
git clone https://github.com/firecracker-microvm/firecracker-containerd.git
cd firecracker-containerd

# 构建 containerd + firecracker 控制插件
make
sudo make install

3.3.3 生成 Guest 镜像

1
2
3
# 使用 rootfs-builder 构建 rootfs
cd tools/rootfs-builder
./rootfs-builder.sh -t ubuntu

3.4 配置文件示例

containerd 配置 (/etc/containerd/config.toml):

1
2
3
4
5
6
7
8
9
10
11
12
13
version = 2

[plugins."io.containerd.runtime.v1.linux"]
shim = "containerd-shim-aws-firecracker-v1"
runtime = "aws.firecracker"

[plugins."io.containerd.internal.v1.opt"]
path = "/var/lib/containerd/opt"

[plugins."io.containerd.snapshotter.v1.devmapper"]
root_path = "/var/lib/containerd/io.containerd.snapshotter.v1.devmapper"
pool_name = "fc-thinpool"
base_image_size = "10GB"

4. 核心组件深度剖析

4.1 Firecracker 控制插件 (Control Plugin)

位置:嵌入在 containerd 二进制文件中。

职责:

  • 接收 containerd 的容器管理请求(创建、启动、删除等)
  • 管理与 Firecracker MicroVM 相关的元数据
  • 调用 Firecracker Shim 创建和管理 VM 生命周期

4.2 Firecracker Shim (containerd-shim-aws-firecracker-v1)

位置:独立的二进制进程,位于 /usr/local/bin/

职责:

  • 作为 containerd 与 Firecracker VMM 之间的桥梁
  • 每个 MicroVM 对应一个 Shim 进程(与标准容器的 Shim 设计一致)
  • 管理 VMM 进程的生命周期
  • 通过 vsock 与 Guest 内的 Agent 通信

启动方式:

  • 由控制插件通过 exec 启动
  • 传递 Firecracker API socket 路径、内核参数、网络配置等

4.3 Firecracker VMM (虚拟机监视器)

位置:宿主机上运行的用户空间进程。

核心机制:

  1. 打开 /dev/kvm 设备文件
  2. 通过 ioctl 系统调用创建 VM 实例
  3. 为每个 vCPU 创建独立的线程
  4. 分配 Guest 内存(通过 KVM 的 KVM_SET_USER_MEMORY_REGION)
  5. 加载内核镜像(vmlinux)到 Guest 内存
  6. 配置虚拟设备(virtio-net, virtio-blk, virtio-vsock)

vCPU 线程状态:

  • 当 Guest OS 空闲时,vCPU 线程处于睡眠状态
  • 当 Guest 需要执行指令时,vCPU 线程通过 KVM_RUN ioctl 进入 VM 模式
  • VM Exit 时返回用户空间处理 I/O 请求

4.4 Guest Agent (firecracker-agent)

位置:运行在 MicroVM 内部,作为 PID 1 进程。

核心职责:

  1. 生命周期管理:通过 runc 在 Guest 内部创建、启动、停止容器
  2. I/O 代理:将容器的 stdout/stderr 通过 vsock 转发给宿主机 Shim
  3. 事件上报:将容器状态变化实时上报给宿主机
  4. 存储管理:处理块设备的挂载(通过 virtio-blk)

启动流程:

  • Guest 内核启动 → 执行 /init (即 Agent)
  • Agent 初始化 vsock 监听
  • 等待宿主机 Shim 的连接和指令

4.5 vsock 通信通道

Agent 与 Shim 之间的通信方式:

方向 协议 作用
宿主机 → Guest ttrpc over vsock 下发容器管理指令
Guest → 宿主机 ttrpc over vsock 返回执行结果、I/O 流、事件

ttrpc 协议:

  • 轻量级 RPC 协议,专为低资源环境设计
  • 基于 Protocol Buffers 序列化
  • 支持双向流式通信

5. 工作流程与数据流详解

5.1 完整事件流(从命令到容器运行)

当用户执行 nerdctl run --runtime aws.firecracker ... 时,后台发生以下事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[步骤 1] 命令解析
nerdctl 解析命令行参数,构建 containerd API 请求。

[步骤 2] API 路由
containerd 接收请求,根据 --runtime 参数路由到 Firecracker 控制插件。

[步骤 3] 启动 Shim
控制插件启动 containerd-shim-aws-firecracker-v1 进程。
传入参数:VM ID、内核镜像路径、rootfs 路径、网络配置。

[步骤 4] 启动 VMM
Shim 通过 REST API 或命令行参数启动 Firecracker VMM 进程。
Firecracker 打开 /dev/kvm,创建 VM 实例。

[步骤 5] 硬件初始化
分配 Guest 内存,创建 vCPU 线程。
加载 vmlinux 内核镜像到 Guest 内存。
启动 vCPU,Guest 开始引导。

[步骤 6] Guest 启动
Guest 内核初始化,执行 /init (firecracker-agent)。
Agent 打开 vsock 设备,开始监听宿主机连接。

[步骤 7] 建立通信通道
Shim 通过 Unix Domain Socket 与 Firecracker VMM 交互。
VMM 通过 virtio-vsock 设备将数据路由到 Agent。
(实际上,Shim 连接的是 VMM 暴露的 UNIX socket,VMM 内部将其映射到 vsock)

[步骤 8] 容器创建
Shim 通过 vsock 向 Agent 下发 CreateContainer 请求。
Agent 调用 Guest 内的 runc 创建容器:
- 解析 OCI spec
- 创建 Namespace (PID, NET, MNT, UTS, IPC)
- 挂载 rootfs
- 配置 Cgroups (在 Guest 内)

[步骤 9] 容器启动
Agent 通过 runc 启动容器进程。
容器进程在 Guest 的隔离环境中运行。

[步骤 10] I/O 流处理
容器的 stdout/stderr 被 Agent 捕获。
通过 vsock 发回给 Shim。
Shim 将输出转发给 nerdctl (最终显示在用户终端)。

5.2 关键时序图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
User       nerdctl     containerd   Control     Shim      Firecracker    Guest      runc
| | | Plugin | VMM Agent |
|--run------>| | | | | | |
| |--Create---->| | | | | |
| | |--Route--->| | | | |
| | | |--Start--->| | | |
| | | | |--exec---->| | |
| | | | | |--KVM_API-->| |
| | | | | | (create VM) |
| | | | | |--load------>| |
| | | | | | (vmlinux) | |
| | | | | |--boot----->| |
| | | | | | |--init--> |
| | | | |<--UNIX--- | | |
| | | | | socket | | |
| | | | |---vsock--(virtio)------>| |
| | | | |--CreateContainer-------->| |
| | | | | | |--exec--> |
| | | | | | | runc |
| | | | |<--stdout-----------------| |
|<--output---| | | | | | |

6. 通信机制:vsock 技术内幕

6.1 vsock 的存在形式

vsock 并非单一的组件,而是贯穿整个技术栈的一套标准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
┌─────────────────────────────────────────────────────────────────┐
│ 宿主机 (Host) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 宿主机程序 (Shim) │ │
│ │ 使用 AF_UNIX 套接字(Unix Domain Socket 文件) │ │
│ └────────────────────┬─────────────────────────────────────┘ │
│ │ 文件 I/O │
│ ┌────────────────────┴─────────────────────────────────────┐ │
│ │ Firecracker VMM 进程 │ │
│ │ 扮演"翻译桥梁"角色: │ │
│ │ - 宿主机端:监听 AF_UNIX 套接字 │ │
│ │ - 客户机端:模拟 virtio-vsock 设备 │ │
│ └────────────────────┬─────────────────────────────────────┘ │
│ │ virtio vsock packet │
│ ┌────────────────────┴─────────────────────────────────────┐ │
│ │ KVM 硬件虚拟化层 │ │
│ └────────────────────┬─────────────────────────────────────┘ │
└───────────────────────┼─────────────────────────────────────────┘
│ VM Exit / Enter (硬件切换)
┌───────────────────────┼─────────────────────────────────────────┐
│ │ │
│ ┌────────────────────┴─────────────────────────────────────┐ │
│ │ 客户机 (Guest) │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ virtio-vsock PCI 设备 │ │ │
│ │ │ 半虚拟化设备,通过 virtqueue 与 VMM 交换数据 │ │ │
│ │ └────────────────────┬────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────┴────────────────────────────┐ │ │
│ │ │ Guest 内核 vsock 协议栈 │ │ │
│ │ │ 实现 AF_VSOCK 套接字地址族 │ │ │
│ │ └────────────────────┬────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────┴────────────────────────────┐ │ │
│ │ │ Guest 程序 (Agent) │ │ │
│ │ │ 使用 AF_VSOCK 套接字进行通信 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

6.2 三个层次详细说明

层次 1:标准化通信协议 (AF_VSOCK)

  • 定义:Linux 内核中的套接字地址族,类似于 AF_INET(TCP/IP)
  • API:支持标准的 socket(), connect(), bind(), listen(), send(), recv() 系统调用
  • 寻址方式:(CID, Port) 二元组
    • CID (Context Identifier):32 位整数,标识通信端点
      • 约定:宿主机 CID = 2,每个 MicroVM 分配唯一 CID
    • Port:标识同一端点上的不同服务

示例代码 (Guest Agent 端):

1
2
3
4
5
6
7
int sock = socket(AF_VSOCK, SOCK_STREAM, 0);
struct sockaddr_vm addr = {
.svm_family = AF_VSOCK,
.svm_cid = VMADDR_CID_HOST, // 连接宿主机
.svm_port = 12345
};
connect(sock, (struct sockaddr*)&addr, sizeof(addr));

层次 2:虚拟硬件设备 (virtio-vsock)

  • 设备类型:半虚拟化 PCI 设备
  • 数据结构:三个环形缓冲区 (virtqueue)
    • 接收队列 (RX):VMM → Guest
    • 发送队列 (TX):Guest → VMM
    • 事件队列 (Event):用于通知
  • 数据包:vsock packet (包含 header + payload)

层次 3:Firecracker 的 “混合” 实现

宿主机端:

  • Firecracker VMM 创建 AF_VSOCK 套接字
  • 改为创建 AF_UNIX 套接字(文件系统上的 socket 文件)
  • Shim 进程通过读写这个文件与 VMM 通信

客户机端:

  • 使用标准的 AF_VSOCK 套接字
  • 通过 virtio-vsock 设备与 VMM 交换数据

VMM 的角色:翻译桥接器

  • 一端:Unix Domain Socket (宿主机)
  • 一端:virtio-vsock 设备 (客户机)
  • 在两套不同的通信机制间透明中继数据

6.3 为什么选择 vsock?

对比项 vsock TCP/IP (virtio-net) AF_UNIX (仅在宿主机)
Guest ↔ Host 通信 ✅ 原生支持 ✅ 需要网络配置 ❌ 不支持跨 VM
无需 IP 地址 ✅ 基于 CID ❌ 需要分配 IP
无需网络协议栈 ✅ 轻量级 ❌ 完整 TCP/IP 栈
安全隔离 ✅ 独立于网络 ⚠️ 暴露在网络上
性能 ✅ 低延迟 ⚠️ 有网络开销
配置复杂度 ✅ 简单 ❌ 需要 CNI 配置

结论:vsock 是 MicroVM 控制通信的理想选择,提供高效、安全、简单的 Guest-Host 通信通道。


7. 总结与展望

7.1 核心结论

  1. nerdctl --runtime aws.firecracker 并非直接启动 MicroVM,而是通过 containerd + firecracker-containerd 技术栈实现。

  2. 完整调用链:

    1
    2
    3
    nerdctl → containerd API → Firecracker 控制插件 → 
    Firecracker Shim → Firecracker VMM → KVM →
    Guest Kernel → Agent → runc → 业务容器
  3. 本质对比:

    • 标准容器:containerd → runc
    • MicroVM:containerd → microVM → guest kernel → agent → runc
  4. 隔离层级:硬件虚拟化 (KVM) 提供了比 Namespace/Cgroups 更强的安全边界。

  5. 通信机制:vsock 作为 Guest-Host 通信通道,通过三个层次(协议标准、虚拟设备、VMM 桥接)实现高效、安全的控制平面。

7.2 适用场景

场景 推荐方案
多租户环境 MicroVM (Firecracker) ✅
高安全要求 MicroVM (Firecracker) ✅
轻量、高密度 标准容器 (runc) ✅
快速启动 标准容器 (runc) ✅
运行不受信代码 MicroVM (Firecracker) ✅
与 k8s 集成 两者均可 (通过 containerd)

7.3 未来发展方向

  1. 性能优化:持续降低 MicroVM 的启动延迟和内存开销
  2. Kubernetes 集成:通过 containerd 的 CRI 插件支持 Pod 级别隔离
  3. 更多 VMM 支持:Cloud Hypervisor, QEMU 等
  4. 镜像加速:Nydus, Dragonfly 等 P2P 分发技术
  5. 可观测性:增强 MicroVM 层面的监控和日志能力

附录

A. 常用命令参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 检查 Firecracker 是否安装
firecracker --version

# 启动 MicroVM 容器
nerdctl run --runtime aws.firecracker -it alpine:latest sh

# 查看 MicroVM 进程
ps aux | grep firecracker

# 查看 containerd 运行时列表
containerd namespaces ls

# 查看 containerd 运行时配置
ctr plugins ls | grep firecracker

B. 常见问题排查

问题 可能原因 解决方案
open /dev/kvm: permission denied 用户不在 kvm 组 sudo usermod -aG kvm $USER
runtime "aws.firecracker" not found containerd 未正确配置 检查 /etc/containerd/config.toml
Failed to start VM 内核镜像或 rootfs 路径错误 检查文件路径和权限
Agent timeout vsock 通信超时 检查 Guest 镜像是否正确包含 Agent

C. 参考资源


本文档基于 nerdctl --runtime aws.firecracker 的技术探索实践整理而成,旨在为容器与虚拟化技术爱好者提供一份系统性的技术参考。