首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >给 AI Agent 加一个"读档"键:我把 CubeSandbox 的快照/回滚/克隆真跑了一遍

给 AI Agent 加一个"读档"键:我把 CubeSandbox 的快照/回滚/克隆真跑了一遍

原创
作者头像
一只牛博
发布2026-07-10 10:22:05
发布2026-07-10 10:22:05
710
举报
文章被收录于专栏:AIAI

做 Agent 的人大概都有过这样一个瞬间:模型自己写代码、自己敲命令,跑到一半把环境搞坏了——依赖装崩了、目录删错了,或者顺着一条错路越走越远。这时候你会特别想要游戏里那种"读档"键:退回到刚才还没出事的那个存档点,重来一次。

再往前想一步,如果我在做 RL 采样或者给 Agent 做树搜索,我要的是从同一个中间状态分叉出好几条路并行去试,而不是每条路都从头冷启动一遍。

这两件事,Docker 都给不了。容器能 commit 成镜像,但那是磁盘层的快照,进程和内存状态丢了;而且 commit 一次好几秒,谈不上随手存档。传统虚拟机能存内存快照,可它太重,起一个要好几秒。

所以当我看到 CubeSandbox 号称能对运行中的沙箱做百毫秒级的快照、克隆、回滚,而且每个沙箱还是带独立内核的硬件级隔离时,我是有点将信将疑的。这套东西是腾讯云开源的(Apache 2.0),底层用 Rust + KVM 写。我先把代码 clone 到本地翻了一遍(项目在 github.com/TencentCloud/CubeSandbox),然后租了台云服务器亲手部署,专门验证它这个"存档 / 读档 / 分身"到底是不是真的。

下面是我的实测记录。结论先放这:是真的,但它有自己的边界,我会一并如实写出来。

一、先到起跑线:部署踩的那些坑

部署流程就不写了、网上一搜一堆,我不打算把它当重点。但我这一路踩的坑挺真实的,顺手记一记。

我第一台机器随手开了台 CentOS,结果连门都没进去:它的 glibc 是 2.17,而 CubeSandbox 的二进制在 glibc 2.31 上编译,直接跑不起来;它默认还是 ext4,而快照要用 XFS 的 reflink,ext4 玩不了。glibc 又没法单独升级,索性重装成官方最推荐的 OpenCloudOS 9——默认 XFS、glibc 够新,而且 CubeSandbox 的 PVM 内核本身就是基于 OpenCloudOS 内核构建的,同源最省事。重开的这台是 SA9.LARGE8,4 核 8G,100G 系统盘,按量计费跑完即销毁。

img
img

机器一开好,我第一件事是把环境自查一遍,免得又白忙:

img
img

四项全绿——架构是 x86_64,glibc 2.38(≥2.31),盘是 xfs 100G。这台才算过了门槛。对比第一台 CentOS 的 glibc 2.17,这一步就是我交的学费。

接下来装 PVM 内核。PVM 是一套页表式的嵌套虚拟化,不需要宿主机把 VT-x/AMD-V 透传给虚拟机,普通云 VM 就能跑 KVM——这也是我一台普通云主机也能玩的原因。内核包在 Releases 页(kernel-*opencloudos9.cubesandbox.pvm.host*.x86_64.rpm)上,575M,我老老实实 wget,然后就傻眼了:

img
img

eta 5h 50m。这种几百兆的二进制,国内直连 GitHub 基本没法等,但是幸运的是官方提供给用户的cnb源下载地址:wget https://cnb.cool/CubeSandbox/CubeSandbox/-/releases/download/v0.4.0/kernel-6.6.69_opencloudos9.cubesandbox.pvm.host_gb85200d80fa2-1.x86_64.rpm。(注意,下面涉及的脚本官方也是提供了cnb源的)。

内核到手后,rpm -ivh --oldpackage 装上,再把它设成默认启动项。我没用手动数 index 那套(容易设错),直接按文件名把 PVM 内核挑出来设默认:

img
img

grubby --default-kernel 回显的就是那个 ...pvm.host 内核,说明默认项设对了。接着配内核启动参数。官方给的是一条在线脚本:

代码语言:bash
复制
curl -sL https://raw.githubusercontent.com/TencentCloud/CubeSandbox/master/deploy/pvm/grub/host_grub_config.sh | bash

