Skip to main content

03 - E2B 与快照机制

前置02 篇的 microVM 部分。

本篇回答:microVM 启动要 125 毫秒,为什么托管沙箱能做到"几乎瞬时"?暂停三小时后恢复,里面跑着的进程为什么还在?

本篇会用到的词

意思
快照(snapshot)把一个正在运行的沙箱整体存下来,包括内存和磁盘。恢复出来的是一个「已经跑起来的」环境,不是刚开机的环境
memfile内存快照文件。进程、打开的文件、网络连接状态全在里面 —— 这就是暂停三小时后进程还在的原因
rootfs根文件系统快照,装好的依赖都在这里
按需分页恢复时不把整份内存快照读进来,而是用到哪一页才加载哪一页。所以快照体积大不直接等于恢复慢
envdE2B 跑在沙箱内部的代理服务,宿主通过它执行命令、读写文件,而不是直接伸进沙箱操作
冷启动从零创建一个可用沙箱所花的时间。它是这一层最核心的体验指标 —— 用户等不了 30 秒

一、服务端的组件划分

E2B 把 SDK(e2b-dev/E2B,★13,474)和服务端(e2b-dev/infra,★1,331)分成两个仓库。快照机制在服务端那个仓库里,这是本篇的主要拆解对象。

E2B 基础设施架构

图片来源:e2b-dev/infra readme-assets/,Apache-2.0

packages/ 下的组件:

组件职责
api对外 API
client-proxy客户端流量代理
dashboard-api控制台后端
orchestrator沙箱生命周期与快照,本篇重点
envd跑在沙箱内部的守护进程
nomad-nodepool-apm节点池弹性伸缩

1.1 envd:沙箱内部的那一半

官方对它的描述只有一句:

Daemon that runs inside a sandbox that allows interacting with the sandbox via calls from the SDK.

这个设计决定了沙箱的能力边界。 SDK 调用的不是宿主上的 Docker API,而是沙箱内部的一个进程。这意味着:

  • 执行命令、读写文件、装依赖,都由沙箱内的 envd 完成 —— 宿主不需要有能力"伸进"沙箱
  • envd 有独立的版本号,官方要求「任何影响行为的改动都必须升版本」,因为宿主侧与沙箱内的协议必须匹配
  • 快照恢复时,envd 连同它管理的所有进程一起被恢复

二、冷启动的真正解法:不启动

orchestrator 的命令行工具暴露了核心机制。这几个子命令直接说明了实现路径:

子命令作用
create-build从零构建一个环境模板
resume-build从已有快照恢复
copy-build在本地与 GCS 之间复制快照
mount-build-rootfs挂载快照的根文件系统
inspect-build检查快照头与数据块
diff-build比较两个快照的差异

关键在于 "build"在这里不是镜像,是一份完整的虚拟机状态快照,包含两部分:

memfile   —— 内存快照:进程、打开的文件、网络连接状态,全都在里面
rootfs —— 根文件系统快照

2.1 为什么这比启动快

常规启动 —— 每一步都要重做一遍启动 microVM引导 Guest 内核初始化用户态启动 envd装依赖 pip install …这一步最慢,动辄几十秒就绪快照恢复 —— 上面这些在打快照时已经做完了把 memfile 映射进内存按需分页,不必全量加载挂载 rootfs就绪依赖、进程、打开的文件都已经在快照里了快照存的不只是磁盘,还有内存 —— 进程、打开的文件、网络连接状态全在 memfile 里,所以恢复出来的是一个「已经跑起来的」环境,不是一个刚开机的环境。
关键在「按需分页」:恢复时并不需要把整份内存快照读进来,用到哪一页才加载哪一页,所以快照体积大并不直接等于恢复慢。

装依赖那一步被彻底跳过了 —— 它在制作快照的时候已经做完,结果直接固化在内存镜像里。

这就是托管沙箱与自建容器方案最大的体验差距:自建方案每次都要 pip install,而快照方案里那些包早就在内存中处于「已导入」状态。

