ᕕ( ᐛ )ᕗ Jimyag's Blog

E2B Infra 项目分析:AI Code Interpreter 的开源基础设施

· 1859 字 · 约 9 分钟

分析基线:本文基于你本地仓库 content/blog/211-E2B Infra 项目分析:AI Code Interpreter 的开源基础设施/files/infra 在 2026-03-01 的代码状态分析。

先说结论:

  1. 这是一个“控制面 + 数据面 + 调度层 + 云资源编排”都在一个仓库的完整基础设施项目,不只是单个 API 服务。
  2. 核心执行链路是:API -> Template Manager / Orchestrator(gRPC) -> Client Proxy -> Sandbox(Envd)
  3. 官方自托管主路径是 GCP + Terraform + Nomad;AWS 目录存在,但目前仍是建设中状态。
  4. 如果你的目标是“私有化 AI Code Interpreter”,这个仓库基本覆盖了可运行闭环。

阅读导航

为了减少“信息很全但阅读跳跃”的问题,本文按三层结构组织:

  1. 第一部分(1-23):先讲项目边界、目录、部署入口、API 与关键机制,适合第一次通读。
  2. 第二部分(24-33):按模块深挖控制面、调度、构建、安全、网络、存储、观测、升级、多云与计费,适合带着问题查细节。
  3. 第三部分(34 + 参考链接):给出阅读边界和后续扩展方向。

第一部分:项目总览与落地路径

A. 项目边界与代码地图

1. 这个项目能做什么

从代码和 OpenAPI 看,它支持的核心能力是:

  1. Sandbox 生命周期管理:创建、连接、暂停、恢复、删除、超时续期。
  2. Template 生命周期管理:创建构建请求、触发构建、查询构建状态和日志、标签与别名管理。
  3. 持久卷能力:卷创建、挂载、删除(受 feature flag 控制)。
  4. 观测与治理:团队指标、sandbox 指标、构建日志、admin 节点与运维接口。
  5. 多组件自托管:API、orchestrator、template-manager、client-proxy、ingress、redis、loki、otel、clickhouse。

2. 根目录逐项说明

按你关心的“挨个文件/目录作用”,先从仓库根目录开始:

  1. .env.template:自托管变量模板,定义 GCP/域名/Postgres/集群规格/可选组件等关键参数。
  2. README.md:项目定位与云厂商支持状态概览。
  3. self-host.md:官方自托管流程,含前置依赖、make 部署顺序、Secrets 配置、集群初始化。
  4. DEV-LOCAL.md:本地开发模式,如何起本地依赖和本地服务。
  5. DEV.md:远程开发(SSH/VSCode)指引。
  6. Makefile:全仓统一入口,封装 init/plan/apply/build-and-upload/migrate/test/local-infra 等操作。
  7. go.work:Go workspace,把多包工程拼成一个工作区。
  8. VERSION:版本号文件。
  9. iac/:Terraform 基础设施层(云资源 + Nomad job 发布)。
  10. packages/:业务与基础服务实现。
  11. spec/:四套 API 定义(主 API、edge、dashboard、hyperloop)。
  12. tests/:集成测试和周期性测试。
  13. scripts/:环境、发布、生成、检查相关脚本。

3. packages 目录逐项说明

这是核心代码层,每个子目录职责如下:

  1. packages/api:主控制面 API(Gin + OpenAPI 校验),负责鉴权、业务编排、调 orchestrator/template-manager。
  2. packages/orchestrator:节点侧核心调度执行服务,管理 sandbox、网络、存储、模板缓存、事件、gRPC 服务。
  3. packages/client-proxy:流量代理层,把外部会话请求路由到具体 sandbox 节点,并处理健康/排空。
  4. packages/dashboard-api:Dashboard 侧 API,提供构建与记录查询等能力。
  5. packages/envd:运行在 sandbox 内/边界处的执行代理组件。
  6. packages/db:数据库访问层与迁移逻辑。
  7. packages/clickhouse:事件与指标存储相关逻辑。
  8. packages/auth:认证鉴权通用模块(API key / access token / Supabase token)。
  9. packages/shared:跨服务共享组件(logger、telemetry、grpc proto、feature flags 等)。
  10. packages/docker-reverse-proxy:镜像相关反向代理服务。
  11. packages/local-dev:本地依赖栈拉起与种子数据工具。
  12. packages/nomad-nodepool-apm:Nomad autoscaler 的自定义 APM 插件。
  13. packages/otel-collector:OTel collector 相关配置与构建。