问题是这条 curl 又卡在 GitHub 上,要么慢、要么干脆不动。这里可以直接用官方提供的cnb源下载*curl -sL* *https://cnb.cool/CubeSandbox/CubeSandbox/-/git/raw/master/deploy/pvm/grub/host_grub_config.sh* *| bash*,还有一种土办法,也是我实际用的:脚本本身很小,直接在本机(浏览器打开上面那个 raw 地址,或者我已经 clone 到本地的仓库里)把 host_grub_config.sh 的内容复制下来,到服务器上 vim 一个同名文件粘进去,再 bash 执行——完全绕开 GitHub,几秒搞定:

img
img

Generating grub configuration file ... done,参数配好,reboot。重启之后是关键的验证一步:

img
img

uname -r 里带着 opencloudos9.cubesandbox.pvm.hostlsmod 里也能看到 kvm_pvm——PVM 内核进去了,KVM 能力有了。这既是后面所有实验的地基,也顺带证明我确实是在一台真实的 x86 机器上跑,不是嘴上说说。

然后是一键安装。安装脚本 online-install.sh 同样挂在 GitHub 上(https://github.com/tencentcloud/CubeSandbox/raw/master/deploy/one-click/online-install.sh),所以我照搬上面那招——先在本机把脚本内容复制过来,在服务器上落成一个 online-install.sh 文件。但这个脚本和上一个有个关键区别:它执行时还会再去下载一个两百多兆的发布包,脚本能复制,发布包可复制不了。好在它内置了国内镜像开关 MIRROR=cn,能改从腾讯 CDN 拉。所以我是这么跑的:

代码语言:bash
复制
CUBE_PVM_ENABLE=1 MIRROR=cn bash online-install.sh
img
img

当然这里依旧可以使用官方提供的cnb下载源进行下载(推荐):

代码语言:bash
复制
curl -sL https://cnb.cool/CubeSandbox/CubeSandbox/-/git/raw/master/deploy/one-click/online-install.sh -o online-install.sh
  CUBE_PVM_ENABLE=1 MIRROR=cn bash online-install.sh

两个开关各司其职,思路正好对应上面两类问题:脚本(小)靠"本机复制"拿到,发布包(大)靠 MIRROR=cn 走镜像。发布包从 cnb.cool 镜像下,228M 只花了 22 秒(10.2M/s),接着它自动装 docker、containerd 这些依赖,识别出节点内网 IP 是 10.206.0.5。跑完之后,一整套组件都拉起来了:

img
img

install complete (role=control)。顺着这张图的服务清单,其实能看出它的架构轮廓:cubemaster 编排、cubelet 管节点、cube-api 是 E2B 兼容的 API 网关、cube-proxy 做路由、cube-egress 管出网,再加上 Docker 里的 MySQL 和 Redis。装完打开 :12088 的 Web 控制台,节点是绿的:

img
img

节点 10.206.0.5 状态 Ready,8 核 9.2G 的容量都认出来了,版本 v0.4.0。最后一步,用官方预置的代码沙箱镜像建一个模板。这里同样要注意镜像仓库:海外用 cube-sandbox-int.tencentcloudcr.com,国内一定换成 -cn 的,不然又得等:

代码语言:bash
复制
cubemastercli tpl create-from-image \
  --image cube-sandbox-cn.tencentcloudcr.com/cube-sandbox/sandbox-code:latest \
  --writable-layer-size 1G \
  --expose-port 49999 --expose-port 49983 --probe 49999

这是整个部署里最慢的一步(要拉镜像再构建),不过因为镜像仓库也走了国内,这次我从敲命令到 READY 只等了大约 40 秒:

img
img

从时间戳看,11:37:36 提交、11:38:16READY,拿到一个 template_id,后面全靠它。到这里,起跑线才算真正到了——下面才是我这趟真正想验证的东西。

二、三个动作,对应 Agent 的三种刚需

CubeSandbox 的快照能力,核心就三个 SDK 动作。我把它们翻译成人话:

SDK 动作

大白话

对 Agent 意味着什么

create_snapshot()

存档

干高风险操作前,先留个档

rollback(id)

读档

搞砸了,原地退回存档点重来

clone(n=N)

分身

一个状态,fork 成 N 个并行去试

它整体兼容 E2B SDK,但快照这套用的是它自己的 cubesandbox 包。因为一键安装不会把仓库 clone 到服务器,我干脆在服务器上建了个目录,用腾讯云的 PyPI 源装 SDK:

img
img

装的是 cubesandbox 0.3.0。这里有个容易踩的点:快照脚本读的环境变量是 CUBE_API_URLCUBE_TEMPLATE_ID,跟 E2B 那套的 E2B_API_URL 不是一回事,别搞混。脚本我都直接在服务器本机跑,连 127.0.0.1:3000,省掉开防火墙和证书的麻烦,这是最不容易翻车的做法。

三、存档:一行代码,给运行中的沙箱打完整快照

先看最基础的存档。这段代码创建一个沙箱,然后对它打一次快照:

代码语言:python
复制
from cubesandbox import Sandbox

with Sandbox.create(template=TEMPLATE_ID) as sb:
    print(f"sandbox: {sb.sandbox_id}")
    snapshot = sb.create_snapshot()
    print(f"snapshot created: {snapshot.snapshot_id}")

跑出来是这样:

img
img

create_snapshot() 返回一个 snapshot_id(形如 snap-5d4f3a2e...),它可以直接当模板传给 Sandbox.create(template=...) 再拉起一个新沙箱。也就是说,一次快照既是"存档",本身又是一个可复制的模板——这个特性后面会反复用到。

但光"能存"不够,我更关心它存的到底是什么:只存了磁盘,还是连内存状态一起?我写了个脚本验证:在源沙箱里写一个标记文件,打快照,再从这个快照拉起一个全新的沙箱,看标记还在不在:

代码语言:python
复制
MARKER = "hello from snapshot"

with Sandbox.create(template=TEMPLATE_ID) as src:
    src.run_code(f"open('/tmp/marker.txt','w').write('{MARKER}')")
    snapshot = src.create_snapshot()

with Sandbox.create(template=snapshot.snapshot_id) as cloned:
    result = cloned.run_code("print(open('/tmp/marker.txt').read())")
    assert result.logs.stdout[0].strip() == MARKER
img
img

末尾那行 OK: filesystem state preserved in cloned sandbox——从快照里长出来的新沙箱,读到了原沙箱写下的内容。对 Agent 来说,这意味着我可以在它跑到某个关键节点时按下"存档",把它当时脑子里(内存)和手上(磁盘)的东西一起冻住。

四、读档:让 Agent 犯错之后能"退回去"

这是我最想验证的一个,也是整篇的核心。回滚脚本我特意写成一眼能看懂的版本:同一个文件,内容从 v0 一路改到 v2,中间在 v1 打了个 checkpoint,然后回滚到 v1

代码语言:python
复制
sb = Sandbox.create(template=base_id)

sb.run_code("open('/tmp/v.txt','w').write('v1')")
checkpoint = sb.create_snapshot()          # 在 v1 存档

sb.run_code("open('/tmp/v.txt','w').write('v2')")   # 改成 v2
sb.rollback(checkpoint.snapshot_id)         # 读档,退回 v1

关键的两行,我在图里用箭头标了出来:

img
img

before rollback: 'v2'after rollback: 'v1'。肉眼可见,状态被拽了回去。

有个细节值得说:rollback()原地回滚的,沙箱的 sandbox_id 不变,回滚完接着用同一个 sb 对象调 run_code() 就行,不用重连、不用换实例。这跟"重开一个沙箱"是两码事——它就是同一个沙箱,只是时间被倒回去了。对应到 Agent,就是"这一步走错了,退回上一个安全点接着跑",而不是整个会话推倒重来。

那回滚之后还能继续、还能给新走法再存档吗?我又验了一遍:回到 v1,继续写成 v3,再从这条新分支打一次快照,克隆出来验证读到的是 v3

img
img

OK: rollback + continue + re-snapshot all consistent。这说明回滚不是"死档",它更像 git 的分叉:你可以退回任意一个存档点,然后从那里开一条新分支继续跑。对 Agent 来说,这正是"换条路重试"该有的样子。

五、分身:一个状态,fork 出 N 个

存档、读档都成了,剩下"分身"。clone(n=N) 一行,从一个正在运行的沙箱 fork 出 N 个,每个都继承它当前的完整状态:

代码语言:python
复制
src = Sandbox.create(template=TEMPLATE_ID)
src.run_code("open('/tmp/shared.txt','w').write('shared state')")

clones = src.clone(n=3)      # 一行,分身出 3 个
img
img

三个 clone 的 sandbox_id 各不相同,但都读到了 file='shared state'。它们是三个独立、互不干扰的沙箱,却从同一个"决策点"分叉出来。这就是 RL rollout、树搜索采样想要的底座:从一个中间状态一次铺开好几条平行世界,各自往下探。

clone 还能并发。我用 concurrency=5 一次 fork 10 个:

img
img

10/10 clones inherited the origin marker,十个全部继承了源沙箱的标记。对应的就是并行 rollout——一次采一批样本。

六、它到底有多快?顺便说说底层的 Rust

到这我已经信它"能做",但"有多快"得用数字说话。我不想抄官方 README,而是用它自带的基准脚本,在我自己这台机器上测——分别测单个克隆、10 个并发克隆:

img
img

读一下这张图(单位毫秒):单个克隆平均 1023ms(最快 997ms);10 个并发克隆(concurrency=5)总墙钟 1028ms,摊薄到每个约 103ms

这组数字讲了个很漂亮的故事:fork 10 个的总耗时(~1.03 秒),几乎和 fork 1 个(~1.02 秒)一样,于是摊薄下来每个才 100 毫秒出头。克隆这件事几乎是"批发免费"的——你并发得越多,单个越便宜。

这背后是它的存储引擎 CubeCoW,一个用 Rust 写的库(仓库里 cubecow/ 那个 crate)。它的核心是拿 XFS 的 FICLONE ioctl 做 reflink,克隆和快照都是 O(1) 的元数据操作:不真拷数据,只让新卷和旧卷共享同一批数据块,之后谁写谁才复制(copy-on-write);快照也只落真正改过的脏页,没动过的页靠 reflink 白嫖。这就是为什么"分身"几乎不花钱——它压根没在搬数据。

我大致翻了下这块代码:CubeCoW 用一个 Engine trait 抽象后端,ReflinkEngine 是当前基于 XFS reflink 的实现,再编成静态库给 Go 写的 Cubelet 调。Rust 在这种系统级的地方确实合适,能把 ioctl 这类底层能力封装得干净,又没有 GC 负担,内存安全还省了一大类坑。至于 VMM 那部分(hypervisor/),是在 Cloud Hypervisor(RustVMM)基础上裁剪的,用 seccomp 把系统调用面收紧了——毕竟是跑不可信代码的地方,越小越好。这块我只读了个大概,感兴趣的可以自己去仓库翻。

有一点我必须诚实说明:我实测单个克隆约 1 秒,和官方宣传的"< 60ms 启动"不是一回事。官方那个是裸金属上的快照恢复,而我这是PVM 云 VM 上完整的"快照 + 克隆 + 启动",还隔着一层嵌套虚拟化,慢是正常的。拿实测数字说话、把它和官方场景分开,才对得起"实测"两个字。真正有说服力的不是那个 60ms,而是"并发 10 个和 1 个几乎一样快"这条曲线——那是我亲自跑出来的。

七、它的边界:我把它跑挂了

光说好话不叫实测。上面那个基准,我本来想再测一组 20 并发,结果它直接报错了,就是上面那张图最下面那段:

代码语言:txt
复制
cubesandbox._exceptions.ApiError: CubeMaster returned error code 130597: no more resource

在我这台 4 核 8G 上,并发克隆到十几个就摸到单机资源上限了。这不是 bug,是机器太小——每个 MicroVM 都要占一份内存和算力,20 个一起开,它认为塞不下就直接拒绝了。官方说单机能跑数千个沙箱,那是在几十核、大内存的机器上;我这台小机器十几个到顶,合理。这个"翻车"反而让我踏实:快是真快,但它不是魔法,密度受制于你给多少资源。

其他几个边界也一并列出来,给想上手的人省点时间:非必须 x86 + Linux,目前在0.5.0版本已经支持了arm,但是前面的版本还是不支持arm,这一点要注意;

img
img

快照/克隆强依赖 XFS reflink,ext4 用不了;我只在单机测了功能和小规模并发,大规模、生产级的表现没测,上生产前该看它的网络加固文档。

八、写在最后

绕回开头那个"读档键"。跑完这一圈,我的判断是:CubeSandbox 确实把"给运行中的 Agent 沙箱做存档、读档、分身"这件事,做成了一行 SDK 就能调的能力,底层用 Rust + KVM + XFS reflink 把它做得够快够便宜。对做 Agent 平台、Code Interpreter、或者 RL 训练采样的人来说,这套"快照 / 回滚 / 克隆"是实打实能省事的——尤其那个"退回上一个安全点继续跑"的能力,我在别的方案里没见过这么顺手的。

把 RustVMM、KVM、eBPF、CoW 这些东西缝成一个开箱即用、还全部开源的产品,工程量不小。我已经给它点了 star,项目地址放这,觉得有用的可以去支持一下:👉 https://github.com/TencentCloud/CubeSandbox

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、先到起跑线:部署踩的那些坑
  • 二、三个动作,对应 Agent 的三种刚需
  • 三、存档:一行代码,给运行中的沙箱打完整快照
  • 四、读档:让 Agent 犯错之后能"退回去"
  • 五、分身:一个状态,fork 出 N 个
  • 六、它到底有多快?顺便说说底层的 Rust
  • 七、它的边界:我把它跑挂了
  • 八、写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档