CubeSandbox S3 兼容 Volume 插件:为 AI Agent 沙箱接入 AWS S3 / MinIO / COS / R2 生命周期持久卷
2026/9/16 17:34:39 网站建设 项目流程

CubeSandbox S3 兼容 Volume 插件:为 AI Agent 沙箱接入 AWS S3 / MinIO / COS / R2 生命周期持久卷

【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox

本篇是 CubeSandbox 开源仓库中 S3 兼容 Volume 插件的完整操作手册与实现解读。它面向需要手工部署插件、或将默认捆绑的 MinIO 替换为任意 S3 兼容对象存储的运维与开发人员,完整覆盖依赖安装、插件构建、CubeMaster/Cubelet 双侧注册、SDK 验证与故障排查。读完本文,你将能够在自己的 Cube 集群上把 AWS S3、腾讯云 COS、Cloudflare R2、MinIO 等对象存储变成 AI Agent 沙箱的持久卷,实现“销毁沙箱、数据保留、下次重挂”的生命周期持久化。

插件定位:给临时沙箱一块“永生”的磁盘

CubeSandbox 的沙箱默认是临时的——沙箱被销毁后,其内部数据随之消失。S3 兼容 Volume 插件为沙箱提供用户作用域(user-scoped)的持久卷:通过 e2b 兼容的/volumesAPI 完成卷的创建、挂载、卸载与删除,底层由对象存储承载数据。插件本身是单个静态 Go 二进制,内置 S3 客户端(控制面不再需要任何 S3 命令行工具),数据面挂载则使用标准的s3fsFUSE 驱动。整个方案与厂商无关——后端不过是配置文件里的一个ENDPOINT字段。

该插件以 COS 插件为蓝本,但有两处关键差异(见 examples/volume/s3/README.md):

  • 后端从腾讯云 COS 泛化为任意 S3 兼容端点
  • 挂载驱动 s3fs 同时支持amd64arm64(cosfs 仅支持amd64)。

两者可以在同一集群中共存(默认安装会同时注册coss3两个 driver)。

版本要求:Cube 平台 ≥ 0.6.0,Python SDKcubesandbox≥ 0.6.0。Hook 协议与框架细节见 Volume Plugin framework。

前置条件与“你是否需要本页”

在动手之前,先对照下表确认自己的处境:

你的情况去向
默认安装(一键安装 / Helm 默认),只是想用 S3 Volume直接看用户教程 S3 Volumes——安装器已启动 MinIO、装好插件、写好凭据,直接用 SDK 即可,无需任何配置
想把捆绑的 MinIO 换成外部 S3从下文「构建并安装插件与凭据」一节开始,只需编辑volume-s3.conf
从零部署 S3 Volume 插件阅读本文全部内容

前置条件清单

