OpenSandbox 实战:在 Kubernetes 中挂载 PVC 持久化卷,让 AI Agent 沙箱数据跨生命周期留存
2026/9/14 18:55:31 网站建设 项目流程

OpenSandbox 实战:在 Kubernetes 中挂载 PVC 持久化卷,让 AI Agent 沙箱数据跨生命周期留存

【免费下载链接】OpenSandboxSecure, Fast, and Extensible Sandbox runtime for AI agents.项目地址: https://gitcode.com/GitHub_Trending/ope/OpenSandbox

导读

本指南以 OpenSandbox 仓库中的 kubernetes-pvc-volume-mount 示例 与配套文档 docs/examples/kubernetes-pvc-volume-mount.md 为主体,系统讲解如何把 Kubernetes PersistentVolumeClaim(PVC)挂载进 OpenSandbox 沙箱,使写入的数据在沙箱销毁后依然存在,供后续沙箱复用。你将掌握「自带 PVC(Bring Your Own)」「服务端托管持久化」「服务端托管临时存储」三种模式的选型与配置、Pool 模式下共享 PVC 的预挂载方式,以及 PVC 生命周期清理的底层机制,并能在自己的 Kubernetes 集群中直接跑通完整示例。

背景:为什么沙箱需要 PVC 卷挂载

AI Agent 的沙箱默认是瞬态的——沙箱进程结束后文件系统随之消失。对于需要长期积累产物的场景(网页快照、报告、模型权重缓存、共享数据集),OpenSandbox 提供了运行时无关的卷模型:在 OSEP-0003: Volume and VolumeBinding Support 中定义了volumes[]字段,每个卷条目包含一个强类型后端结构(hostossfspvcnfs)以及公共挂载属性(namemountPathreadOnlysubPath)。

其中pvc后端是运行时无关的抽象:在 Kubernetes 上映射为 PersistentVolumeClaim,在 Docker 上映射为 named volume。也就是说,同一个 SDK 请求可以在不同运行时上表达一致的「挂载持久化命名卷」语义。

volumes: - name: shared-data pvc: claimName: "my-shared-volume" mountPath: /mnt/data

从源码看,Python SDK 在 sdks/sandbox/python/src/opensandbox/models/sandboxes.py 中定义了对应的PVCVolume模型。PVC模型的核心字段如下:

字段(SDK)字段(API)默认值说明
claim_nameclaimName必填Kubernetes 中的 PVC 名称或 Docker named volume 名称
create_if_not_existscreateIfNotExistsTruetrue时,卷不存在则自动创建
delete_on_sandbox_terminationdeleteOnSandboxTerminationFalsetrue时,沙箱删除时移除自动创建的卷;预存在的卷永远不会被移除
storage_classstorageClassNone自动创建 PVC 使用的 StorageClass,None表示集群默认;Docker 运行时忽略
storagestorageNone自动创建 PVC 的容量请求(如"1Gi");Docker 运行时忽略
access_modesaccessModesNone自动创建 PVC 的访问模式(如["ReadWriteOnce"]);Docker 运行时忽略

Volume模型通过@model_validator强制校验每个卷条目必须且只能指定一个后端host/pvc/ossfs),mountPath必须是绝对路径——这些约束在客户端即可提前拦截非法请求。

两种模式的选择:PVC 生命周期归谁所有

OpenSandbox 支持两种 PVC 供应模式,区别在于「谁拥有 PVC 的生命周期」:

模式createIfNotExistsdeleteOnSandboxTerminationPVC 归属适用场景
自带 PVC(BYO)false忽略你(在集群中预先创建)长生命周期共享存储;模型缓存;多个沙箱复用的基线数据集
服务端托管·持久truefalse(默认)你(首次创建后归你)首次使用时按需创建,跨沙箱生命周期保留
服务端托管·临时truetrue服务端仅限单个沙箱的临时存储;沙箱终止时自动清理

两种模式在挂载方式上完全一致,区别只在供应与清理环节。选择的关键问题是:PVC 的生命周期应该由你的平台管理,还是由沙箱自己管理?

前置条件

CSI 驱动

