腾讯云服务器跑通 Cube Sandbox:从 PVM 内核到 65 ms 冷启动的全程实战

适合第一次想把 Cube Sandbox 真正跑起来的开发者。本文用一台普通腾讯云 CVM(OpenCloudOS 9.4 / 8C16G / 无嵌套虚拟化),从空白系统一路推到 Sandbox.create() 65ms 完成、E2B SDK 直接打印 Hello World,全程命令、日志、截图都来自真实复现,无遮挡。

关键词:Cube Sandbox · OpenCloudOS 9 · PVM 内核 · E2B 兼容 · AI Agent 沙箱 · MicroVM


在这里插入图片描述

0. 这台机器长什么样

先把机器档案摆在前面,方便你拿自己的 CVM 对照:

OS OpenCloudOS 9.4
默认内核 6.6.119-47.8.oc9.x86_64
CPU AMD EPYC 9K65(8 vCPU)
内存 15 GiB
系统盘 200 GB XFS
公网 IP 129.211.223.113
嵌套虚拟化 未开放/proc/cpuinfo 无 vmx / svm,/dev/kvm 不存在)

第一件事永远是先跑一遍核对脚本(脚本是我上一篇文章沉淀下来的 cube_preflight.sh,与官方 online-install.sh 的 preflight 一一对齐),免得你后面踩到坑还以为是自己 yum 配错了:

scp cube_preflight.sh root@129.211.223.113:/root/
ssh root@129.211.223.113 'bash /root/cube_preflight.sh'

实测输出(红=阻断 / 黄=提示 / 绿=通过):

cube_preflight.sh 在新机器上的全量核查结果:除 KVM 外 10 项全绿

读图三秒看懂:

  • ✅ OS / 架构 / Root / 命令 / 内存 15GB / / 是 XFS / 磁盘 193 GB / NetworkManager / 镜像出网 全都过;
  • ❌ 仅 /dev/kvm 不存在(CPU flags 里既没有 vmx 也没有 svm)。

这种"只差一刀"的环境正是 Cube Sandbox 设计的主战场之一 —— 普通云服务器没有嵌套虚拟化,靠官方提供的 PVM 宿主机内核把 KVM 能力补上来,再走和裸金属一样的一键安装流程。下面我就一步步把这台机器从"差一刀"推到"全绿可用"。


1. 第一步:从 GitHub Releases 下载 PVM 宿主机内核

PVM 内核就是 Cube Sandbox 项目自己维护的一个 Linux 内核 fork,把"普通云上没有 KVM"这件事在内核态解决掉。它只通过 GitHub Releases 单包发布(不走 yum 源),文件大小 575 MB。

直接 github.com 下载在国内会卡到 100KB/s,先看官方 CNB 镜像:

URL_CNB="https://cnb.cool/CubeSandbox/CubeSandbox/-/releases/download/v0.2.2/kernel-6.6.69_cube.pvm.host.005.x_gb85200d80fa2-1.x86_64.rpm"
curl -L -C - --retry 5 --retry-delay 3 -o kernel-pvm.rpm "$URL_CNB" \
  -w "\n[done] http=%{http_code} size=%{size_download} time=%{time_total}s avg=%{speed_download}B/s\n"
sha256sum kernel-pvm.rpm

实测输出:

CNB 镜像 56.9 秒下完 575 MB,平均 10.1 MB/s,sha256 校验通过

速度比 GitHub 直连快了一个量级:56.9s 跑完 575 MB,平均 10.1 MB/s。sha256 校验我也顺手做了 —— 装内核之前一定要做这一步,这是后面所有故事的地基。

找最新版本号的小窍门:curl -fsSL https://api.github.com/repos/tencentcloud/CubeSandbox/releases/latest | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['tag_name']); [print(a['name']) for a in d['assets']]",文件名带 cube.pvm.host 的就是宿主机内核。


2. 第二步:装内核 + 改 GRUB + 重启

