Skip to content

:本地镜像 & 远程镜像实战文档

本文基于实际操作经验,介绍两种制作自定义 Cube Sandbox 模板的完整流程:

  • 方式一:本地构建镜像(镜像只在当前机器,无需推送到远端)
  • 方式二:远程镜像(镜像已推送到 Registry,集群从远端拉取)

友情链接,建议同步阅读 从 OCI 镜像制作模板自带镜像接入 文档。


前置条件

  • 已安装 cubemastercli 并加入 $PATH
  • 已安装 Docker
  • CubeMaster 服务正常运行(cubemastercli tpl list 能返回结果)
  • 已找到 mkcert CA 证书路径(SDK 连接沙箱时需要):
bash
# 通常在这里
ls ~/.local/share/mkcert/rootCA.pem

方式一:本地构建镜像制作模板

适合在 CubeMaster 所在机器上本地开发调试,镜像无需推送到远端 Registry。

Step 1:写 Dockerfile

所有 Cube 镜像必须包含 envd(负责沙箱内的通信协议),推荐基于官方 cubesandbox-base 构建,envd 已预装。

dockerfile
FROM ghcr.io/tencentcloud/cubesandbox-base:latest

# 安装你需要的工具和依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 python3-pip curl wget git vim jq \
    && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir numpy pandas requests httpx

保存为 /tmp/Dockerfile.cube-test

Step 2:构建镜像

bash
docker build -f /tmp/Dockerfile.cube-test -t my-sandbox:v1 .

Step 3:验证 envd 正常

制作模板前,先本地跑一下验证 envd 能正常响应 /health,避免模板构建时探活失败:

bash
cid=$(docker run -d my-sandbox:v1)
sleep 2
docker exec "$cid" curl -s -o /dev/null -w "envd /health => %{http_code}\n" http://127.0.0.1:49983/health
docker rm -f "$cid"

期望输出:envd /health => 204

如果不是 204,检查 Dockerfile 的 ENTRYPOINT 是否正确(见常见问题)。

Step 4:制作模板

bash
cubemastercli tpl create-from-image \
  --image my-sandbox:v1 \
  --writable-layer-size 2G \
  --expose-port 49983 \
  --probe 49983 \
  --probe-path /health

命令立即返回 job_idtemplate_id

job_id:      718b7ebd-5a2c-4f33-85d0-1c36f0d1b3ee
template_id: tpl-01adfa335c03460cb4a09225
status:      PENDING
phase:       PULLING

Step 5:等待模板就绪

bash
cubemastercli tpl watch --job-id <job_id>

等待输出 status: READY 即完成:

status:       READY
phase:        READY
progress:     100%
distribution: 1/1 ready, 0 failed

Step 6:验证模板可用

两种 SDK 均可验证,任选其一。

方式 A:e2b_code_interpreter(需要 SSL 证书)

bash
export CUBE_TEMPLATE_ID=<template_id>
export E2B_API_URL=http://127.0.0.1:3000
export E2B_API_KEY=e2b_000000
export SSL_CERT_FILE=~/.local/share/mkcert/rootCA.pem

python3 - << 'EOF'
import os
from e2b_code_interpreter import Sandbox
with Sandbox.create(template=os.environ["CUBE_TEMPLATE_ID"]) as sb:
    r = sb.commands.run("python3 --version && echo hello-cube")
    print(r.stdout)
EOF

期望输出:

Python 3.x.x
hello-cube

方式 B:cubesandbox SDK(无需 SSL 证书,推荐)

尚未安装 cubesandbox SDK或安装失败?参考(常见问题 的cubesandbox SDK 安装失败)。

bash
export CUBE_API_URL=http://127.0.0.1:3000
export CUBE_TEMPLATE_ID=<template_id>
export CUBE_PROXY_NODE_IP=127.0.0.1   # 本机;远程访问填 CubeProxy 节点 IP

python3 - << 'EOF'
import os, time
from cubesandbox import Sandbox, Config
from cubesandbox._exceptions import ApiError

cfg = Config(
    api_url=os.environ["CUBE_API_URL"],
    template_id=os.environ["CUBE_TEMPLATE_ID"],
    proxy_node_ip=os.environ.get("CUBE_PROXY_NODE_IP", ""),
)

def run_with_retry(sb, code, max_retries=10, interval=1.0):
    for i in range(max_retries):
        try:
            return sb.run_code(code)
        except ApiError as e:
            if e.status_code == 502 and i < max_retries - 1:
                time.sleep(interval)
            else:
                raise

with Sandbox.create(config=cfg) as sb:
    r = run_with_retry(sb, 'import sys; print(sys.version); print("hello-cube")')
    for line in r.logs.stdout:
        print(line, end="")