Kubernetes 的 PVC 需要 Container Storage Interface (CSI) 驱动来供应和挂载存储。选择一个与你的存储后端匹配的驱动,例如 Alibaba Cloud CSI Driver 支持:

  • Cloud Disk(EBS)——块存储,高性能单节点读写
  • NAS——共享文件存储,多节点读写(ReadWriteMany
  • OSS——对象存储,大规模共享读
  • CPFS——高性能并行文件系统
  • LVM——本地卷管理

OpenSandbox Server

服务端必须运行在 Kubernetes runtime 上,并使用 BatchSandbox 工作负载提供者。官方 Helm chart 已授予 BYO 挂载与服务端自动供应所需的 RBAC 权限(对persistentvolumeclaimsget/create/list/delete/patch)。这一点在服务端源码中有印证:_ensure_pvc_volumes会在挂载前做 PVC 所有权预检,如果服务账号缺少get权限(403),会fail-closed拒绝挂载,而不是降级继续——因为无法确认所有权时,挂载一个已被其他沙箱托管的 PVC 会让当前沙箱暴露在被清理的风险中(见 server/opensandbox_server/services/k8s/kubernetes_service.py)。

Python SDK

uv pip install opensandbox

模式一:自带 PVC(Bring Your Own)

适用于 PVC 属于你平台基础设施的一部分的场景——例如多个沙箱复用的共享 NAS,或预置了模型权重的磁盘。

1. 创建 PVC

# pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc namespace: opensandbox spec: accessModes: - ReadWriteOnce storageClassName: <your-storage-class> resources: requests: storage: 10Gi
kubectl apply -f pvc.yaml kubectl get pvc my-pvc -n opensandbox # 应显示 Bound

2. 在沙箱中挂载它

from opensandbox import Sandbox from opensandbox.models.sandboxes import PVC, Volume sandbox = await Sandbox.create( image="python:3.11", volumes=[ Volume( name="data-volume", pvc=PVC( claimName="my-pvc", createIfNotExists=False, # 绝不自动供应 BYO claim ), mountPath="/mnt/data", readOnly=False, ), ], ) result = await sandbox.commands.run("ls -la /mnt/data") print("\n".join(msg.text for msg in result.logs.stdout))

OpenSandbox 永远不会删除这个 PVC:沙箱终止后 claim 保持 Bound,数据对下一个挂载它的沙箱依然可用。

3. 运行端到端示例

仓库提供了完整的可运行脚本 examples/kubernetes-pvc-volume-mount/main.py,它依次完成:创建沙箱 → 向/mnt/data写入标记文件 → 杀掉沙箱 → 创建第二个绑定同一 PVC 的沙箱 → 确认标记文件仍在。

export OPEN_SANDBOX_API_KEY=your-api-key export OPEN_SANDBOX_BASE_URL=http://localhost:8080 export SANDBOX_PVC_NAME=my-pvc python examples/kubernetes-pvc-volume-mount/main.py

脚本中的关键参数均可用环境变量覆盖:SANDBOX_PVC_NAME(默认my-pvc)、SANDBOX_IMAGE(默认python:3.11)。同时脚本为ConnectionConfig设置了request_timeout=timedelta(minutes=10),因为创建带卷的沙箱需要更长等待时间。执行成功后的输出如下:

图中展示了完整的两步验证流程:第一个沙箱写入Hello from OpenSandbox!后被 kill,第二个沙箱挂载同一 PVC 读取到相同内容,最终输出Data persistence verified!

Pool 模式:预挂载共享 PVC

Pool 的 Pod 在沙箱分配之前就已创建,因此 Pool 共享的存储必须内置于 Pool Pod 模板中。先创建 PVC,再通过Pool.spec.template把它挂载到每个预热 Pod:

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: shared-workspace-pvc namespace: opensandbox spec: accessModes: [ReadWriteMany] storageClassName: <your-rwx-storage-class> resources: requests: storage: 100Gi --- apiVersion: sandbox.opensandbox.io/v1alpha1 kind: Pool metadata: name: shared-workspace-pool namespace: opensandbox spec: template: spec: containers: - name: sandbox-container image: python:3.11 command: ["sleep", "3600"] volumeMounts: - name: shared-workspace mountPath: /workspace volumes: - name: shared-workspace persistentVolumeClaim: claimName: shared-workspace-pvc capacitySpec: bufferMax: 10 bufferMin: 2 poolMax: 20 poolMin: 5

先向集群应用 Pool 清单,再从中分配沙箱。沙箱通过extensions.poolRef选择预配置的 Pool,且不再传递每沙箱的volumes列表

::: warning Pool 静态存储限制 PVC 必须已存在,且其访问模式与存储后端必须允许所有被调度的预热 Pod 挂载它。OpenSandbox 不会创建、变更或删除 Pool 模板引用的 PVC。沙箱创建请求不能在同时使用extensions.poolRef时再添加volumes,因为所选预热 Pod 已存在。 :::

模式二:服务端托管 PVC

适用于沙箱应该自己拥有存储的场景。服务端在 claim 名称第一次被引用时按需创建 PVC,是否在沙箱终止后保留由deleteOnSandboxTermination控制。

按需供应并持久保留(默认)

这是经典的「首个沙箱供应、后续沙箱复用」模式——例如一个崩溃、重启后依然存活的预热缓存,但永远不需要手动kubectl apply

sandbox = await Sandbox.create( image="python:3.11", volumes=[ Volume( name="cache", pvc=PVC( claimName="agent-cache", createIfNotExists=True, # 首次使用时自动供应 deleteOnSandboxTermination=False, # 默认:保留 PVC storageClass="alibaba-cloud-disk-ssd", # 可选;默认使用集群默认 storage="20Gi", # 可选;默认取服务端配置 accessModes=["ReadWriteOnce"], # 可选;默认 ReadWriteOnce ), mountPath="/mnt/cache", ), ], )

第一次调用供应agent-cache;后续使用相同claimName的调用会挂载已有 PVC 并跳过供应。PVC 会一直保留,直到你用kubectl删除它。服务端供应时若未显式指定storage,会回退到服务端配置的storage.volume_default_size(见 kubernetes_service.py 中default_size = self.app_config.storage.volume_default_size的实现)。

按需供应并自动清理

仅限单个沙箱的临时存储——沙箱终止(包括 TTL 到期)时服务端回收 PVC。

sandbox = await Sandbox.create( image="python:3.11", timeout=600, # 10 分钟沙箱 volumes=[ Volume( name="scratch", pvc=PVC( claimName=f"scratch-{run_id}", # 每次运行唯一;opt-in 的 PVC 被独占 createIfNotExists=True, deleteOnSandboxTermination=True, storage="5Gi", ), mountPath="/mnt/scratch", ), ], )

::: tip 清理范围 服务端只会删除它按此 opt-in 供应的 PVC。预存在的 PVC 和以deleteOnSandboxTermination=false供应的 PVC 永远不会被触碰。Opt-in 的 PVC 由创建它的沙箱独占——第二个沙箱尝试挂载相同claimName会被以409 CONFLICT拒绝。请为每个沙箱使用唯一的claimName;如果需要共享存储,请使用非 opt-in 或预存在的 PVC。 :::

PVC 生命周期:服务端如何做到精确清理

清理行为由创建时的模式决定:

来源沙箱终止时的清理
预存在 PVC(BYO)服务端永不触碰
自动创建,deleteOnSandboxTermination=false(默认)PVC 保留;由调用方负责清理
自动创建,deleteOnSandboxTermination=true服务端删除 PVC

对于 opt-in 的 PVC,服务端会打上opensandbox.io/volume-managed-by=serveropensandbox.io/id=<sandbox-id>两个标签,然后通过两条分层路径执行清理(源码实现见 kubernetes_service.py):

  1. ownerReferences(主路径)——PVC 创建后立即被 patch 指向沙箱的工作负载自定义资源。一旦该 CR 被删除(包括由控制器驱动的 TTL 到期,这种路径根本不会到达DELETE /sandboxes/{id}API),Kubernetes 垃圾回收就会级联删除 PVC。
  2. 标签选择器清扫(兜底)——在DELETE /sandboxes/{id}时,服务端按上述标签列出 PVC 并尽力删除。这能覆盖ownerReferencespatch 失败(例如 RBAC 问题)的情况,确保 API 返回时 PVC 已消失。

两条路径都只匹配服务端打标的 PVC,因此 BYO 和 opt-out 的 claim 永远不会被回收。PVC 删除后,底层 PV 遵循其StorageClass.reclaimPolicy决定去留。

值得一提的是,服务端在创建沙箱前会做所有权预检:即使createIfNotExists=false的请求指向了一个已被其他沙箱打标托管的 PVC,也会被_reject_pvc_owned_by_other_sandbox拦截,避免该沙箱的存储被原 owner 的清理逻辑误删。

重要注意事项

::: warning

  • 每沙箱的volumes不能与extensions.poolRef组合使用。Pool 模式下请按上文所述在Pool.spec.template中预挂载已有共享 PVC。
  • 多个沙箱可以挂载同一 PVC(如果访问模式允许,如ReadWriteMany)——但仅当该 PVCopt-in 自动清理时。以deleteOnSandboxTermination=true创建的 PVC 由创建沙箱独占,其他沙箱挂载会被服务端以409 CONFLICT拒绝。
  • 同一请求中对相同claimName的所有挂载,createIfNotExistsdeleteOnSandboxTermination必须一致;不一致会被以400 INVALID_PARAMETER拒绝。服务端在_ensure_pvc_volumes中按 claim 聚合校验这些标志,一旦发现冲突,在任何副作用发生前直接返回 400(见 kubernetes_service.py)。 :::

继续深入

  • 卷模型的完整设计(含hostossfsnfs等后端、跨运行时映射与安全约束):oseps/0003-volume-and-volumebinding-support.md
  • 可运行的示例脚本:examples/kubernetes-pvc-volume-mount/main.py
  • Python SDK 卷模型源码:sdks/sandbox/python/src/opensandbox/models/sandboxes.py
  • 服务端 Kubernetes PVC 供应与清理实现:server/opensandbox_server/services/k8s/kubernetes_service.py
  • 服务端测试覆盖:PVC 挂载、生命周期与冲突拒绝逻辑见 server/tests/test_routes_snapshots.py 同目录的 k8s 相关测试
  • 更完整的示例说明文档:docs/examples/kubernetes-pvc-volume-mount.md

【免费下载链接】OpenSandboxSecure, Fast, and Extensible Sandbox runtime for AI agents.项目地址: https://gitcode.com/GitHub_Trending/ope/OpenSandbox

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询