管理 GKE 存储上的 Agent Sandbox

本文档提供了在 GKE 上管理 Agent Sandbox 存储空间的参考实现,这些实现根据智能体的生命周期需求量身定制。

根据代理的数据生命周期需求,选择以下配置之一:

如需详细了解如何选择存储解决方案,请参阅为 AI 智能体工作负载选择存储。

以下文档使用 dynamic-rwo StorageClass 进行自动磁盘类型选择,以预配与调度 GKE Pod 上的 Agent Sandbox 的节点机器类型兼容的磁盘。为确保 GKE 为智能体的存储空间预配 Hyperdisk Balanced 卷,您必须在兼容机器系列(例如 N4)的节点上,将 Agent Sandbox 调度到 GKE Pod 上;否则,GKE 会回退到 pd-balanced。

本文档使用 ReadWriteOnce (RWO) 访问权限实现私有隔离工作区数据访问模式。在此模式下,代理启动时会有一个私有隔离存储目录,该目录只有该代理拥有读写权限。

除非另有说明,否则本文档中的参考实现均使用直接沙盒创建,这适用于可容忍数秒启动延迟时间的代理。如需为有状态或时间点恢复工作区实现亚秒级启动延迟时间,您必须使用 GKE 暖池上的 Agent Sandbox。将存储空间绑定到已认领的暖池 Pod 需要自定义脚本和特权 DaemonSet。如需查看参考实现,请参阅此 GitHub 示例。

替代访问模式

如需支持其他访问模式,您可以修改配置中的卷和快照定义:

  • 协作工作区:将 accessModes 更改为 ReadWriteMany,并使用支持 RWX 的 StorageClass,例如 Filestore 多共享(企业版)(enterprise-multishare-rwx)。
  • 探索分支工作区:将模板文件夹装载为只读,并提供单独的可写暂存器。对于基本模板,您必须使用支持多个只读挂接的存储,例如采用 ReadOnlyMany (ROX) 访问模式的 Hyperdisk ML 或采用 RWX 访问模式的 Filestore Multishares。

准备工作

确保您有一个已安装 Agent Sandbox 控制器的 GKE 集群。如果没有,请仅完成在 GKE 上安装 Agent Sandbox 的以下部分,然后返回本文档:

  1. 定义环境变量
  2. 创建 GKE 集群
  3. 安装 Agent Sandbox
  4. 应用建议的安全政策

如果您已完成该文档的部署沙盒化环境部分,请删除其创建的示例资源,以便您的节点有足够的容量来容纳本文档中的沙盒:

kubectl delete sandboxclaim sandbox-claim --ignore-not-found
kubectl delete sandboxwarmpool python-runtime-warmpool --ignore-not-found
kubectl delete sandbox sandbox-example-2 --ignore-not-found

配置有状态工作区

使用此模式可保留智能体的最新文件状态。当智能体必须在智能体会话暂停或终止(Agent Sandbox 被删除)时保留状态,并在智能体会话激活(Agent Sandbox 重新创建)时从最新状态恢复数据时,此功能非常有用。

本部分中的参考实现使用直接沙盒创建,适用于可容忍多秒启动延迟时间的代理。

此方法使用标准 GKE PersistentVolumeClaim (PVC) 资源将沙盒关联到包含用户数据的预先存在的 PVC。

有状态工作区模式遵循以下事件序列:

  1. 预配:管理员或编排器使用确定性标识符(例如 pvc-agent-1)为每个智能体对话手动预配一个专用 PVC。
  2. 引用:在 Sandbox 资源中,您可以使用 volumes 块中的 persistentVolumeClaim 字段来指定现有卷的确切 claimName。
  3. 延迟时间:创建沙盒后,GKE 必须将 Compute Engine 磁盘动态挂接到节点虚拟机,这会产生标准的几秒延迟。
  4. 持久性:在会话终止(删除 Sandbox 资源)时,GKE 会分离磁盘,但不会销毁 PVC,这有助于确保为下一个会话保留最新状态。

如需配置在会话之间保留数据的有状态工作区,请完成以下小节中的步骤。

预配永久性工作区 (PVC)