EOF

期望输出:

Python 3.x.x
hello-cube

⚠️ cubesandbox SDK 的 run_code 依赖 Jupyter kernel(49999 端口)。 自定义模板需在 Dockerfile 中安装 jupyter_kernel_gateway ipykernel, 并在制作模板时同时暴露 49983 和 49999 两个端口。


方式二:远程镜像制作模板

适合团队共享镜像多节点集群场景,镜像推送到 Registry 后,集群各节点都能拉取。

Step 1:写 Dockerfile

与方式一相同,参考上方 Dockerfile 示例。

Step 2:构建镜像(带 Registry 前缀)

bash
docker build -f /tmp/Dockerfile.cube-test \
  -t ccr.ccs.tencentyun.com/<命名空>/<镜像>:v1 .

Step 3:验证 envd 正常

同方式一 Step 3。

Step 4:登录并推送镜像

bash
# 登录 Registry(如需认证)
docker login ccr.ccs.tencentyun.com

# 推送
docker push ccr.ccs.tencentyun.com/<命名空>/<镜像>:v1

Step 5:制作模板

bash
cubemastercli tpl create-from-image \
  --image ccr.ccs.tencentyun.com/<命名空>/<镜像>:v1 \
  --writable-layer-size 2G \
  --expose-port 49983 \
  --probe 49983 \
  --probe-path /health

私有仓库需要加认证参数:

bash
cubemastercli tpl create-from-image \
  --image ccr.ccs.tencentyun.com/<命名空>/<镜像>:v1 \
  --writable-layer-size 2G \
  --expose-port 49983 \
  --probe 49983 \
  --probe-path /health \
  --registry-username <用户> \
  --registry-password <>

Step 6 & 7:等待就绪 + 验证

与方式一 Step 5、Step 6 完全相同。


两种方式对比

本地镜像远程镜像
是否需要推送❌ 不需要✅ 需要 push
适用场景单机开发调试团队共享、多节点集群
镜像名格式my-sandbox:v1registry/ns/image:tag
私有仓库认证不需要可能需要 --registry-username/password
速度快(本地直接读)取决于网络和镜像大小

常见问题

💡 一、apt-get 报 Temporary failure resolving

现象: Docker build 时 apt 无法解析域名。

原因: Docker 容器内默认使用 8.8.8.8 作为 DNS,内网机器无法访问。--dns 参数在旧版 docker build(非 buildx)中不支持,写 /etc/resolv.conf 也会被 Docker 覆盖。

解决: 查出内网 apt 镜像站 IP,直接硬编码到 sources.list

bash
# 1. 查镜像站 IP(在宿主机上执行)
nslookup mirrors.tencent.com 9.218.233.130 | grep Address | tail -1
# => Address: 30.163.240.137

# 2. Dockerfile 里替换 apt 源
RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://30.163.240.137/ubuntu|g' /etc/apt/sources.list && \
    sed -i 's|http://security.ubuntu.com/ubuntu|http://30.163.240.137/ubuntu|g' /etc/apt/sources.list

pip 同理,用 IP 替换 pypi 镜像域名:

dockerfile
RUN pip install --no-cache-dir \
    -i http://30.163.240.137/pypi/simple/ \
    --trusted-host 30.163.240.137 \
    numpy pandas

💡 二、envd /health 不返回 204

现象: Step 3 验证时 curl 返回非 204,或连接拒绝。

原因: 镜像的 ENTRYPOINT / CMDcube-entrypoint.sh 覆盖掉了,导致 envd 没有启动。

解决: Dockerfile 里确保 ENTRYPOINT 使用 cube-entrypoint.sh

dockerfile
ENTRYPOINT ["/usr/local/bin/cube-entrypoint.sh"]
CMD ["your-app-command"]

或者在自定义 entrypoint 里手动拉起 envd:

bash
/usr/bin/envd -port 49983 >/var/log/envd.log 2>&1 &
exec "$@"

💡 三、SDK 报 SSL: CERTIFICATE_VERIFY_FAILED

现象: Python SDK 调用沙箱时报证书错误。

原因: SDK 通过 HTTPS 访问沙箱域名(*.cube.app),但本机不信任 Cube 内置的 mkcert CA。

解决: 设置环境变量指向 CA 证书:

bash
export SSL_CERT_FILE=~/.local/share/mkcert/rootCA.pem

或者在代码里临时禁用(仅测试环境):

python
import ssl, warnings
warnings.filterwarnings('ignore')
ssl._create_default_https_context = ssl._create_unverified_context

