llm-wiki wiki · sources 2026-05-13

agent-sandbox 架构与设计思路分析

2026-05-13 · 来源:agent-sandbox-architecture-analysis.md

architecturek8s-operatorai-infraagent-sandboxsig-apps

原文:raw/agent-sandbox-architecture-analysis.md · 仓库:kubernetes-sigs/agent-sandbox · 分析版本 v0.4.5+11 (HEAD e1d8898)

一句话定位

kubernetes-sigs/agent-sandboxK8s SIG Apps 官方孵化的 Sandbox CRD 与 controller,把 AI Agent runtime 那种"长寿命、有状态、单实例、可暂停、有稳定身份"的容器形态建模成第一类 K8s 资源——比 Deployment(无状态副本)和 StatefulSet(编号 Pod)都更精准。核心手段:1 个 Sandbox CR = 1 个 Pod + 1 个 headless Service + 持久 PVC + 可选 Template/Claim/WarmPool 三件套,隔离机制(gVisor/Kata/NetworkPolicy)完全委托给标准 K8s 原语,controller 只做生命周期编排。

核心架构图

flowchart TB subgraph USER["用户 / AI Agent 进程"] direction LR Yml["kubectl YAML"] GoSDK["Go SDK (clients/go)"] PySDK["Python SDK"] end subgraph API["Kubernetes API Server"] direction LR subgraph CORE["agents.x-k8s.io/v1alpha1"] SB["Sandbox (CR)
Spec: PodTemplate · VolumeClaim…
ShutdownTime · ShutdownPolicy
Replicas (0|1)"] end subgraph EXT["extensions.agents…"] SC["SandboxClaim (CR)
user-friendly"] WP["SandboxWarmPool
Spec: Replicas N · TemplateRef · UpdateStrategy"] TPL["SandboxTemplate
Spec: PodTemplate · VolumeClaim…
NetworkPolicy · EnvVarsPolicy"] end SC -- "owns Sandbox" --> SB WP -- "owns N× Sandbox
(pre-warmed, 等候 Claim 领养)" --> SB SC -- "references" --> TPL WP -- "TemplateRef" --> TPL end subgraph CTRL["agent-sandbox-controller (cmd/agent-sandbox-controller)"] R["SandboxReconciler (always)
+ SandboxClaimReconciler (--extensions=true)
+ SandboxTemplateReconciler
+ SandboxWarmPoolReconciler
+ SimpleSandboxQueue (per-template FIFO O(1) adopt)"] end subgraph NODE["Kubelet 节点"] direction LR Pod["Pod (1:1 per Sandbox)
runtimeClassName: gvisor / kata / runc"] Svc["Service (ClusterIP=None, headless)
{sandbox}.{ns}.svc.cluster.local"] PVC["PVC: {tplname}-{sandboxname}
持久存储,Sandbox 重启不丢"] NP["NetworkPolicy (Template-shared)
默认 deny + 仅放行公网 egress
(deny RFC1918 + metadata server)"] Pod <--> Svc Pod <--> PVC end USER -- "apply / RPC / kubectl exec" --> API API -- "watch + reconcile" --> CTRL CTRL -- "creates / owns" --> NODE classDef u fill:#1e3a8a,stroke:#1e40af,color:#fff classDef a fill:#7c2d12,stroke:#9a3412,color:#fff classDef c fill:#14532d,stroke:#166534,color:#fff classDef n fill:#5b21b6,stroke:#6d28d9,color:#fff class USER,Yml,GoSDK,PySDK u class API,CORE,EXT,SB,SC,WP,TPL a class CTRL,R c class NODE,Pod,Svc,PVC,NP n
原 ASCII 图
┌──────────────────────────────────────────────────────────────────────────────┐
│                              用户 / AI Agent 进程                              │
│                                                                              │
│   kubectl YAML        ▼ Go SDK (clients/go)        ▼ Python SDK             │
│                                                                              │
└─────────────┬────────────────────────┬──────────────────────┬────────────────┘
              │                        │                      │
              ▼ (apply)                ▼ (RPC / kubectl exec) ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                          Kubernetes API Server                              │
│                                                                             │
│  Group: agents.x-k8s.io/v1alpha1            Group: extensions.agents...     │
│  ┌──────────────────┐                       ┌───────────────────────┐       │
│  │  Sandbox  (CR)   │ ◄───── owns ────────  │  SandboxClaim     (CR)│       │
│  │                  │                       │   (user-friendly)     │       │
│  │  Spec:           │ ◄── owns (pre-warm) ──┤                       │       │
│  │   PodTemplate    │                       └──────────┬────────────┘       │
│  │   VolumeClaim... │                                  │ references         │
│  │   ShutdownTime   │       ┌───────────────────┐      │                    │
│  │   ShutdownPolicy │       │ SandboxWarmPool   │      ▼                    │
│  │   Replicas (0|1) │       │  Spec:            │     ┌──────────────────┐  │
│  └────────┬─────────┘       │   Replicas: N     │     │ SandboxTemplate  │  │
│           │                 │   TemplateRef     │────►│  Spec:           │  │
│           │                 │   UpdateStrategy  │     │   PodTemplate    │  │
│           │                 └─────────┬─────────┘     │   VolumeClaim... │  │
│           │                           │ owns          │   NetworkPolicy  │  │
│           │                           │ N×Sandbox     │   EnvVarsPolicy  │  │
│           │                           ▼               └──────────────────┘  │
│           │              (pre-warmed Sandboxes 等候被 Claim 领养)            │
└───────────┼─────────────────────────────────────────────────────────────────┘
            │ watch + reconcile
            ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│           agent-sandbox-controller (cmd/agent-sandbox-controller)            │
│           ┌──────────────────────────────────────────────────────┐           │
│           │  SandboxReconciler (always)                          │           │
│           │  + SandboxClaimReconciler   (--extensions=true)      │           │
│           │  + SandboxTemplateReconciler  ↑                      │           │
│           │  + SandboxWarmPoolReconciler  ↑                      │           │
│           │  + SimpleSandboxQueue (per-template FIFO O(1) adopt) │           │
│           └────────────────────────┬─────────────────────────────┘           │
└────────────────────────────────────┼─────────────────────────────────────────┘
                                     │ creates / owns
                                     ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  Kubelet 节点                                                                 │
│  ┌─────────────────────────┐    ┌────────────────────────────────────┐      │
│  │  Pod  (1:1 per Sandbox) │ ←─►│  Service (ClusterIP=None, headless) │      │
│  │  ┌───────────────────┐  │    │   ⇒ {sandbox}.{ns}.svc.cluster.local│      │
│  │  │ container         │  │    └────────────────────────────────────┘      │
│  │  │ runtimeClassName: │  │    ┌────────────────────────────────────┐      │
│  │  │   gvisor / kata   │◄─┤    │ PVC: {tplname}-{sandboxname}        │      │
│  │  │   / runc          │  │    │   (持久存储,Sandbox 重启不丢)        │      │
│  │  └───────────────────┘  │    └────────────────────────────────────┘      │
│  └─────────────────────────┘    ┌────────────────────────────────────┐      │
│                                 │ NetworkPolicy (Template-shared)     │      │
│                                 │  默认 deny + 仅放行公网 egress       │      │
│                                 │  (deny RFC1918 + metadata server)   │      │
│                                 └────────────────────────────────────┘      │
└──────────────────────────────────────────────────────────────────────────────┘

模块分层

职责
CRD 定义层 4 个 CRD 的 Go 类型 + kubebuilder 标注 → controller-gen 自动生成 YAML。Spec/Status 即 API 契约。
Reconciler 层 controller-runtime 风格的 4 个 reconciler。watch + owns + 幂等 reconcile。Claim/WarmPool/Template 通过 --extensions=true 开关启用。
运行时支持 绝对时间 + TTL 过期计算、Prometheus 指标、per-template-hash 的 FIFO 领养队列(O(1) WarmPool adopt)。
入口二进制 单二进制 agent-sandbox-controller,flags 控制 extensions 开关、leader election、并发度、OTel tracing、pprof。
Client SDK Go / Python / 标准 K8s clientset。Go SDK 提供远程 exec、文件传输、gateway 隧道、追踪注入——给 AI Agent runtime 调用 Sandbox 用。
打包 Helm chart(CRDs / RBAC / Deployment / metrics Service)+ 单独的 k8s/crds/ 给手工 kubectl apply。
示例与集成 17 个 examples/:hello-world / kata-gke / openclaw / hermes / langchain / jupyter / vscode / chrome / HPA / Kueue / Cilium。"读完 examples 就会用"。

分层关键约束api/extensions/api/ 是被 K8s API server 直接读的契约,只能改不能删;internal/ 严格私有;controllers/ 不允许 import extensions/(核心稳定、扩展演进的边界)。

关键数据流

flowchart TD T0["t=0 — 用户 kubectl apply Sandbox 或 SandboxClaim"] AS["apiserver 收到 CR"] Rec["Reconcile(sandbox)
controllers/sandbox_controller.go
· checkSandboxExpiry (L1003)
· reconcilePVCs (L853) → 创建 PVC
· reconcilePod (L531)
 - 有 SandboxPodNameAnnotation → warm-pool 领养,复用现有 Pod
 - 否则 Create Pod {sandbox.Name}
· reconcileService (L398) → headless Service (ClusterIP=None)
· 算 Conditions (Ready / Finished)
· Update Status + requeueAfter(min(timeLeft, 2s))"] Run["Pod 起来 → 容器内 Agent runtime (OpenClaw / Hermes / …)
AI Agent 进程 ← SDK (gateway/tunnel/exec) ← 外部 caller
(在 gVisor / Kata 内运行,宿主机内核被隔离)"] Exp["t = ShutdownTime — handleSandboxExpiry (L922)
ShutdownPolicy=Retain (default): 删 Pod+Svc,保留 Sandbox CR (status: expired)
ShutdownPolicy=Delete: 整个 Sandbox 也删掉"] T0 --> AS -- "watch event" --> Rec --> Run --> Exp
原 ASCII 图
   t=0       用户 kubectl apply Sandbox 或 SandboxClaim
              │
              ▼
   ┌─────────────────────────┐
   │ apiserver 收到 CR       │
   └────────────┬────────────┘
                │ watch event
                ▼
   ┌────────────────────────────────────────────────────────┐
   │ Reconcile(sandbox) — controllers/sandbox_controller.go │
   │  ├─ checkSandboxExpiry      (line 1003)                │
   │  ├─ reconcilePVCs           (line 853)  ──► 创建 PVC    │
   │  ├─ reconcilePod            (line 531)                 │
   │  │    ├─ 如果有 SandboxPodNameAnnotation:              │
   │  │    │    └─ 走"warm pool 领养"分支,复用现有 Pod      │
   │  │    └─ 否则 Create Pod {sandbox.Name}                │
   │  ├─ reconcileService        (line 398)  ──► 创建        │
   │  │    └─ headless Service (ClusterIP=None)             │
   │  ├─ 算 Conditions (Ready/Finished)                     │
   │  └─ Update Status + requeueAfter(min(timeLeft, 2s))    │
   └────────────────────────────┬───────────────────────────┘
                                │
                                ▼
   ┌─────────────────────────────────────────────────────┐
   │ Pod 起来 → 容器内 Agent runtime (OpenClaw/Hermes/…) │
   │                                                     │
   │ AI Agent 进程 ←── SDK (gateway/tunnel/exec) ─────── │ ← 外部 caller
   │ (在 gVisor / Kata 内运行,宿主机内核被隔离)          │
   └─────────────────────────────────────────────────────┘
                                │
                                │ t = ShutdownTime(绝对时间)
                                ▼
   ┌─────────────────────────────────────────────────────┐
   │ handleSandboxExpiry (line 922)                      │
   │   ├─ ShutdownPolicy=Retain (default): 删 Pod+Svc,   │
   │   │    保留 Sandbox CR (status 显示 expired)         │
   │   └─ ShutdownPolicy=Delete: 整个 Sandbox 也删掉      │
   └─────────────────────────────────────────────────────┘

SandboxClaim 快/慢路径

flowchart TD U["user kubectl apply SandboxClaim"] GoC["SandboxClaimReconciler.getOrCreateSandbox
(sandboxclaim_controller.go:320)"] Fast["【快路径】adoptSandboxFromCandidates (L719)
· query SimpleSandboxQueue[templateHash]
· 找到 1 个 WarmPool 已就绪的 Sandbox
· patch: 清掉 WarmPool 的 OwnerReference
· patch: 把 Claim 设为新 owner
≈ 0 启动延迟"] Slow["【慢路径】createSandbox (L385)
没找到 / WarmPoolPolicy=none / 自定义 env (与 warmpool 不兼容)
· 读 SandboxTemplate
· 复制 PodTemplate + VolumeClaimTemplates
· 注入 claim 的额外 env / labels
· SetControllerReference(claim, sandbox)
· Create Sandbox CR
≈ Pod 冷启动延迟(秒级)"] U --> GoC GoC --> Fast GoC --> Slow classDef f fill:#14532d,stroke:#166534,color:#fff classDef s fill:#7c2d12,stroke:#9a3412,color:#fff class Fast f class Slow s
原 ASCII 图
   user kubectl apply SandboxClaim
        │
        ▼
   SandboxClaimReconciler.getOrCreateSandbox  (sandboxclaim_controller.go:320)
        │
        ├─►【快路径】adoptSandboxFromCandidates (line 719)
        │     query SimpleSandboxQueue[templateHash]
        │     找到 1 个 WarmPool 已就绪的 Sandbox
        │     ├─ patch: 清掉 WarmPool 的 OwnerReference
        │     └─ patch: 把 Claim 设为新 owner   ≈ 0 启动延迟
        │
        └─►【慢路径】createSandbox (line 385)
              没找到 / WarmPoolPolicy=none / 自定义 env (跟 warmpool 不兼容)
              ├─ 读 SandboxTemplate
              ├─ 复制 PodTemplate + VolumeClaimTemplates
              ├─ 注入 claim 的额外 env / labels
              ├─ SetControllerReference(claim, sandbox)
              └─ Create Sandbox CR  ≈ Pod 冷启动延迟(秒级)

设计决策与哲学

关键组件深入解读

SandboxReconciler 的三阶段子资源 reconcile

Reconcile() 主流程的工程亮点不在于复杂,而在于幂等三步永远按同一顺序reconcilePVCsreconcilePodreconcileService。每一步内部都是 "Get → Create or Adopt or Validate ownership → Patch labels/annotations" 的三步式幂等。Service 一定是 headlessClusterIP: None),selector 用 agents.x-k8s.io/sandbox-name-hash=<hash>(hash 防止 sandbox 名过长触碰 K8s label 63 字符上限)。这导致每个 Sandbox 自动有 DNS A record:{sandbox}.{ns}.svc.{clusterDomain}——成为 Sandbox "稳定身份"承诺的实现基础。

requeueAfter = max(timeLeft, 2s) 是个被反复琢磨的小细节:既避免热循环打爆 apiserver,又保证即将过期的 Sandbox 准时被清理,不依赖 informer event。

SandboxClaimReconciler 的 adoption 协议

这是 extensions 子系统最有意思的代码,~60 行实现了 "WarmPool → Claim 的所有权热迁移":

  1. SimpleSandboxQueue 拿到候选 Sandbox 名(已 Ready 的);
  2. 乐观锁 patch:用 OptimisticLockErrorMsg resourceVersion 保护,避免两个并发 Claim 抢同一个 Sandbox;
  3. 清掉 WarmPool 标识标签;
  4. OwnerReferences 置换:移除 WarmPool 的 controller ref,加 Claim 的 controller ref——后续删 WarmPool 不会再级联删这个 Sandbox;
  5. Claim status 记录 AdoptedSandboxName,下次 reconcile 走 short-circuit。

关键不变量是 4 + 5 必须原子(同一个 patch 请求里)。这就是 sandboxclaim_pod_exclusivity_test.go 守护的不变量。

与同类对比

维度 agent-sandbox HiClaw 普通 K8s Deployment
抽象层级 基础设施(Sandbox 原语) 应用层(Manager + Workers 协作) 基础设施(无状态副本)
单实例 + 稳定身份 ✅(核心设计目标) ⚠️(Worker 之间通过 Matrix 房间)
持久存储 ✅(MinIO + PVC)
多 Agent 编排 ❌(单 sandbox,多 sandbox 靠上层) ✅(Team/Manager CRD)
WarmPool 预热
隔离机制 委托 K8s(gVisor/Kata 任选) Higress 网关 + 凭据隔离 取决于 runtimeClass
治理 K8s SIG Apps 官方孵化 阿里 Higress 团队开源 K8s core

定位结论:agent-sandbox 与 HiClaw 互补不竞争。前者提供"安全运行 1 个有状态 Agent 容器"的基础设施;后者提供"管 N 个 Agent 协作 + IM 平面 + 凭据网关"的应用层。理论上 HiClaw 的 Worker runtime 完全可以跑在 agent-sandbox 之上——一个用 Sandbox 做隔离底座,一个用 Worker CRD 做协作上层。这种分层正是云原生生态的健康样态。

相关页面