# 1. 安装内核(--oldpackage 跳过版本对比,避免比已装更老时报错)
rpm -ivh --oldpackage kernel-pvm.rpm

# 2. grubby 列出所有内核 + 当前默认
grubby --info=ALL | grep -E "^kernel|^index|^title"
grubby --default-kernel

实测:

rpm -ivh 装好之后,grubby 自动把新内核 index=0 设为默认

注意一个很省心的细节:RPM 装完 grubby 已经自动把 PVM 内核切到 index=0,所以官方文档里那条 grubby --set-default-index=<index> 在 OpenCloudOS 9 上默认可以省掉

接下来跑官方提供的 host_grub_config.sh,它会把 PVM 内核需要的一长串启动参数(elevator=nooptransparent_hugepage=neverkvm.nx_huge_pages=neverpti=off 等等)合并到 GRUB_CMDLINE_LINUX,并按 key 去重,再 grub2-mkconfig 一次。我先把脚本下到本地看了一眼内容再跑(74 行纯 bash,不下软件不联网,安全):

curl -fsSL https://cnb.cool/CubeSandbox/CubeSandbox/-/git/raw/master/deploy/pvm/grub/host_grub_config.sh \
  -o host_grub_config.sh
cat host_grub_config.sh   # 看一眼,确认安全
bash host_grub_config.sh

执行结果:

host_grub_config.sh 把一长串 PVM 启动参数合入 GRUB 并重新生成 grub.cfg

最后 reboot。我用一个 SSH 探活循环监控:服务器 26 秒就回来了,且 uname -r 已经是 6.6.69-cube.pvm.host.005.x-gb85200d80fa2,主机已成功进入 PVM 内核。


3. 第三步:modprobe kvm_pvm,让 /dev/kvm 出现

进入新内核后还差最后一道:加载 kvm_pvm 模块。这一步很短但效果"立竿见影":

modprobe kvm_pvm
lsmod | grep kvm
ls -la /dev/kvm
echo 'kvm_pvm' > /etc/modules-load.d/kvm-pvm.conf   # 开机自动加载

kvm_pvm 加载成功;/dev/kvm 设备节点出现

读图重点:

  • kvm_pvm 49 KB → 拉起 kvm 1.18 MB → 关联 kmem_cacheirqbypass,整条 KVM 模块链路就绪。
  • /dev/kvm 真的出现了crw-rw-rw- 1 root kvm 10, 232 May 22 17:04 /dev/kvm
  • 内存仍是 15 GiB,PVM 内核基本不吃内存,这一点很关键。

到这里这台 CVM 在 Cube Sandbox 看来就和一台有 KVM 能力的机器完全等价了。


4. 第四步:一键安装 Cube Sandbox,2 分 25 秒拿到完整服务栈

curl -sL https://cnb.cool/CubeSandbox/CubeSandbox/-/git/raw/master/deploy/one-click/online-install.sh \
  | CUBE_PVM_ENABLE=1 MIRROR=cn bash

CUBE_PVM_ENABLE=1 是给 Cube Sandbox 安装器的暗号 —— 让它在沙箱(guest)端用 PVM 优化版的 vmlinux,否则一旦回退到普通 guest 内核,整个性能预期会大打折扣。

实测整套部署只用了 2 分 25 秒(17:04:51 → 17:07:16)。安装器干的事可以从日志里读出来,简化版如下:

[online-install] downloading cube-sandbox-one-click-46424e7.tar.gz...    438 MB, 平均 10.5 MB/s
[one-click-runtime] installed PVM guest kernel as .../cube-kernel-scf/vmlinux
[one-click-runtime] starting cube-proxy / cube-sandbox-redis / cube-sandbox-mysql ...
[one-click-runtime] cube proxy dns ready via cube-proxy-coredns
[one-click-runtime] started network-agent  pid=8459
[one-click-runtime] started cubemaster     pid=8460
[one-click-runtime] started cube-api       pid=8461
[one-click-runtime] started cubelet        pid=8462
[quickcheck] 1/5 check network-agent healthz   ... OK
[quickcheck] 2/5 check network-agent readyz    ... OK
[quickcheck] 3/5 check cubemaster /notify/health ... OK
[quickcheck] 4/5 check cube-api  /health       ... OK
[quickcheck] 5/5 check essential sockets and config ... OK
[one-click-runtime] core services ready
[one-click-runtime] webui listening on 12088
[one-click] install complete (role=control)

装完之后,整台机器的运行时全景如下(这张图我会建议你也保存一份,相当于一张"Cube Sandbox 拓扑速记"):

安装完成后的完整运行时:5 个 Docker 容器 + 4 个独立服务进程 + 多端口监听

读图重点:

  • 5 个 Docker 容器全部 healthycube-webui(12088)、cube-proxy(80/443)、cube-proxy-corednscube-sandbox-redis(6379)、cube-sandbox-mysql(3306)。
  • 4 个独立进程cube-api(3000) 是 E2B 兼容的 REST 网关、cubemaster(8089) 是调度器、cubelet(9966/9998/9999) 是节点 agent、network-agent(19090) 给 CubeVS 做 eBPF 网络。
  • /usr/local/bin 装入 4 个公开命令cubemastercli(管模板/沙箱)、cubecli(管节点)、cube-runtimecontainerd-shim-cube-rs(containerd Shim v2 集成)。

到这一步,机器已经是一台**“开机即可对外提供 E2B 兼容 API 的 Agent 沙箱节点”**。


5. 第五步:建代码解释器模板(19 秒 READY)

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

输出立刻给出 job_idtemplate_id

cubemastercli tpl create-from-image:job 与 template id、status=PENDING、phase=PULLING

然后用 cubemastercli tpl watch --job-id <id> 跟进度,整个模板从 PULLING 到 READY 只用了 19 秒(17:07:35 → 17:07:54),最后 tpl list 看一眼:

cubemastercli tpl list:tpl-90d8...c7b0 STATUS=READY

记住这串 tpl-90d8079679a2410c8b64c7b0,下一步 Python SDK 会用它。


6. 第六步:装 e2b-code-interpreter,写第一段 Agent 代码

yum install -y python3 python3-pip
pip3 config set global.index-url https://mirrors.ustc.edu.cn/pypi/simple
pip3 install e2b-code-interpreter

export E2B_API_URL="http://127.0.0.1:3000"
export E2B_API_KEY="dummy"
export CUBE_TEMPLATE_ID="tpl-90d8079679a2410c8b64c7b0"
export SSL_CERT_FILE="/root/.local/share/mkcert/rootCA.pem"   # 一键脚本生成的 mkcert 根证书

第一段 Hello World 我刻意写得"啰嗦一点",在沙箱内打印环境信息、跑一次重计算、再写一个文件验证 IO:

import os, time
from e2b_code_interpreter import Sandbox

t0 = time.perf_counter()
sb = Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"], timeout=120)
print(f"[host] sandbox created in {(time.perf_counter() - t0) * 1000:.1f} ms, id={sb.sandbox_id}")

res = sb.run_code("""
import platform, sys, os, socket
print('hello from cube sandbox')
print('python   :', sys.version.split()[0])
print('platform :', platform.platform())
print('hostname :', socket.gethostname())
print('cpus     :', os.cpu_count())
print('cwd      :', os.getcwd())
""")
print(res.logs.stdout)

跑一次:

hello_cube.py:73.1 ms 创建沙箱,guest 内核 6.6.69-cube.pvm.guest,sum 验算正确