💡 四、模板一直卡在 phase: PULLING

现象: tpl watch 长时间停在 PULLING 阶段不动。

原因: CubeMaster 节点拉不到镜像(网络不通、Registry 认证失败等)。

排查:

bash
# 查看任务详情,找 last_error 字段
cubemastercli tpl status --job-id <job_id> --json | jq '.last_error'

# 在 CubeMaster 所在节点手动 docker pull 验证
docker pull <镜像地>

本地镜像补充说明: 如果用本地镜像(未推送到远端),CubeMaster 会直接从本地 Docker 读取,无需网络,不会卡在 PULLING。确认镜像名拼写正确即可:

bash
docker images | grep <镜像>

💡 五、模板 status: FAILED(BUILDING 阶段)

现象: 模板构建失败,卡在 BUILDING 阶段后变为 FAILED。

排查:

bash
cubemastercli tpl status --job-id <job_id> --json | jq '.last_error'

常见原因:

  • --writable-layer-size 设置过小,构建时磁盘写满
  • 节点磁盘空间不足:df -h 检查

💡 六、distribution: 0/N ready 长时间不变

现象: 模板状态已 READY,但 distribution 显示 0 个节点就绪。

原因: artifact 正在分发到各节点,多节点集群时正常,稍等即可。

排查长时间未恢复:

bash
# 检查目标节点 Cubelet 日志
journalctl -u cubelet -f

💡 七、模板制作卡住,沙箱一直处于 running 状态且无法删除

现象: 制作模板时(快照阶段)卡住不动,tpl status 显示任务长时间无进展;同时有沙箱残留,状态一直是 runningDELETE /sandboxes/:id 也无法删除。

原因: 磁盘空间不足,快照写入失败,导致沙箱进程卡死,无法正常退出,也无法响应删除请求。

排查:

bash
# 检查宿主机磁盘使用情况
df -h

# 重点关注 Docker 数据目录和 Cube 数据目录
df -h /data/docker/lib/
df -h /var/lib/cube/ 2>/dev/null || df -h /data/cube/ 2>/dev/null

