给 Agent 选沙箱底座,调研一圈下来,所有答案指向同一个名字:Firecracker。AWS Lambda 和 Fargate 的底层引擎是它,E2B、Fly.io、Modal 这些叫得上名字的沙箱产品,底层也几乎全是它。
我花了一段时间读它的官方仓库(37k+ stars,Apache-2.0)和 design、snapshotting 文档原文,这篇是整理后的笔记。读完你应该能回答几个问题:它为什么安全、为什么快、API 长什么样、快照机制怎么工作、哪些坑官方已经替你标出来了。

一、它是什么,不是什么
| 对比 | 是什么 | 不是什么 |
|---|---|---|
| vs QEMU | 只模拟 5 种设备的极简 VMM,约 5 万行 Rust | QEMU 模拟整套 PC 硬件(BIOS、PCI、USB、显卡),数百万行 C |
| vs Docker | 每个实例独立内核,硬件级隔离 | 容器共享宿主内核,namespace 和 cgroup 是同一内核上的障眼法 |
| vs K8s | 单机的「造 VM 引擎」 | 不做调度、不管集群、不管镜像分发 |
关键认知是:Firecracker 刻意只做一件事,在一台机器上把 VM 造得又快又小又安全,其余一概不管。被它「不管」的那些事,正是你自己的系统要补的空间,这个后面详谈。
二、极简主义即安全
传统 VM(QEMU)为兼容性模拟一整台 PC:BIOS 自检、PCI 总线、软驱、VGA 显卡。每一行设备模拟代码都是攻击面,每一秒 BIOS 自检都是启动延迟。
Firecracker 全部砍掉,只留 5 样:
- virtio-net(网卡)
- virtio-block(磁盘)
- vsock(宿主机与 guest 的通信通道)
- 串口控制台(调试用)
- i8042 键盘控制器的一部分(只为了让 guest 能请求重启)
没有 BIOS(内核直接加载),没有 PCI 枚举,没有任何遗留设备。结果:
- 内存开销约 5 MiB/实例(QEMU 是数百 MB)
- 启动约 125 ms(QEMU 是秒级到分钟级)
- guest 能打到的设备模拟代码,只有几千行经过安全审计的 Rust
它的使命原文值得品味:让工作负载同时拥有「硬件虚拟化的安全隔离」和「容器的速度与灵活」。这句话就是整个 microVM 品类的定义。
三、一个 microVM 就是一个进程
官方原文:"Each Firecracker process encapsulates one and only one microVM."
每个 FC 进程内部的线程分工:
firecracker 进程(= 一个沙箱)
├── API 线程 ← 你在 socket 上发的 HTTP 指令由它处理(不在性能快路径上)
├── VMM 线程 ← 设备模拟(网卡/磁盘/vsock),单 epoll 事件循环
└── vCPU 线程 ×N ← 每个 guest CPU 核一个,跑 KVM_RUN 主循环,直接执行 guest 代码
「一 VM 一进程」带来的三样东西都很实在:
- 隔离白得:进程边界是 Linux 最成熟的隔离单位,seccomp、cgroup、namespace 全套直接可用
- 管理白得:上层系统管理沙箱等于管理进程,PID、kill、/proc 监护,都是后端工程师的日常
- 故障隔离白得:一个沙箱的 VMM 崩了就是死了一个进程,邻居无感
官方性能承诺:每核每秒可创建 5 个 microVM(36 核即 180/s),并发数只受硬件资源限制。
四、安全模型:假设每个 guest 都是恶意的
FC 的威胁模型可以这样概括:vCPU 线程一旦跑起来,就当作在执行敌对方的代码来防。防御分四层:
第 1 层:KVM 硬件虚拟化边界 ← guest 内核与宿主内核的墙(CPU 指令级)
第 2 层:seccomp ← FC 进程自身只允许几十个必需 syscall,按线程加载,
在执行 guest 代码前就位
第 3 层:Jailer ← 生产标配:先用特权建好 cgroup/chroot/namespace,
降权后再 exec FC 二进制
第 4 层:速率限制器 ← 磁盘/网络双令牌桶限速,防 noisy neighbor
有一个诚实的边界值得点名:FC 明确不做网络流量过滤。egress 白名单是你的系统要做的事。极简主义的另一面,是知道什么不该自己做。
五、API 面:全部控制手段
FC 启动后在 Unix socket 上开一个 REST API(有 OpenAPI 规范),一共十几条:
- 配置:
/boot-source(内核)、/drives(磁盘)、/network-interfaces(网卡)、/machine-config(vCPU/内存) - 生命周期:
PUT /actions(启动)、PATCH /vm(Paused / Resumed) - 快照:
PUT /snapshot/create、PUT /snapshot/load - 运行时:速率限制、气球驱动(内存回收)、mmds(元数据服务)
上层系统和 FC 的全部关系,就是这十几条 HTTP 调用加进程管理。
六、快照机制:自建系统天天打交道的部分
这是全文重点,create、pause、resume 全部机制都在这里。
创建快照:三步走
PATCH /vm {"state": "Paused"} ← 冻结:vCPU 停、设备停,API 还活着
PUT /snapshot/create {...} ← 落盘:产出两个文件
PATCH /vm {"state": "Resumed"} ← 解冻(或之后直接 kill)
snapshot/create 四个字段:
| 字段 | 取值 | 含义 |
|---|---|---|
snapshot_type |
Full(默认)/ Diff |
全量内存 vs 只存脏页 |
snapshot_path |
文件路径 | vmstate:CPU 寄存器、设备状态(KB 级) |
mem_file_path |
文件路径 | memory:Full 是完整拷贝,Diff 是稀疏文件(只有脏页占盘) |
sync_snapshot_files |
默认 true | 返回前是否 fsync。构建模板必须 true,高频 checkpoint 可考虑 false |
官方原话是暂停后 "the guest memory gets frozen in time"。内存被时间冻结,写盘多久都不影响一致性,所以停机窗口只算暂停本身,不算写盘时间。
恢复快照
硬前提:只能在 boot 之前 load。一个 FC 进程的一生只有两种活法,boot(冷启动)或 load(快照恢复),二选一。
json
PUT /snapshot/load
{
"snapshot_path": ".../vmstate",
"mem_backend": { "backend_type": "File", "backend_path": ".../memory.bin" },
"resume_vm": true,
"track_dirty_pages": true,
"clock_realtime": "restore"
}
字段要点:
backend_type: "File":内核管缺页,即 MAP_PRIVATE mmap。多 VM 内存 COW 共享的官方原生路径,零补丁backend_type: "Uffd":把缺页处理交给你自己的用户态进程(unix socket 通信),缺页时去远端按需拉页的官方接口,同样零补丁resume_vm: true:加载完直接跑,省一条 PATCHtrack_dirty_pages: true:为下一次 Diff 快照开启 KVM 脏页跟踪。注意此配置不随快照保存,每次 load 后要重设clock_realtime: "restore"(x86_64,宿主内核 ≥ 5.16):恢复时自动推进 guest 时钟,省掉「pause 后 TLS 证书过期」这一整类 bug
MAP_PRIVATE 与不可变纪律(最重要的一节)
官方明确:恢复时不做全量读入,而是对内存文件建 MAP_PRIVATE 映射。运行时按需缺页加载,写入走 COW 匿名内存。N 个 FC 进程 mmap 同一个 memory.bin,内核 page cache 只有一份,谁写谁复制单页。单机千级沙箱密度就来自这里。
配套铁律:内存文件在 VM 整个生命周期内必须保留且视为不可变,外部修改会损坏 guest 内存,导致未定义行为。
这条纪律有一个直接推论:沙箱 mmap 着共享模板的 memory.bin 时,不能把脏页 diff 回模板文件(一写就污染所有兄弟)。正确做法是先拷一份私有副本,之后 diff 写自己的文件。工程上把模板目录设为 0444 只读,从机制上杜绝手滑。
Diff 快照:机制、合并、坑
脏页跟踪二选一:
| 方式 | 原理 | 代价 |
|---|---|---|
track_dirty_pages: true |
KVM 脏页日志,硬件级精确 | CPU 开销,官方原话是「抵消大页的大部分好处」 |
| 不开跟踪 | mincore(2) 探测常驻页 |
文件更大(仍是稀疏),且必须禁用 swap |
Diff 文件一般不能直接恢复(例外:对已 boot 的 VM 做的 diff 可以)。恢复 diff 链要先合并:
bash
snapshot-editor edit-memory rebase --memory-path base.bin --diff-path layer1.diff
多层按创建顺序逐层 rebase。vmstate 不能合并,用最后一次 diff 时产出的那个。另外注意,Diff 快照在官方仍是 developer preview 状态,自建系统 v1 别 all-in 它。
克隆安全问题(多沙箱共享快照必读)
官方专门警告:同一个快照恢复 N 次,N 个 VM 出生时是「同一个人」。相同随机数种子、相同唯一 ID、相同密钥。后果是 guest 内 /dev/urandom 产出相同序列,token 可预测,多租户场景这是真漏洞。
缓解措施:
- VMGenID(宿主内核 ≥ 5.18):恢复时内核自动重新播种 PRNG。这是宿主内核版本的硬性检查项
- VMClock 的
vm_generation_counter:用户态程序可感知「我被恢复过」,自行重新生成密钥
还有两条克隆副作用:vsock 连接恢复时被重置(listen 还在,已有连接断开);TCP 连接不保证存活。控制面设计要假设:恢复后的沙箱,所有长连接都需重建。
版本兼容性
vmstate 有版本号:快照不能跨 FC 版本恢复,不能跨 CPU 微架构(Intel 与 AMD 不互通)。推论是模板文件必须与 FC 版本加 CPU 型号绑定管理,模板元数据记录 fc_version 和 cpu_model,恢复前校验。一条字段,省一次「升级 FC 后全站沙箱起不来」的事故。
七、为什么它成了事实标准
- 生产背书:AWS Lambda 每天以它为底座跑海量函数调用,地球上最严苛的多租户不可信代码场景
- 生态位:Kata Containers、Flintlock 用它做容器运行时,E2B、Fly.io、Modal 用它做 Agent 沙箱
- 工程质量:Rust 内存安全,性能回归直接挂 CI,issue 区干净
- 设计克制:API 稳定、设备面冻结、版本政策清晰。基础设施最宝贵的品质是「无聊」,FC 很无聊
八、它留给你的是什么
| FC 做了(别重复造) | FC 故意不做(你的空间) |
|---|---|
| 造 VM、独立内核隔离、快照/恢复、COW mmap、限速、seccomp/jailer | 多 VM 编排、状态机、快照文件管理、netns 分配、egress 策略、模板分发、调度、API 产品化 |
左栏是它八年、AWS 数百工程师年的积累,你一行都不用写。右栏是普通后端工程师几个月的活,那就是你的沙箱系统。
两个边界案例值得点名,它们把「故意不做」画得更清楚。
一是 guest 里没有任何 agent。FC 不提供 guest 内的 exec、文件读写服务,只留一条 vsock 通道给你自己用。这不是缺功能,是刻意的分工:VMM 不进 guest。E2B 的 envd 就是在这个空位里长出来的,后面拆 E2B 时会讲到。
二是扩展要走上游。forkd 为了做运行中分叉(live-fork)需要 MAP_SHARED 内存后端,这是 vanilla FC 唯一缺的机制。它的做法是 vendor 一份 FC 打补丁,同时把改动写成正式 proposal 提给上游。FC 的边界意识传染给了生态:它故意不做的,你要么自己补,要么推动上游,没有第三条含糊的路。
下一篇把镜头拉远,聊聊沙箱技术几十年的降价史,看 Firecracker 在这条主线上站的是什么位置。
智创者社区:apmen.cn