创建使用确定性标识符(例如 pvc-agent-1)的专用 PersistentVolumeClaim (PVC)。

  1. 将以下清单保存为 pvc-agent-1.yaml:

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: pvc-agent-1 # Derived directly from the deterministic assignment ID
      namespace: default
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: dynamic-rwo # Selects disk type compatible with the node machine family
      resources:
        requests:
          storage: 10Gi
    
  2. 应用清单:

    kubectl apply -f pvc-agent-1.yaml
    

由于存储类别使用动态卷绑定,因此磁盘尚未挂接到任何节点。它会一直处于 Pending 状态,直到请求它的 Pod 被调度。

部署 Agent Sandbox

部署 Sandbox 自定义资源,引用确定性 PVC。

  1. 将以下清单保存为 sandbox-agent-1.yaml:

    apiVersion: agents.x-k8s.io/v1beta1
    kind: Sandbox
    metadata:
      name: sandbox-agent-1 # Traceable sandbox name
      namespace: default
    spec:
      podTemplate:
        spec:
          runtimeClassName: gvisor # Required
          automountServiceAccountToken: false # Required
          securityContext:
            runAsNonRoot: true # Required
            runAsUser: 1000
            fsGroup: 1000 # Grant group access to the volume
          nodeSelector:
            sandbox.gke.io/runtime: gvisor # Required
          tolerations:
          - key: "sandbox.gke.io/runtime"
            value: "gvisor"
            effect: "NoSchedule" # Required
          containers:
          - name: agent
            image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0
            ports:
            - containerPort: 8888
            volumeMounts:
            - name: workspace-disk
              mountPath: /workspace # Mounts the private disk into the container
            resources:
              limits:
                cpu: "500m"
                memory: "1Gi" # Required
            securityContext:
              capabilities:
                drop: ["ALL"] # Required
          volumes:
          - name: workspace-disk
            persistentVolumeClaim:
              claimName: pvc-agent-1 # Binds this specific Sandbox to Agent 1's PVC
          restartPolicy: OnFailure
    
  2. 应用清单:

    kubectl apply -f sandbox-agent-1.yaml
    
  3. 等待沙盒准备就绪:

    kubectl wait sandbox sandbox-agent-1 \
        --for=condition=Ready \
        --timeout=300s
    

    GKE 会验证节点容量并挂接磁盘,这需要几秒钟的时间。容器在用户空间 gVisor 内核中初始化。在 Autopilot 集群中,沙盒可能需要几分钟才能准备就绪,因为 GKE 可能需要为其创建节点。

从智能体写入数据

通过将文本文件写入装载的磁盘,模拟活跃的 AI 智能体在其工作区内执行文件修改。

# Set the active Pod name
POD_NAME=sandbox-agent-1

# Write a state file to the persistent directory
kubectl exec ${POD_NAME} -- sh -c "echo 'Workspace State Saved - Agent 1' > /workspace/modified_data.txt"

# Confirm the file exists on the disk
kubectl exec ${POD_NAME} -- cat /workspace/modified_data.txt

结束代理会话

如需模拟在代理空闲时缩容或终止会话,请删除 Sandbox 资源,但保留底层存储。

kubectl delete sandbox sandbox-agent-1

GKE 会卸载并分离磁盘。pvc-agent-1 PVC 仍会保留,从而保留数据。

重新激活代理会话

如需重新激活会话,请重新部署引用同一 PVC 的新 Sandbox 资源,然后等待沙盒准备就绪:

kubectl apply -f sandbox-agent-1.yaml

kubectl wait sandbox sandbox-agent-1 \
    --for=condition=Ready \
    --timeout=300s

磁盘会重新挂接(产生挂接延迟),并且容器会启动。

验证数据保留

检查新创建的沙盒容器,验证之前的会话数据是否已保留。

# Set the active Pod name of the new session
NEW_POD_NAME=sandbox-agent-1

# Read the file from the newly booted sandbox
kubectl exec ${NEW_POD_NAME} -- cat /workspace/modified_data.txt

输出应显示 Workspace State Saved - Agent 1。

清理资源

删除 Agent Sandbox 和关联的永久性卷声明:

kubectl delete sandbox sandbox-agent-1
kubectl delete pvc pvc-agent-1

配置时间点恢复和所有权转移

使用此模式克隆数据集,以运行并行实验、调试或独立工作。智能体的 Workspace 会从历史数据集(或共享状态)进行初始化,并将后续修改保存到单独的私有可写层,而不会修改基本模板。