项目说明
运行中的 Cube 集群至少包含CubeMasterCubeletCubeAPI(端口通常为3000
沙箱模板一个templateID(见下文 SDK 验证一节)
S3 兼容存储一个 bucket,以及一对对该 bucket 具备读写权限的访问密钥(bucket 缺失时插件会自动创建)
本机权限CubeMaster / Cubelet 宿主机上的sudo(用于安装软件、编辑配置、重启服务)

单机开发:CubeMaster 与 Cubelet 在同一主机上,依赖只需安装一次。多节点:按下一节的表格在对应节点分别安装。

1. 安装依赖:谁需要什么

插件控制面(Create/Destroy)通过 HTTP 直接访问端点,因此仅充当 CubeMaster 的节点本小节什么都不用装;数据面(Attach/Detach)才需要挂载工具:

工具安装位置用途(Hook)
s3fsCubeletattach / detach(FUSE 挂载)
jq任意节点(可选)调试时手工阅读插件输出

方式 A:安装脚本

Cubelet 节点:

sudo ./install-deps.sh --s3fs

单机(两种角色同宿主机),并附带 jq 便于调试:

sudo ./install-deps.sh --all

只检查不安装,追加--check-only。脚本同时兼容 Debian/Ubuntu(apt,安装s3fs)与 RHEL 系(dnf/yum,安装s3fs-fuse,依赖 EPEL),见 examples/volume/s3/install-deps.sh。

方式 B:手动安装

# Cubelet — Debian/Ubuntu sudo apt-get install -y s3fs # Cubelet — RHEL/CentOS(需要 EPEL) sudo yum install -y epel-release && sudo yum install -y s3fs-fuse

验证安装

Cubelet —— s3fs

ls /dev/fuse && echo "FUSE ok" s3fs --version | head -1

两者都必须成功;/dev/fuse缺失会直接导致 attach 失败。

CubeMaster —— 用插件的 create 钩子验证凭据

插件自身的create钩子就是最好的校验:凭据错误、端点错误或权限缺失都会立刻报错。

/usr/local/services/cubetoolbox/CubeMaster/plugin/cube-volume-s3 \ --op create --volume-id preflight-check --name preflight /usr/local/services/cubetoolbox/CubeMaster/plugin/cube-volume-s3 \ --op destroy --volume-id preflight-check

两条命令都必须输出"error":""且退出码为 0。出现InvalidAccessKeyIdAccessDenied说明密钥对缺少对 bucket 的读写权限。

2. 构建并安装插件与凭据

构建二进制

从仓库根目录构建(使用项目构建器镜像,无需本地 Go 工具链):

make cube-volume-s3 # -> _output/bin/cube-volume-s3

或使用本地 Go 工具链(≥ 1.25):

cd examples/volume/s3 && make # -> bin/cube-volume-s3

一键发布包与容器镜像已内置编译好的二进制,位于<prefix>/{CubeMaster,Cubelet}/plugin/cube-volume-s3

安装到两个 plugin 目录

PREFIX=/usr/local/services/cubetoolbox sudo install -m 0755 _output/bin/cube-volume-s3 \ "$PREFIX/CubeMaster/plugin/cube-volume-s3" sudo install -m 0755 _output/bin/cube-volume-s3 \ "$PREFIX/Cubelet/plugin/cube-volume-s3" sudo install -m 0600 volume-s3.conf.example \ "$PREFIX/CubeMaster/plugin/volume-s3.conf" sudo install -m 0600 volume-s3.conf.example \ "$PREFIX/Cubelet/plugin/volume-s3.conf"

编辑volume-s3.conf

配置文件采用KEY=VALUE行格式(保持与source兼容),由插件自行解析而不是执行(见 internal/config/config.go 的Load实现)。字段如下:

字段说明必填
ACCESS_KEY_IDAccess key ID
SECRET_ACCESS_KEYSecret access key
BUCKET承载所有卷的 bucket
ENDPOINTS3 兼容端点 URL(见下表)
REGIONSigV4 签名区域;默认us-east-1
S3FS_EXTRA_OPTS额外 s3fs 挂载选项,以空白分隔(如 MinIO 需要-ouse_path_request_style)。多选项的值可加引号以保持文件可被source;插件会去除引号。设置-ouse_path_request_style同时会把插件自身 S3 客户端切换为 path-style 寻址

常见后端端点对照:

提供商ENDPOINTREGION
AWS S3https://s3.<region>.amazonaws.combucket 所在区域
腾讯云 COShttps://cos.<region>.myqcloud.combucket 所在区域(如ap-guangzhou
Cloudflare R2https://<account-id>.r2.cloudflarestorage.comauto
MinIOhttp://<minio-host>:9000任意值

完整模板见 examples/volume/s3/volume-s3.conf.example。配置含明文密钥,必须 root 属主、权限600

sudo chown root:root "$PREFIX/CubeMaster/plugin/volume-s3.conf" "$PREFIX/Cubelet/plugin/volume-s3.conf" sudo chmod 600 "$PREFIX/CubeMaster/plugin/volume-s3.conf" "$PREFIX/Cubelet/plugin/volume-s3.conf"

插件在可执行文件同目录下查找volume-s3.conf,也可用环境变量CUBE_S3_CONFIG覆盖路径(源码中还支持CUBE_S3_PASSWD_FILECUBE_S3_LOCK_DIR两个调试/CI 覆盖项,见 cmd/cube-volume-s3/main.go 的包注释)。挂载基目录不在此处配置——由 Cubelet 在 attach 时传入(默认/data/cube-shared/volume,见下一节)。

从源码看,配置解析还做了两项防御:internal/config/config.go 会校验四个必填字段非空,并支持ADDRESSING_STYLE=path旧字段兼容;解析时按第一个#截断行内注释(与 shellsource行为一致),但值内不支持带引号的#

3. 配置 CubeMaster

编辑 CubeMaster 配置(常见路径/usr/local/services/cubetoolbox/CubeMaster/conf.yaml),添加Controller插件(Create / Destroy):

volume_plugins: - name: s3 type: binary binary_path: /usr/local/services/cubetoolbox/CubeMaster/plugin/cube-volume-s3

name: s3就是 API / SDK 眼中的driver。当Volume.create("x")省略 driver 时,使用列表中的第一个条目——默认安装现在把s3排在第一位,因此省略 driver 即路由到 S3。

4. 配置 Cubelet

编辑 Cubelet 配置(常见路径/usr/local/services/cubetoolbox/Cubelet/config/config.toml)。

确认挂载父目录(可选;以下为默认值):

[plugins."io.cubelet.internal.v1.storage"] volume_plugin_base_dir = "/data/cube-shared/volume"

添加Node插件(Attach / Detach):

[[plugins."io.cubelet.internal.v1.storage".volume_plugins]] name = "s3" type = "binary" binary_path = "/usr/local/services/cubetoolbox/Cubelet/plugin/cube-volume-s3"

name必须与 CubeMaster 一致(本例均为s3)。插件返回的host_path形如<volume_plugin_base_dir>/s3-<volumeID>,满足框架对host_path必须位于volumeBaseDir之内的要求(框架层约束详见 docs/guide/volume-plugin.md 的 Registration and Configuration 一节)。

5. 重启服务并验证

sudo systemctl restart cube-sandbox-cubemaster sudo systemctl restart cube-sandbox-cubelet sudo systemctl restart cube-sandbox-cube-api sleep 5 systemctl is-active cube-sandbox-cubemaster cube-sandbox-cubelet cube-sandbox-cube-api

验证插件已加载

grep -aF '[volume] registered' /data/log/CubeMaster/cubemaster-req.log | tail -5 grep -aF '[plugin_volume] initialized' /data/log/Cubelet/Cubelet-req.log | tail -5

预期输出:

[volume] registered binary plugin "s3" at /usr/local/services/cubetoolbox/CubeMaster/plugin/cube-volume-s3 [plugin_volume] initialized binary plugin "s3" at /usr/local/services/cubetoolbox/Cubelet/plugin/cube-volume-s3

手动 attach 测试(在 Cubelet 节点上):

/usr/local/services/cubetoolbox/Cubelet/plugin/cube-volume-s3 \ --op attach \ --sandbox-id test-sandbox \ --namespace default \ --volume-id test-vol \ --ref-count 0 \ --volume-base-dir /data/cube-shared/volume

成功标志:stdout 输出一行 JSON,包含"host_path":"/data/cube-shared/volume/s3-test-vol""error":""

测试后清理:

/usr/local/services/cubetoolbox/Cubelet/plugin/cube-volume-s3 \ --op detach --sandbox-id test-sandbox --namespace default \ --volume-id test-vol --ref-count 0 \ --metadata '{"mount_dir":"/data/cube-shared/volume/s3-test-vol"}'

钩子调用契约(源码视角)

每次操作是一次独立进程cube-volume-s3 --op <op> [--<key> <value> ...],stdout 输出单个 JSON 对象,日志走 stderr,退出码 0 且"error":""表示成功(见 cmd/cube-volume-s3/main.go)。main.go中的parseFlags定义了--op--volume-id--name--sandbox-id--namespace--ref-count--volume-base-dir--private-data--metadata等参数,run函数按--op分发到四个钩子:

  • create:确保 bucket 存在(缺失时创建),然后 PUT 0 字节的volumes/<id>/目录对象;
  • destroy:递归删除该前缀下的所有对象;
  • attach:执行 s3fs 挂载并返回host_path(幂等——重复 attach 复用已有挂载点,返回的metadata记录mount_dirvolume_id);
  • detachrefCount > 0时直接返回成功(不卸载),refCount == 0时才执行卸载。

6. 准备 SDK 环境

开发机上(必须能访问 CubeAPI):

pip install 'cubesandbox>=0.6.0' export CUBE_API_URL=http://<cubeapi-host>:3000 export CUBE_TEMPLATE_ID=<your-template-id> # 挂载卷的远程沙箱 I/O 需要(数据面走 CubeProxy) export CUBE_PROXY_NODE_IP=<cubeproxy-or-cubelet-node-ip> # 集群开启鉴权时: # export CUBE_API_KEY=<your-key>

7. 用 SDK 验证完整生命周期

from cubesandbox import Sandbox, Volume # ① 创建 Volume(bucket 中会出现目录对象 volumes/<id>/) vol = Volume.create("my-data", driver="s3") print("volume_id:", vol.volume_id) # ② 创建沙箱并挂载 with Sandbox.create(volume_mounts={"/workspace": vol}) as sb: sb.files.write("/workspace/hello.txt", "from S3 volume") print(sb.files.read("/workspace/hello.txt")) # ③ 退出 with → 沙箱销毁、卷卸载(bucket 数据保留) # ④ 删除 Volume(bucket 前缀被清除——不可逆) Volume.destroy(vol.volume_id) print("done")

确认对象落进了 bucket。任意 S3 浏览器均可;MinIO 的mc是单二进制、无需 Python:

source /usr/local/services/cubetoolbox/CubeMaster/plugin/volume-s3.conf mc alias set cube "$ENDPOINT" "$ACCESS_KEY_ID" "$SECRET_ACCESS_KEY" mc ls --recursive "cube/$BUCKET/volumes/"

确认 Cubelet 挂载命名空间内的 s3fs 挂载(沙箱运行期间执行):

CPID=$(pgrep -f "cubelet --config" | head -1) nsenter -t "$CPID" -m -- cat /proc/mounts | grep s3fs

调试提示:Cubelet 运行在独立的挂载命名空间(unshare(CLONE_NEWNS))中,宿主根命名空间的/proc/mounts通常看不到这些 FUSE 挂载点。binary 插件由 Cubelet fork 出来、天然继承该命名空间,无需 nsenter;手动在宿主上挂载通常不会出现在沙箱内(见 docs/guide/volume-plugin.md Debugging 一节)。

自动化验证

COS 示例的 examples/volume/cos/verify_volume.py 与 driver 无关——把它指向本 driver 即可:

cd ../cos export CUBE_API_URL=http://127.0.0.1:3000 export CUBE_TEMPLATE_ID=tpl-xxxx export CUBE_PROXY_NODE_IP=127.0.0.1 export CUBE_VOLUME_DRIVERS=s3 # 脚本默认跳过 cfs/s3/nfs(这些在 COS 演示环境中未部署); # 本环境已部署 s3,清空跳过列表: export CUBE_VOLUME_SKIP_DRIVERS= python3 verify_volume.py

该脚本覆盖了 HTTP 响应契约(POST /volumes201、DELETE204、删除后GET404)、逐 driver 的完整生命周期(创建→列出→单查→进沙箱检查挂载点与读写→删除→确认消失)、多沙箱共享、跨沙箱持久化,以及挂载不存在卷、非法卷名、未知 driver 等负向用例,并输出分组报告。

8. 故障排查

症状检查
unknown driver: s3CubeMastervolume_plugins缺条目,或未重启
no plugin registered for driver "s3"Cubelet 缺同名插件,或未重启
Attach 失败,s3fs mount failedls /dev/fuse;核对volume-s3.conf中的凭据与ENDPOINT;运行上文第 5 节的手动 attach 查看 s3fs 具体报错
Attach 失败,s3fs 日志出现NoSuchKey(针对volumes/<id>/Create 必须 PUT 带尾斜杠的目录对象(s3fs mkdir 依赖它)。前缀下放一个.keep文件是另一个 key,不满足该 stat。若旧版本插件仍在写.keep,请升级插件
open config ...: no such file or directoryvolume-s3.conf必须位于插件二进制同目录,或用CUBE_S3_CONFIG指向它
InvalidAccessKeyId/SignatureDoesNotMatch密钥对错误、缺少 bucket 权限,或REGION与端点 SigV4 期望不符
bucket 名含点号s3fs 默认使用 virtual-hosted-style 寻址,对含点号的 bucket 名会破坏 TLS。改用不含点号的 bucket,或在volume-s3.conf中设置S3FS_EXTRA_OPTS=-ouse_path_request_style(MinIO 通常也需要)
SDK 写失败CUBE_PROXY_NODE_IP未设置;CubeAPI 或模板未 READY
省略 driver 的Volume.create没有走 s3默认安装中s3volume_plugins首项(默认 driver);若未生效,检查volume_plugins的顺序

更多平台级排查见 Volume Plugin framework 的 Troubleshooting 章节。

后端布局:一个 bucket、每卷一个前缀

<bucket>/volumes/<volumeID>/ ← 每个 Volume 一个前缀

Attach 时,s3fs 将BUCKET:/volumes/<volumeID>挂载到宿主机/data/cube-shared/volume/s3-<volumeID>/,随后 Cubelet 通过 virtiofs 将该目录暴露给 microVM。

Hook 行为与 RefCount

HookrefCount行为
CreateController确保 bucket 存在(缺失时创建),然后 PUT 0 字节volumes/<id>/对象(s3fs 目录对象)
DestroyController列出并删除前缀下所有对象
AttachNode0s3fs 挂载 → 返回host_path
AttachNode> 0返回已有host_path;不重复挂载
DetachNode> 0无操作
DetachNode0fusermount -u保留bucket 中的数据

RefCount 语义与框架一致:Cubelet 维护每节点引用计数并传给 Node Hook,节点本地计数翻转(0→1 或 1→0)时 Cubelet 会通知 CubeMaster 更新t_cube_volume.refcount,控制面DELETE /volumes在该计数非零时被拒绝(409),详见 docs/guide/volume-plugin.md 的 RefCount 小节。

源码级实现细节

  • 挂载幂等与安全:attach/detach 通过flock序列化同一节点上同一卷的并发操作(见 internal/lockfile/lockfile.go——插件每次操作都是独立短进程,锁必须是跨进程的文件锁,进程内 Mutex 无法串行化)。s3fs 挂载后还要用mountpoint -q复核“真的是挂载点”,因为 s3fs 认证失败时也可能退出码为 0(见 internal/s3fsmnt/mount.go)。
  • s3fs 参数构造MountArgs组装-ourl=<ENDPOINT>-oendpoint=<REGION>-opasswd_file=-oallow_other(Cubelet 以不同用户遍历挂载点做 virtiofs 绑定)并追加S3FS_EXTRA_OPTS。凭据文件按 bucket 区分(/etc/cube/.passwd-s3fs-volume-<bucket>),仅当内容变化时才重写,避免多插件实例竞争(见 internal/s3fsmnt/mount.go 的EnsurePasswdFile)。
  • 控制面客户端:Create/Destroy 使用 minio-go(credentials.NewStaticV4),PathStyle与 s3fs 的-ouse_path_request_style联动,保证控制面与数据面寻址方式一致;ParseEndpoint明确拒绝带路径前缀的端点(如https://host/s3proxy),因为 minio-go 无法携带 URL 路径,会导致控制面与数据面分裂到不同根(见 internal/s3api/client.go)。
  • 销毁的容错边界RemoveVolumeDir只把 bucket 或 key “不存在”视为成功(前缀已不存在),其余错误一律向上传播,避免 CubeMaster 在对象尚未删除时丢弃卷记录;并发首次建桶时用BucketAlreadyOwnedByYou/BucketAlreadyExists容错(见 internal/s3api/client.go)。

设计要点总结

  • 一个 bucket、每卷一个前缀:与 COS 示例一致。多 bucket 场景通常运行多个插件实例并取不同driver名,或扩展 Create 接受 bucket。框架只要求 Hook 协议与driver一致性。
  • 无需 S3 命令行工具:控制面在插件二进制内使用 minio-go——一个约 8MB 的静态二进制,替代了每个控制节点上约 100MB 的 AWS CLI 安装。
  • bucket 自动创建:Create 先探测 bucket;已存在的 bucket不需要s3:CreateBucket权限,只有缺失时才会创建(典型场景即捆绑的 MinIO)。
  • Destroy 仅容忍 not-found:bucket 或 key 缺失视为前缀已删;其余错误全部传播,防止卷记录被删而对象残留。
  • 凭据永不进入沙箱:它们只存在于 CubeMaster/Cubelet 上 root 属主、权限600的配置中;microVM 看到的只是一个文件系统。
  • private_data承载 key 前缀:Create 的结果(最大 1024 字节,永不下发 SDK 客户端)在 Attach 时回传,attach 据此记录挂载来源。
  • 并发安全:同卷 attach/detach 由 per-volumeflock串行化,两个沙箱同时启动不会重复挂载。
  • Destroy 不可逆:会删除整个volumes/<id>/前缀。API 的 refcount 保护(挂载中DELETE /volumes返回 409)是防止删除已挂载卷的手段;真正的风险是节点崩溃后残留过期 refcount——集群级计数可能已归零而节点仍持有挂载。

仓库布局与测试

examples/volume/s3/ ├── Makefile # build / fmt / lint / test ├── install-deps.sh # 宿主依赖 + 检查(s3fs / jq) ├── volume-s3.conf.example ├── cmd/cube-volume-s3/main.go # 参数解析、Hook 分发、stdout JSON └── internal/ ├── config/ # volume-s3.conf 解析 ├── s3api/ # 经 minio-go 实现 create / destroy ├── s3fsmnt/ # s3fs 挂载 / 卸载 └── lockfile/ # 跨进程 per-volume flock

运行单元测试(无需云端访问):

cd examples/volume/s3 && make test # 或,在项目构建器镜像中从仓库根目录执行: make cube-volume-s3-test

Makefile 使用CGO_ENABLED=0产出静态二进制,保证在任意 glibc/musl 宿主上可运行(见 examples/volume/s3/Makefile)。插件同时覆盖configs3apis3fsmntlockfile四个包的单元测试(*_test.go),不依赖真实云环境即可验证解析、端点校验、参数构造与锁语义。

延伸阅读

文档内容
S3 Volumes(用户教程)终端用户快速上手
Volume Plugin framework协议、RefCount、Hook 语义、源码索引
COS 示例本插件所参照的参考实现(binary Shell + rpc Go)
verify_volume.py与 driver 无关的 SDK 集成验证脚本

【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox

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

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

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

立即咨询