几个细节非常值得读:

  1. 沙箱端到端创建只花了 73.1 ms(从 SDK Sandbox.create() 调用到 sandbox_id 返回,含网络往返)。
  2. 沙箱里 uname -r6.6.69-cube.pvm.guest.005.x-gb85200d80fa2,注意是 pvm.guest,和宿主的 pvm.host 配对 —— 这就是 CUBE_PVM_ENABLE=1 起的作用。
  3. hostname=tpl-90d8:与模板 ID 前缀一致,便于排查。
  4. 沙箱内 cpus=2 是模板默认配置;/sandbox-data/note.txt 写入读出一切正常。
  5. listdir / 看见的是一个独立的 Linux 根bin/etc/lib/proc...),不是宿主机的 /,这就是"内核级隔离"在文件系统层的直观体现。

7. 第七步:把它当 Agent 用 —— shell + 数据 + 画图 + 多沙箱隔离

光打印 Hello 没什么意思,这一节我用一个稍长的脚本把 Cube Sandbox 的几大核心能力一次性跑全:

  1. 沙箱内执行 shell(id / uname / df);
  2. 沙箱内 pip install numpy matplotlib
  3. 沙箱内画 sin/cos 图,写入 /workspace/plot.png
  4. sb.files.read() 把图取回宿主机;
  5. 同时再开两个沙箱,在 sb1/tmp/secret.txt,验证 sb2 看不见。

跑一次:

agent_demo.py:shell + matplotlib 画图 + 文件回传 + 两沙箱隔离验证全部成功

然后是沙箱里画的、宿主机拉回来的那张图:

Cube Sandbox 内部 matplotlib 绘制的 sin/cos 图(沙箱画图 → 宿主取回)

回看运行结果:

  • 三个沙箱依次 65 / 77 / 65 ms 冷启动;
  • 隔离验证sb2 sees /tmp/secret.txt ? False —— 完美隔离;
  • 沙箱内 df -h / 显示 overlay2 1.1G 正好对应 --writable-layer-size 1G
  • 沙箱里 pip install 成功(走 USTC 镜像)—— 说明 Cube Sandbox 默认给沙箱开了出网(你也可以用 CubeVS 做细粒度网络策略,那是另一个话题)。

如果你做过 E2B 集成会发现:这段代码里完全没出现 Cube 字样from e2b_code_interpreter import Sandbox + Sandbox.create(...) 是原汁原味 E2B SDK 调用,唯一改的就是 E2B_API_URL 这一个环境变量。这就是官方说的"零成本迁移"。


8. 重头戏:冷启动延迟 benchmark(连开 30 个)

README 上写"60 ms 单并发 / P95 90 ms 50 并发",到底是不是真的?我连续创建了 30 个沙箱:

times_ms = []
for i in range(30):
    t0 = time.perf_counter()
    sb = Sandbox.create(template=T, timeout=60)
    dt = (time.perf_counter() - t0) * 1000
    times_ms.append(dt); print(f"[{i+1:>2}/30] {dt:7.1f} ms")

实测:

冷启动 benchmark, N=30:min 57.8 / P50 65.4 / mean 66.1 / P95 78.3 / max 81.0 ms

指标 实测 README 官方(参考)
min 57.8 ms -
P50 65.4 ms 60 ms(裸金属,单并发)
mean 66.1 ms 67 ms(裸金属,50 并发)
P95 78.3 ms 90 ms(裸金属,50 并发)
max 81.0 ms -

这是在普通云 CVM + PVM 这种"二次虚拟化"场景下测出来的数据,P50 65.4 ms、P95 78.3 ms —— 完全配得上 README 里那条"百毫秒级"基线。要知道,30 次连开里没有一次破百,对比 Docker 的 ~200 ms 已经领先 3-5 倍,对比传统 VM(秒级)就是数量级的差距。

回想这个数据是怎么实现的:模板启动时,Cube Sandbox 已经在节点上预先做好了 rootfs 解包、运行时初始化、网络栈准备,一次 Sandbox.create 实际上是在预热好的资源池里 Clone 一个 MicroVM,这就是 README 里"基于资源池化预置和快照克隆技术,跳过耗时初始化流程"的工程含义。