本部分中的参考实现使用直接沙盒创建,适用于可容忍多秒启动延迟时间的代理。此方法依赖于编排器在启动新的沙盒会话之前,从历史 VolumeSnapshot 动态预配新的 PersistentVolumeClaim (PVC)。

创建 VolumeSnapshotClass

创建 VolumeSnapshotClass,用于指定 CSI 驱动程序和删除政策。对于 Hyperdisk,请使用 pd.csi.storage.gke.io 驱动程序。

  1. 将以下清单保存为 1-snapshot-class.yaml:

    apiVersion: snapshot.storage.k8s.io/v1
    kind: VolumeSnapshotClass
    metadata:
      name: standard-rwo-snapshot
    driver: pd.csi.storage.gke.io
    deletionPolicy: Delete
    
  2. 应用清单:

    kubectl apply -f 1-snapshot-class.yaml
    

预配初始工作区

创建一个卷,供智能体执行初始工作。

  1. 将以下清单保存为 2-source-pvc.yaml:

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: agent-source-pvc
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: dynamic-rwo
      resources:
        requests:
          storage: 10Gi
    
  2. 应用清单:

    kubectl apply -f 2-source-pvc.yaml
    

生成状态数据

部署沙盒 Pod 以将数据写入卷。

  1. 将以下清单保存为 3-source-sandbox.yaml:

    apiVersion: agents.x-k8s.io/v1beta1
    kind: Sandbox
    metadata:
      name: agent-session-v1
      namespace: default
    spec:
      podTemplate:
        spec:
          runtimeClassName: gvisor
          automountServiceAccountToken: false # Required
          securityContext:
            runAsNonRoot: true # Required
            runAsUser: 1000
            fsGroup: 1000 # Grant group access to the volume
          nodeSelector:
            sandbox.gke.io/runtime: gvisor # Required
          tolerations:
          - key: "sandbox.gke.io/runtime"
            value: "gvisor"
            effect: "NoSchedule" # Required
          containers:
          - name: agent
            image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0
            ports:
            - containerPort: 8888
            volumeMounts:
            - name: workspace
              mountPath: /workspace
            resources:
              limits:
                cpu: "500m"
                memory: "1Gi" # Required
            securityContext:
              capabilities:
                drop: ["ALL"] # Required
          volumes:
          - name: workspace
            persistentVolumeClaim:
              claimName: agent-source-pvc
          restartPolicy: OnFailure
    
  2. 应用清单:

    kubectl apply -f 3-source-sandbox.yaml
    
  3. 等待沙盒准备就绪,然后写入状态文件。sync 命令会将写入内容刷新到磁盘,以便下一部分中的快照包含该文件的内容:

    kubectl wait sandbox agent-session-v1 \
        --for=condition=Ready \
        --timeout=300s
    
    POD_NAME=agent-session-v1
    kubectl exec ${POD_NAME} -- sh -c "echo 'Point-in-Time Snapshot - v1' > /workspace/state.txt && sync"
    

归档历史状态 (CSI VolumeSnapshot)

触发 CSI VolumeSnapshot 以冻结当前状态。创建快照时,请遵循磁盘快照的最佳实践。

  1. 将以下清单保存为 4-volume-snapshot.yaml:

    apiVersion: snapshot.storage.k8s.io/v1
    kind: VolumeSnapshot
    metadata:
      name: agent-session-v1-snapshot
    spec:
      volumeSnapshotClassName: standard-rwo-snapshot
      source:
        persistentVolumeClaimName: agent-source-pvc
    
  2. 应用清单:

    kubectl apply -f 4-volume-snapshot.yaml
    
  3. 等待快照准备就绪,可供使用:

    kubectl wait volumesnapshot agent-session-v1-snapshot \
        --for=jsonpath='{.status.readyToUse}'=true \
        --timeout=600s
    

从快照恢复卷

部署一个新的 PVC,其 dataSource 指向 CSI VolumeSnapshot。

  1. 将以下清单保存为 5-restored-pvc.yaml:

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: agent-restored-pvc
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: dynamic-rwo
      dataSource:
        name: agent-session-v1-snapshot
        kind: VolumeSnapshot
        apiGroup: snapshot.storage.k8s.io
      resources:
        requests:
          storage: 10Gi
    
  2. 应用清单:

    kubectl apply -f 5-restored-pvc.yaml
    

