nashzhou blog

thinking, coding, and writing

E2B系统初识


E2B 架构深度解析

1. 概述

E2B 是一个云原生沙箱环境即服务(Sandbox-as-a-Service)平台。其核心架构旨在高效、安全地创建和管理基于轻量级虚拟机(Firecracker MicroVM)的隔离开发环境。整个系统由客户端 SDKClient-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 是一个多功能服务,充当系统的统一入口网关。它不是传统意义上的简单代理,而是一个多端口网关服务,承担以下核心职责:

  • 服务角色:

    1. Edge API Server - 处理所有外部 SDK 传入的请求(类似独立的 API 网关)
    2. Traffic Proxy - 代理转发至沙箱内部的通信流量
  • 架构实现: 使用 cmux 组件进行端口复用,在内部启动了三个服务器:

  • grpcSrvrestSrv 共享 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 )

  • 核心职责:

    1. 向上对接: 为 SDK 提供标准化 REST API,处理认证、验证、限流等跨领域关注点。
    2. 向下协调: 协调多个后端服务(Orchestrator、模板管理、缓存等),实现复杂业务逻辑。
    3. 内部治理: 提供服务发现、负载均衡、错误处理、监控等基础设施功能。

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 退出通道 } ```

    关键设计: ExecutionIDSandboxID 的双重标识符是支持沙箱暂停(Pause)/恢复(Resume)功能的核心,确保了在复杂生命周期管理中的数据一致性、网络安全性和监控准确性。

4. 新 VM 启动流程深度剖析

创建一个新沙箱是一个复杂的异步过程,涉及多组件协作。

  1. 请求接收与路由 (API -> Orchestrator):

    • SDK 请求经 Client-Proxy 的 Edge API (3001) 到达 API Service。
    • API Service 通过 getLeastBusyNode 函数选择 CPU 负载最低的 Orchestrator 节点。
    • API Service 通过 gRPC 调用目标 Orchestrator 的 Create 方法。
  2. 资源分配 (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(...)

  3. Firecracker VM 启动:

    • 通过 exec 系统调用启动 Firecracker 进程 (fc.NewProcess),传递 --api-sock 参数指定其 API 套接字路径。
    • 由此获得一个 fcHandle,用于后续通过 HTTP 调用与此 VM 实例交互的 Firecracker API。
    • 此时创建的是一个最基础版本的 VM。
  4. 快照恢复(如果指定了模板):

    • 如果请求中包含了 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)

  5. 恢复 VM 运行:

    • 调用 fcHandle.Resume()p.client.resumeVM(),让恢复完成的 VM 开始运行。
  6. 等待环境就绪:

    • 等待 VM 内部的 envd 守护进程启动并准备好接收命令。
    • Orchestrator 通过 HTTP 向 VM 内预定义的端点发送 init 命令,配置环境变量等。

      go err = sbx.WaitForEnvd(...) initErr := s.initEnvd(...) // 内部使用 HTTP Client 向沙箱 IP 发送 POST 请求 response, err := httpClient.Do(request)

  7. 健康监控与注册:

    • 启动定期健康检查 (Checks)。
    • 将新沙箱的元数据(ID, Orchestrator ID, IP)注册到 Catalog 中,以便 Client-Proxy 能够正确路由后续流量。

5. 关键技术:差异化管理(Diff)

为了极致优化启动速度和资源效率,E2B 广泛使用了差异化管理。

5.1 内存 Diff (UFFD)

  • 机制:利用 Linux 的 userfaultfd (UFFD) 系统调用实现内存页的按需故障处理
  • 流程
    1. 恢复快照时,大部分内存区域被注册为 UFFD 区。
    2. VM 启动后,当其 CPU 尝试访问一个尚未加载的页面时,会触发一个页故障(Page Fault)。
    3. Orchestrator 中的 UFFD 处理线程会捕获此故障。
    4. 处理线程从快照差异数据中找出对应的页面数据,提供给 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、快照)来提供安全、隔离且启动极快的容器环境。ExecutionIDSandboxID 的设计、Catalog 的路由机制以及基于 UFFD 的内存恢复是其实现高性能和复杂生命周期管理的关键创新。