本文是「Agent 沙箱技术专题」的接入篇。专题此前的《Agent 沙箱技术核心架构方案:从 microVM 隔离到硬件加速快照的七层设计》讲的是沙箱这件事本身的七层技术栈(隔离边界、快照、压缩、镜像、调度、安全、编排标准);本文换一个视角,只回答一个工程问题:当一个团队已经拥有 Kubernetes 平台,要把 agent 代码执行能力接进来,架构应该怎么搭、按什么顺序落地、哪些坑必须提前知道。
全文以
kubernetes-sigs/agent-sandboxv1.0.4 为准(API 已全量v1beta1)。所有结论标注一手来源;官方尚未公开或仍在 roadmap 中的项,单独列在第 9 节,不做乐观外推。
0. 一句话结论
Kubernetes 官方 Agent Sandbox 不是沙箱,而是沙箱的编排器。官方 README 的第一句 Scope 就是全篇最关键的架构约束:
"Agent Sandbox is a sandbox orchestrator. It delegates low-level container isolation to secure 'Sandbox Runtimes' (like gVisor or Kata Containers) by managing Pods configured to use these runtimes (via RuntimeClass)."
这句话直接决定了接入方案的分工:
| 你想要的 | 谁负责 | 在哪里配置 |
|---|---|---|
| 沙箱生命周期、身份、存储、热池 | Agent Sandbox(本项目) | CRD + 控制器 |
| 内核/网络隔离强度 | Sandbox Runtime(gVisor / Kata / urunc) | podTemplate.spec.runtimeClassName |
| 数据面访问(执行命令、读写文件) | Router + sandboxd + SDK | 第 4 节 |
| 租户隔离、出网管控、凭证最小化 | NetworkPolicy + 准入策略 + RBAC | 第 3、5 节 |
把这条边界记牢,后面 90% 的架构决策都是它的推论。
1. 接入前的治理与版本基线
1.1 项目在 Kubernetes 治理体系中的确切位置
这是接入评审时最容易被问、也最容易答错的一点,先钉死三个口径:
| 问题 | 确切答案 | 来源 |
|---|---|---|
| 是不是 SIG Apps 子项目? | 是,kubernetes-sigs/agent-sandbox,SIG Apps 官方 README 子项目清单中列出,设双周 Subproject Meeting |
kubernetes/community sig-apps/README.md |
有没有 kubernetes/enhancements 的 KEP? |
没有。它走 kubernetes-sigs 子项目路线,不进入 K8s 官方发布节奏 |
检索 kubernetes/enhancements 未命中 |
| 是不是 CNCF 项目? | 不是。CNCF 博客是第三方视角介绍,非托管公告 | cncf.io 相关文章性质 |
| 立项时间 | 2025-11,Google Open Source Blog 正式宣布,并在 KubeCon Atlanta 2025 做技术深潜 | opensource.googleblog.com 2025-11 |
⚠️ 一个高频误读:项目 roadmap 里出现的 "KEP #597""KEP #747" 是 agent-sandbox 仓库内的 PR 编号,不是
kubernetes/enhancements的 KEP。其中 PR #747 的标题才是KEP-539.2: Standardizing Sandbox Runtime Interfaces。写方案文档时务必区分,否则会被评审直接打回。
1.2 版本与安装形态
最新 release 为 v1.0.4。安装清单有三种粒度,对应三种接入策略:
# 全量(核心 + 扩展)——绝大多数生产接入用这个
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.4/sandbox-with-extensions.yaml
# 仅核心(只要 Sandbox,不要模板/热池/Claim)
kubectl apply -f .../v1.0.4/sandbox.yaml
# 仅扩展(配合已装核心)
kubectl apply -f .../v1.0.4/extensions.yaml
此外还提供 Helm Chart、OLM bundle(面向 OpenShift/OperatorHub)、kubectl kustomize k8s/ 源码渲染,以及下游发行版:GKE Agent Sandbox(2026-09 GA,Google 托管 add-on)、Red Hat build of Agent Sandbox。
v1.0.0 是一次硬性分水岭:核心与扩展 API 全面转 v1beta1,移除 v1alpha1 与 conversion webhook。官方给出的升级路径是:先升 v0.5.x → 迁移存储至 v1beta1 → 确认 CRD storedVersions 仅 v1beta1 → 再升 v1.0.0 → 最后清理 webhook 资源。新接入方直接从 v1beta1 起步即可,但不要在方案里写 v1alpha1。
验证安装:
kubectl get crd sandboxes.agents.x-k8s.io
kubectl get deploy agent-sandbox-controller -n agent-sandbox-system
2. 接入架构总览:五层模型
┌──────────────────────────────────────────────────────────────────────┐
│ L5 平台治理接入 准入策略(VAP/OPA) · APF 隔离 · 配额 · 可观测 · RBAC │
├──────────────────────────────────────────────────────────────────────┤
│ L4 运行时接口接入 sandboxd(gRPC:9090 + REST:8080) · SDK 四连接模式 │
├──────────────────────────────────────────────────────────────────────┤
│ L3 网络接入 Sandbox Router(数据面) · 托管 NetworkPolicy(默认拒绝)│
├──────────────────────────────────────────────────────────────────────┤
│ L2 运行时接入 RuntimeClass → gVisor / Kata(+FC) / urunc │
├──────────────────────────────────────────────────────────────────────┤
│ L1 控制面接入 Sandbox · SandboxTemplate · SandboxWarmPool · Claim │
│ + agent-sandbox-controller(声明式 reconcile) │
└──────────────────────────────────────────────────────────────────────┘
↑ 接入顺序建议:L1 → L2 → L3 → L4 → L5(每层可独立验证)
为什么是这个顺序:L1 决定资源模型(后续所有东西都挂在 CRD 上);L2 是安全边界,必须在任何真实负载进来之前确定;L3 决定谁能碰到沙箱;L4 决定 agent 代码怎么被调用;L5 才是规模化与合规。反过来做(比如先上 SDK 再补安全)会返工。
3. L1 控制面接入:四个 CRD 的分工与接线
3.1 四件套的职责矩阵
| CRD | API 组 / 版本 | 职责 | 何时用 |
|---|---|---|---|
Sandbox |
agents.x-k8s.io/v1beta1 |
单个有状态、单例、稳定身份的工作负载 | 裸接入、调试、或不需要模板化治理时 |
SandboxTemplate |
extensions.agents.x-k8s.io/v1beta1 |
可复用的沙箱"蓝图"+ 安全策略载体 | 生产必用:策略只能挂在模板上 |
SandboxWarmPool |
extensions.agents.x-k8s.io/v1beta1 |
预热池,用 replicas 控制规模 |
需要亚秒级分配时 |
SandboxClaim |
extensions.agents.x-k8s.io/v1beta1 |
从热池领取沙箱,屏蔽底层细节 | 多租户按需分配 |
关键设计点:SandboxTemplate 与 Sandbox 共享同一个 SandboxBlueprint(podTemplate + volumeClaimTemplates + service)。官方在类型定义里明确注释:新增字段会同时提升到两者——这意味着"模板"和"裸沙箱"的差异只在策略层(NetworkPolicy 管理、env 注入策略、卷模板策略),而不在工作负载定义层。
3.2 核心字段速查(决定接入能力边界)
Sandbox.spec
| 字段 | 说明 | 接入注意事项 |
|---|---|---|
podTemplate.spec |
完整 PodSpec | 隔离、资源限制、sidecar 都在这里 |
podTemplate.metadata |
仅 labels/annotations | 系统保留键(agents.x-k8s.io/*、extensions.agents.x-k8s.io/*)会被控制器过滤 |
volumeClaimTemplates |
PVC 模板 | 创建后不可变(CRD 有 XValidation 硬约束) |
service |
*bool,是否自动建 headless Service |
不设时保留已有 Service 但不新建——这是向后兼容行为,别误当默认开启 |
operatingMode |
Running / Suspended,默认 Running |
挂起/恢复的正式 API(替代旧的 replicas: 0 语义) |
lifecycle.shutdownTime |
绝对过期时间 | 不设则永不过期 |
lifecycle.shutdownPolicy |
Retain(默认)/ Delete |
只管 Sandbox 对象本身;Pod 与 Service 过期时一律删除 |
Sandbox.status(做监控与就绪判断用)
conditions:Ready(权威信号)、Suspended、PodScheduled(镜像 Pod 的调度原因)、FinishedserviceFQDN、service、selector、podIPs、nodeName
⚠️ 官方类型定义里明确写了一个坑:
Suspended条件在恢复后不会被移除,可能残留。消费方必须把Ready当唯一权威信号,不要靠Suspended条件是否存在来推断当前状态。
3.3 SandboxTemplate:策略的落点
这是生产接入真正需要投入的地方——三个策略字段全部默认最严:
| 字段 | 默认值 | 含义 |
|---|---|---|
networkPolicyManagement |
Managed |
控制器自动生成并维护一份模板级共享 NetworkPolicy |
networkPolicy |
省略 = 严格安全默认 | 入向仅放行 Router;出向仅公网,阻断 RFC1918 与云 metadata |
envVarsInjectionPolicy |
Disallowed |
Claim 不得注入任何环境变量 |
volumeClaimTemplatesPolicy |
Disallowed |
Claim 不得指定卷模板 |
networkPolicyManagement: Unmanaged 会把网络完全交给外部系统(如 Cilium)。官方注释里还留了一个必须提前告知团队的告警:
默认拒绝姿态会阻断 sidecar 端口。如果 Pod 里有 Istio proxy、监控 agent 等 sidecar,必须在
Ingress里显式放行它们的端口,否则健康检查会失败。
3.4 SandboxWarmPool 与 SandboxClaim:热池语义
# SandboxTemplate(节选)
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxTemplate
metadata:
name: secure-datascience-template
spec:
podTemplate:
spec:
runtimeClassName: gvisor # ← L2 在这里接
automountServiceAccountToken: false
containers:
- name: runtime
image: <your-runtime-image>
resources:
limits: { cpu: "2", memory: 4Gi }
---
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxWarmPool
metadata:
name: sandboxwarmpool-example
spec:
replicas: 20 # 可被 HPA/KEDA 接管
updateStrategy:
type: OnReplenish # 默认;Recreate 会立即清掉陈旧未领取沙箱
sandboxTemplateRef:
name: secure-datascience-template # 必须与模板 metadata.name 完全一致
---
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxClaim
metadata:
name: sandbox-workflow-123
spec:
warmPoolRef:
name: sandboxwarmpool-example
lifecycle:
ttlSecondsAfterFinished: 600
shutdownPolicy: DeleteForeground
必须理解的三个语义(都会影响你的容量与延迟设计):
spec.env与spec.volumeClaimTemplates会强制冷启动——因为环境变量与卷无法注入到已在运行的预热 Pod。想吃到亚秒级热池分配,Claim 里就不能设这两个字段。(additionalPodMetadata例外,它会就地应用到被领取的沙箱。)- 领取即转移所有权:沙箱一旦被 Claim 领取,
SandboxWarmPool就不再管理或替换它,由 Claim 控制器接管生命周期。 updateStrategy只作用于未领取的沙箱:OnReplenish(默认)保留陈旧沙箱直到被领取或手动删除;Recreate立即删除陈旧未领取沙箱。已领取的沙箱永远不会被池子动。
SandboxClaim.status 会把 sandbox.name、sandbox.podIPs、sandbox.serviceFQDN 镜像出来——这是接入层做路由与就绪判断的唯一必要读点,不需要再去读 Sandbox 对象。
3.5 标签域白名单:一个容易踩的接入约束
自 v0.5.0 起,SandboxClaim.spec.additionalPodMetadata.labels 的键必须带允许列表内的域名前缀,否则 Claim 会被拒(Ready=False, reason=InvalidMetadata)。默认允许列表是 sandbox.users.io(子域也通过)。
允许列表从控制器命名空间下一个可选 ConfigMap agent-sandbox-config 的 allowed-label-domains 键读取,且文件内容会替换默认值而非追加:
apiVersion: v1
kind: ConfigMap
metadata:
name: agent-sandbox-config
namespace: agent-sandbox-system
data:
allowed-label-domains: |
example.com
sandbox.users.io # ← 别忘了保留它,否则既有消费方会被拒
改完必须 rollout restart 控制器(只在启动时读一次)。注意:additionalPodMetadata 里的 annotations 走的是另一套受限域黑名单(cluster-autoscaler.kubernetes.io/safe-to-evict 被豁免),不受这个白名单管辖。
4. L2 运行时接入:隔离强度是部署选择,不是 API 限制
4.1 接入方式只有一行
spec:
podTemplate:
spec:
runtimeClassName: gvisor # 或 kata-qemu / kata-vm-isolation / urunc
官方文档的定位表述很明确:"Isolation depth is a deployment choice, not a limitation of the API."
官方 examples 中给出 RuntimeClass 接入示例的运行时:
| 运行时 | RuntimeClass 示例名 | 官方示例 | 适用 |
|---|---|---|---|
| gVisor (runsc) | gvisor |
examples/openclaw-gvisor-sandbox、quickstart/overlays/gvisor |
兼容性优先、开销敏感 |
| Kata + QEMU | kata-qemu |
examples/kata-gke-sandbox |
要独立 guest kernel |
| Kata + Firecracker | kata-fc 系 |
examples/firecracker-sandbox |
更快启动的 VM 隔离 |
| Kata on AKS | kata-vm-isolation |
examples/kata-aks-sandbox |
AKS 内置 Pod Sandboxing |
| urunc(社区) | urunc |
社区集成 | unikernel/单应用内核 |
4.2 运行时选型矩阵(隔离强度 × 开销)
| 维度 | gVisor | Kata + Firecracker | 普通 runc 容器 |
|---|---|---|---|
| 隔离机制 | 用户态内核,拦截 syscall | 硬件虚拟化(KVM)+ 独立 guest kernel | 共享宿主内核 |
| 隔离强度 | 中高 | 高 | 低(不是安全边界) |
| 启动开销 | 毫秒级 | ~100–300ms 量级 | 最快 |
| I/O 开销 | 约 10–30%(syscall 密集场景 5–15%) | 接近原生 | — |
| 接入代价 | 装 RuntimeClass 即可 | 需要支持嵌套虚拟化的机型(如 Intel N2) | 零 |
Kata 接入的硬性前置条件(官方 GKE 示例原文要点,踩坑率极高):
- 机型:必须 Intel N2 系列;E2 不支持嵌套虚拟化,AMD(N2D)与 ARM(T2A)也不支持
- OS 镜像:必须 Ubuntu;COS 是只读的,会挡住 installer
- 区域:必须选有 N2 硬件的可用区
官方给了一个非常好的隔离验证方法(接入后必做):
# 宿主内核
kubectl get nodes -o wide # 记下 KERNEL-VERSION
# 沙箱内内核
SELECTOR=$(kubectl get sandbox kata-gke-example -o jsonpath='{.status.selector}')
POD_NAME=$(kubectl get pod -l $SELECTOR -o jsonpath='{.items[0].metadata.name}')
kubectl exec -it $POD_NAME -- uname -r
两者内核版本不同 = 隔离生效;相同 = RuntimeClass 没生效。这个"内核版本差"检查建议直接写进接入验收清单。
4.3 一个必须提前知道的连带影响
启用 gVisor/Kata 后,直接 kubectl port-forward 到 Pod 的方式不再可用,必须改走 Sandbox Router。这直接决定了 L3 的接入形态——也就是说,只要你选了安全运行时,Router 就从"可选"变成"必需"。
5. L3 网络接入:Router 数据面契约 + 默认拒绝
5.1 职责切分:创建与路由分离
创建面(控制) 数据面(路由)
SDK ──► K8s API ──► Controller ──► Pod + Service
▲
HTTP client ──X-Sandbox-*──► Router ─────┘
(Router 无状态,从不创建/查询 Sandbox)
官方对 Router 的定位说得很死:"The router never creates or looks up Sandbox resources." 目标沙箱不存在时返回 502(带重试窗口)。
5.2 请求契约(自建客户端时必须实现)
| 头 | 必需 | 默认 | 说明 |
|---|---|---|---|
X-Sandbox-ID |
✅ | — | 沙箱 Pod 名,必须是合法 DNS-1123 label(≤63 字符) |
X-Sandbox-UID |
— | — | Sandbox CR UID;开启缓存时用于安全快路径(UID 当能力凭证) |
X-Sandbox-Namespace |
— | default |
同样受 DNS-1123 校验 |
X-Sandbox-Port |
— | 8888 |
整数,[1, 65535] |
X-Sandbox-Pod-IP |
— | — | 直连 IP,绕过缓存与 DNS |
解析优先级(首个命中即用):
X-Sandbox-Pod-IP(显式覆盖)- 按
X-Sandbox-UID查缓存(需--cache-enabled=true) - 按
namespace/name查缓存 —— 这一条是热池沙箱可路由的关键,因为热池沙箱没有 per-sandbox Service,DNS 形式必然 NXDOMAIN - DNS 形式
http://<id>.<ns>.svc.<cluster-domain>:<port>(兼容回退)
🔐 一个精妙的安全设计:尚未被任何 Claim 领取的热池 Pod 会被排除出名字索引,只能通过 UID 访问——这就保住了"UID 即能力凭证"的性质,调用方无法靠猜池子生成的名字碰到未领取的池内 Pod。
转发时被剥离的请求头:
Host:让net/http使用上游 URL 的 hostAuthorization:Router 自己消费它(如--authz-mode=tokenreview走 K8s TokenReview),若转发给沙箱,任何沙箱都能冒充调用方去打 K8s API
重试与超时:仅拨号类失败重试(默认 3 次重试 / 共 4 次尝试,200→400→800ms 退避);请求体可能已发送后的失败(响应超时、流中断)立即上抛,因为重放可能造成副作用重复。--proxy-timeout 默认 180s,但对 WebSocket 升级连接不生效(否则会 3 分钟切断健康的 code-server/Jupyter 会话)。
协议升级:Connection: Upgrade 透明转发;升级请求会剥离 Origin——因为 vscode-server/Jupyter 会校验 Origin 与 Host 一致做 CSRF 防护,而 Router 已改写 Host。非升级请求保留 Origin,CORS 预检不受影响。
5.3 托管 NetworkPolicy:默认拒绝的具体形态
由 SandboxTemplate 创建的沙箱,控制器会生成一份模板级共享的严格策略:
- 入向:仅允许来自 Sandbox Router
- 出向:仅允许公网;阻断 RFC1918 内网与云 metadata 端点
networkPolicy 字段是标准 NetworkPolicySpec 的受限子集——podSelector 与 policyTypes 被刻意排除,因为由控制器管理以保证严格的默认拒绝姿态。
⚠️ 两个必须告知团队的副作用:
- sidecar 端口默认被阻断(见 3.3);
- 裸
Sandbox不套用任何 NetworkPolicy——"kernel isolation, but wide-open egress"。这是接入设计中最容易漏掉的一条:只要不走模板,就完全没有网络隔离。生产接入建议用准入策略禁止裸 Sandbox。
5.4 Router 部署形态
sandbox-router/deploy/ 下提供 Deployment + Service 清单。自 v1.0.2 起 Router 默认命名空间从 default 迁到 agent-sandbox-system(破坏性变更)。SDK 把 sandbox-router-svc 硬编码为路由目标,用户调用 sb.Run(...) 时完全看不到 Router。
Router 的企业级能力(Go 重写版):TLS/mTLS、Prometheus 指标、OTel tracing、证书热加载、结构化日志、优雅退出;鉴权侧支持 --authz-mode=tokenreview 与 Ed25519 scoped-token v2(绑定 Sandbox UID、port、HTTP method、upstream path,支持多密钥轮换)。
⚠️ 官方在 threat model 里主动披露了一个当前弱点:默认授权器是
AllowAll(为兼容 Python 客户端),因此X-Sandbox-Pod-IP的 IP 类别校验是目前唯一的 SSRF 防线(阻断169.254.169.254这类 metadata 地址)。生产接入必须配置自定义 authorizer,这是方案里的强制项而非可选项。
6. L4 运行时接口接入:sandboxd 与 SDK
6.1 为什么会有 sandboxd
KEP-539.2: Standardizing Sandbox Runtime Interfaces 解决的是"沙箱内部执行命令与操作文件的接口不统一"的问题。它对比了两条路线:
- REST/OpenAPI(受阿里 OpenSandbox 启发):简单、可调试、多语言友好、企业安全与可观测天然契合;代价是小载荷高频调用开销高、流式要靠 SSE/WebSocket
- gRPC/Protobuf(受 E2B
envd启发):二进制、多路复用、原生双向流;代价是工具链门槛与不可读性
结论是混合可插拔,落地为一个统一守护进程 sandboxd:
sandboxd
├── gRPC :9090 → ProcessService(流式进程 I/O)
└── HTTP :8080 → FilesystemService(无状态文件操作 + 探针)
为什么这样切:Start 是长生命周期 server-streaming RPC(stdout/stderr 持续流动直到 ExitEvent),HTTP/1.1 无法干净建模,所以进程走 gRPC;文件操作是简单请求/响应,标准 HTTP 语义天然映射,且避免大二进制传输的 base64 包装开销,所以文件走 REST。
| 服务 | 端口 | 关键接口 |
|---|---|---|
| ProcessService | :9090 gRPC |
Start(server stream) / Execute(unary) / WriteStdin / SendSignal / ResizeTTY |
| FilesystemService | :8080 REST |
`GET |
Conformance 分级(第三方运行时接入的依据):
- Core:Core Execution + Filesystem(不含
Watch)—— 最低要求 - Full:Core + Jupyter 支持 + 文件
Watch
6.2 安全注意(sandboxd 自己的边界)
- 两个端口默认绑
0.0.0.0,同 Pod 网络可达;必须靠 NetworkPolicy/服务网格保护,或用--listen-host=127.0.0.1限制回环 sandboxd不做客户端认证、不终止 TLS——安全边界由外部承担/v1/metadata只允许放非敏感的工作负载配置(sandbox ID、workspace 路径);绝不能放编排器凭证、K8s token、云厂商 key,因为沙箱里的不可信代码可以自己 loopback 查它- 路径穿越防护:所有路径过
SanitizePath,../类尝试返回 403
6.3 与 Router 的一个硬性不兼容
官方明确:当前
sandbox-router接受 HTTP/2 客户端连接,但在上游连接上禁用了 HTTP/2,因此无法承载 sandboxd 的 gRPCProcessService。
这意味着接入时必须在两条路径间做选择:
| 接入形态 | 可用性 | 说明 |
|---|---|---|
Router + REST(:8080) |
✅ | 走 HTTP 数据面 |
Router + gRPC(:9090) |
❌ | Router 不是 sandboxd 的完整传输通道 |
| 直连 Pod(port-forward / Pod IP / headless DNS) | ✅ | 需入向策略放行调用方;默认托管策略只放行 router |
6.4 SDK 的四种连接模式(接入层选型)
| 模式 | 流量路径 | 适用 |
|---|---|---|
| Gateway | Client → LB(Gateway API) → Router → Pod | 生产外部接入(GKE Gateway 等) |
| Tunnel | Localhost → kubectl port-forward → Router → Pod |
本地开发 / CI(无需公网 IP) |
| In-Cluster | Client → Pod 直连(Pod IP 或 cluster DNS) | 集群内工作负载(绕过 Router) |
| Direct URL | Client → 指定 api_url |
自定义域名 / 手工指定 Router |
from k8s_agent_sandbox import SandboxClient
from k8s_agent_sandbox.models import SandboxLocalTunnelConnectionConfig
client = SandboxClient(connection_config=SandboxLocalTunnelConnectionConfig())
sandbox = client.create_sandbox(warmpool="python-sandbox-warmpool", namespace="default")
try:
print(sandbox.commands.run("echo 'hello'").stdout)
finally:
sandbox.terminate()
⚠️ namespace 双轨坑:
router_namespace控制 Router 服务在哪(默认agent-sandbox-system),而create_sandbox(namespace=...)控制 沙箱 Pod 调度到哪。两者搞混会得到services sandbox-router-svc not found。
就绪等待机制(影响你的延迟预算):create_sandbox() 是完全 watch 驱动的,不轮询。Claim 控制器在领取热池沙箱时,会把绑定沙箱名、Pod IP、转发的 Ready 条件在同一次状态更新中发布,所以通常第一个携带沙箱名的 watch 事件就同时带了 Ready=True。sandbox_ready_timeout 默认 180s,只是兜底上限。
📌 官方明确的反模式:不要用
get_*轮询判断就绪——轮询间隔T会在控制器延迟之上平均多加T/2。
SDK 生态现状:Python(PyPI k8s-agent-sandbox,最低 Python 3.11)、Go(sigs.k8s.io/agent-sandbox/clients/go/sandbox,支持 Options.Runtime = RuntimeSandboxd)、TypeScript(v1.0.1 起提供初版)。
7. L5 平台治理接入:准入、APF、可观测、规模化调参
7.1 准入策略:把安全默认变成强制
官方 examples 提供了完整策略样例(examples/policy、examples/composing-sandbox-nw-policies):
| 机制 | 用途 |
|---|---|
| ValidatingAdmissionPolicy (VAP) | secure-sandbox-vap:对沙箱工作负载强制安全默认姿态 |
| OPA Gatekeeper | 防止给 Sandbox 用 SA 授予权限 |
| Kyverno | 策略示例 |
| ClusterNetworkPolicy(SIG Network 参考实现 kube-network-policies) | 集群级默认拒绝 + FQDN 出网白名单,叠加在模板托管策略之上 |
| Cilium | demo-cilium-egress 按 workload identity 做 egress 域名白名单 |
建议纳入准入的强制项:① 禁止裸 Sandbox(强制走模板);② 强制 runtimeClassName 非空;③ 强制 automountServiceAccountToken: false;④ 强制 resources.limits 存在。
7.2 APF 隔离:高并发接入的必需品
官方强烈建议在claim 速率 > 50/s 或 API Server 被其他工作负载共享的集群上应用 examples/apf-insulation/apf-insulation.yaml。它给控制器专用 APF 并发席位,并把流量拆成三个优先级类:claim 路径 > 批量补池 > events——目的是让补池突发无法把 claim 采纳写入挤出队列。
7.3 规模化调参:官方 benchmark 验证过的配置
这是全文最有价值的一段——官方给出了 benchmark 实测的推荐参数组合(GKE live A/B,75 claim 突发 / 150 沙箱热池):
args:
- --extensions
# API 连接:当在途请求接近单连接 ~100 流上限时才有收益
- --api-connections=4
- --separate-watch-connection=true
# 并发:官方验证 200/150/2/1 优于 1000/1000/500/100 与 50/50/1/1
- --sandbox-concurrent-workers=200
- --sandbox-claim-concurrent-workers=150
- --sandbox-warm-pool-concurrent-workers=2 # 按活跃池数量定
# 热池:平滑补池,不加延迟
- --sandbox-warm-pool-replenish-delay=0
- --sandbox-warm-pool-max-refill-rate=100 # ≥ 单池稳态 claim 到达率
# 缓存:>5000 Pod 的集群才有收益
- --cache-label-selectors=true
# 写削减:≥200 claims/s 才有收益
- --disable-claim-events=true
- --disable-claim-observability-annotations=true
# 刻意不设:--sandbox-write-behind-window(见下)
关键默认值(docs/configuration.md):
| Flag | 默认 | 说明 |
|---|---|---|
--sandbox-concurrent-workers |
100 |
Sandbox 并发 reconcile 上限 |
--sandbox-claim-concurrent-workers |
50 |
Claim 并发 |
--sandbox-warm-pool-concurrent-workers |
1 |
同一池的 reconcile 被 workqueue 串行化,worker 只提供跨池并发 |
--sandbox-template-concurrent-workers |
1 |
极少需要 >2 |
--sandbox-warm-pool-max-batch-size |
300 |
单批创建/删除上限;补池轮次 ≈ ceil(replicas/batchSize) |
--sandbox-warm-pool-readiness-grace-period |
5m |
超时未 Ready 视为卡住并替换 |
--kube-api-qps / --kube-api-burst |
-1 / 10 |
默认关闭客户端限流,交给服务端 APF(推荐) |
--api-connections |
1 |
kube-apiserver 单 HTTP/2 连接默认 100 并发流上限 |
--sandbox-warm-pool-max-refill-rate |
0(不限速) |
令牌桶平滑补池 |
--cache-label-selectors |
false |
缓存范围从 O(集群 Pod) 降到 O(沙箱 Pod) |
--sandbox-write-behind-window |
0(关闭) |
写合并窗口 |
7.4 性能基线与调参取舍(必读)
热池采纳延迟(PR 级 benchmark,45/s 泊松到达 × 4 池):
| 配置 | p50 | p90 | p99 |
|---|---|---|---|
稳态调优(replenish-delay=0 + max-refill-rate=100) |
92 ms | 182 ms | 376 ms |
突发调优(replenish-delay=20s,不限速) |
前 10s ~57ms,随后 ~1.4s 冷启动 | 2.28s 稳态 | — |
75-claim 突发(GKE live):最优配置 p50 41.4s / p90 74.2s / p99 82.6s,75/75 全部成功(对比 baseline p50 50.4s)。注意这里是冷启动地板(该集群 Pod 冷启动 p50 ~42–50s),热池采纳本身是亚秒级。
三条最有价值的取舍结论:
--sandbox-warm-pool-max-refill-rate是唯一"无失败模式"的优化:稳定带来 p50/p90 约 15% 改善(50.4s → 41.4s),rate=80 与 100 统计上无差异,启用的价值远大于具体取值。必须 ≥ 单池稳态 claim 到达率,否则池会抽干。--sandbox-warm-pool-replenish-delay有真实脆弱性,默认不要开:单独看是中性的,但在负载下哪怕一次瞬态 API 超时就足以触发池抽干——实测 1/75 claim 失败让 p90 从 74s 飙到 205s。--sandbox-write-behind-window=250ms是把双刃剑:409 写冲突 −44%(5447→3077)、突发 p50 −13%,但稳态 p50 +58%(320→507ms)。仅在冲突率高或突发为主且 SLO 容忍 ~500ms 稳态 p50 时启用。
worker 数量有地板也有天花板:50/50/1/1 相对推荐值 p50 +11%(确有欠配置惩罚);1000/1000/500/100 则徒增 API Server 争用。推荐值 200/150/2/1 是实测胜出的。
7.5 可观测接入
- 控制器指标:
--metrics-bind-address(默认:8080),支持 TLS(--metrics-secure-serving+--metrics-cert-dir,惯例端口:8443) - 追踪:
--enable-tracing(OTLP,读标准OTEL_EXPORTER_OTLP_ENDPOINT) - 剖析:
--enable-pprof/--enable-pprof-debug(生产关闭,会暴露堆内容、命令行、goroutine 栈) - Helm Chart 暴露
controller.enableTracing/controller.enablePprof等,并可选用 ServiceMonitor + PrometheusRule - 事件:v1.0.4 新增
SandboxPodCreated/SandboxReady/SandboxSuspended/SandboxExpired/PodSucceeded/PodFailed等生命周期事件(可用--disable-sandbox-events关闭)
8. 分阶段落地路线(可直接抄的接入顺序)
| 阶段 | 目标 | 关键动作 | 验收标准 |
|---|---|---|---|
| P0 基线 | 装得上、跑得通 | 装 v1.0.4 sandbox-with-extensions.yaml;建 1 个裸 Sandbox |
kubectl wait --for=condition=Ready 通过 |
| P1 隔离 | 安全边界成立 | 建 RuntimeClass;模板中设 runtimeClassName;做内核版本差验证 |
沙箱内 uname -r ≠ 宿主内核 |
| P2 网络 | 默认拒绝生效 | 部署 Router;确认托管 NetworkPolicy 生成;配自定义 authorizer(禁用 AllowAll) | 跨租户不可达;metadata 端点不可达 |
| P3 接口 | agent 能跑代码 | 选 sandboxd 或 python-runtime;接 SDK;定四种连接模式 |
执行 + 文件读写 + 大文件 read_to 通过 |
| P4 热池 | 延迟达标 | SandboxWarmPool + SandboxClaim;Claim 不设 env/volumeClaimTemplates |
采纳 p50 亚秒级 |
| P5 规模化 | 扛住并发 | 应用 APF insulation;按 benchmark 调 worker 与 refill rate;开 cache-label-selectors |
目标 claim 速率下无池抽干 |
| P6 治理 | 合规与成本 | 上 VAP/Gatekeeper;HPA/KEDA 接管池规模;挂监控与生命周期事件 | 准入拦截生效;成本可观测 |
成本杠杆提示:官方已完成 HPA + Cold Standby Nodes (CSN) 集成,用于"大幅降低空闲基础设施成本";keda-scale-to-zero 示例演示池缩容到 0 再拉起。
9. 限制清单:接入方案里必须写明的"还没做到"
把这一节写进方案能显著降低交付风险。以下均为官方 roadmap 明确标注 Planned / In Progress 的项:
| 类别 | 尚未实现 |
|---|---|
| 生命周期 | Auto Suspend/Resume(自动挂起非活跃沙箱并按流量恢复)、Scale to Zero、Startup Actions、突发沙箱 TTL 自动清理 |
| 网络与身份 | Claim 时动态挂载 L4/L7 NetworkPolicy、Sandbox/Pod 身份关联(claim 时按调用者分配安全主体)、Claim 时存储定制 |
| 编排 | API 解耦运行时(Portable Backend)、SandboxTemplate/WarmPool 滚动更新、Smart Warmpool Selection、单 Pod 多沙箱、严格 1:1 Sandbox↔Pod 映射 |
| 接口 | 一等公民 Router(当前是"young"组件)、MCP Server、TypeScript SDK 完善、Python SDK 高级方法 |
| 规模 | claim 延迟 200ms → 100ms → 50ms、控制器支撑 1000+ claims/s(当前已验证 300 沙箱/秒)、TFFI 基准 |
| 可观测 | 控制器自定义指标 / Prometheus 细粒度计数器、OSS UI 仪表盘、参考架构文档 |
已确认的能力边界(不要误以为已 GA):
- 内存快照(Pod Snapshot)目前仅 GKE + Python SDK 支持(依赖
podsnapshot.gke.io,GKE ≥ 1.35.2-gke.1269000);Go SDK 无等价能力 - 已完成的是 PVC-based suspend/resume(保留卷、干净挂回),不是内存快照
- 多集群(fleet)语义尚未提供——单控制器单集群,跨集群调度需自建
已知工程坑(社区实测):源码树 k8s/ overlay 引用 ko:// 镜像直接部署会 InvalidImageName;Router 命名空间口径不一致;跨命名空间 NetworkPolicy 导致 504;/execute 不经 shell(管道/&&/变量需 sh -c 包裹);Write() 只接受纯文件名不接受路径分隔符。
10. 安全基线:五条信任边界落到接入动作
官方 Threat Model 定义了五条信任边界,接入方案里应逐条对应到具体动作:
| 信任边界 | 威胁 | 接入动作 |
|---|---|---|
| User → K8s API | 未授权创建沙箱 | RBAC 收紧 Sandbox/SandboxClaim/SandboxTemplate |
| 系统控制面 → 工作负载 | 控制器/Router 被攻破 | 视为可信系统组件,最小权限;控制器 NS 与租户 NS 分离 |
| 跨租户隔离 | 沙箱间横向攻击 | 托管 NetworkPolicy 默认拒绝 + ClusterNetworkPolicy 叠加 |
| 工作负载 → 宿主/节点 | 容器逃逸 | runtimeClassName 强制 gVisor/Kata(本项目不提供隔离) |
| 工作负载 → 控制面 | 用 SA token 打 K8s API | 模板默认 automountServiceAccountToken=false;裸 Sandbox 用 VAP 强制 |
官方已实现的一个高价值防护(值得单独讲):系统保留标签过滤。因为 Sandbox.spec.podTemplate.metadata 允许租户提交任意 labels,攻击者可伪造 Service 选择器标签 agents.x-k8s.io/sandbox-name-hash,让自己的 Pod 匹配到别的租户 Service,从而劫持流量(网络隔离绕过的原语)。控制器的防护是:
- 创建路径与采纳路径都过滤掉
agents.x-k8s.io/、extensions.agents.x-k8s.io/前缀的用户键 - Service 选择器标签在合并用户标签之后才由控制器赋值,无法被覆盖
- 采纳/更新时清洗旧控制器写入
propagated-labels中的系统保留键
Router 侧的两个已知弱点(必须补偿):① 默认 AllowAll 授权器 → 必须配自定义 authorizer;② Router 当前未实现连接级限流与升级连接超时 → 需在 Ingress/Gateway/Envoy 层做限流与连接数限制。
11. 横向定位:什么时候该选它
| 场景 | 推荐 | 理由 |
|---|---|---|
| 已有 K8s 集群、数据不出 VPC、需平台化治理 | K8s Agent Sandbox | 复用现有调度/网络策略/安全审查;把"agent 在哪跑代码"变成一种 workload 类型 |
| 无 K8s 足迹、要最快落地 | E2B / Modal / Vercel 等托管平台 | 一个 API key 即可 |
| 极强多租户规模化 + 快照分叉(RL 训练) | 阿里云 Agent Sandbox / OpenKruise Agents / DeepSeek DSec | DSec 单 unit 约 160 节点、日均 300 万沙箱、峰值 38 万并发 |
| 需要 E2B 协议兼容 + K8s 内多租户治理 | OpenKruise Agents | E2B 兼容 + Teams/API Key |
| 超大规模 agent 密度(超出 K8s 控制面能力) | Google Agent Substrate | 与 agent-sandbox 互补,非替代 |
已确认的生态接入(有一手来源):LangChain/LangGraph、Lovable、OpenHands、Ray/RLlib、SWE-bench 类 RL 训练、NVIDIA NeMo Gym/OpenShell、JupyterLab、ADK、Pulumi、VSCode/Playwright/Chrome/Aider/Windows 等官方示例。
⚠️ 一个必须澄清的误传:kagent 并不是直接接入 agent-sandbox,它转向的是 Google 的 Agent Substrate。两者是不同项目。
12. 结语:这份标准真正解决了什么
回到最开始那句话。K8s 官方 Agent Sandbox 的价值不在于它提供了多强的隔离(它刻意不提供),而在于它把"agent 的执行环境"从一个各家自定义的临时方案,变成了一种有 CRD 契约、有安全默认、有生命周期语义、有热池与采纳协议的一等 K8s workload。
对接入方的实际收益可以归纳为三条:
- 安全默认是"买一送一"的:
automountServiceAccountToken=false、模板级默认拒绝 NetworkPolicy、系统标签过滤——这些在自建方案里通常要写一堆准入策略才勉强做到,这里是默认行为。 - 延迟曲线是"可规划"的:热池采纳亚秒级、冷启动几十秒,官方给了完整的 benchmark 与调参取舍,容量规划有据可依。
- 风险是"被披露"的:Router 默认
AllowAll、内存快照仅 GKE、Auto Suspend 未实现、replenish-delay 的池抽干脆弱性——这些都被官方主动写进 threat model 和 benchmark 文档。一份把限制写清楚的标准,比一份看起来完美的标准更适合做架构决策。
附录:核心参考来源
| # | 来源 | 用途 |
|---|---|---|
| 1 | kubernetes-sigs/agent-sandbox | Scope、Motivation、Desired Characteristics |
| 2 | roadmap.md | 已完成 / 进行中 / 规划项 |
| 3 | docs/configuration.md | 全部控制器 flag 与默认值 |
| 4 | docs/performance-tuning.md | benchmark 数据与调参取舍 |
| 5 | docs/security/threat_model.md | 五条信任边界与缓解措施 |
| 6 | sandbox-router/README.md | 数据面契约与校验规则 |
| 7 | KEP-539.2 Standardizing Sandbox Runtime Interfaces | sandboxd 架构与 Conformance |
| 8 | KEP-694 Suspend/Resume Beta | operatingMode API 决策 |
| 9 | KEP-119 Sandbox Suspended State | 条件层级与依赖矩阵 |
| 10 | api/v1beta1/sandbox_types.go | 核心字段与系统保留标签 |
| 11 | extensions/api/v1beta1 | Template/WarmPool/Claim 字段 |
| 12 | SIG Apps README | 治理归属 |
| 13 | Google Open Source Blog 2025-11 立项公告 | 立项时间与 KubeCon 发布 |
| 14 | Kubernetes Blog 2026-03-20 | 抽象缺口论证 |
| 15 | examples/ | 55+ 接入示例(Kata/gVisor/Firecracker/RL/Jupyter/VSCode…) |
| 16 | releases | 版本演进与破坏性变更 |
本文基于 kubernetes-sigs/agent-sandbox v1.0.4 与 2026-09-29 时点的官方仓库快照整理;项目迭代较快(2025-11 立项至今 v0.1.1 → v1.0.4),接入前请以目标版本 tag 为准复核字段与 flag。
读者留言
COMMENTS 暂无还没有留言,来说第一句?