在AWS云上使用EC2 嵌套虚拟化实例部署Cube Sandbox的实践和问题

如果要在EC2实例上测试microvm类型的sandbox,需要使用bare metal 实例才支持 KVM/Hyper-V。近期AWS 宣布虚拟 EC2 实例支持嵌套虚拟化,现在一台8系列就能跑 KVM。本文档记录了在 AWS EC2 嵌套虚拟化环境(c8i.2xlarge)部署 Cube Sandbox 的完整过程。

但是目前Cube Sandbox还在测试阶段存在较多问题,可能还需观望一段时间。

环境配置

启动c8i.2xlarge (8 vCPU, 16GB RAM)实例后验证嵌套虚拟化

bash 复制代码
ls -la /dev/kvm"
# crw-rw-rw-. 1 root kvm 10, 232 May 16 14:10 /dev/kvm

cat /sys/module/kvm_intel/parameters/nested"
# Y

安装 Docker工具

bash 复制代码
sudo dnf install -y docker"
sudo systemctl enable --now docker && docker --version"

sudo curl -sL 'https://github.com/docker/compose/releases/download/v2.27.1/docker-compose-linux-x86_64' -o /usr/local/bin/docker-compose && sudo chmod +x /usr/local/bin/docker-compose

安装其他依赖

shell 复制代码
sudo curl -sL https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz | sudo tar xz -C /usr/local/bin --strip-components=1 ripgrep-14.1.1-x86_64-unknown-linux-musl/rg

安装 Cube Sandbox

bash 复制代码
sudo su root -c "curl -sL https://cnb.cool/CubeSandbox/CubeSandbox/-/git/raw/master/deploy/one-click/online-install.sh | MIRROR=int bash"

环境patch

Cube Sandbox 在 AWS EC2 嵌套虚拟化环境需要三个补丁

修补 Cubelet

创建模板到达DISTRIBUTING 100% 后失败,错误:target kernel file .../rfs-xxx.vm not existimage version file .../version not exist

检查发现代码中存在两条镜像分发路径:

  1. Distribution handler 路径image_distribution_handler.go):

    • ensureDistributedTemplateImage → 下载 .ext4
    • materializeDistributedTemplateRuntimeFiles → 调用 RefreshArtifactRuntimeFiles 创建 .vm + version
  2. CreateImage gRPC 路径services/images/service.gocube_image_pull_route.go):

    • EnsureImageEnsurePmemFile → 下载 .ext4 + 只验证不创建 .vmversion

模板创建时的分发走的是第 2 条路径(CreateImage gRPC),跳过了 materializeDistributedTemplateRuntimeFiles。在Cubelet/internal/cube/server/images/ext4image/utils.goEnsurePmemFile 中添加 refreshKernelFilerefreshImageVersionFile 调用(这两个函数在源码中已存在,只是未被此路径调用):

go 复制代码
func EnsurePmemFile(ctx context.Context, instanceType, imageRef string) error {
    if err := EnsurePmemRootfs(ctx, instanceType, imageRef); err != nil {
        return err
    }
    if err := refreshKernelFile(ctx, instanceType, imageRef); err != nil {
        return fmt.Errorf("refresh kernel file failed: %w", err)
    }
    if err := refreshImageVersionFile(ctx, instanceType, imageRef); err != nil {
        return fmt.Errorf("refresh image version file failed: %w", err)
    }
    return validateArtifactRuntimeFilesPresent(ctx, instanceType, imageRef)
}

编译后部署

bash 复制代码
cd /tmp/cubesandbox-src/Cubelet
sudo CGO_ENABLED=1 go build -o /usr/local/services/cubetoolbox/Cubelet/bin/cubelet ./cmd/cubelet

修补 CubeShim

VM 启动时 guest kernel panic at fpu__init_cpu_xstate (General Protection Fault)。检查发现AWS EC2 嵌套虚拟化环境不支持 XSAVE 指令。Guest 内核 6.6.1199 在初始化 FPU 时尝试使用 XSAVE 指令,导致 GPF。

内核启动日志如下

复制代码
[    0.000000] traps: PANIC: invalid opcode in kernel mode at fpu__init_cpu_xstate

位于CubeShim/shim/src/hypervisor/config.rs部分在内核命令行添加 noxsave 参数,禁用 XSAVE 功能:

rust 复制代码
let params = vec![
    "root=/dev/pmem0".to_string(),
    "rootflags=dax,errors=remount-ro ro".to_string(),
    "rootfstype=ext4".to_string(),
    "panic=30".to_string(),
    "no_timer_check".to_string(),
    "noreplace-smp".to_string(),
    "printk.devkmsg=on".to_string(),
    "console=hvc0".to_string(),
    "net.ifnames=0".to_string(),
    "audit=0".to_string(),
    "LANG=C".to_string(),
    "raid=noautodetect".to_string(),
    "earlyprintk=ttyS0".to_string(),
    "agent.debug_console".to_string(),
    "agent.debug_console_vport=1026".to_string(),
    "mitigations=off".to_string(),
    "noxsave".to_string(),  // ← 必须添加,AWS EC2 嵌套虚拟化必需
];

编译并部署新的部署CubeShim

bash 复制代码
cd /tmp/cubesandbox-src/CubeShim
cargo build --release --manifest-path shim/Cargo.toml
sudo cp shim/target/release/cube-shim /usr/local/services/cubetoolbox/CubeShim/bin/

修补 Guest 镜像

添加 noxsave 后内核正常启动,但 init (cube-agent) 立即退出。错误为exitcode=0x00000200 (exit code 2 = 参数错误),console 输出:error: Found argument 'noxsave' which wasn't expected

排查发现,Linux 内核 (init/main.c):将不识别的命令行参数作为 argv 传递给 init 进程。

c 复制代码
/* Unknown options are passed to init */
argv_init[args] = param;

因此 noxsaveLANG=Craid=noautodetect 等未知参数被传递给 /sbin/init(即 cube-agent)。cube-agent 使用 clap 解析命令行参数,遇到未知参数 noxsave 时报错退出。

在 guest 镜像中添加 init wrapper 脚本过滤未知参数:

bash 复制代码
sudo mount -o loop /usr/local/services/cubetoolbox/cube-image/cube-guest-image-cpu.img /tmp/guest
cd /tmp/guest
sudo mv sbin/init sbin/init.real
sudo tee sbin/init << 'EOF'
#!/bin/bash
FILTERED_ARGS=()
while [ $# -gt 0 ]; do
    case "$1" in
        -c|--config|-h|--help|-v|--version|init|exec)
            FILTERED_ARGS+=("$1")
            ;;
        -*)
            shift
            ;;
    esac
    shift
done
exec /sbin/init.real "${FILTERED_ARGS[@]}"
EOF
sudo chmod +x sbin/init
sudo umount /tmp/guest

注意 :每次更新 cube-guest-image-cpu.img 后需重新应用此补丁。

启动服务

模板创建在 BUILDING 阶段卡在20%不动最终失败,错误日志显示 no space left on device 或类似磁盘空间不足的错误。

空间不足问题