9. 这台机器现在是什么状态

把上面所有步骤的产物归纳一下:

维度 之前 现在
内核 6.6.119-47.8.oc9.x86_64(默认) 6.6.69-cube.pvm.host.005.x-gb85200d80fa2(PVM 宿主)
/dev/kvm 不存在 crw-rw-rw- 已生成
Cube Sandbox 服务 未安装 5 个 Docker 容器 + 4 个独立进程,5/5 quickcheck 全过
cubemastercli tpl list / tpl-90d8079679a2410c8b64c7b0 STATUS=READY
e2b-code-interpreter / v2.6.2,环境变量已 export
一次冷启动 / 65 ms 量级(P50 65.4ms)
端口 / 80/443 (cube-proxy)、3000 (cube-api)、12088 (webui) 等

接下来你可以做什么? 几个方向我自己马上会去试:

  1. 把它接到现有的 LangChain / LangGraph / LlamaIndex Agent:因为是 E2B 兼容,绝大多数 Agent 框架"换一个 E2B_API_URL"就能直接跑你私有部署的 Cube Sandbox,不用重写工具。
  2. 跑 SWE-Bench 这种基准:README 视频演示里就有 RL + SWE-Bench 场景;这台机器 8C16G + Cube 已经够开 30+ 沙箱并发跑评测。
  3. 开 CubeVS 网络策略:限制沙箱只能访问白名单域名,这是防"prompt 注入诱导 curl 外泄"的关键一道。
  4. 打 webui:浏览器开 http://<IP>:12088 就能看到沙箱、模板、节点的可视化面板(前提是把 12088 加到安全组)。

10. 写在最后:为什么这条路值得走

在写这篇之前我做了一台 2C2G 机器的"环境核对"练习,结论是:Cube Sandbox 对环境的硬要求其实只有三条 —— /dev/kvm、内存 ≥ 8GB、/data/cubelet 在 XFS 上。前两条把绝大多数普通 CVM 挡在门外,但这并不意味着你必须买裸金属:

  • PVM 内核这条路是真的能走通,而且不慢:给一台没有嵌套虚拟化的腾讯云 CVM 装上 PVM 内核 + 一键脚本部署 + 建模板 + 跑通 SDK,全程不到 10 分钟(我从 17:04 开始下载内核,17:18 已经在跑 30 沙箱 benchmark);
  • OpenCloudOS 9 是 Cube Sandbox 的"first-class"目标平台:必需命令、文件系统类型、NetworkManager、yum、Docker 一键装 —— 全程零适配;
  • 冷启动 65 ms 是真的,不是营销数字,普通 CVM + PVM 都能复现。

如果你是在做 AI Agent / 代码执行 / RL 训练 / E2B 私有化,我会给一个朴素建议:在你的开发环境里先按这篇文章跑通一遍。等你接下来给 Agent 写第二个、第三个工具时,"这段代码能不能放心跑在生产里"这个问题会有一个非常具体的答案——一个 65 ms 起、内核级隔离、E2B 完全兼容、可由你自己审计的 MicroVM。


附录 A:cube_preflight.sh 完整版

与官方 online-install.sh 的 preflight 完全对齐,可独立跑,1 秒出结果。

#!/usr/bin/env bash
set -u
c_red='\033[0;31m'; c_grn='\033[0;32m'; c_yel='\033[0;33m'; c_cyn='\033[0;36m'; c_rst='\033[0m'
ok()   { printf "${c_grn}[ OK ]${c_rst} %s\n" "$*"; }
warn() { printf "${c_yel}[WARN]${c_rst} %s\n" "$*"; }
fail() { printf "${c_red}[FAIL]${c_rst} %s\n" "$*"; }
hd()   { printf "\n${c_cyn}== %s ==${c_rst}\n" "$*"; }

