E2B 架构深度解析
1. 概述
E2B 是一个云原生沙箱环境即服务(Sandbox-as-a-Service)平台。其核心架构旨在高效、安全地创建和管理基于轻量级虚拟机(Firecracker MicroVM)的隔离开发环境。整个系统由客户端 SDK、Client-Proxy 网关、API 服务层和Orchestrator 执行层组成,实现了从请求到沙箱生命周期的全流程管理。
2. 核心组件交互流程
整个系统的请求始于 E2B SDK,终结于一个运行的沙箱虚拟机。
sequenceDiagram
participant SDK
participant ClientProxy
participant APIService
participant Orchestrator
participant Firecracker
participant VM Envd
SDK->>ClientProxy: 1. REST/gRPC请求 (创建Sandbox)
Note over ClientProxy: Edge API (端口:3001)
ClientProxy->>APIService: 2. 转发API请求
APIService->>Orchestrator: 3. gRPC调用 (选择负载最低的节点)
Note over Orchestrator: 4. 资源分配(网络、存储、内存)
Orchestrator->>Firecracker: 5. 创建/恢复VM进程
Firecracker-->>Orchestrator: VM进程句柄
Note over Orchestrator, VM Envd: 6. 等待envd守护进程启动
Orchestrator-->>APIService: Sandbox元数据 (ID, IP等)
APIService-->>ClientProxy: 响应
ClientProxy-->>SDK: 返回Sandbox信息
SDK->>ClientProxy: 7. 发送执行命令请求
Note over ClientProxy: Traffic Proxy (端口:3002)
ClientProxy->>Orchestrator: 8. 根据Sandbox ID路由
Orchestrator->>VM Envd: 9. 转发请求至沙箱内envd
VM Envd-->>Orchestrator: 命令执行结果
Orchestrator-->>ClientProxy: 响应
ClientProxy-->>SDK: 返回结果
3. 组件详述
3.1 E2B SDK
SDK 是开发者与 E2B 平台交互的入口,封装了与后端服务的所有通信。
示例:创建沙箱
// E2B SDK 示例
const response = await fetch('/sandboxes', {
method: 'POST',
headers: {
'Authorization': 'Bearer <api_key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
templateID: 'base', // 基础模板
timeout: 3600, // 超时时间(秒)
metadata: { project: 'test' }, // 元数据
envVars: { NODE_ENV: 'development' } // 环境变量
})
});
3.2 Client-Proxy(客户端代理网关)
Client-Proxy 是一个多功能服务,充当系统的统一入口网关。它不是传统意义上的简单代理,而是一个多端口网关服务,承担以下核心职责:
-
服务角色:
- Edge API Server - 处理所有外部 SDK 传入的请求(类似独立的 API 网关)
- Traffic Proxy - 代理转发至沙箱内部的通信流量
-
架构实现: 使用
cmux组件进行端口复用,在内部启动了三个服务器: -
grpcSrv和restSrv共享 3001 端口(Edge API) -
trafficProxy监听 3002 端口(沙箱流量代理)go // 初始化代码示例 trafficProxy, err := e2bproxy.NewClientProxy( tel.MeterProvider, serviceName, uint(proxyPort), catalog, orchestrators, useProxyCatalogResolution, useDnsResolution ) -
内部架构:
┌─────────────────────────────────────────────────────────────┐ │ client-proxy │ │ │ │ ┌─────────────────┐ ┌─────────────────────┐│ │ │ Edge API │ │ Sandbox Proxy ││ │ │ (edgePort:3001) │ │ (proxyPort:3002) ││ │ │ │ │ ││ │ │ ┌─────────────┐ │ │ ┌─────────────────┐││ │ │ │ gRPC Proxy │ │ │ │ HTTP Proxy │││ │ │ │ │ │ │ │ │││ │ │ └─────────────┘ │ │ └─────────────────┘││ │ │ ┌─────────────┐ │ │ ││ │ │ │ REST API │ │ │ ││ │ │ │ (Gin) │ │ │ ││ │ │ └─────────────┘ │ │ ││ │ └─────────────────┘ └─────────────────────┘│ │ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │ 共享组件 ││ │ │ • Sandbox Catalog (Redis/Memory) ││ │ │ • Orchestrator Pool ││ │ │ • Service Discovery ││ │ │ • Telemetry & Logging ││ │ │ • Authorization Manager ││ │ └─────────────────────────────────────────────────────────┘│ └─────────────────────────────────────────────────────────────┘ -
关键子组件:
- Edge Pool: 管理 Client-Proxy 实例集群,实现实例间的相互感知、协调,提供高可用性、负载均衡和优雅的节点维护。
- Orchestrators Pool: 维护到所有 Orchestrator 节点的连接池,通过服务发现自动管理节点状态(加入/离开),为请求提供路由和负载分散能力。
-
Catalog (Redis & DNS 查询): 核心路由表。这是一个多对一的查询系统,通过
Sandbox ID查找其所在的Orchestrator ID。Catalog 结构体设计用于分布式环境,使用 Redis 作为共享存储保证一致性,并使用本地缓存提升性能。go type CatalogEntry struct { OrchestratorID string // Orchestrator 节点标识 ExecutionID string // 执行实例标识 StartedAt time.Time // 启动时间 MaxLifetime time.Duration // 最大生存时间 } -
请求处理路径:
- API 请求:
请求 -> client-proxy:3001 -> Edge API -> 调用 orchestrator - 沙箱流量:
请求 -> client-proxy:3002 -> orchestrator -> sandbox
3.3 API Service 层
API Service 层部署在 Google Cloud Platform (GCP) 上,是系统的业务逻辑和协调中心。
- 部署与负载均衡:
- 通过 Nomad 任务调度器进行部署。
- 使用 Google Cloud Load Balancer 在请求级别实现负载均衡,将流量分发到健康的 API 服务实例。
-
基础设施通过 Terraform 模块 (
packages/cluster/network/main.tf) 定义,集群规模由api_cluster_size控制。 -
关键配置:
go const ( serviceName = "orchestration-api" maxMultipartMemory = 1 << 27 // 128 MiB maxUploadLimit = 1 << 28 // 256 MiB maxReadTimeout = 75 * time.Second maxWriteTimeout = 75 * time.Second // 超时设置 > 600s (GCP LB 上游空闲超时) 以防止竞态条件 idleTimeout = 620 * time.Second defaultPort = 80 ) -
核心职责:
- 向上对接: 为 SDK 提供标准化 REST API,处理认证、验证、限流等跨领域关注点。
- 向下协调: 协调多个后端服务(Orchestrator、模板管理、缓存等),实现复杂业务逻辑。
- 内部治理: 提供服务发现、负载均衡、错误处理、监控等基础设施功能。
3.4 Orchestrator 与沙箱管理
Orchestrator 是沙箱生命周期的直接管理者,通过 gRPC 接口提供服务。
-
gRPC 服务接口 (
SandboxServiceClient):go type SandboxServiceClient interface { Create(ctx context.Context, in *SandboxCreateRequest, opts ...grpc.CallOption) (*SandboxCreateResponse, error) Update(ctx context.Context, in *SandboxUpdateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) List(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SandboxListResponse, error) Delete(ctx context.Context, in *SandboxDeleteRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) Pause(ctx context.Context, in *SandboxPauseRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) ListCachedBuilds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SandboxListCachedBuildsResponse, error) } -
沙箱 (Sandbox) 数据结构: Orchestrator 管理的沙箱是一个复杂的实体,包含多种资源和管理单元。
```go type Sandbox struct { Resources // 资源:网络插槽、rootfs 提供程序、内存后端 Metadata // 元数据:配置、运行时信息、生命周期时间戳 files storage.SandboxFiles // 文件存储管理 cleanup Cleanup // 资源清理编排器 process fc.Process // Firecracker 进程句柄 template template.Template // 所用模板 Checks Checks // 健康监测系统 APIStoredConfig *orchestrator.SandboxConfig // API存储的配置 }
type Metadata struct { Config Config // 沙箱配置 Runtime RuntimeMetadata // 运行时信息 (TemplateID, SandboxID, ExecutionID...) StartedAt time.Time // 启动时间 EndAt time.Time // 计划结束时间 }
type Resources struct { Slot *network.Slot // 网络资源槽 rootfs rootfs.Provider // 根文件系统提供者 memory uffd.MemoryBackend // UFFD 内存后端 uffdExit chan error // UFFD 退出通道 } ```
关键设计:
ExecutionID和SandboxID的双重标识符是支持沙箱暂停(Pause)/恢复(Resume)功能的核心,确保了在复杂生命周期管理中的数据一致性、网络安全性和监控准确性。
4. 新 VM 启动流程深度剖析
创建一个新沙箱是一个复杂的异步过程,涉及多组件协作。
-
请求接收与路由 (API -> Orchestrator):
- SDK 请求经 Client-Proxy 的 Edge API (3001) 到达 API Service。
- API Service 通过
getLeastBusyNode函数选择 CPU 负载最低的 Orchestrator 节点。 - API Service 通过 gRPC 调用目标 Orchestrator 的
Create方法。
-
资源分配 (Orchestrator):
-
异步网络分配: 从网络池中获取一个网络插槽(Slot),为 VM 分配 IP 等网络配置。
go ipsCh := getNetworkSlotAsync(childCtx, tracer, networkPool, cleanup, allowInternet) -
存储准备: 初始化基于 NBD (Network Block Device) 的存储后端,准备根文件系统。
go fcUffdPath := sandboxFiles.SandboxUffdSocketPath() -
内存准备: 初始化 UFFD (Userfaultfd) 内存后端,为差异化的内存快照恢复做准备。
go fcUffd, err := serveMemory(...)
-
-
Firecracker VM 启动:
- 通过
exec系统调用启动 Firecracker 进程 (fc.NewProcess),传递--api-sock参数指定其 API 套接字路径。 - 由此获得一个
fcHandle,用于后续通过 HTTP 调用与此 VM 实例交互的 Firecracker API。 - 此时创建的是一个最基础版本的 VM。
- 通过
-
快照恢复(如果指定了模板):
- 如果请求中包含了
templateID,Orchestrator 不会从头启动,而是通过 Firecracker API 的LoadSnapshot操作来恢复一个预先创建的快照。 - 这是一个高性能的关键步骤。通过 UFFD 机制,内存页是按需加载(延迟加载)的,即只有当 VM 内的 CPU 实际访问某块内存时,才会从快照差异中加载该页,极大减少了启动时的内存占用和等待时间。
-
代码实现:
go err = p.client.loadSnapshot(&snapshotConfig) // 内部通过 HTTP PUT 调用 Firecracker 的 /snapshot/load 接口 _, err = c.client.Operations.LoadSnapshot(&snapshotConfig)
- 如果请求中包含了
-
恢复 VM 运行:
- 调用
fcHandle.Resume()或p.client.resumeVM(),让恢复完成的 VM 开始运行。
- 调用
-
等待环境就绪:
- 等待 VM 内部的
envd守护进程启动并准备好接收命令。 -
Orchestrator 通过 HTTP 向 VM 内预定义的端点发送
init命令,配置环境变量等。go err = sbx.WaitForEnvd(...) initErr := s.initEnvd(...) // 内部使用 HTTP Client 向沙箱 IP 发送 POST 请求 response, err := httpClient.Do(request)
- 等待 VM 内部的
-
健康监控与注册:
- 启动定期健康检查 (
Checks)。 - 将新沙箱的元数据(ID, Orchestrator ID, IP)注册到 Catalog 中,以便 Client-Proxy 能够正确路由后续流量。
- 启动定期健康检查 (
5. 关键技术:差异化管理(Diff)
为了极致优化启动速度和资源效率,E2B 广泛使用了差异化管理。
5.1 内存 Diff (UFFD)
- 机制:利用 Linux 的
userfaultfd(UFFD) 系统调用实现内存页的按需故障处理。 - 流程:
- 恢复快照时,大部分内存区域被注册为 UFFD 区。
- VM 启动后,当其 CPU 尝试访问一个尚未加载的页面时,会触发一个页故障(Page Fault)。
- Orchestrator 中的 UFFD 处理线程会捕获此故障。
- 处理线程从快照差异数据中找出对应的页面数据,提供给 VM。
- 优势:实现延迟加载,避免启动时一次性加载所有内存页,极大降低内存峰值占用并加速启动流程。
// 从Client-Proxy通过Edge API,转发过来,带了template ID,(可选:AllowInternetAccess,Secure,AutoPause,AutoPause,Metadata)
func (a *APIStore) PostSandboxes(c *gin.Context) {
func (a *APIStore) startSandbox(
func (o *Orchestrator) CreateSandbox(
// API 层,通过getLeastBusyNode函数,查找CPU占用最少的orchestrator节点
// 通过gRPC调用该节点的Create函数
func (s *server) Create(ctxConn context.Context, req *orchestrator.SandboxCreateRequest)
func ResumeSandbox(
// 异步网络分配:ipsCh := getNetworkSlotAsync(childCtx, tracer, networkPool, cleanup, allowInternet)
// NBD存储后端:fcUffdPath := sandboxFiles.SandboxUffdSocketPath()
// UFFD内存后端:fcUffd, err := serveMemory(
fcHandle, fcErr := fc.NewProcess(
// 通过 exec 系统调用,基于startScriptV1/V2 创建 Firecracker VM,同时会设置 --api-sock {{ .FirecrackerSocket }}
// 从而实现通过fcHandle.client.Operations进行Firecracker API调用,func newApiClient(socketPath string) *apiClient {
// 此时创建的是基础版VM
// 创建的process会与
fcStartErr := fcHandle.Resume(
// 如果指定了template,就需要加载快照
err = p.client.loadSnapshot(
_, err = c.client.Operations.LoadSnapshot(&snapshotConfig)
func (a *Client) LoadSnapshot(params *LoadSnapshotParams, opts ...ClientOption)
// 通过构建Http PUT请求,发起真实的Firecracker API调用
result, err := a.transport.Submit(op)
// 接着恢复VM运行
err = p.client.resumeVM(childCtx)
// 等待VM的Envd
err = sbx.WaitForEnvd(
initErr := s.initEnvd(syncCtx, tracer, s.Config.Envd.Vars, s.Config.Envd.AccessToken)
// 通过构建Http Post请求,向VM的发送init命令
response, err := httpClient.Do(request)
// 虚拟机配置流程旨在实现高性能和资源效率,利用写时复制文件系统、用户模式内存页面错误处理以及 Firecracker 的轻量级虚拟化技术。该系统支持通过模板创建新虚拟机,以及通过快照恢复之前暂停的虚拟机,从而实现快速的沙盒生命周期管理。整个流程均采用遥测技术进行检测,并包含全面的错误处理和清理机制,以确保资源管理和系统稳定性。
5.2 文件 Diff (块级去重)
- 层级:通常在块设备级别(如 QCOW2 映像)或文件系统级别进行。
- 策略:
- 空块检测:通过
IsEmptyBlock等函数跳过全零块,避免存储和传输。 - 增量存储:只存储与基础模板版本不同的数据块。
- 块级粒度:通常使用 4KB 大小的块,在存储效率和 I/O 性能之间取得平衡。
- 优势:节省大量存储空间和网络带宽,加快模板分发和沙箱创建速度。
总结
E2B 架构是一个精心设计的分布式系统,其核心在于通过 Client-Proxy 实现灵活的请求路由和网关功能,通过 Orchestrator 实现高效的沙箱生命周期管理,并充分利用 Firecracker 和 差异化管理技术(UFFD、快照)来提供安全、隔离且启动极快的容器环境。ExecutionID 和 SandboxID 的设计、Catalog 的路由机制以及基于 UFFD 的内存恢复是其实现高性能和复杂生命周期管理的关键创新。