任何在云端运行代码的 agent 都需要某种 sandbox。这一篇是针对一个特定选择:如何在 AWS 和 Kubernetes 上自建 sandbox 层。这是 Amplitude 的 agent 平台实际出货的架构,对于其他团队也适用。
你应该构建自己的 sandbox 基础设施吗?
对于大多数团队,定制 sandbox 层可能不值得努力,但你仍然需要正确地运维 sandbox 基础设施。这篇文章(特别是 secrets proxies 和 plugins)即使你使用的是 vendor 基础设施也可能有帮助。
E2B、Daytona、Ctrlz、Cal 的供应商可以在一个下午内让你跑起 agent 代码。它们工程化得很好,冷启动处理得好,按秒计费。你放弃的是:
- 数据驻留(E2B、Daytone 和一些其他支持 managed on-prem 和自托管)
- 端到端所有权。你得到 vendor 提供的东西
- 每个模板的灵活性。偏离既定路径后,定制镜像和定制工具会变得困难
对于大多数团队,这些权衡是可以接受的。
先看看开源选项。它们在托管 vendor 和完整自建之间,可能比你走的路更容易。
如果需要 snapshot/restore 或 warm pools 在你的路线图上,评估开源栈是否已经覆盖了这些,然后再投入自建。
Amplitude 自建是因为他们需要严格的数据驻留、与 Amplitude 现有集群绑定的定制网络策略,以及与 agent 平台配置 agent 方式一致的 sandbox 开发者体验。当这些约束对你重要时,控制一个接近端到端的平台也有价值。
核心架构
每个 agent 运行获得自己的短生命周期 microVM pod。Agent 平台拥有生命周期。网络默认锁定,secrets 永远不会进入 pod。
控制平面是单一接口 SandboxBackend。工具和 agent runtime 持有一个 backend 实例,从不直接接触 Kubernetes。
每个 agent 运行获得自己的短生命周期 microVM pod。Agent 平台拥有生命周期。网络默认锁定,secrets 永远不会进入 pod。
数据平面:Sandbox handle
get_or_create 为新 sandbox 时:
- 创建 SandboxClaim 资源,key 为 sandbox_id。SIG controller 选取并在 sandbox nodepool 上调度 pod。
- 观察直到 pod 报告 ready,然后读取其 IP。
- 铸造 per-sandbox proxy token,持久化
{sandbox_id, allow-list}到 Redis,并将 token 写入 pod 的文件系统。 - 运行每个 plugin 的
setup()(克隆 repos、drop 配置文件)再返回。
handle 返回的是一个包装了一个小型 FastAPI 服务器的 HTTP client,该服务器在每个 pod 内作为 PID 1 运行。
handle 暴露的操作包括:exec 和 exec_stream(命令执行)、read_file、write_file、list_files(文件系统)、start_process、get_process、kill_process、get_process_logs(后台进程)、wait_for_port、wait_for_exit(阻塞等待)、passthrough_request 和 passthrough_stream(代理到 pod 内的 localhost)。
三种运行模式
Out of sandbox(默认):
- 编排 LLM 留在平台上
- Sandbox 暴露 shell、fs 和 python 工具桥接到 pod
- 最适合作为更大推理循环的一部分需要执行任意代码的 agent
In sandbox:
- 一个完整的 agent runtime 完全在 pod 内运行(浏览器 agent、研究 agent 等)
- 平台通过 in-pod HTTP passthrough router 驱动它,将调用转发到 agent 的 localhost API
- 最适合长自主 agent 循环,你希望 runtime 的整个工具 surface 在 sandbox 边界后面
Hybrid( delegation):
- 编排 LLM 留在平台上,但一个完整的 agent runtime 在 pod 内运行用于重活(今天通常是 coding agent 如 Claude Code 或 Codex)
- 平台通过相同的 passthrough router 驱动 in-pod agent;较轻的工具(shell、fs)仍然可以桥接进来用于 prep 或检查
- 最适合当你想要平台侧编排但有专用 in-pod agent 在 sandbox 边界后面时
插件层:Secrets 永不入 Pod
Sandbox 从空开始,没有 baked 进镜像的凭证,这保持了隔离模型的简单性。但大多数 agent flow 在运行时仍然需要出站访问:调用模型 API、克隆私有 repo 或写入指向 secret proxy 的配置文件。没有插件层,你倾向于在平台上使用一次性 setup 脚本、bake 进镜像的 secrets 或注入 pod 的 env vars——所有这些都与你最初想要的隔离模型背道而驰。
插件是解决这个问题的机制。你在 agent YAML 中声明 sandbox 允许做什么;平台在 pod 交给 agent 之前运行 setup 并 wire 代理 allow-list。每个插件是一个可复用、可组合的单元,最多有两个角色:
- Setup。 克隆 repos、drop 配置文件、在 boot 时安装 per-sandbox state。
- Secrets-proxy。 处理到特定上游的出站 HTTP,通过重写请求、交换凭证并在有用时转换 vendor API 之间的差异。
安全五层
没有单一层本身是足够的。架构设计成这样:任何一个失败(内核-syscall escape、错误配置的 NetworkPolicy、泄漏的凭证)都不会给 agent 一条通往集群、生产网络或其他租户的路径。有五层值得讨论,每层相对便宜;组合起来才是使整体设计可防御的原因。
第一层:Kata microVM
每个 sandbox 作为 Kubernetes 标准 runtimeClassName 字段的 Kata microVM 运行。每个 pod 有自己的内核,所以 syscall escape 落在 sandbox VM 内部而不是主机上。这是你可以对普通 runc 容器做的最大单一安全升级;如果今天运行 gVisor,Kata 是多租户 agent 工作负载的强选择。
第二层:Restricted Pod Admission
sandbox namespace 强制执行 restricted pod security standard。这给了 sandbox 非 root 容器、所有 Linux capabilities 被 drop、没有 privilege escalation、没有 host namespaces 和 default seccomp profile。Service-account token automounting 是关闭的,所以 sandbox 即使弄清了 API 服务器在哪里也无法到达 Kubernetes API。
第三层:网络隔离
每个 sandbox 以 default-deny egress 策略开始,然后通过 pod label 选择加入两级之一:
- Standard tier。 HTTPS 到公共互联网和 secret proxy。RFC1918 范围(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)和 link-local(169.254.0.0/16)被明确排除。
- Restricted tier。 仅 secret proxy。对于不应该直接接触公共互联网的 agent。
RFC1918 排除比最初看起来更重要。它切断了 AWS 实例元数据(IMDS)、peered VPC、其他 in-cluster ClusterIPs 和 Kubernetes API 本身。
第四层:Platform-Only Ingress
单独的 ingress NetworkPolicy 将每个 sandbox pod 锁定为仅接受来自 agent 平台 pods 的连接。没有 sandbox-to-sandbox 流量,即使在同一节点上。Agent 不能相互探查。
第五层:Per-Sandbox Secret Proxy
规则简单:secrets 永远不入 pod,但 tokens 可以。 当 sandbox 创建时,平台生成随机 per-sandbox proxy token,写入 pod 的文件系统,并在 Redis 中存储一个小信封:{sandbox_id, allow-list-of-plugin-types}。
token 是 sandbox 曾经可以访问的唯一凭证形状的对象。当 sandbox 发出需要凭证的出站 HTTP 请求时,它通过 secret proxy,proxy:
- 从请求中提取 token(通常是
Authorization: Bearer <token>) - 在 Redis 中查找 token
- 验证请求的 plugin type 在 token 的 allow-list 中
- 让匹配的 plugin handler 重写 headers、body 或 URL 并注入上游凭证
- 转发请求到上游并流式返回响应
allow-list 按 plugin type keyed,作为能力边界。一个只声明 anthropic 的 agent flow 不能使用相同的 proxy token 克隆私有 GitHub repo,即使它弄清了 URL。Capabilities 是 per-token、静态声明在 agent YAML 中、在 proxy 强制执行,所以 agent 不能在运行时升级。
Warm Pools:将冷启动从 10s 降到 50ms
冷 sandbox 是大约 10 秒的事情:image pull、microVM boot、container start、in-pod server readiness probe。SandboxWarmPool 预拉、预 boot 的 pod 将热路径降到平均约 50ms(对于 VM boot 和 readiness);plugin setup(如克隆 repos 等)仍在配置时运行,通常主导端到端延迟。成本每个 agent 线程只付一次。
SIG 提供了 SandboxWarmPool CRD:声明每个模板 N 个预热 pod,controller 以 FIFO 方式分发并在后台补充池。团队根据流量调整每个模板的副本数量。有稳定流量的模板(如被半打 flows 击中的 coding-agent 或 browser 模板)保持一些 warm pods;长尾模板运行冷。
隔离的 nodepool
Sandbox 流量是突发且不可预测的。将 sandbox 与稳定状态工作负载共同调度会使两者变差:sandbox 在压力下被驱逐,稳定状态工作负载受到 noisy neighbors 影响。带有 aggressive autoscaler 的专用 nodepool 隔离成本尖峰,并保持 noisy neighbors 远离你的核心舰队。
结论
大多数团队不应该从头构建自定义 sandbox 层。托管 vendor 是最快的路径;Kubernetes 上的开源选项在 roll-your-own 之前值得评估。Amplitude 在数据驻留、特定于集群的网络和与 agent 平台一致的 sandbox 体验是不可协商的时候选择了后者。
架构清晰地将关注点分离:小型 SandboxBackend 控制平面配置 claims、运行 plugins 和 mint proxy token;HTTP Sandbox 数据平面保持工具远离 Kubernetes。Agent 作者在同一 YAML 中声明 template、plugins 和 idle TTL,就像模型和工具一样。
安全性堆栈Kata microVM、restricted pod admission、分层 default-deny egress、仅平台 ingress、带静态 allow-list 的 per-sandbox secret proxy,所以凭证永远不入 pod。Warm pools、隔离的 autoscaling nodepool 和 thread-scoped 复用与 idle TTL 保持延迟和成本对突发 agent 流量合理。