4. iac 目录逐项说明

  1. iac/provider-gcp:主部署实现(Terraform backend/provider/module 调用)。
  2. iac/provider-gcp/init/*:先创建 bucket、secret、service account 等基础资源。
  3. iac/provider-gcp/nomad-cluster/*:GCP 上 Nomad 集群与 node pool(api/build/clickhouse/loki/…)。
  4. iac/provider-gcp/nomad/*:把服务渲染为 Nomad job 并发布(api/template-manager/redis/autoscaler/…)。
  5. iac/provider-gcp/nomad/jobs/*.hcl:各服务运行时作业模板和环境变量注入入口。
  6. iac/modules/job-*:可复用 Nomad job Terraform module(orchestrator/client-proxy/ingress/…)。
  7. iac/provider-aws:AWS provider 骨架,目前内容明显少于 GCP 路径。

B. 部署与自托管入口

5. 部署依赖与组件清单

self-host.md.env.template、Terraform 与 Nomad 模板汇总,依赖分为:

  1. 基础工具 Packer Terraform 1.5.x gcloud CLI Go Docker npm

  2. 云与外部服务 GCP project Cloudflare account + domain PostgreSQL (文档注明 Supabase DB 目前受支持) 对象存储 bucket (模板、构建产物、FC 组件)

  3. 运行时基础设施组件 Nomad Consul Redis (managed 或 job) ClickHouse Loki OpenTelemetry Collector Grafana/Posthog (可选但推荐)

6. 官方自托管流程(GCP)

主流程可归纳为:

  1. 配置 .env.{dev|staging|prod} 并执行 make set-env ENV=dev
  2. make provider-login 完成云登录。
  3. make init 初始化 Terraform 和基础资源。
  4. make build-and-upload 构建并上传服务产物。
  5. make copy-public-builds 准备 Firecracker/kernel 公共构建件。
  6. 在 Secret Manager 填入关键密钥(Cloudflare token、Postgres、JWT、Posthog 等)。
  7. make plan-without-jobs && make apply 先建云底座。
  8. make plan && make apply 再部署 Nomad jobs。
  9. 初始化集群数据(用户、团队、基础模板)。

C. API 与执行链路

7. API 分组与职责

spec 文件可分 4 套 API:

  1. spec/openapi.yml:主 API(团队、sandbox、template、volume、admin、access token、api key)。
  2. spec/openapi-edge.yml:edge 侧 API(service discovery、sandbox logs/metrics 聚合)。
  3. spec/openapi-dashboard.yml:dashboard API(build 列表、build 状态、sandbox record)。
  4. spec/openapi-hyperloop.yml:hyperloop API(/me/logs,用于 sandbox 内身份与日志上报链路)。

8. 核心 API 流程

下面是最重要的几个流程(按代码调用链):

  1. 创建 sandbox:POST /sandboxes API ParseBody templateCache.ResolveAlias + Get 校验 team limit / network / volumeMount orchestrator.CreateInstance (gRPC) 返回 sandbox metadata

  2. 连接已存在 sandbox:POST /sandboxes/{id}/connect 先 KeepAliveFor 若运行中则直接返回 若不存在则查询最后 snapshot 基于 snapshot 调 startSandbox 恢复

  3. 暂停/恢复 sandbox:POST /sandboxes/{id}/pause|resume pause: orchestrator.RemoveSandbox(带状态机检查) resume: 检查当前状态 -> 拉取 snapshot -> startSandbox

  4. 触发模板构建:POST /v3/templates + POST /v2/templates/{templateID}/builds/{buildID} v3 负责注册 build 请求和元数据 v2 start 负责选择 builder node 并调用 template-manager.CreateTemplate 后续通过 build status / logs API 轮询进度

  5. 卷创建与删除:POST /volumesDELETE /volumes/{id} 写 DB 记录 调用 orchestrator Volume gRPC 创建或删除底层目录/卷 删除采用异步清理策略

9. 服务启动与流量路径

packages/orchestrator/main.go 看,节点内同时跑多类服务:

  1. sandbox proxy(数据流量代理)。
  2. tcp egress firewall(出网限制)。
  3. network/nbd/template cache 等底层资源管理。
  4. hyperloop HTTP server。
  5. gRPC server(sandbox/volume/template/info 服务)。
  6. cmux 在同端口复用 HTTP 健康检查与 gRPC。
  7. 可选 nfs proxy + portmapper(有持久卷挂载时)。

外部典型链路:

SDK/CLI -> API -> Orchestrator gRPC -> Sandbox Sandbox 会话流量 -> Client Proxy -> 对应节点 sandbox 日志/指标 -> OTel/Vector -> Loki/ClickHouse/Grafana

D. 运行限制与机制要点

10. 使用方限制与兼容性要求

这部分是“真正会踩坑”的地方,尤其是你提到的旧机器兼容问题。

  1. 仅 Linux 作为有效运行基座(本地开发文档也明确 Linux 必需)。
  2. 强依赖 NBD 与 hugepages。 本地开发和 orchestrator 文档都要求先 modprobe nbd、配置 vm.nr_hugepages,没有这些能力会直接卡住。
  3. GCP 是主路径,AWS 还不是同等成熟路径。 代码结构上 provider-gcp 明显完整,provider-aws 目前只是骨架。
  4. 并发、时长、资源都有硬限制,不是无限开箱即用。 包括团队并发、单团队资源上限、每次查询上限、上传体积上限等。

11. 你提到的 6.x 内核新特性问题

这个仓库里确实有“新内核特性依赖”。

  1. overlayfs 使用了 fsconfig/fsmount 新挂载接口,代码注释明确要求内核 6.8+。 这意味着在更老内核上,模板构建或相关挂载流程可能失败。
  2. 默认 sandbox guest kernel 是 vmlinux-6.1.158。 这是 guest 内核版本,不等于宿主机内核版本要求。
  3. UFFD(userfaultfd)路径使用了 UFFD_FEATURE_WP_ASYNC 测试和相关逻辑。 这属于较新的内核能力域,旧内核或裁剪过的内核配置可能出现功能降级或不可用。
  4. cgroup 代码里有 kernel 6.12+ 的 TODO(per-FD peak reset)。 说明团队已经在对接更高版本内核特性,但该项当前还不是硬依赖。

结合起来可以给一个实用判断:

  1. 如果机器内核 < 6.8,先假设“模板构建挂载链路不可用”。
  2. 即便内核足够新,也要确认内核模块与系统参数(nbd、hugepages、namespace、nftables/iptables)齐全。
  3. 对旧机器最稳妥的路径是先跑最小化本地 smoke,再进云上完整部署。

12. 网络机制是怎么实现的

网络不是“简单放行”,而是多层控制:

  1. 每个 sandbox 分配独立 network namespace + veth/tap。
  2. host 侧通过 iptables/nat 建立转发和地址转换。
  3. nftables 做 slot 级规则集,内置默认 deny 私网段(10/8、172.16/12、192.168/16 等)并允许必要控制面地址。
  4. TCP 流量按端口分流到三类防火墙代理口: 80 走 HTTP Host 检查 443 走 TLS SNI 检查 其他端口走 CIDR-only 路径(避免 SSH 这类 server-first 协议被阻塞)
  5. 对域名 allowlist 场景,代理会二次校验解析 IP,避免 DNS 伪造把合法域名导向内网地址。

默认行为上:

  1. 若未设置 egress 规则,默认允许访问。
  2. 若显式 allowInternetAccess=false,会注入 0.0.0.0/0 deny(全网禁出)。
  3. 若设置了 allowOut 域名,会自动补 DNS 服务器地址以保证域名可解析。

13. allowOut 域名的代码级实现细节

你前面问到“infra 怎么知道要访问哪个域名”,核心是运行时从连接里提取,不是预先声明。

  1. 端口分路 在 tcp firewall 里,80/443/其他端口分成三条监听路径。 80 用 HTTP Host,443 用 TLS SNI,其他端口只做 CIDR 检查。

  2. 443 的 SNI 解析来源 SNI 解析不是 infra 自己写 TLS 解析器,而是调用 github.com/inetaf/tcpproxy。 代码通过 AddSNIMatchRoute 注册 TLS 路由,再从 tcpproxy.Conn.HostName 读取解析后的主机名。

  3. allowOut 的前置校验 当 allowOut 里出现域名时,API 层强制要求 denyOut0.0.0.0/0(ALL_TRAFFIC)。 原因是默认策略是 allow,如果不先 block-all,域名 allowlist 没有实际约束力。

  4. 地址与域名拆分 allowOut 会被拆成 AllowedCidrsAllowedDomains。 若有域名,系统会自动把默认 DNS (8.8.8.8) 加入允许地址,避免“配置了域名却无法解析”。

  5. 域名匹配规则 支持三种: 精确匹配(api.openai.com) 全通配(*) 后缀通配(*.example.com

  6. 防 DNS 绕过 命中域名规则后,代理会按域名重新拨号,并在连接前校验解析出的 IP 是否落入内网/保留网段。 如果解析到内网地址会直接阻断,避免沙箱内 /etc/hosts 或 DNS 污染把白名单域名导向内网。

  7. 非 HTTP/TLS 的边界 非 80/443 流量没有 Host/SNI 可读,域名规则无法发挥作用,只能按 IP/CIDR 控制。 这也是为何数据库、私有协议等出站策略通常要配 CIDR。

14. 其他容易忽略的实现细节

  1. allowInternetAccess=false 和网络规则会叠加 如果显式禁网,会覆盖成 deny all;不是简单与 allowOut 并列。

  2. maskRequestHost 会做 ASCII 校验 非 ASCII 主机名会在 API 层被拒绝,避免混淆域名输入。

  3. 查询接口有明确批量上限 如 sandbox metrics 一次最多 100 个,避免观测接口把后端打挂。

  4. 并发限制在多层生效 团队级并发、节点级 sandbox 上限、节点级“启动中 sandbox 上限”同时存在。

  5. 构建与恢复高度依赖 feature flags 包括 chunker 行为、缓存策略、限流参数、firecracker 版本映射等,线上行为不只由代码版本决定。

15. 存储、缓存与快照机制

这个仓库的存储是“对象存储 + 本地/NFS 缓存 + 差分块”组合。

  1. 存储后端抽象为 StorageProvider,支持 GCP bucket、AWS S3、本地文件系统。
  2. 默认模板内核和构建产物路径是对象存储(GCS/S3),通过 OpenSeekable/OpenBlob 抽象访问。
  3. 有 NFS/本地缓存包装层 WrapInNFSCache,减少重复远端读取。
  4. 差分文件(memfile/rootfs)通过 chunker 按块读取,支持流式和缓存。
  5. 快照上传不是整盘覆盖,而是: snapfile + metadata + memfile diff + rootfs diff 组合上传。

这套设计的直接收益:

  1. 冷启动与恢复速度更可控。
  2. 网络和对象存储流量成本可压缩。
  3. 快照链条支持增量构建和恢复。

16. 计费与用量统计是怎么做的

从代码看,这个仓库更像“计量与事件采集层”,不是完整结算系统。

  1. sandbox 创建/关闭会发 InstanceStarted/InstanceStopped 事件,携带 CPU/RAM/Disk/Duration 等字段。
  2. 团队指标查询走 ClickHouse(如 concurrent_sandboxessandbox_start_rate)。
  3. API 会基于团队 limits 在入口处做配额拦截(并发数、CPU、内存、超时时长)。
  4. 当团队并发超限时,接口直接返回上限错误,并给出 billing 文档链接。

实务上可理解为:

  1. 平台内核已有“可计费数据采集能力”。
  2. 真正出账、套餐、财务对账逻辑很可能在这个仓库之外的产品层实现。

17. 关键限制参数清单

按代码里可见的硬限制整理如下:

  1. API 请求体限制: 主 API 约 16 MiB hyperloop API 约 256 MiB
  2. 列表与批量查询限制: /v2/sandboxes 分页上限 100 /sandboxes/metrics 每次最多 100 个 sandbox dashboard build 列表上限 100
  3. 资源配额限制: 团队并发由 SandboxConcurrency 控制 CPU、内存上限受 team limits 约束 运行时长受 MaxLengthHours 限制
  4. 节点侧限制: 每节点最大 sandbox 数受 feature flag 控制(默认 200) 启动中 sandbox 也有限流(避免节点瞬时抖动)

18. 部署风险与建议

如果你准备真正自托管,建议按下面优先级排查:

  1. 内核与模块能力先行 先确认 nbdhugepages、namespace、nftables/iptables、overlayfs 新挂载接口能力。
  2. 云资源配额预申请 文档里提到 GCP 配额可能不足,实际部署前先扩容 CPU/SSD 等关键配额。
  3. Secrets 与域名链路 Cloudflare、Postgres、JWT、可观测性 token 缺一项都可能半成功半失败。
  4. 先最小化上线再扩容 先跑 API + orchestrator + client-proxy + postgres + redis 的最小闭环,再加 clickhouse/loki/otel。
  5. 把 feature flags 当“运行时开关系统” 这个项目大量行为受 feature flags 控制,生产环境要先定义旗标基线。

E. 上手资料与索引

19. 分层架构图

下面是按控制面、数据面、日志面、存储面分层的一张简化图。

  flowchart TB
    subgraph Clients["使用侧"]
      SDK["SDK / CLI / Dashboard"]
    end

    subgraph Control["控制面"]
      API["packages/api"]
      TM["template-manager (gRPC)"]
      ORCH["orchestrator (gRPC)"]
      DB["PostgreSQL"]
      REDIS["Redis (catalog/state)"]
      FF["LaunchDarkly feature flags"]
    end

    subgraph DataPlane["数据面"]
      CP["client-proxy"]
      SBX["Sandbox (Firecracker + envd)"]
      NET["tcp firewall + network namespace"]
    end

    subgraph Storage["存储面"]
      OBJ["GCS/S3 buckets"]
      CACHE["NFS/local cache"]
      SNAP["snapshot diffs (memfile/rootfs)"]
    end

    subgraph Observability["日志与观测面"]
      OTEL["OTel collector / vector"]
      CH["ClickHouse"]
      LOKI["Loki"]
      GRAF["Grafana / metrics API"]
      PH["Posthog / analytics collector"]
    end

    SDK --> API
    API --> DB
    API --> REDIS
    API --> TM
    API --> ORCH
    API --> FF

    SDK --> CP
    CP --> SBX
    ORCH --> SBX
    ORCH --> NET

    TM --> OBJ
    ORCH --> OBJ
    ORCH --> CACHE
    ORCH --> SNAP

    API --> PH
    ORCH --> OTEL
    OTEL --> CH
    OTEL --> LOKI
    CH --> GRAF

20. 最小可运行自托管步骤(dev)

这里是“只保留必填项”的最小路径,目标是先把核心闭环跑起来。

  1. 准备 .env.dev
1
cp .env.template .env.dev
  1. 先填这 5 个必填变量(来自 .env.template Required block)
1
2
3
4
5
GCP_PROJECT_ID=...
GCP_REGION=...
GCP_ZONE=...
DOMAIN_NAME=...
POSTGRES_CONNECTION_STRING=...
  1. 选择环境并登录云
1
2
make set-env ENV=dev
make provider-login
  1. 初始化基础资源
1
make init
  1. 构建并上传服务与运行时产物
1
2
make build-and-upload
make copy-public-builds
  1. 在 GCP Secret Manager 填关键密钥 必须至少补齐: e2b-cloudflare-api-token e2b-postgres-connection-string

  2. 先部署基础设施,再部署 jobs

1
2
make plan-without-jobs && make apply
make plan && make apply
  1. 初始化集群基础数据(用户/团队/基础模板)
1
make -C packages/shared prep-cluster
  1. 快速健康检查
1
curl -fsSL "https://api.${DOMAIN_NAME}/health"

21. 真实 API 示例(curl)

下面三条就是你提到的最常见链路:创建 sandbox、连接 sandbox、查看模板构建日志。

先准备环境变量:

1
2
3
4
5
export E2B_DOMAIN="api.<your-domain>"
export E2B_API_KEY="e2b_xxx"
export TEMPLATE_NAME_OR_ID="base"
export TEMPLATE_ID="<template-id>"
export BUILD_ID="<build-id>"
  1. 创建 sandbox
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -sS -X POST "https://${E2B_DOMAIN}/sandboxes" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ${E2B_API_KEY}" \
  -d '{
    "templateID": "'"${TEMPLATE_NAME_OR_ID}"'",
    "timeout": 300,
    "metadata": {
      "source": "blog-example"
    }
  }'
  1. 连接(续期)sandbox 先把上一步返回里的 sandboxID 记成变量:
1
export SANDBOX_ID="<sandbox-id>"
1
2
3
4
5
6
curl -sS -X POST "https://${E2B_DOMAIN}/sandboxes/${SANDBOX_ID}/connect" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ${E2B_API_KEY}" \
  -d '{
    "timeout": 300
  }'
  1. 查看模板构建日志
1
2
curl -sS "https://${E2B_DOMAIN}/templates/${TEMPLATE_ID}/builds/${BUILD_ID}/logs?limit=100&direction=forward" \
  -H "X-API-Key: ${E2B_API_KEY}"

22. 关键源码定位索引

下面这些路径可以直接对应本文关键结论:

  1. 443 SNI 分流入口: packages/orchestrator/internal/tcpfirewall/proxy.goAddSNIMatchRoute
  2. Host/SNI 提取与 egress 判定: packages/orchestrator/internal/tcpfirewall/handlers.go
  3. allowOut 校验(域名要求 denyOut=0.0.0.0/0): packages/api/internal/handlers/sandbox_create.govalidateNetworkConfig
  4. 地址/域名拆分与默认 DNS: packages/api/internal/orchestrator/create_instance.go + packages/shared/pkg/sandbox-network/firewall.go
  5. network namespace/veth/tap 与 NAT 规则: packages/orchestrator/internal/sandbox/network/network.go
  6. nftables 规则与私网 deny: packages/orchestrator/internal/sandbox/network/firewall.go
  7. 存储后端抽象(GCP/AWS/Local): packages/shared/pkg/storage/storage.go + storage_google.go + storage_aws.go
  8. 快照上传(snap + metadata + diffs): packages/orchestrator/internal/sandbox/snapshot.go
  9. 团队并发配额拦截: packages/api/internal/orchestrator/create_instance.go
  10. 团队指标查询(ClickHouse): packages/api/internal/handlers/team_metrics.go

23. 我对这个仓库的工程判断

  1. 这是偏“平台工程”的仓库,不是单体应用仓库,理解成本主要在跨层调用而不是单个函数复杂度。
  2. OpenAPI + 代码生成 + middleware 校验让接口契约比较明确,便于二次开发。
  3. 运行稳定性主要依赖 Nomad job 参数、feature flags 和集群容量配置,业务代码只是其中一环。
  4. 真正的部署门槛在云资源配额、Secrets、网络与观测栈联通,而不是 go build

第二部分:核心机制深挖(按模块)

A. 控制面与调度

24. 控制面内部状态机与失败回滚

这一层最关键的是“创建/删除/暂停/恢复”不是直接打点执行,而是先做状态占位、再调节点、最后统一收敛。

  1. 创建链路 packages/api/internal/orchestrator/create_instance.gosandboxStore.Reserve(...) 占位并做并发协同,再进入 placement 与 gRPC 创建。 如果中途失败,会走 deferred cleanup,调用 remove 逻辑做回收,避免“DB 有记录但节点上没有实例”。
  2. 删除链路 packages/api/internal/orchestrator/delete_instance.go 通过 StartRemoving 做去重,区分 StateActionKillStateActionPause。 对无效状态转换(如已经不在可暂停状态)有 InvalidStateTransitionError 分支,避免重复操作把状态机打乱。
  3. 暂停链路 packages/api/internal/orchestrator/pause_instance.go 先触发 orchestrator pause/snapshot,再写 paused sandbox 元数据(含 auto resume config)。 有 PauseQueueExhaustedError 明确错误类型,避免把“队列满”与“系统故障”混为一类。
  4. 超时淘汰链路 packages/api/internal/orchestrator/evictor/evict.go 到期后按策略选择 pausekill,并在优雅退出时等待 in-flight eviction 完成。

这意味着控制面的重点不是“一个接口对应一个动作”,而是“动作 + 幂等 + 回滚 + 统计事件”四件套一起执行。

25. 调度与容量管理细节

调度由两层组成:API 侧 placement 决策 + Nomad node pool 资源供给。

  1. placement 算法层 packages/api/internal/orchestrator/placement/* 可见 best-of-k、cpu 兼容性、placement benchmark 等实现与测试,说明调度不是纯随机,而是有资源拟合与性能对比机制。
  2. 节点状态层 packages/api/internal/orchestrator/nodemanager/* 维护节点状态映射(healthy/draining/unhealthy)和 in-progress placement 指标,避免瞬时过量启动压垮单节点。
  3. Nomad node pool 层 iac/provider-gcp/variables.tf + iac/provider-gcp/nomad/main.tf api/build/clickhouse/loki/orchestrator 拆分 node pool,便于按角色扩容。
  4. 自动扩缩容层 iac/provider-gcp/nomad/jobs/nomad-autoscaler.hcl + packages/nomad-nodepool-apm/plugin/plugin.go 自定义 APM 插件按 node pool 统计 ready+eligible 节点数,给 autoscaler 提供扩缩容信号。

推断: 从代码结构看,它更偏“节点池容量驱动”的粗粒度扩缩,而不是“按租户精细弹性”。

B. 构建系统与产物发布

26. 模板构建完整链路与缓存策略

你现在文档已有 API 名称,但“构建内部如何走”还可以更细。

  1. 构建请求入口 spec/openapi.yml/v3/templates/v2/templates/{templateID}/builds/{buildID}.../status.../logs
  2. 构建执行核心 packages/orchestrator/internal/template/build/* 按 phase 执行,失败会在 phase 上报状态,日志走 build logs API 可查询。
  3. 增量构建能力 packages/orchestrator/README.md 里的 create-buildresume-buildshow-build-diffinspect-build。 从命令与参数可见支持 from-build/to-build 链式增量,非每次全量重建。
  4. 缓存与块层 packages/orchestrator/internal/sandbox/block/* + SNAPSHOT_CACHE_DIR 差分块、快照缓存、chunk cache 共存,核心目标是降低恢复与构建 I/O。
  5. 构建取消与治理 spec/openapi.yml 存在 admin cancel builds 接口,支持团队级停止正在进行构建。

推断: 它把“构建系统”做成了可独立运维子系统,不只是模板字段存库。

26.1 后台如何自己完成镜像制作(代码链路)

如果从“平台后台怎么自己做出镜像”看,完整链路是:

  1. 客户端先上传 COPY/ADD 需要的文件包 调用 GET /templates/{templateID}/files/{hash} 请求上传地址(返回 signed URL 和 present)。 对应代码: packages/api/internal/handlers/template_layer_files_upload.go packages/orchestrator/internal/template/server/upload_layer_files_template.go packages/orchestrator/internal/template/build/storage/paths/paths.go 对象存储路径是 cacheScope/files/{hash}.tar
  2. 触发构建 调用 POST /v2/templates/{templateID}/builds/{buildID},提交 fromImage/fromTemplate/steps/startCmd/readyCmd/force/fromImageRegistry。 对应代码: packages/api/internal/handlers/template_start_build_v2.go
  3. API 侧落库并选择 builder 节点 API 会校验团队权限、写入 build 元信息(包括 builder node、CPU 信息、dockerfile/steps JSON),然后调用 template-manager。 对应代码: packages/api/internal/handlers/template_start_build_v2.go
  4. API 把请求转成 gRPC TemplateCreate CreateTemplate 会组装 TemplateConfig,把 steps 转成 TemplateStep(含 filesHash),并异步启动状态同步。 对应代码: packages/api/internal/template-manager/create_template.go packages/orchestrator/template-manager.proto
  5. template-manager 收到请求后异步启动 build goroutine 先创建 build cache 与内存日志缓冲,再调用 builder.Build(...);失败会回填 reason。 对应代码: packages/orchestrator/internal/template/server/create_template.go packages/orchestrator/internal/template/cache/build_cache.go
  6. 实际镜像构建由 phase 管线完成 构建器注释里给出了主流程:拉基础镜像 -> 注入基础层 -> 提取 rootfs -> 启动 FC VM 做 provisioning -> 执行用户 steps -> finalize(startCmd/readyCmd) -> snapshot -> 上传模板与缺失层。 对应代码: packages/orchestrator/internal/template/build/builder.go packages/orchestrator/internal/template/build/phases/base/* packages/orchestrator/internal/template/build/phases/user/* packages/orchestrator/internal/template/build/phases/steps/* packages/orchestrator/internal/template/build/phases/finalize/* packages/orchestrator/internal/template/build/phases/optimize/*
  7. COPY/ADD 在后台如何落地 执行 step 时,commands/copy.go 会按 filesHash 从对象存储下载 tar,传入 sandbox 的 /tmp 解包后搬运到目标路径,并处理 owner/permissions。 对应代码: packages/orchestrator/internal/template/build/commands/copy.go
  8. 缓存与上传策略 构建会使用 hash index + layer upload tracker,避免重复上传;可选 NFS cache 降低对象存储读压。 对应代码: packages/orchestrator/internal/template/build/builder.go packages/orchestrator/internal/template/build/storage/cache/* packages/orchestrator/internal/template/build/layer/*
  9. 状态与日志如何被上层看到 template-manager 的 TemplateBuildStatus 从 build cache 返回状态与日志;API 再聚合为 /status/logs。 对应代码: packages/orchestrator/internal/template/server/template_status.go packages/api/internal/handlers/template_build_status.go packages/api/internal/handlers/template_build_logs.go
  10. 失败处理与回收 构建失败会把错误转成用户可读 reason(phase + message);并删除失败 build 的对象存储前缀,减少脏产物。 对应代码: packages/orchestrator/internal/template/build/builderrors/errors.go packages/orchestrator/internal/template/build/builder.go

一句话总结: 这个项目的“镜像制作”不是外部 CI 做完再导入,而是平台内部通过 template-manager + orchestrator phase pipeline 在运行时完成构建、快照和发布。

C. 安全、网络与数据面

27. 安全模型与权限边界

认证在 API 层是组合认证器,不是单一 token。

  1. 认证链 packages/api/main.go ApiKeyAuthenticatorAccessTokenAuthenticatorSupabaseTokenAuthenticatorSupabaseTeamAuthenticatorAdminTokenAuthenticator 组合执行。
  2. 契约层 spec/openapi.yml 定义了 ApiKeyAuthAccessTokenAuthSupabase1TokenAuthSupabase2TeamAuthAdminTokenAuth
  3. 团队边界 大量接口显式携带 teamID 或从 auth context 解出 team,日志也在鉴权后附带 team 信息。
  4. 数据面访问边界 sandbox URL 可配置认证访问(spec 字段描述已体现),并配 access token 机制给 envd 通信。

还缺少的文档化项:

  1. 密钥轮换 SOP(API key、admin token、supabase secrets)。
  2. 最小权限策略(谁能调 admin 接口,谁能取消团队构建)。
  3. 审计日志归档与保留策略。

28. 网络策略边界与协议差异

你前面已写 80/443/其他端口三路分流,这里补“边界行为”:

  1. 域名规则只对可提取 Host/SNI 的流量有效。 非 80/443 的 server-first 协议,无法可靠取域名,只能走 CIDR 规则。
  2. allowOut 域名白名单并不等于 DNS 放行所有地址。 实现里会对解析结果做私网拦截校验,防止域名绕过到内网。
  3. 默认策略与 deny all 的关系。 如果不先 denyOut=0.0.0.0/0,域名 allowlist 约束会被默认放行语义弱化。
  4. 规则层次。 API 参数校验 -> 控制面下发 -> 节点 nftables/iptables 执行 -> tcp firewall 运行时判定。

这也是为什么同一个 allowOut 配置在 HTTP 请求和数据库连接上表现可能不同。

29. 存储、卷、快照与一致性语义

  1. 对象存储抽象 packages/shared/pkg/storage/* 支持 GCP/AWS/Local,统一 OpenBlob/OpenSeekable 能力。
  2. 快照内容 packages/orchestrator/internal/sandbox/snapshot.go 由 snapfile + metadata + memfile diff + rootfs diff 组成,不是整盘镜像覆盖。
  3. 卷服务 packages/orchestrator/internal/volumes/service.go 有 teamID/volumeID 校验,路径拼接有安全检查,防止 path traversal。
  4. 缓存层 NFS/local cache + chunk cache + snapshot cache 三层组合,目的是降对象存储读压。

一致性语义建议在文档中明确:

  1. pause 快照是“近实时恢复点”,不是跨版本强一致事务快照。
  2. volume 删除是资源删除语义,不保证应用级事务回滚。
  3. 多节点缓存下,读到旧构建/旧快照的窗口期取决于缓存刷新策略。

D. 运维、演进与商业化

30. 可观测性、告警与排障手册

项目里“采集能力”已经有,但“怎么运维”文档还不够。

  1. 采集路径 服务 -> OTel -> ClickHouse/Loki -> Grafana(部分场景还接 Posthog)。
  2. 关键观测源 packages/orchestrator/main.go 初始化 sandbox observer、host stats、clickhouse event delivery。
  3. 指标数据模型 ClickHouse migrations 已有 sandbox_metrics_gaugeteam_metrics_gauge/sumsandbox_events
  4. 健康状态模型 服务状态至少有 Healthy/Draining/Unhealthy,并且 health handler 会映射输出。

建议在文档新增默认告警基线:

  1. API 5xx 比例、P95 延迟、create sandbox 失败率。
  2. 节点 ready 数与 draining 比例。
  3. pause queue exhausted 计数。
  4. build 卡在 building 状态超时。
  5. ClickHouse 写入失败与积压。

31. 升级、回滚与兼容矩阵

当前代码与 IaC 已具备升级基础,但文档没形成完整操作手册。

  1. Nomad job 升级策略 多个 job 配了 canary、stagger、auto_promote、auto_revert。
  2. 服务优雅下线 orchestrator/client-proxy 有显式 draining 流程,并等待传播后再关服务。
  3. 杀进程时间窗口 Nomad client max_kill_timeout 可到 24h,兼容长连接和长任务。
  4. 兼容风险 已知内核能力依赖(如 overlayfs 新挂载接口、UFFD 特性),旧机器可能直接不可用。

建议加一个兼容矩阵表(最低要求):

  1. Host kernel 版本门槛。
  2. Firecracker 版本与 guest kernel 对应关系。
  3. nbd/hugepages/cgroup/network namespace 必要项。
  4. 文件系统与磁盘类型建议。

32. 多云与国内化差异说明

  1. 官方主路径是 GCP self-host.mdiac/provider-gcp/* 明显最完整。
  2. AWS 支持仍偏骨架 仓库存在 provider-aws,但覆盖度低于 GCP。
  3. 对象存储抽象有利于替换 即使官方模板偏 GCS,运行时代码通过 storage provider 抽象可落 S3 兼容服务。

推断: 从工程成熟度看,GCP 是“默认产品路径”,其他云厂商是“可移植但需要工程补完”。

33. 计量到计费闭环

现在文档写了“有计量”,但业务上还缺“如何结算”的闭环描述。

  1. 已有的计量输入 InstanceStarted/InstanceStopped 事件、team/sandbox metrics、product usage 相关迁移历史。
  2. 已有的数据承载 ClickHouse 表与 TTL 策略(如 team metrics 延长到 90 天的迁移)。
  3. 已有的限额执行点 API 入口会依据 team limits 做硬拦截(并发、资源、时长)。
  4. 尚未在本仓库内完整可见的部分 账单出具、套餐定价、补扣费、对账流程更像在产品外围系统实现。

建议文档补充一个“计费闭环时序图”:

  1. usage 采集(create/pause/kill/build)。
  2. 归档与聚合(分钟/小时/天)。
  3. 限额联动(软提醒/硬拦截)。
  4. 对账与账单导出。

第三部分:收束与参考

A. 阅读边界

34. 补充后的阅读边界

到这里,这篇文章已经覆盖了“理解项目并可落地部署”的大部分关键面,但仍有两类内容建议后续单开文章:

  1. 实操 runbook 面向 SRE 的逐条故障手册(症状 -> 查询命令 -> 处理步骤)。
  2. 安全与合规 密钥生命周期、审计合规、数据保留与删除策略。

参考链接

#E2b #Sandbox #Terraform