# 查看占用最大的目录
du -sh /data/docker/lib/overlay2/* 2>/dev/null | sort -rh | head -10

解决:

  1. 释放磁盘空间(清理无用镜像和容器):
bash
# 清理已退出的容器、无用镜像、build cache
docker system prune -f

# 如果空间还不够,清理无 tag 的镜像
docker image prune -f
  1. 强制清理残留沙箱(正常 API 删不掉时):
bash
# 查看残留沙箱进程
ps aux | grep -E 'firecracker|qemu|cubelet' | grep -v grep

# 强制 kill 对应进程(替换为实际 PID)
kill -9 <PID>

# 或通过 Cubelet 日志找到沙箱对应的进程组
journalctl -u cubelet -n 100 | grep <sandboxID>
  1. 重新制作模板:清理完磁盘后重新提交 tpl create-from-image,原失败的 job 无需处理。

预防: 制作模板前确认宿主机磁盘剩余空间 > 镜像大小 × 2(需要同时存放 rootfs 和快照文件)。


💡 八、probeprobe-path 参数说明

这两个参数告诉 CubeMaster 用哪个端口和路径来探测容器是否就绪。探活返回 HTTP 2xx 后,模板才会进入 READY 状态。配错是模板构建失败最常见的原因之一。

参数含义:

参数说明
--expose-port声明容器对外暴露的端口
--probe探活使用的端口,必须已在 --expose-port 中声明
--probe-path探活的 HTTP GET 路径,该路径必须返回 2xx

不同镜像的正确配置:

官方 sandbox-code 镜像(代码执行沙箱,envd 监听 49983,Jupyter kernel 监听 49999):

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 \
  --probe-path /

官方 sandbox-browser 镜像(浏览器沙箱,envd 监听 49983):

bash
cubemastercli tpl create-from-image \
  --image cube-sandbox-cn.tencentcloudcr.com/cube-sandbox/sandbox-browser:latest \
  --writable-layer-size 1G \
  --expose-port 49983 \
  --probe 49983 \
  --probe-path /health

基于 cubesandbox-base 的自定义镜像(envd 监听 49983):

bash
cubemastercli tpl create-from-image \
  --image my-registry/my-sandbox:v1 \
  --writable-layer-size 2G \
  --expose-port 49983 \
  --probe 49983 \
  --probe-path /health

自定义镜像 + 额外应用端口(比如应用在 8080 提供服务):

bash
cubemastercli tpl create-from-image \
  --image my-registry/my-app:v1 \
  --writable-layer-size 2G \
  --expose-port 49983 \
  --expose-port 8080 \
  --probe 49983 \
  --probe-path /health

常见配错场景:

错误配置现象正确做法
--probe 端口未在 --expose-port 中声明参数校验报错--expose-port--probe,端口保持一致
--probe-path 填了不存在的路径(如 /healthz,实际是 /health探活超时,模板 FAILED本地先 curl http://127.0.0.1:<port><path> 确认返回 2xx
sandbox-code 镜像只暴露了 49983,未暴露 49999模板 READY,但 run_code 报连接失败sandbox-code 需同时暴露 49999 和 49983
--probe-path 漏写开头的 /(写成 health 而非 /health探活 404路径必须以 / 开头

本地验证 probe 配置(制作模板前先跑):

bash
cid=$(docker run -d <你的镜>)
sleep 2
docker exec "$cid" curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:<probe端口><probe-path>
# 期望: 200 或 204
docker rm -f "$cid"

💡 九、cubesandbox SDK 安装失败(历史问题,已修复)

现象: 在旧版仓库上 pip install 报错 BackendUnavailable: Cannot import 'setuptools.backends.legacy'

原因: 早期 sdk/python/pyproject.toml 误填了不存在的 build-backend setuptools.backends.legacy:build(PEP 517 setuptools 官方 backend 只有 setuptools.build_metasetuptools.build_meta:__legacy__ 两个,setuptools.backends.legacy 从未存在过 —— 不是版本问题,是笔误)。

解决: 已在源仓库改为 setuptools.build_meta。如果你的本地副本仍是旧版,请 git pull 拿到最新 dev-snapshot,或手动改一下:

bash
cd CubeSandbox/sdk/python
sed -i 's|setuptools.backends.legacy:build|setuptools.build_meta|g' pyproject.toml
pip install .

💡 十、如何向沙箱传递环境变量

Cube Sandbox 支持三种方式,适用于不同场景。

方式 1:模板创建时传入(全局默认 env)

通过 --env 烘焙进模板快照,所有从该模板创建的沙箱都会继承:

bash
cubemastercli tpl create-from-image \
  --image cube-sandbox-cn.tencentcloudcr.com/cube-sandbox/sandbox-code:latest \
  --writable-layer-size 1G \
  --expose-port 49999 \
  --probe 49999 \
  --env MY_ENV=production \
  --env DB_HOST=10.0.0.1

⚠️ 写入模板的 env 对所有使用该模板的用户可见,不要用于存放密钥等敏感信息

方式 2:创建沙箱实例时传入(推荐)

Sandbox.create()envs 参数,动态注入,只作用于当前沙箱实例:

python
from e2b_code_interpreter import Sandbox

with Sandbox.create(
    template="tpl-xxxx",
    envs={
        "API_KEY": "sk-xxx",
        "DEBUG": "true",
        "MY_CUSTOM_VAR": "hello"
    }
) as sandbox:
    result = sandbox.run_code("""
import os
print(os.environ.get('API_KEY'))
print(os.environ.get('DEBUG'))
print(os.environ.get('MY_CUSTOM_VAR'))
""")
    print(result)

方式 3:沙箱内通过代码设置(运行时临时变量)

python
with Sandbox.create(template="tpl-xxxx") as sandbox:
    # 通过 shell 设置(只在该次命令子进程内有效)
    sandbox.commands.run("export FOO=bar && echo $FOO")

    # 通过 Python 设置(在同一个 kernel session 内持久)
    sandbox.run_code("""
import os
os.environ['RUNTIME_VAR'] = 'dynamic_value'
print(os.environ['RUNTIME_VAR'])
""")

三种方式对比:

方式时机作用范围适用场景
--env(模板创建时)构建模板时所有沙箱实例固定配置(运行环境、默认参数)
envs={}(SDK 创建时)创建沙箱时单个沙箱实例推荐 — 动态配置(密钥、用户参数)
代码内 os.environ运行时当前进程临时变量

最常用的是方式 2,通过 Sandbox.create(envs={...}) 传入,灵活且不污染模板。


快速参考

bash
# 列出所有模板
cubemastercli tpl list
cubemastercli tpl list -o wide

# 查看模板详情
cubemastercli tpl info --template-id <template_id>

# 删除模板
cubemastercli tpl delete --template-id <template_id>

# 必要环境变量(使用 SDK 时)
export CUBE_TEMPLATE_ID=<template_id>
export E2B_API_URL=http://127.0.0.1:3000
export E2B_API_KEY=e2b_000000
export SSL_CERT_FILE=~/.local/share/mkcert/rootCA.pem

微信公众号《有机系统》作者整理的个人学习笔记与教程。