2.2 按需分页与预取

resume-build 的两个标志暴露了内存加载策略:

-cold          每次迭代前清空缓存,模拟冷启动
-no-prefetch 关闭内存预取

存在 -no-prefetch 说明默认是开启预取的。 内存快照可能有几百 MB 到几 GB,如果恢复时必须全量读入才能开始执行,就谈不上"瞬时"。

实际做法是按需分页:先映射,访问到哪一页再加载哪一页,同时后台预取可能用到的页。-cold 这个标志的存在则说明官方在认真测量冷热两种情况的差异 —— 这类基准工具通常只有真的在优化这条路径的项目才会写。

三、暂停与恢复:把运行中的进程冻起来

resume-build 支持四种触发快照的方式:

-pause                        启动后立刻快照
-signal-pause <signal> 等待指定信号后快照(如 SIGUSR1)
-cmd-pause <cmd> 执行一条命令,成功后快照
-cmd-signal-pause <cmd> 通过 envd 启动命令,再等 SIGUSR1 后快照

3.1 最后一种是给长任务准备的

官方文档里的例子:

# 通过 envd 在沙箱里启动一个长跑任务,然后从宿主侧触发快照
sudo go run ./cmd/resume-build -from-build $BUILD3 -to-build $BUILD4 \
-storage .local-build -cmd-signal-pause "python3 /home/user/workspace/job.py"

# 在另一个终端,等任务跑到想要的位置时发信号
sudo kill -SIGUSR1 <resume-build-pid>

这实现的是「把一个正在运行的 Python 进程连同它的内存状态一起存下来,之后原地恢复」。

对 Agent 的意义:一个跑了二十分钟、加载了大模型权重或大量数据的分析任务,可以在用户离开时暂停、回来时恢复,不需要重新加载。这与 Agent 持久化执行专题里的检查点是同一类问题的不同层次 —— 那里存的是应用级状态,这里存的是整个虚拟机的物理内存。

3.2 构建链:环境的增量演进

# 每一步都基于上一步的快照,产出一个新快照
sudo go run ./cmd/resume-build -from-build $BUILD1 -to-build $BUILD2 \
-storage .local-build -cmd-pause "apt install curl"

sudo go run ./cmd/resume-build -from-build $BUILD2 -to-build $BUILD3 \
-storage .local-build -cmd-pause "pip install requests"

这是 Docker 分层构建的等价物,但层的内容是内存状态而非文件系统差异

它带来一个 Docker 做不到的能力:可以把「已经 import 好、初始化完成的运行时」作为基础层。下游沙箱恢复后,解释器已经在内存里,连 import 耗时都省掉了。

四、存储与工具链

能力说明
本地 / GCS 双后端-storage .local-build-storage gs://bucket,同一套命令
inspect-build检查 memfile 或 rootfs 的头部、数据块,可只看前 N 块
diff-build比较两个快照的差异,支持可视化
NBD 设备用网络块设备暴露快照,需关闭 inotify 事件监听

diff-build 值得单独说:它让"这一层构建到底改了什么"变成可检查的。自建快照方案时,缺的往往不是快照本身,而是这类调试工具 —— 没有它,一个几 GB 的内存镜像出了问题无从下手。

五、可迁移的三条结论

1. 冷启动优化的终局是不启动。 无论用什么隔离技术,只要还在走「启动 → 初始化 → 装依赖」这条路,就有一个下限。快照把这条路径整个绕过去了。

2. 沙箱内要有一个自己的守护进程。 envd 的存在让宿主不需要具备"伸进沙箱"的能力,这本身就是一层安全收益 —— 宿主与沙箱之间只有一个明确定义的协议接口。

3. 内存快照的代价是存储。 每个快照都是完整的内存镜像,几百 MB 起步。构建链的每一层都要存一份。这与 Agent 持久化执行 · DBOS里的写放大是同一类问题:性能的代价通常落在存储上,不是 CPU 上。

下一篇04 - 开源产品怎么做

← 回到 专题索引  ·  Agent Infra 板块总览