启动已恢复的 Agent Sandbox 会话

预配引用新恢复的 PVC 的全新 Sandbox 资源。

  1. 将以下清单保存为 6-restored-sandbox.yaml:

    apiVersion: agents.x-k8s.io/v1beta1
    kind: Sandbox
    metadata:
      name: agent-session-v2-restored
      namespace: default
    spec:
      podTemplate:
        spec:
          runtimeClassName: gvisor
          automountServiceAccountToken: false # Required
          securityContext:
            runAsNonRoot: true # Required
            runAsUser: 1000
            fsGroup: 1000 # Grant group access to the volume
          nodeSelector:
            sandbox.gke.io/runtime: gvisor # Required
          tolerations:
          - key: "sandbox.gke.io/runtime"
            value: "gvisor"
            effect: "NoSchedule" # Required
          containers:
          - name: agent
            image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0
            ports:
            - containerPort: 8888
            volumeMounts:
            - name: workspace
              mountPath: /workspace
            resources:
              limits:
                cpu: "500m"
                memory: "1Gi" # Required
            securityContext:
              capabilities:
                drop: ["ALL"] # Required
          volumes:
          - name: workspace
            persistentVolumeClaim:
              claimName: agent-restored-pvc
          restartPolicy: OnFailure
    
  2. 应用清单:

    kubectl apply -f 6-restored-sandbox.yaml
    
  3. 等待沙盒准备就绪:

    kubectl wait sandbox agent-session-v2-restored \
        --for=condition=Ready \
        --timeout=600s
    

    此沙盒可能需要比源沙盒更长的时间才能准备就绪,因为 GKE 会在快照准备就绪后根据快照创建其磁盘。

验证持久性和恢复

验证代理能否读取历史数据。

NEW_POD_NAME=agent-session-v2-restored
kubectl exec ${NEW_POD_NAME} -- cat /workspace/state.txt

预期输出:Point-in-Time Snapshot - v1

清理资源

删除 Agent Sandbox、PVC 和 VolumeSnapshot:

kubectl delete sandbox agent-session-v1
kubectl delete sandbox agent-session-v2-restored
kubectl delete pvc agent-source-pvc
kubectl delete pvc agent-restored-pvc
kubectl delete volumesnapshot agent-session-v1-snapshot
kubectl delete volumesnapshotclass standard-rwo-snapshot

配置临时工作区

当智能体在活跃状态下需要存储卷来存储临时文件时,请使用临时工作区模式。删除 Agent Sandbox 时无需保留任何数据。

配置为在 1 秒内启动

在 GKE 暖池上使用 Agent Sandbox,在后台预先预配空卷。

定义 SandboxTemplate 资源

在 volumeClaimTemplate 块中定义临时存储后备。

  1. 将以下清单保存为 stateless-template.yaml:

    apiVersion: extensions.agents.x-k8s.io/v1beta1
    kind: SandboxTemplate
    metadata:
      name: stateless-sandbox-template
      namespace: default
    spec:
      podTemplate:
        spec:
          runtimeClassName: gvisor
          automountServiceAccountToken: false
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            fsGroup: 1000 # Grant group access to the volume
          nodeSelector:
            sandbox.gke.io/runtime: gvisor
          tolerations:
          - key: "sandbox.gke.io/runtime"
            value: "gvisor"
            effect: "NoSchedule"
          containers:
          - name: agent
            image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0
            ports:
            - containerPort: 8888
            volumeMounts:
            - name: ephemeral-disk
              mountPath: /workspace
            resources:
              limits:
                cpu: "500m"
                memory: "1Gi" # Required
            securityContext:
              capabilities:
                drop: ["ALL"] # Required
      volumeClaimTemplates:
      - metadata:
          name: ephemeral-disk
        spec:
          accessModes: ["ReadWriteOnce"]
          storageClassName: dynamic-rwo # Selects disk type compatible with node machine family
          resources:
            requests:
              storage: 10Gi
    

