Claude Code、Codex、Gemini CLI 这类编码 Agent 的工作模式是「想一步、跑一步、看结果、再想」:一轮任务可能装依赖、跑测试、起服务,执行几十条命令。AI 从「写代码」走向「执行、验证、迭代代码」之后,执行环境就从脚手架变成了基础设施。这条赛道上,开源世界最火的 E2B 选了一条最重的路——Firecracker microVM(AWS Lambda 底层的虚拟化技术),却把沙箱启动做到了毫秒级。本文拆解它为什么这么做、架构怎么搭、快在哪一层,并对照 Anthropic 自己披露的分层沙箱方案,看沙箱选型的底层逻辑。
一、为什么 Agent 的代码必须关起来跑
传统工具调用——搜索、查库、发邮件——行为可枚举、可审计。代码执行不同,它是图灵完备的:Agent 一旦能执行任意代码,它能做的事和一个程序员坐在终端前完全一样,而你无法预测 LLM 会生成什么。一段看起来无害的「帮我分析这份 CSV」,生成的 Python 里可能就藏着把环境变量打包外传的语句。
威胁模型有三类:
- 提示注入逃逸:恶意网页或文档内容诱使 Agent 执行攻击者代码,读取密钥或对外发起连接;
- 资源滥用:无限循环、fork bomb、写满磁盘,拖垮整台宿主机;
- 横向渗透:读取环境变量里的凭据、访问同机其他租户的数据。
模型层防不住这件事。Anthropic 在 2026 年 5 月的工程博客《How we contain Claude across products》里给过一个真实数字:内部红队用钓鱼让员工把恶意提示贴给 Claude,引导它读取 ~/.aws/credentials 并外传,25 次尝试成功了 24 次——提示词约束与分类器都靠不住,只能靠环境层兜底。这个结论是所有沙箱选型的起点。
由此,Agent 沙箱有三个硬指标:
- 隔离性:默认里面的代码是恶意的,要保证它伤不到宿主机、碰不到其他租户;
- 启动速度:毫秒到秒级——每条命令都等半分钟环境就绪,交互体验直接报废;
- 生命周期弹性:用户聊到一半去吃饭,沙箱要能随时冻结省钱,回来时现场原样恢复,连跑了一半的进程都在。
这三条放在一起,传统方案就被筛掉了大半:Docker 隔离偏弱,传统 VM 启动慢,而且两者都没有原生的「暂停 / 恢复」语义。
二、Docker、gVisor、Firecracker:syscall 走到哪,谁在检查
判断隔离强弱,最干净的视角是:应用的系统调用(syscall)走到哪里、由谁检查、谁接触宿主机内核。以一段 Python 代码 open("/tmp/test.txt", "w") 触发的 open/write 调用为例:
- Docker / runc:容器里的进程本质是宿主机上的 Linux 进程,隔离由内核原语组合而成——namespace 造独立视图,cgroups 限资源,seccomp 过滤危险调用,capabilities 拆分 root 权限。问题在于所有容器共享同一个宿主机内核,内核有任何漏洞就可能容器逃逸。跑「可信的自家服务」可以接受,跑「AI 刚生成的陌生代码」不够。
- gVisor:Google 开源(Cloud Run 在用),核心组件 Sentry 是一个用 Go 写在用户态的「应用内核」,拦截并翻译绝大部分 syscall,宿主机内核只暴露一小撮受限调用。隔离上了一个台阶,代价是兼容性与性能——Linux 语义不可能复刻完整,社区就有 Claude Code 在 gVisor ARM64 下卡死在
epoll_pwait的真实 bug 报告。 - Firecracker:AWS 开源的 microVM 运行时,走硬件虚拟化(CPU 的 VT-x / AMD-V → KVM → Firecracker 作为用户态 VMM)。每台 microVM 有自己独立的 guest 内核,设备模型裁剪到只剩 5 个模拟设备(virtio-net、virtio-block、virtio-vsock、串口控制台、精简键盘控制器),每 VM 内存开销 5 MiB 以内,官方口径启动 125 ms 以内——就算恶意代码在 guest 内核里提了权,面对的攻击面也比完整 Linux 内核小几个数量级。
| 方案 | 隔离机制 | syscall 路径 | 原生启动 | 主要代价 |
|---|---|---|---|---|
| Docker / runc | namespace + cgroup,共享宿主内核 | 应用 → 宿主内核 | 约 10–50ms | 内核漏洞即逃逸口 |
| gVisor | 用户态内核 Sentry 拦截翻译 | 应用 → Sentry → 少量受限调用 | 约 50–100ms | 兼容性与性能损耗 |
| Firecracker | KVM 硬件虚拟化,独立 guest 内核 | 应用 → guest 内核 → VM exit | VMM 125ms 内,端到端约 1s | 需要 KVM 与 root,运维最重 |
规律很直白:隔离越强,原生启动越慢——端到端冷启动(含 guest 内核 boot 与服务就绪)实测约 1 秒,这正是 E2B 要用快照优化掉的部分。
Anthropic 的选择恰好印证了「场景决定方案」:claude.ai 网页端的代码执行跑在 gVisor 临时容器里(多租户、短会话、与容器体系无缝集成);本地 Claude Code 用操作系统自带沙箱——macOS Seatbelt / Linux Bubblewrap,策略是读全放开、工作区可写、默认断网,权限弹窗因此减少了 84%,运行时还以 sandbox-runtime 开了源;Cowork 这种让 AI 长时间无人值守干活的产品则直接上完整虚拟机。威胁模型越接近「恶意租户」,隔离边界越往硬件层走;越接近「手滑的助手」,越留在操作系统层。
三、E2B 架构:控制面与数据面分离
E2B 把整套后端开源(仓库现名 e2b-dev/runtime,原 infra,Go monorepo)。官方架构文档的核心设计是两条:沙箱即恢复的快照——模板是预启动 VM 的内存、磁盘、VM 状态三件套存于对象存储,「创建」沙箱就是恢复快照;控制面与数据面分离——API 决定沙箱在哪运行,各节点的 orchestrator 决定怎么运行,沙箱流量不经过 API。
| 组件 | 跑在哪 | 职责 |
|---|---|---|
| API | API 节点 | 公开 REST:沙箱生命周期、鉴权、配额、调度选点 |
| client-proxy | API 节点 | 无状态边缘路由:解析 3000-i7fa3.域名 这类地址并转发 |
| orchestrator | 每台沙箱节点(root) | 真正驱动 Firecracker:创建 / 暂停 / 恢复 / 销毁,管网络与存储 |
| envd | 每台 VM 内部 | 沙箱内守护进程:进程执行与文件系统 API,SDK 一切操作的最终执行者 |
| template-manager | 构建节点 | 同一二进制的另一角色:把 Docker 镜像烘焙成 VM 快照模板 |
状态存储分工:PostgreSQL 存持久实体(团队、模板、构建、快照记录),Redis 是运行中沙箱的事实来源兼路由表,对象存储按构建 ID 存模板与快照工件,ClickHouse 存事件与指标。
flowchart LR
SDK["SDK / 客户端"] -- 管理流量 --> API["API 控制面"]
API -- 调度选点 --> ORCH["Orchestrator 节点代理"]
ORCH -- 驱动 --> FC["Firecracker microVM"]
FC --> ENVD["envd VM 内守护进程"]
SDK -- 执行流量 --> PROXY["client-proxy 边缘路由"]
PROXY -- 查路由表 --> REDIS[("Redis")]
PROXY -- 直连节点 --> ORCH
API -- 写路由表 --> REDIS
两条流量路径不相交带来的好处很实际:API 挂了不影响正在跑的沙箱,执行流量再大也压不到控制面。路由的关键在 Redis——沙箱创建时 API 挑一台节点并写入「沙箱 ID → 节点 IP」映射,之后每个执行请求到达 client-proxy 都查这张表转发。用 Redis 而不是 Postgres,是因为每个请求都查、QPS 高、数据小,而且允许丢失——节点会定期重新上报自己承载的沙箱来自愈。如果沙箱已被暂停(路由表查不到),client-proxy 会回调 ResumeSandbox 接口先唤醒再重试,暂停对用户完全透明。
VM 内侧同样有一层设计:envd 监听 49983 端口,提供进程服务(启动、stdout/stderr 流、PTY)与文件系统服务(stat、move、watch),SDK 的每行代码最终都落在它身上。它的鉴权令牌由 orchestrator 通过 /init 握手下发,源头是经 Firecracker MMDS(元数据服务)注入 VM 的哈希——沙箱里的代码自己拿不到这个令牌,外面的客户端没有令牌也连不进来。
四、「创建沙箱」其实是「恢复快照」
读源码最有意思的发现是:启动一台 microVM 没有任何黑盒。orchestrator 先用 unshare -m 给独立挂载视图、ip netns exec 给独立网络栈,再 exec 一个 Firecracker 进程放进专属 cgroup;Firecracker 起来后在 Unix socket 上监听官方 REST API,编排器按固定顺序发 9 个 HTTP 请求完成配置并启动:
| # | 请求 | 作用 |
|---|---|---|
| 1 | PUT /metrics |
指标输出到 FIFO |
| 2 | PUT /boot-source |
内核路径与内核启动参数 |
| 3 | PUT /drives |
挂 ext4 根文件系统(经 NBD 的写时复制层) |
| 4 | PUT /network-interfaces |
tap 网卡、MAC 与限速 |
| 5 | PUT /machine-config |
vCPU 与内存规格 |
| 6 | PUT /entropy |
硬件随机数设备 |
| 7 | PUT /balloon |
内存气球,回收空闲页 |
| 8 | PUT /mmds |
元数据:沙箱 ID、访问令牌哈希 |
| 9 | PUT /actions |
InstanceStart,正式启动 |
几十行 shell 加 9 个 HTTP 请求,就是「启动一台沙箱」的全部秘密。这套 API 是 Firecracker 官方公开的,任何人用 curl --unix-socket 都能复现(需要 Linux、KVM 与 root)。
那毫秒级启动从哪来?答案是两段式生命周期:
- 模板构建(离线,慢,只做一次):执行
e2b template build时,构建机上真正冷启动一台 VM,按 Dockerfile 装依赖、启动服务、等就绪探针通过——然后立刻拍快照,把 memfile(内存)、rootfs.ext4(磁盘)、snapfile(VM 状态)三件套存进对象存储。这个阶段要几十秒到几分钟,但发生在任何用户请求之前。 - 创建沙箱(在线,快,做无数次):调用
Sandbox.create()时编排器从快照恢复 VM——guest 内核从被冻结的那条指令继续执行,内核 boot、init、装依赖、起服务全部跳过,因为那些事在拍快照之前就做完了。恢复路径甚至比冷启动更短:rootfs 先指向 /dev/null 拉起 FC 进程,并行等 FC socket、UFFD socket、rootfs 就位,PUT /snapshot/load只登记内存不加载,最后PATCH /vm恢复执行。
类比游戏存档:BOSS 战前存个档,之后每次「开始游戏」都是读档直接站在 BOSS 房门口,没人重新过一遍新手村。E2B 没有让冷启动变快,而是让冷启动只发生一次——毫秒级启动的代价被转移到了离线的模板构建阶段,而构建是可以慢的。官方宣传口径是 150ms 级启动一个全新沙箱,社区实测几十到一两百毫秒不等,取决于模板大小与本地缓存命中。
五、毫秒级启动的三个关键技术
快照是总开关,但「恢复快照」这条路径上还剩两块大头:内存和磁盘。E2B 各用一招解决。
① UFFD:内存按需取页。 恢复时 Firecracker 根本不读内存文件,只把内存区域注册给 Linux 的 userfaultfd 机制;guest 碰到哪一页触发缺页,编排器里的 Go handler 就现场从快照文件里填哪一页,另有可选的预取器按模板构建期记录的「新沙箱必然碰哪些页」清单预热。社区分析估算,512MB 的沙箱启动实际要读的内存可能只有几十 MB。打个比方:冷启动像搬家公司把整个家一次性运来,UFFD 像人先住进去、要用什么让管家送什么——大多数时候你根本用不到全部家当。
② COW rootfs:磁盘写时复制。 模板磁盘只读、被节点上所有沙箱共享;每个沙箱的写入落进私有 COW 缓存,由一个进程内用户态 NBD 服务器拼成完整块设备暴露给 Firecracker。一千个沙箱共享一份 10GB 模板,磁盘成本约等于一份。
③ 暂停是恢复的逆过程,也是弹性的来源。 暂停时 cgroup 冻结全部进程,内存做脏页 diff、磁盘做 COW diff,先落本地缓存再异步上传对象存储(API 与节点共享 80 秒预算)。由此沙箱获得了「无服务器」属性:空闲自动冻结停止计费,流量来了经 client-proxy 自动唤醒;恢复还优先调度回原节点,本地缓存命中可以跳过对象存储读取。用户聊到一半去吃饭,沙箱不烧钱,回来现场原样。
六、隔离的五层叠加与选型边界
回到「默认代码是恶意的」,E2B 的隔离从硬件到应用叠了五层:
| 层 | 机制 | 防什么 |
|---|---|---|
| 虚拟化 | KVM microVM,独立 guest 内核,设备模型裁剪到 5 个 | 逃逸到宿主机或其他租户 |
| 网络 | 每沙箱独立 netns + veth + tap,nftables 出站防火墙(支持按域名黑白名单) | 沙箱互访、扫内网、未授权外联 |
| 资源 | 每沙箱独立 cgroup 限额,网卡与磁盘限速,vCPU 可超卖 | 吵闹邻居、资源耗尽 |
| 认证 | 团队 API key 管控制面;每沙箱独立 access token 经 MMDS 注入 | 跨团队操作、他人连入你的沙箱 |
| 流量 | 节点代理转发前校验 per-sandbox 令牌,文件端点用签名 URL | 猜中沙箱域名就能访问 |
网络那层值得多说一句:出站防火墙不是简单封 80/443,而是按域名黑白名单放行——nftables 规则里做 SNI/Host 检查。这正是 Anthropic 踩坑后的教训:他们最大的一次泄露是恶意文件带着攻击者的 API key,让 Claude 把文件传到攻击者自己的 Anthropic 账户——出口代理看到 api.anthropic.com 就放行了。修法是在 VM 内加防御性中间人代理,只放行携带 VM 自身会话令牌的请求。出站控制比入口拦截更重要,因为数据总是经由「看起来合法」的路径离开。
这条赛道正在快速铺开:Kubernetes SIG 立项了 agent-sandbox 项目(文档直接引用 envd 作数据面守护进程的范式),阿里云 Agent Sandbox 推出了基于 MicroVM 的二代运行时,Modal、Cloudflare 都在为 Claude Managed Agents 提供托管沙箱,Vercel Sandbox 同样采用 Firecracker。社区分析普遍提到 Manus、Perplexity 等产品用 E2B 承载过代码执行——一个 Agent 任务一台完整的虚拟电脑,内含浏览器、终端、文件系统。
选型没有银弹。gVisor 和 Firecracker 各有清晰的适用边界,Anthropic 按产品线混用三种方案就是最好的例证;E2B 的价值在于把 Firecracker 这条最重的路,做成了一套任何人都能读、能部署、能复现的开源实现——编排逻辑全部可读,连它自己定制的 Firecracker 工件都在公共对象存储上开放下载,self-host 不需要自己编译。当执行环境成为 Agent 的标配基础设施,理解这套「控制面 / 数据面分离 + 快照时间挪移 + 写时复制」的组合拳,比记住任何一个产品名词都重要。
参考资料
- E2B Runtime 架构文档(官方,ARCHITECTURE.md)——控制面 / 数据面、UFFD、COW rootfs、网络槽位的一手出处
- How we contain Claude across products(Anthropic 工程博客,2026-05-25)——三层产品线隔离方案与红队数据
- Firecracker 官网——125ms 启动、5 MiB 内存开销、5 个模拟设备的官方口径
- e2b-dev/runtime(E2B 开源仓库)——全部后端源码
- Agent Sandbox:E2B 沙箱源码解析(知乎)——社区源码级分析,9 个 PUT 请求时序出处
- AI Agent 的代码执行沙箱:从容器到微虚拟机的隔离之道(atbug)——六种沙箱方案对比与威胁模型
读者留言
COMMENTS 暂无还没有留言,来说第一句?