基于 containerd 与 Firecracker 的 MicroVM 容器技术实践
目录
- 引言与背景
- 技术架构全景
- 前置条件与环境搭建
- 核心组件深度剖析
- 工作流程与数据流详解
- 通信机制:vsock 技术内幕
- 总结与展望
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
| lsmod | grep kvm
ls -la /dev/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" 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
| git clone https://github.com/firecracker-microvm/firecracker-containerd.git cd firecracker-containerd
make sudo make install
|
3.3.3 生成 Guest 镜像
1 2 3
| 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 (虚拟机监视器)
位置:宿主机上运行的用户空间进程。
核心机制:
- 打开
/dev/kvm 设备文件
- 通过
ioctl 系统调用创建 VM 实例
- 为每个 vCPU 创建独立的线程
- 分配 Guest 内存(通过 KVM 的
KVM_SET_USER_MEMORY_REGION)
- 加载内核镜像(
vmlinux)到 Guest 内存
- 配置虚拟设备(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 进程。
核心职责:
- 生命周期管理:通过
runc 在 Guest 内部创建、启动、停止容器
- I/O 代理:将容器的
stdout/stderr 通过 vsock 转发给宿主机 Shim
- 事件上报:将容器状态变化实时上报给宿主机
- 存储管理:处理块设备的挂载(通过 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 核心结论
-
nerdctl --runtime aws.firecracker 并非直接启动 MicroVM,而是通过 containerd + firecracker-containerd 技术栈实现。
-
完整调用链:
1 2 3
| nerdctl → containerd API → Firecracker 控制插件 → Firecracker Shim → Firecracker VMM → KVM → Guest Kernel → Agent → runc → 业务容器
|
-
本质对比:
- 标准容器:
containerd → runc
- MicroVM:
containerd → microVM → guest kernel → agent → runc
-
隔离层级:硬件虚拟化 (KVM) 提供了比 Namespace/Cgroups 更强的安全边界。
-
通信机制:vsock 作为 Guest-Host 通信通道,通过三个层次(协议标准、虚拟设备、VMM 桥接)实现高效、安全的控制平面。
7.2 适用场景
| 场景 |
推荐方案 |
| 多租户环境 |
MicroVM (Firecracker) ✅ |
| 高安全要求 |
MicroVM (Firecracker) ✅ |
| 轻量、高密度 |
标准容器 (runc) ✅ |
| 快速启动 |
标准容器 (runc) ✅ |
| 运行不受信代码 |
MicroVM (Firecracker) ✅ |
| 与 k8s 集成 |
两者均可 (通过 containerd) |
7.3 未来发展方向
- 性能优化:持续降低 MicroVM 的启动延迟和内存开销
- Kubernetes 集成:通过 containerd 的 CRI 插件支持 Pod 级别隔离
- 更多 VMM 支持:Cloud Hypervisor, QEMU 等
- 镜像加速:Nydus, Dragonfly 等 P2P 分发技术
- 可观测性:增强 MicroVM 层面的监控和日志能力
附录
A. 常用命令参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| firecracker --version
nerdctl run --runtime aws.firecracker -it alpine:latest sh
ps aux | grep firecracker
containerd namespaces ls
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 的技术探索实践整理而成,旨在为容器与虚拟化技术爱好者提供一份系统性的技术参考。