启动 SandboxWarmPool 资源

  1. 将以下清单保存为 stateless-warmpool.yaml:

    apiVersion: extensions.agents.x-k8s.io/v1beta1
    kind: SandboxWarmPool
    metadata:
      name: stateless-warmpool
      namespace: default
    spec:
      replicas: 2 # Keep two standby Pods with pre-attached empty disks
      sandboxTemplateRef:
        name: stateless-sandbox-template
    
  2. 应用这两个清单:

    kubectl apply -f stateless-template.yaml
    kubectl apply -f stateless-warmpool.yaml
    
  3. 等待预热池的沙盒准备就绪:

    kubectl wait sandboxwarmpool stateless-warmpool \
        --for=jsonpath='{.status.readyReplicas}'=2 \
        --timeout=600s
    

    如果您在池的沙盒准备就绪之前创建 SandboxClaim 资源,则控制器可能会为声明创建新沙盒,而不是从池中分配一个沙盒。在 Autopilot 集群中,沙盒可能需要几分钟才能准备就绪,因为 GKE 可能需要为其创建节点。

创建 SandboxClaim 资源

定义一个在用户开始会话时触发的 SandboxClaim 资源。

  1. 将以下清单保存为 stateless-sandbox-claim.yaml:

    apiVersion: extensions.agents.x-k8s.io/v1beta1
    kind: SandboxClaim
    metadata:
      name: agent-1-claim
    spec:
      warmPoolRef:
        name: stateless-warmpool
    
  2. 应用清单:

    kubectl apply -f stateless-sandbox-claim.yaml
    

验证亚秒级执行

等待声明准备就绪,捕获 Agent Sandbox Pod 名称,然后验证 /workspace 目录是否已装载并可立即使用:

kubectl wait sandboxclaim agent-1-claim --for=condition=Ready --timeout=300s
export POD_NAME=$(kubectl get sandboxclaim agent-1-claim -o jsonpath='{.status.sandbox.name}')
kubectl exec ${POD_NAME} -- ls -la /workspace

终止代理会话

如需结束代理会话,请删除 SandboxClaim 资源。删除声明也会删除声明的沙盒及其卷:

kubectl delete sandboxclaim agent-1-claim

清理资源

删除 SandboxWarmPool 和 SandboxTemplate 资源:

kubectl delete sandboxwarmpool stateless-warmpool
kubectl delete sandboxtemplate stateless-sandbox-template

配置为启动时间为数秒

如需实现可容忍多秒延迟时间的临时工作区,请直接创建 Agent Sandbox,不使用温池。

定义无状态 Agent Sandbox

  1. 将以下清单保存为 sandbox-direct-stateless.yaml:

    apiVersion: agents.x-k8s.io/v1beta1
    kind: Sandbox
    metadata:
      name: sandbox-direct-stateless
      namespace: default
    spec:
      podTemplate:
        spec:
          runtimeClassName: gvisor
          automountServiceAccountToken: false
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            fsGroup: 1000 # Grant group access to the volume
          nodeSelector:
            sandbox.gke.io/runtime: gvisor
          tolerations:
          - key: "sandbox.gke.io/runtime"
            value: "gvisor"
            effect: "NoSchedule"
          containers:
          - name: agent
            image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0
            ports:
            - containerPort: 8888
            volumeMounts:
            - name: ephemeral-disk
              mountPath: /workspace
            resources:
              limits:
                cpu: "500m"
                memory: "1Gi" # Required
            securityContext:
              capabilities:
                drop: ["ALL"] # Required
          restartPolicy: OnFailure
      volumeClaimTemplates:
      - metadata:
          name: ephemeral-disk
        spec:
          accessModes: ["ReadWriteOnce"]
          storageClassName: dynamic-rwo
          resources:
            requests:
              storage: 10Gi
    

部署 Agent Sandbox

如需动态预配磁盘并将其挂接到预定节点,请应用清单:

kubectl apply -f sandbox-direct-stateless.yaml

验证启动延迟时间和执行情况

监控 Pod 状态,观察 Pod 转换为 Running 状态之前的挂接延迟时间:

kubectl get pods -w

当 Pod 处于运行状态时,按 Control+C 停止监控。

捕获 Pod 名称并验证 /workspace 目录是否可用:

POD_NAME=sandbox-direct-stateless
kubectl exec ${POD_NAME} -- ls -la /workspace

终止代理会话

删除 Sandbox 资源以自动终止 Pod 并销毁其临时存储空间:

kubectl delete sandbox sandbox-direct-stateless

后续步骤