文章转载于:个性化沙盒会是每个人的第三只手 - 王焱的文章 - 知乎 作者:王焱
链接:https://zhuanlan.zhihu.com/p/1978863528287966982
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
沙盒现在是 agent 必不可缺的一部分,沙盒加大模型可以理论上模拟人类所有的操作。 并且随着沙盒技术的不断迭代,还会创造出新的玩法。
现在每个人已经把个人云盘视作理所当然的事情。但二十年前,云盘刚出现的时候,很多人会质疑,我有移动硬盘了,这个的需求真的那么大么?
同理,未来每个人也会有一个自己独属的沙盒,你在这个沙盒上的所有操作都会持久化的保存起来。 未来每个人都会有一个私人沙盒,像你的第三只手一样。
这里对比 docker 和 microVM,分析两种技术的差异。 通过对比,告诉大家为什么 microVM 更好,会更适配这种技术方案。
除了本地沙盒这个特例外,常见沙盒技术方案有两种,docker 和 microVM(firecracker)。 docker 来自于当年大家开发完软件后,想要实现一次打包,满世界部署的需求。 microVM 来自于 AWS 的内部虚拟机需求。既要硬件级隔离(安全),又要容器级敏捷(百毫秒、几 MB),产物就是这个“极薄”的 VMM——Firecracker。 microVM 出发点跟 Agent 的沙盒需求更接近,理论上更适配 Agent 的沙盒需求。 事实上也是如此,我们内部做实验和探索的时候,经过分析,初阶需求,docker 可以的,microVM 一定可以。但高阶需求,microVM 可以的,docker 往往就比较难了。 本篇文章会做一个详细的展开和探讨,为什么 microVM 的上限更高。
接下来四篇的 Agent 文章。 基于 dify 和 n8n 的对比,什么才是一个好的 workflow 框架? 基于 openhands/openmanus/suna 等的对比,什么才是一个好的自主决策 agent 框架? Agent 时代的产品经理需要新增什么技能?像做自媒体一样做 Agent。 Agent 智能硬件开发的流程和坑点。 写完有一段时间了,也会改改放出来。
这篇文章是团队成员——不喝可乐 完成的,经过了多轮的思考和讨论。
这并不是技术,而是迫切的需求,在计算机系统中,隔离要解决三个核心问题:安全、资源、环境
不同时代的隔离技术,侧重点不同:
核心问题 :在多任务操作系统中,如果不隔离进程会发生什么呢?
历史教训:MS-DOS 时代的混乱 在 1981-1995 年的 MS-DOS 时代,操作系统没有进程隔离 。这导致了三大灾难:
a. 内存冲突:任何程序都能访问任意内存地址 比如,游戏程序的 bug 可能覆盖文档编辑器的数据,就会导致文件损坏,数据丢失 b. 系统崩溃:一个程序的错误会导致整个系统死机 c. 安全漏洞:病毒可以直接读写任何程序的内容,严重会导致密码,隐私信息的丢失
技术突破:虚拟内存的诞生 为了解决这些问题,现代操作系统引入了虚拟内存 机制
核心原理: a. 每个进程看到的是虚拟地址 ,而不是真实的物理地址 b. CPU 的 MMU(内存管理单元)自动转换地址 c. 操作系统维护页表 ,控制访问权限
实际效果: 进程 A:虚拟地址 0x1000 → 物理地址 0x5000 进程 B:虚拟地址 0x1000 → 物理地址 0x8000 两个进程都以为自己在用 0x1000,但实际在不同位置!
进程隔离的局限性:无法隔离环境 虽然虚拟内存解决了内存安全问题,但所有进程仍然共享同一个操作系统环境 。这导致了新的问题: 局限性 1:依赖冲突,机器上比如有 2 个 Python 项目,用到两个版本的 Py,问题是系统只能安装一个版本,指向一个版本,这就会导致启动时库的冲突。 局限性 2:环境不一致,代码可能在开发环境正常运行,但是部署出去,可能会出现各种 Bug,比如遇到系统库不兼容,依赖版本不匹配
所以其实本质上,进程隔离(虚拟内存)仅仅是解决了内存安全,进程独立,但其他功能依然是共享的,并没有解决环境问题,这就是为什么需要容器技术(Docker)
隔离技术的演进,从本质上来讲,是需求驱动的,不同时代的计算场景,对隔离的需求完全不一样
时代背景:云计算和微服务的兴起 基于进程隔离的局限性,这里就导入了 Docker 的核心理念了,打包整个环境 Docker 的官方定义:一个用于开发、发布和运行应用程序的开发平台。Docker 可以让你把应用和基础设施分开,因此可以进行快速交付。
什么是容器? 容器是针对应用每个组件的隔离进程。每个组件都运行在独立的环境中,完全与机器上的其他东西进行隔离;无论什么应用,均可打包成统一的镜像,可以在开发,测试,生产环境无缝运行
容器的优势呢? 每个容器包含运行所需的一切:代码、运行时、系统工具、库,不依赖主机上的预装依赖;容器隔离运行,对主机和其他容器影响最小,有一定的安全性;每个容器独立管理,拥有自己的生命周期,删除并不会影响其他容器;容器可以在任何地方运行,开发机器上的容器在数据中心或云端以相同方式工作。
Docker 的技术实现 底层技术:Linux Namespace + Cgroups Docker 通过 Linux 内核的两大特性实现隔离: Namespace :隔离进程、网络、文件系统、主机名等(7 种隔离) Cgroups :限制 CPU、内存、磁盘 I/O 等资源使用 就是为什么 Docker 能实现"环境隔离",但仍然"共享内核"。
Docker 的局限性呢? 核心问题:所有容器均共享宿主主机内核 这就会有一个比较严重的问题,若内核有漏洞,所有容器均会受影响,恶意容器可能通过内核漏洞逃逸到宿主机,那么在多租户的场景下,就会大大展现安全性的不足。 小结:Docker 完美的解决了 "环境" 问题,但是在"安全"方面还不够。特别是在 Serverless、AI Agent 等需要运行不可信代码的场景。这就是为什么 E2B 这个沙盒没有用 Docker 作为底层,而是使用 Firecracker。
时代背景:不可信代码的挑战 全新的应用场景
核心特点:需要运行用户提交的任意代码;这个需求中,就需要完全保证多租户隔离,恶意代码维护,硬件级别隔离来防护
那么,Docker 为什么就显得不够用了呢? 回顾 1.2.1 的结论:
AWS 的真实困境:AWS Lambda 需要在同一台物理机器上运行数千上万个不同客户的函数,若使用 Docker,这其实是做不到的,安全性必须放在首要地位,这是不可被替代的,同时,在商业上,也是完全不可被接受的,所以需要虚拟机级别的隔离 ,但同时又需要保持容器级别的速度。
Firecracker 的核心理念:安全 + 速度 Firecracker 是一个开源的虚拟化技术,专为创建和管理安全、多租户的容器和函数服务而设计的 技术实现: 核心技术:KVM + 极简化
硬件虚拟化:基于 KVM (kernel Virtual Machine) 创建 micro VM
最小化攻击面:移除不必要的设备和功能,减少安全风险
资源隔离:独立的 vCPU、内存、网络、存储
系统调用过滤:使用 seccomp 限制可用的系统调用
Jailer 进程:通过 cgroup、namespace、chroot 进一步隔离
核心对比:
| 指标 | 数值 | 对比 Docker |
|---|---|---|
| 启动时间 | ≤ 50ms | Docker: 几秒 |
| 创建速率 | 5 microVMs/核/秒 | 36 核可达 180 VMs/秒 |
| 内存开销 | < 5MB(VMM 进程) | Docker: 几 MB |
| 隔离级别 | 硬件级(独立内核) | Docker: 进程级(共享内核) |
总结: E2B 为什么选择 Firecracker? 关于这点,我们需要知道 E2B 的设计核心理念,在后续章节会有详细介绍
| 特性 | 指标 | 说明 |
|---|---|---|
| 启动速度 | < 90ms | 从代码到执行的沙盒创建时间 |
| 隔离性 | 分离且隔离的运行时 | 零风险执行 AI 生成的代码 |
| 持久化 | 无限持久化 | 沙盒可以永久存活 |
| 兼容性 | OCI/Docker 兼容 | 可使用任何 Docker 镜像 |
| 并行化 | 大规模并行 | 支持并发 AI 工作流 |
为什么选择 Docker? 从 Daytona 的代码和定位可以看到,Docker 是 AI Agent 时代代码沙盒的首选方案,其实理由很简单:
场景 1:AI Agent 代码执行
场景 2:CI/CD 流水线
Docker 容器是 AI Agent 时代代码沙盒的首选方案,但这有且仅限于:
但对于多租户,不可信代码的场景,Docker 的安全性就暴露无疑 接下来,我们以 Daytona 为例子,深入分析 Daytona 沙盒的实现细节
核心架构(三层理解)
核心职责:
核心职责:
核心职责:
text1. 用户调用SDK daytona.create(language="python") ↓ 2. SDK发送HTTP请求到API Service POST /api/v1/sandbox ↓ 3. API Service验证权限和配额 - 检查API Key - 验证CPU/内存配额 ↓ 4. API Service选择合适的Runner - 根据Region、Class、Snapshot选择 - 优先选择有镜像缓存的Runner ↓ 5. API Service转发请求给Runner POST /runner/sandbox/create ↓ 6. Runner调用Docker API - 拉取镜像(如果本地没有) - 创建容器配置 - 创建并启动容器 - 注入Daemon ↓ 7. Runner返回容器ID给API Service ↓ 8. API Service更新数据库状态 状态:创建中 → 运行中 ↓ 9. SDK轮询状态,等待完成 ↓ 10. 返回沙盒对象给用户
问题: Docker 容器启动虽然快(几秒),但是对于用户来说,其实还是不够看的,还是慢!! 解决方案:预热容器池(Warm Pool) 工作原理:系统启动时,与创建 N 个容器
textdocker run -d --name warm-1 python:3.11 sleep infinity
用户请求到来,从池中取出一个容器,注入用户配置和代码,立即可用 用户结束后,清理容器,创建新的预热容器补充池 效果:启动时间:可能从原来的 5s 降到了 90 ms,这里用户就可以体验到一个无感的启动效率了 这就不得不提起 WarmPoolService 的职责了,它是维护预热容器池,监控池的大小(最大/最小容器),根据使用情况动态调整池大小,定期清理长时间未使用的容器 但,这又需要提出来一个致命问题,Warm Pool 只解决了速度上的问题,并没有解决安全上的问题,接下来,我们继续看 Daytona 是如何加固 Docker 容器的安全性的
从代码角度出发:
texthostConfig := &container.HostConfig{ Privileged: true, // ⚠️ 特权模式 Resources: container.Resources{ CPUPeriod: 100000, CPUQuota: sandboxDto.CpuQuota * 100000, // CPU限制 Memory: sandboxDto.MemoryQuota * 1024 * 1024 * 1024, // 内存限制 MemorySwap: sandboxDto.MemoryQuota * 1024 * 1024 * 1024, // Swap限制 }, StorageOpt: map[string]string{ "size": fmt.Sprintf("%dG", sandboxDto.StorageQuota), // 磁盘限制 },
我们可以看到,这里有严格的 CPU、内存、磁盘限制,并且是 Privileged,接下来,我们从这些角度出发
textCPUPeriod: 100000, // 100ms周期 CPUQuota: sandboxDto.CpuQuota * 100000, // 配额
若 CpuPeriod = 1 ,容器最多使用 1 vCPU,这就是防止单个容器独占整个 CPU
textMemory: sandboxDto.MemoryQuota * 1024 * 1024 * 1024, MemorySwap: sandboxDto.MemoryQuota * 1024 * 1024 * 1024,
若 MemoryQuota = 2,容器最多使用 2GB 内存,禁用 Swap,防止性能下降,超过限制时,容器会被 kill
textif filesystem == "xfs" { hostConfig.StorageOpt = map[string]string{ "size": fmt.Sprintf("%dG", sandboxDto.StorageQuota), } }
限制容器的磁盘使用量,防止恶意代码填满磁盘,只在 XFS 文件系统上生效
首先,什么是特权模式?
为什么 Daytona 使用特权模式?这其实是会有安全风险的
但这样其实无疑会带来比较高级别的安全风险
所以若想加固 Daytona 的安全性,
等等 但这些加固又会带来问题,复杂度加深,兼容性下降,功能受限等等
Docker 的贡献:
由此可见,Docker 其实在云计算时代,可以算是非常完美的解决方案了 但是 Docker 在安全 和 功能性 上形成了两难的境界了
新的安全需求
这就是为什么需要更强的隔离,这就是为什么需要 Firecracker!!!
架构:假设有 3 个容器在一台机器上运行,其实均是使用一个宿主机内核,在一个内核空间,内核漏洞影响所有容器,若容器逃逸,则会获取宿主机权限 Daytona 的矛盾:
| Docker 的问题 | Firecracker 的解决方案 |
|---|---|
| 共享内核 | 每个 microVM 独立内核 |
| 内核漏洞影响所有容器 | 内核漏洞只影响单个 VM |
| 容器逃逸 = 宿主机权限 | VM 逃逸仍在 KVM 隔离内 |
| 特权模式风险高 | 不需要特权模式 |
| 多租户不安全 | 硬件级隔离,天然支持多租户 |
Firecracker 是一个新型虚拟化技术,通过 microVM(微型虚拟机)提供接近容器的速度 和接近传统虚拟机的安全隔离 。
核心特征:
热启动(使用快照恢复):
Firecracker 是一个极简的虚拟机监控器(VMM),专为高密度、多租户的 Serverless 场景优化,提供了接近容器的启动速度 和接近传统虚拟机的安全隔离 。
" 如果这不是我们设计所必须要的,我们不会去构建它" 传统 VMM (QEMU) vs Firecracker
| 组件 | 传统 VMM(QEMU) | Firecracker | 影响 |
|---|---|---|---|
| VMM 大小 | 50-100MB | < 5MB | 启动更快 |
| BIOS | SeaBIOS | 无 | 跳过 BIOS 初始化 |
| PCI 总线 | 完整模拟 | 无 | 减少设备初始化 |
| USB/显卡/声卡 | 支持 | 无 | 减少攻击面 |
| 网络 | 多种模式 | VirtIO Net | 只保留必要的 |
| 存储 | 多种模式 | VirtIO Block | 只保留必要的 |
Firecracker 这种保留设计,会大大减少初始化时间,从可能的几秒降到了几十毫秒,并且有效的减少了攻击面
启动时间分解:
textVMM 启动 (CPU 时间) 加载 firecracker 二进制,初始化 API Server KVM 初始化 创建 VM,配置 vCPU 和内存 Guest Kernel 加载 从磁盘上读取vmlinux.bin,设置启动参数 Guest Kernel 启动 (最耗时) 解压内核,初始化内存,加载驱动,挂载rootfs
这里的 Guest Kernel 启动是最耗时的,基本占领冷启动总耗时的一半了,但是快照功能,完美的规避了这一耗时任务
核心思想:跳过最耗时的 Guest Kernel 启动 快照组成:
textFirecracker 快照 memfile(Guest 内存文件) 完整的虚拟机内存状态 vmstate(MicroVM 状态文件) vCPU 寄存器 设备状态(virtIO Block、VirtIO Net) KVM 状态 rootfs.ext4(磁盘文件)
快照恢复流程: 传统冷启动:(≤ 125ms): VMM 启动 → KVM 初始化 → 加载 Kernel → Guest Kernel 启动 → 应用启动 快照恢复(20-50ms): VMM 启动 → 读取快照元数据 → mmap 映射内存 → 恢复 VM 状态 → 启动 vCPU → 应用恢复 跳过了:Guest Kernel 启动(~70ms)
这里的关键技术: 关键技术:
性能数据:
问题:传统 mmap 的瓶颈 Guest 访问内存 → 缺页异常 → 陷入内核 → 加载内存页 → 返回用户空间 每次缺页都需要陷入内核,开销大 UFFD 的解决方案:用户态缺页处理
这里在 1.2.2 小节完美诠释,这里不再赘述
官方数据提供:36 核机器,每秒可创建 180 个 microVM,这就代表每核心每 200ms 可以创建一个 VM 这里的数据其实是很夸张的,但是认真分析,其实并非无道理,为什么可以做到呢?
三大核心技术:
选择 Docker 场景: 团队内部开发环境,信任的用户,对成本要求敏感,需要简单调用 选择 Firecracker 的场景: 多租户平台,运行不可信代码(AI Agent),高安全需求,Serverless 场景 Firecracker 是非常强大的,但是也同样,超级底层,Firecracker 仅仅只是一个 VMM,没有上层封装,需要手动管理网络、存储、快照;需要自己实现 API、编排、监控。 所以 E2B 跟随着时代的需求 出现了 在 Firecracker 之上构建完整的沙盒平台,提供了 Template 系统(从 Dockerfile 到快照),提供 SDK、API、编排、监控,让我们可以像使用 Docker 那样简单的使用 Firecracker。
textSDK层(Python/TypeScript/Go) REST API API Service层(HTTP/gRPC) gRPC Orchestrator层(Firecracker编排) Unix Socket API Firecracker + envd层(microVM运行时)
各层职责:
| 层级 | 组件 | 职责 | 技术栈 |
|---|---|---|---|
| SDK 层 | Python/TS SDK | 提供开发者友好的 API | Python/TypeScript |
| API 层 | API Service | 请求路由、认证、限流 | Go + HTTP/gRPC |
| 编排层 | Orchestrator | 管理 Firecracker 进程、网络、快照 | Go + KVM |
| 运行时层 | Firecracker + envd | microVM 执行、文件系统、进程管理 | Rust + Go |
Orchestrator 的核心职责:
envd 的核心职责:
问题:为什么需要 Template?
E2B 的解决方案:Template 系统
Docker + Firecracker 混合架构 这是 E2B 独特的设计:构建用 Docker ,运行用 Firecracker 建阶段(使用 Docker): 用户 Dockerfile → Docker 构建 → Docker 镜像 → 导出文件系统 运行阶段(使用 Firecracker): rootfs.ext4 → Firecracker 快照 → 20-50ms 启动 microVM 为什么这样设计呢?
| 阶段 | 技术选择 | 原因 |
|---|---|---|
| 构建 | Docker | 生态成熟,用户熟悉 Dockerfile,构建工具链完善 |
| 运行 | Firecracker | 硬件级隔离,安全性高,启动快(20-50ms) |
优势:给予我们的体验,我们只需要写 Dockerfile,不需要继续学习 Firecracker,运行沙盒,我们拥有的就是 Firecracker 硬件级的隔离,构建时利用 Docker 缓存,运行时,快照启动 20 - 50 ms 相比于 Dayton
| 项目 | 构建 | 运行 | 安全性 |
|---|---|---|---|
| Daytona | Docker | Docker | 低(共享内核) |
| E2B | Docker | Firecracker | 高(硬件隔离) |
完整流程(基于代码)
textStep 1: 用户定义Template配置 ,e2b.Dockerfile(环境定义),e2b.toml(配置:vCPU、内存、磁盘、启动命令) start_cmd(可选的启动脚本); 配置示例(e2b.toml): vcpu_count = 2 memory_mb = 1024 disk_size_mb = 1024 start_cmd = "npm start" Step 2: 构建Docker镜像 , 基础镜像:e2bdev/base:latest ,执行Dockerfile指令 ;安装依赖(apt-get、pip、npm); 复制代码 配置环境变量 生成Docker镜像 Step 3: Docker镜像 → rootfs.ext4 ,启动临时Docker容器 , 导出容器文件系统(docker export),创建ext4文件系统(mkfs.ext4),挂载ext4文件系统 ,复制Docker导出的文件到ext4,注入envd二进制文件 ; 配置systemd启动envd 卸载并保存rootfs.ext4 Step 4: 创建Firecracker快照 ,分配网络槽位(TAP设备、IP地址), 启动NBD设备(挂载rootfs.ext4) , 启动Firecracker进程 ; 配置vCPU、内存; 加载Linux Kernel(vmlinux-6.1.102);挂载rootfs(通过VirtIO Block); 启动Guest Kernel, 等待envd就绪 ,执行start_cmd(环境初始化;例如:npm install、pip install ,暂停microVM(Pause API), 创建快照 ; memfile(Guest内存文件); vmstate(VM状态文件) 上传快照到对象存储(S3/GCS) Step 5: 用户使用Template , SDK调用:sandbox = Sandbox(template="python") , Orchestrator下载快照(如果本地没有缓存); 20-50ms恢复microVM 用户代码开始执行
Docker ---> rootfs 转换
| 步骤 | 技术 | 说明 |
|---|---|---|
| 导出文件系统 | docker export | 导出容器的完整文件系统 |
| 创建 ext4 | mkfs.ext4 | 创建 ext4 文件系统 |
| 注入 envd | 复制二进制 | 将 envd 复制到/usr/local/bin/ |
| 配置启动 | systemd | 配置 envd 开机自启 |
快照优化技术:
2.快照共享
3.增量快照(未来优化)
Template 缓存机制: 本地缓存:下载的快照文件缓存在本地,LRU 策略淘汰不常用的快照,加速 Sandbox 创建 对象存储:S3/GCS 存储所有 Template 快照,按 BuildID 组织,支持版本管理

2.两个代码库职责划分
使用案例:
SDK 层是用户与 E2B 基础设施交互的唯一入口,负责将用户代码转换为 API 请求,并通过友好的接口封装复杂的底层通信。 E2B CLI 是基于 TypeScript 开发的命令行工具,主要用于模板管理和开发调试。 核心命令:
构建流程:
Template 是 E2B 沙盒的预构建环境快照,类似于 Docker Image,但针对 Firecracker microVM 优化。
配置文件:e2b.toml
API 网关层是 E2B 的统一入口。它基于 Gin Web Framework 构建,提供 REST API 接口,并通过 gRPC 与 Orchestrator 通信 请求转发: 由 REST API 入口 到认证授权,处理完成之后到达请求转发,API Service 将请求转换为 gRPC 调用,转发给 Orchestrator
Orchestrator 是 E2B 的大脑,负责管理所有底层资源:Firecracker 实例、网络、存储、Template 加载和沙盒生命周期。它通过 gRPC Server(端口 5008) 接收 API Service 的请求,将高层指令转化为具体的系统操作。
Orchestrator 通过 fc.Process 管理 Firecracker 进程的完整生命周期。
text启动流程: 1 生成启动脚本(StartScriptBuilder) 创建网络命名空间(ip netns) 配置 TAP 设备 和 iptables 构造 Firecracker 启动命令 2 执行 Firecracker 进程 使用 unshare -m 隔离挂载命名空间 3 通过 Unix Socket 配置 microVM ├─ PUT /boot-source (kernel 路径、启动参数) ├─ PUT /drives (rootfs 挂载) ├─ PUT /machine-config (vCPU、内存) ├─ PUT /network-interfaces (TAP 设备) └─ PUT /mmds (元数据服务) 4 启动 VM
关键点:Unix Socket 用来 Orchestrator 与 Firecracker API 通信
这是预构建的沙盒环境,Orchestrator 通过多层缓存机制优化加载速度
Orchestrator 管理两个核心资源池:NetworkPool 和 DevicePool
NetworkPool(网络池) 每个沙盒需要独立的网络环境(TAP 设备 + IP),NetworkPool 预分配资源以加速创建。
| 池类型 | 容量 | 用途 |
|---|---|---|
| 新 Slot 池 | 32 | 预创建全新的网络命名空间和 TAP 设备 |
| 复用 Slot 池 | 100 | 回收已销毁沙盒的网络资源,快速复用 |
DevicePool(NBD 设备池) rootfs.ext4 通过 NBD (Network Block Device) 挂载到 Firecracker。DevicePool 管理有限的 NBD 设备资源。
| 数 | 值 | 说明 |
|---|---|---|
| 最大设备数 | 4096 | modprobe nbd nbds_max=4096 |
| 预分配槽位 | 64 | 提前准备可用的 NBD 设备 |
| 槽位复用 | 是 | 沙盒销毁后立即回收设备编号 |
沙盒生命周期控制 Orchestrator 通过 gRPC 接口管理沙盒的完整生命周期。
textservice SandboxService { rpc Create(SandboxCreateRequest) returns (SandboxCreateResponse); // 创建 rpc Update(SandboxUpdateRequest) returns (Empty); // 更新超时 rpc Delete(SandboxDeleteRequest) returns (Empty); // 删除 rpc Pause(SandboxPauseRequest) returns (Empty); // 暂停 rpc List(Empty) returns (SandboxListResponse); // 列表 }
创建流程: Step1:API Service 发送 gRPC Create 请求 Step2:Orchestrator 接收请求(从网络池获取 Slot,从设备池获取 NBD 设备,从 TemplateCache 加载 template) Step3:初始化 rootfs(创建 NDB 设备:/dev/nbd42,连接到 rootfs.ext4,挂载为块设备) Step4:启动 Firecracker 进程,配置 kernel,rootfs,网络,MMDS, PUT /actions {"action_type": "InstanceStart"} Step5:等待 envd 就绪(envd 从 MMDS 中获取元数据,返回 sandbox_id 和连接信息)
Orchestrator 通过 UFFD(userfaultfd)实现快照的按需加载,这是 20-50ms 启动的关键技术。 工作流程:
text1.Orchestrator启动UFFD服务线程 2.注册内存区域(memfile) 3.Firecracker加载快照(不实际加载内存) 4.microVM启动,访问内存触发缺页 5.UFFD线程处理缺页: 从memfile读取内存页 批量加载(预测下一步需要的页) 通知KVM继续执行
效果:
沙盒运行时层是用户代码真正执行的地方,由 Firecracker microVM 提供隔离环境,envd 守护进程负责与外部通信和资源管理。
envd 是运行在 microVM 内部的 HTTP 服务,充当 Orchestrator 和用户应用之间的桥梁。
核心功能:
| 功能 | 说明 | RPC 接口 |
|---|---|---|
| 文件管理 | 读写文件、创建目录、监听文件变化 | Filesystem Service |
| 进程管理 | 启动进程、捕获输出、管理生命周期 | Process Service |
| 端口转发 | 自动将 localhost 端口暴露到宿主机 | Port Forwarder |
| 元数据获取 | 从 MMDS (169.254.169.254) 获取 sandbox_id 等信息 | MMDS Client |
技术关键点:
E2B 主要使用 TAP 设备,Orchestrator Proxy 发送请求,是通过 TAP 设备转发到 microVM
TAP 设备的优势在于:
| 存储系统 | 数据类型 | 用途 | 连接配置 |
|---|---|---|---|
| PostgreSQL | 核心业务数据 | 持久化、事务性数据 | POSTGRES_CONNECTION_STRING |
| Redis | 缓存与消息 | 高速缓存、分布式锁、PubSub | REDIS_URL / REDIS_CLUSTER_URL |
| ClickHouse | 分析数据 | 日志、监控、事件统计 | CLICKHOUSE_CONNECTION_STRING |
ProgreSQL - 核心数据库
存储内容:用户账号,团队信息,API Key(哈希),template 定义,template 构建记录,沙盒快照元数据,部署集群配置
Redis - 高速缓存与消息队列
基本上就是配合 progreSQL 的运作,缓存 key,缓存 Template 元数据,沙盒事件通知等等
存储内容:Template 的二进制文件(rootfs、kernel、快照)
核心需求:
为什么这些需求很重要呢?
冷启动(镜像已经存在)
时间理论上可以控制在 100 - 500 ms
热启动(Warm Pool) 基于热池分配,按用户需求进行配置已启动的容器,使用 Redis 锁确保只分配到一次。 热池启动给到速度很快,90 ms 以内,但是容器必须一直运行,持续占用,每个容器占用 CPU、内存、内核资源,需要定时检查并补充容器,这看起来没多大,但需求量一旦上去,基本相当于一整台机器都在干一件事情,维持热池的启动,这其实并不可取的,一旦大部分时间不用,沙盒容器处于空闲,会导致资源利用率极低
| 维度 | 数据 | 说明 |
|---|---|---|
| 启动速度 | 20-50ms | UFFD 按需加载 |
| VM 状态 | PAUSED | 快照是暂停状态,不占用 CPU |
| 资源占用 | 几乎为 0 | 快照只是文件,不运行 |
| 内存加载 | 按需 | Guest 访问时才加载内存页 |
| 共享 | 支持 | 多个 VM 可以共享只读快照 |
优势很明显,快照不运行,资源占用为 0,启动时才分配资源,多个 VM 可以共享只读快照,资源利用率很高,只有运行时的 VM 才占用资源
| 方案 | 冷启动 | 热启动 | 代价 | 资源利用率 |
|---|---|---|---|---|
| Docker | 100-500ms | < 90ms | 容器必须一直运行 | 低(大部分时间空闲) |
| Firecracker | ≤ 125ms | 20-50ms | 快照不运行时资源占用为 0 | 高(按需分配) |
问题:为什么 Docker Warm Pool 的容器必须一直运行(state: STARTED)? 答案:因为 Docker 容器的"状态"是进程状态,停止 = 进程退出 = 状态丢失
Docker 容器的本质:
问题:进程退出后会发生什么?
Docker 的困境:
为什么 Docker Commit 不能解决问题?
→ Docker 的本质限制:容器 = 进程,进程停止 = 状态丢失
答案:因为 Firecracker 是 VM,可以保存完整的"机器状态"
VM 的本质:
Firecracker 快照保存了什么?
为什么 Firecracker 恢复这么快?
text需求: day1:安装 numpy,playwright,pandas,chromium。 day2:创建某平台爬虫脚本 day3:运行分析脚本 day4:创建新的工具功能,比如cookie ...
要求:所有操作持久化(文件、环境变量、安装的依赖),随时可恢复,绝对不能丢失数据
方案一 :容器一直运行 优势:简单,无需额外操作 劣势:很明显,会造成资源浪费,成本高,不稳定(容器可能会崩掉) 方案二 :使用 Docker Volume 优势:数据只能持久化在宿主机,容器停止数据不会丢失 劣势:只能持久化指定目录,环境变量、安装的包不会保留,需要手动配置 Volume 方案三 :Docker Commit(Daytona 的方案)
textfunc (d *DockerClient) Commit(ctx context.Context, sandboxId string) error { // 将运行中的容器commit为新镜像 _, err := d.client.ContainerCommit(ctx, sandboxId, types.ContainerCommitOptions{ Reference: fmt.Sprintf("sandbox-%s:latest", sandboxId), Pause: true, // 暂停容器 }) return err }
流程:
优势:所有修改都保留,容器可停止,不占用资源 劣势:commit 很慢(需要保持整个文件系统),镜像根据用户需求会越来越大,每次修改均增大,启动会越来越慢,存储成本
快照: 我们基于 E2B 的处理方式来看 流程:
优势:
劣势(但其实 E2B-dev/info 已经提供了解决方案):
| 方案 | 持久化方式 | 恢复速度 | 存储成本 | 资源占用 |
|---|---|---|---|---|
| Docker 一直运行 | 自然保留 | 0ms | 低 | 高(一直占用) |
| Docker Volume | 部分持久化 | 100-500ms | 低 | 低(停止时) |
| Docker Commit | 完整持久化 | 100-500ms | 高(镜像大) | 低(停止时) |
| Firecracker 快照 | 完整持久化 | 20-50ms | 中(增量) | 低(停止时) |
Warm Pool 创建的 N 个运行的容器,资源会持续占用
快照 = 暂停状态的 VM,不运行,资源利用率理论上是可以达到百分百
若增加用户数量,比如 10000 个,只会占用我们的磁盘内存,并且只有 VM 运行的适合才占用内存,成本可控
每个 VM 独立内核,每个 VM 组件不受干扰
团队内部(5 - 100 人) 可选择 Docker Warm Pool,原因很简单,简单易用,成本可靠,且易维护,并且可以充分信任 多租户平台,给到广大群众使用 Firecracker 快照,资源利用率高,成本可控,安全性高,扩展性好 AI Agent 平台(未来我们的百万甚至千万用户) Firecracker 快照(这是唯一选择)