hd "1. 操作系统"; . /etc/os-release 2>/dev/null || true
echo "  PRETTY_NAME : ${PRETTY_NAME:-unknown}"
case "${ID:-}" in opencloudos) ok "OpenCloudOS 检测到" ;; *) warn "非 OpenCloudOS(${ID:-unknown})" ;; esac

hd "2. 内核 & 架构"; echo "  uname -r : $(uname -r)"; echo "  uname -m : $(uname -m)"
[[ "$(uname -m)" == "x86_64" ]] && ok "x86_64" || fail "需要 x86_64"

hd "3. Root 权限"
[[ "${EUID}" -eq 0 ]] && ok "root" || fail "必须 root"

hd "4. 必需命令"
for cmd in tar awk curl wget python3 systemctl; do
  command -v "$cmd" >/dev/null && ok "$cmd -> $(command -v "$cmd")" || fail "缺命令: $cmd"
done

hd "5. KVM 能力(/dev/kvm)"
if [[ -e /dev/kvm ]]; then
  ok "/dev/kvm -> $(ls -l /dev/kvm)"
else
  fail "/dev/kvm 不存在"
  flags=$(grep -oE '(vmx|svm)' /proc/cpuinfo | sort -u | tr '\n' ' ')
  [[ -n "$flags" ]] && warn "CPU flags: $flags(可能可启用嵌套 KVM)" \
                    || warn "无 vmx/svm flag,必须走 PVM 路径"
fi

hd "6. 内存 (>= 8 GB)"
mem_kb=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
mem_gb=$(awk -v k="$mem_kb" 'BEGIN{printf "%.2f", k/1024/1024}')
echo "  MemTotal: ${mem_kb} KB (~${mem_gb} GB)"
[[ "$mem_kb" -ge 7500000 ]] && ok "内存达标" || fail "内存不达标(min_mem_kb=7500000 写死)"

hd "7. /data/cubelet 所在分区 (要求 XFS)"
target=/data/cubelet; probe="$target"
while [[ ! -e "$probe" ]]; do parent=$(dirname "$probe"); [[ "$parent" == "$probe" ]] && break; probe="$parent"; done
fs_type=$(df -T "$probe" 2>/dev/null | awk 'NR==2 {print $2}')
echo "  探测路径 : $probe"; echo "  文件系统 : ${fs_type:-unknown}"
[[ "$fs_type" == "xfs" ]] && ok "XFS" || fail "需要 XFS"

hd "8. 磁盘空闲空间"
avail_kb=$(df -k / | awk 'NR==2 {print $4}')
avail_gb=$(awk -v k="$avail_kb" 'BEGIN{printf "%.1f", k/1024/1024}')
echo "  / 可用 : ${avail_gb} GB"
[[ "$avail_kb" -ge 31457280 ]] && ok "充足" || warn "建议预留 30GB+"

hd "9. DNS 能力"
if command -v resolvectl >/dev/null; then ok "resolvectl 可用"
else
  state=$(systemctl show -p LoadState --value NetworkManager 2>/dev/null || echo "?")
  [[ "$state" == "loaded" ]] && ok "NetworkManager loaded" || fail "需要 resolvectl 或 NetworkManager"
fi

hd "10. Docker"
command -v docker >/dev/null && ok "$(docker --version)" || warn "未装 docker(一键脚本会自动装)"

hd "11. 网络出网"
for u in https://cnb.cool https://mirrors.ustc.edu.cn; do
  code=$(curl -o /dev/null -s -w "%{http_code}" --max-time 8 "$u" || echo "000")
  [[ "$code" =~ ^[23] ]] && ok "$u -> HTTP $code" || warn "$u -> HTTP $code"
done

hd "汇总"
echo "  本机: ${PRETTY_NAME:-?} | $(uname -r) | $(uname -m) | mem ${mem_gb}GB | / fs ${fs_type:-?}"
echo "  时间: $(date '+%F %T %Z')"

附录 B:参考链接

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