后来找到相同issue(临时目录空间不足问题 GitHub Issue : #240)模板构建过程中需要创建 ext4 镜像。对于 8GB 的 rootfs,构建过程需要约 10GB 临时空间。默认情况下,cubemaster 使用 /tmp 作为临时目录,但/tmp 大小受内存限制

设置环境变量 CUBEMASTER_ROOTFS_ARTIFACT_DIR 指向磁盘空间充足的路径:

bash 复制代码
# 检查 /tmp 是否为 tmpfs
mount | grep /tmp
# 检查磁盘空间
df -h /data
# 设置临时目录
export CUBEMASTER_ROOTFS_ARTIFACT_DIR=/data/CubeMaster/tmp/cubemaster-rootfs-artifacts
export CUBEMASTER_ROOTFS_ARTIFACT_STORE_DIR=/data/CubeMaster/storage

# 创建目录
sudo mkdir -p /data/CubeMaster/tmp/cubemaster-rootfs-artifacts
sudo mkdir -p /data/CubeMaster/storage

组建启动顺序问题

正常的组件启动顺序为network-agent → cubemaster → cubelet,如果启动顺序错误,cubelet 会等待 network-agent 就绪超时(30秒),导致 cubebox-serviceimages-service 插件加载失败。

bash 复制代码
cd /usr/local/services/cubetoolbox/scripts/one-click
export CUBEMASTER_ROOTFS_ARTIFACT_DIR=/data/CubeMaster/tmp/cubemaster-rootfs-artifacts
export CUBEMASTER_ROOTFS_ARTIFACT_STORE_DIR=/data/CubeMaster/storage
sudo -E bash up.sh

手动按顺序启动

bash 复制代码
# 1. network-agent
sudo CUBEMASTER_ROOTFS_ARTIFACT_DIR=/data/CubeMaster/tmp/cubemaster-rootfs-artifacts \
  nohup /usr/local/services/cubetoolbox/network-agent/bin/network-agent \
  --cubelet-config /usr/local/services/cubetoolbox/Cubelet/config/config.toml \
  --state-dir /usr/local/services/cubetoolbox/network-agent/state &

# 2. cubemaster
sudo CUBE_MASTER_CONFIG_PATH=/usr/local/services/cubetoolbox/CubeMaster/conf.yaml \
  CUBEMASTER_ROOTFS_ARTIFACT_STORE_DIR=/data/CubeMaster/storage \
  CUBEMASTER_ROOTFS_ARTIFACT_DIR=/data/CubeMaster/tmp/cubemaster-rootfs-artifacts \
  nohup /usr/local/services/cubetoolbox/CubeMaster/bin/cubemaster &

# 3. cubelet(等 cubemaster 就绪后再启动)
sudo /usr/local/services/cubetoolbox/Cubelet/bin/cubelet \
  --config /usr/local/services/cubetoolbox/Cubelet/config/config.toml \
  --dynamic-conf-path /usr/local/services/cubetoolbox/Cubelet/dynamicconf/conf.yaml

创建模板

模板是 sandbox 的蓝图,包含 rootfs 镜像、内核和启动配置。创建模板会触发完整的流水线:

阶段 说明
PULLING 从 OCI registry 拉取镜像
UNPACKING 解压镜像层到 rootfs
BUILDING 构建 ext4 镜像
PACKING 打包 artifact
DISTRIBUTING 分发到 cubelet 节点
CREATING_TEMPLATE VM 启动验证

使用 cubemastercli 创建模板:

bash 复制代码
# 查看帮助
/usr/local/services/cubetoolbox/CubeMaster/bin/cubemastercli tpl create-from-image --help

# 创建模板
/usr/local/services/cubetoolbox/CubeMaster/bin/cubemastercli tpl create-from-image \
  --image cube-sandbox-int.tencentcloudcr.com/cube-sandbox/sandbox-code:latest \
  --template-id my-template \
  --writable-layer-size 1G

# 查看模板列表
/usr/local/services/cubetoolbox/CubeMaster/bin/cubemastercli tpl list

关键参数说明

  • --probe <port>:健康检查端口,模板创建时会等待该端口就绪
  • --expose-port <port>:暴露端口,允许外部访问 sandbox 内服务

模板创建时,会从镜像配置中提取启动命令:

go 复制代码
command := imageCfg.Entrypoint  // 镜像的 ENTRYPOINT
args := imageCfg.Cmd            // 镜像的 CMD

官方镜像 sandbox-code:latest 的配置如下

  • ENTRYPOINT: None
  • CMD: ['/usr/local/bin/start-lightweight-code-interpreter.sh']

因此模板会使用 CMD 作为启动命令。

创建sandbox

Cube Sandbox 提供与 E2B 兼容的 API,允许直接使用 e2b-code-interpreter Python SDK。这意味着你可以用官方 E2B SDK 的代码,在本地 Cube Sandbox 中运行,无需修改应用层代码。

架构和请求流程

请求流程如下

  1. SDK 发送请求到 CubeAPI (端口 3000)

  2. CubeAPI 调用 Cubemaster (端口 8089) 创建/管理 sandbox

  3. SDK 访问 sandbox 内服务时:

    • DNS 查询 *.cube.app → CoreDNS 返回本机 IP

    • HTTPS 请求到 cube-proxy (端口 443)

    • cube-proxy 根据 Host 头路由到对应 sandbox

Sandbox 内部 基础设施 控制面 & 代理 客户端 code-interpreter (端口49999) envd (端口49983) CoreDNS (端口53) cube-proxy (端口443 HTTPS) Cubemaster (端口8089) CubeAPI (端口3000) Python SDK (e2b-code-interpreter) code-interpreter (端口49999) envd (端口49983) CoreDNS (端口53) cube-proxy (端口443 HTTPS) Cubemaster (端口8089) CubeAPI (端口3000) Python SDK (e2b-code-interpreter) 阶段一:Sandbox 实例创建与路由配置 阶段二:代码执行与文件交互 发起 Sandbox 创建/操作请求 1 委托 Sandbox 生命周期管理 2 配置 *.cube.app 域名解析规则 3 启动并配置 HTTPS 代理路由 4 返回实例就绪状态 5 返回 Sandbox 连接凭证与 URL 6 通过 HTTPS API 发起代码执行请求 7 转发至 Sandbox 内 (健康检查/文件/命令) 8 调度执行 Python 代码 9 返回标准输出 (Stdout/Stderr) 10 封装响应数据 11 返回最终执行结果 12

envd 守护进程

envd 是 Cube Sandbox 与沙箱容器通信的唯一协议端点。所有对沙箱的操作(命令执行、文件读写、健康检查)都必须通过 envd。

Cube 能力 沙箱内端点 没有 envd 会怎样
模板探活 GET :49983/health → 204 模板创建因探活失败而 FAILED
Sandbox.commands.run() POST :49983/process SDK 命令调用全部 404
Sandbox.files.read/write() POST :49983/files 文件操作不可用
Sandbox 初始化 POST :49983/init Sandbox 永远无法 Ready

任何要用作 Cube 模板的镜像,启动时都必须有 envd 在端口 49983 上监听。

SDK 访问 sandbox 内服务的 URL 格式:

  • envd: https://49983-<sandbox_id>.cube.app
  • code-interpreter: https://49999-<sandbox_id>.cube.app

安装 Python SDK

bash 复制代码
sudo dnf install -y python3-pip
pip3 config set global.index-url https://mirrors.ustc.edu.cn/pypi/simple
pip3 install e2b-code-interpreter

SDK 访问 sandbox 时使用 URL https://49983-<sandbox_id>.cube.app,需要 DNS 将 *.cube.app 解析到 Cube Sandbox 服务器 IP。CoreDNS 已预配置 *.cube.app 解析如下

复制代码
# /usr/local/services/cubetoolbox/coredns/Corefile
.:53 {
    bind 169.254.254.53
    template IN A cube.app {
        answer "{{ .Name }} 60 IN A 172.31.42.244"
    }
    template IN A (.*)\.cube\.app {
        answer "{{ .Name }} 60 IN A 172.31.42.244"
    }
}

注意 :CoreDNS 监听在 169.254.254.53:53,但系统默认使用 AWS DHCP DNS(172.31.0.2)。需要将 CoreDNS 设为首选 DNS。

bash 复制代码
# 解除 systemd-resolved 管理,直接配置 resolv.conf
sudo unlink /etc/resolv.conf
sudo bash -c 'cat > /etc/resolv.conf << EOF
nameserver 169.254.254.53
nameserver 172.31.0.2
search ap-northeast-1.compute.internal
EOF'

创建模板

镜像 sandbox-code:latest 是官方提供的 code interpreter 镜像,包含:

复制代码
/usr/bin/envd                                    # envd 二进制
/usr/local/bin/start-lightweight-code-interpreter.sh  # 启动脚本
/opt/lightweight-code-interpreter/               # code interpreter 服务

启动脚本会后台启动 envd,前台运行 code interpreter:

bash 复制代码
#!/bin/sh
ENVD_PORT="${ENVD_PORT:-49983}"
CODE_INTERPRETER_PORT="${CODE_INTERPRETER_PORT:-49999}"

/usr/bin/envd -port "$ENVD_PORT" >/var/log/envd.log 2>&1 &
exec python -m uvicorn --host 0.0.0.0 --port "$CODE_INTERPRETER_PORT" server:app

使用 cubemastercli 创建带 probe 的模板

bash 复制代码
/usr/local/services/cubetoolbox/CubeMaster/bin/cubemastercli tpl create-from-image \
  --image cube-sandbox-int.tencentcloudcr.com/cube-sandbox/sandbox-code:latest \
  --writable-layer-size 1G \
  --expose-port 49999 \
  --expose-port 49983 \
  --probe 49983 \
  --template-id code-interpreter-v2

参数说明

  • --expose-port 49983:暴露 envd 端口
  • --expose-port 49999:暴露 code interpreter 端口
  • --probe 49983:模板创建时对 49983 端口进行健康检查

创建日志:

复制代码
2026/05/17 06:34:30 job_id: c15adf8e-5480-4a10-8e95-2000e2f06e73
2026/05/17 06:34:30 template_id: code-interpreter-v2
2026/05/17 06:34:30 status: PENDING
2026/05/17 06:34:30 phase: PULLING
...
2026/05/17 06:34:36 status: READY

配置环境变量

bash 复制代码
export E2B_API_URL="http://127.0.0.1:3000"
export E2B_API_KEY="dummy"
export CUBE_TEMPLATE_ID="code-interpreter-v2"
export SSL_CERT_FILE="/tmp/rootCA.pem"
变量 说明
E2B_API_URL 将 E2B SDK 请求指向本地 Cube Sandbox,而非 E2B 官方云服务
E2B_API_KEY SDK 强制非空校验,本地部署填任意字符串即可
CUBE_TEMPLATE_ID 模板 ID
SSL_CERT_FILE mkcert 签发的 CA 根证书路径,sandbox HTTPS 连接需要

运行测试代码

初次测试时,代码执行返回 502 Bad Gateway:

python 复制代码
import os
from e2b_code_interpreter import Sandbox

with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
    result = sandbox.run_code("print('Hello!')")

错误日志:

复制代码
e2b.exceptions.TimeoutException: <html>
<head><title>502 Bad Gateway</title></head>
...
X-Cube-Retcode: 330502

分析发现,SDK 创建 sandbox 后立即访问 envd health 接口,但 envd 需要约 2 秒才能启动完成:

复制代码
DEBUG:httpcore.connection:connect_tcp.started host='49983-xxx.cube.app' port=443
INFO:httpx:HTTP Request: GET https://49983-xxx.cube.app/health "HTTP/1.1 502 Bad Gateway"

在创建 sandbox 后等待 2 秒,或添加重试逻辑:

python 复制代码
import os, time
from e2b_code_interpreter import Sandbox

with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sandbox:
    print("Sandbox ID:", sandbox.sandbox_id)
    time.sleep(2)  # 等待 envd 就绪
    result = sandbox.run_code("print('Hello from Cube Sandbox!')")
    print("Result:", result)

成功运行日志

复制代码
Creating sandbox with template: code-interpreter-v2
Sandbox ID: fde16f061e3749ec96018a9a8bcadd79
envd health after 2s: 204
Result: Execution(Results: [Result(Formats: )], Logs: Logs(stdout: ['Hello!\n'], stderr: []), Error: None)

此外创建sandbox的注意事项

  1. 不要指定 command:SDK 创建 sandbox 时不指定 command,让模板使用镜像默认的启动命令。手动指定会阻止 envd 启动。

  2. 不要额外添加 volume :cubemaster 会自动注入 cube_rootfs_rw,额外 volume 会导致快照磁盘数不匹配。

  3. 等待 envd 就绪:创建 sandbox 后需等待约 2 秒让 envd 启动完成。

查看 Sandbox 运行状态

bash 复制代码
# 通过 containerd CLI
sudo ctr -a /data/cubelet/cubelet.sock -n default tasks ls
sudo ctr -a /data/cubelet/cubelet.sock -n default containers ls

# 查看 CubeShim 进程(每个 sandbox 对应一个 shim 进程)
sudo ps aux | grep containerd-shim-cube

# 查看 VMM 日志
sudo grep -a '<sandbox_id>' /data/log/CubeVmm/vmm.log | tail -20
相关推荐
号码认证服务3 小时前
小米、OPPO、VIVO手机支持号码认证显示公司名吗?
java·服务器·网络·经验分享·智能手机·云计算·php
不吃香菜kkk、4 小时前
SonarQube安装配置使用
ci/cd·kubernetes·云计算
weelinking16 小时前
【企业级】企业级大模型合规实战:数据安全与跨境传输的技术解决方案
数据库·人工智能·机器学习·云计算·github
向日的葵00619 小时前
阿里云OSS从0到1实战:为宠物收养系统打造图片上传功能
python·阿里云·云计算·pillow·fastapi·宠物
Kevin-anycode1 天前
阿里云安装ali-instance-cli免公网打开 Web 界面(OpenClaw)
阿里云·云计算
爱笑的源码基地1 天前
拿来即用:基于Spring Cloud+UniApp的智慧工地源码,架构清晰易扩展
java·云计算·源码·智慧工地·程序·开箱即用·数字工地
yyuuuzz1 天前
国际云服务器的技术特点与使用经验
运维·服务器·网络·数据库·云计算·aws
comcoo1 天前
阿里云百炼 + OpenClaw 打造超强自动化 AI
阿里云·自动化·云计算·openclaw安装包
智慧医养结合软件开源2 天前
规范新增·精准赋能,凝聚志愿力量守护老人安康
大数据·安全·百度·微信·云计算