Skip to content

沙箱生命周期

沙箱(Sandbox)是 Cube-Sandbox 的核心运行单元。本页介绍沙箱从创建到销毁的完整生命周期,以及如何让平台自动管理生命周期、降低成本。

本页 SDK 形态与 e2b 保持一致,便于已有 e2b 用户直接迁移。

状态模型

一个沙箱在它的生命周期里会处于以下几种状态之一:

状态含义
running正在运行,CPU/内存被实际占用,可以接收请求与执行代码
pausing平台正在暂停沙箱(保存 VM 快照中),瞬时态
paused沙箱已暂停,VM 内存已落盘为快照,不消耗 CPU 与内存,状态完整保留
resuming平台正在从快照恢复沙箱,瞬时态
terminated沙箱被显式销毁(kill)或因 on_timeout="kill" 超时被回收,无法恢复

状态转换主要由两个参数驱动:

  • timeout(可选):沙箱空闲多久后触发超时,单位为(e2b 的 timeoutMs 是毫秒)。不传时由服务端决定;SDK 不再自带 300 秒之类的默认值。
  • on_timeout:超时后怎么办——"kill"(默认,销毁)或 "pause"(暂停,可之后恢复)。

timeout 取值(语义对齐 e2b):

取值行为
不传使用服务端配置的默认空闲超时;服务端未配置或设为 ≤ 0 时,永不超时
NEVER_TIMEOUT-1永不超时——不会因空闲被自动回收
0立刻超时——空闲后首次扫描即回收
正整数 N空闲 N 秒后触发超时

Go:cubesandbox.NeverTimeout;Python:from cubesandbox import NEVER_TIMEOUT

                       ┌──────────────────────────────────────┐
                       │                                      │
   create()       ┌────▼────┐   timeout & on_timeout=pause   ┌─────────┐
  ───────────────►│ running │ ──────────────────────────────►│ paused  │
                  │         │◄──────── connect() 或          │         │
                  └─┬─────┬─┘     auto_resume 触发的请求     └────┬────┘
                    │     │                                       │
        kill()      │     │ timeout & on_timeout=kill             │ kill()
        ────────────┘     └─────────────────┐                     │
                                            ▼                     ▼
                                      ┌────────────┐
                                      │ terminated │
                                      └────────────┘

创建沙箱

python
from cubesandbox import Sandbox

# 创建沙箱,空闲 60 秒后自动销毁(默认 on_timeout="kill")
sandbox = Sandbox.create(
    template="<your-template-id>",
    timeout=60,                # 单位:秒
)

print(sandbox.sandbox_id)

Sandbox.create() 关键参数:

参数说明
template模板 ID,沙箱基于它启动;缺省读环境变量 CUBE_TEMPLATE_ID
timeout可选,空闲超时(秒),见上文取值说明
lifecycle生命周期策略,详见下文 "平台自动暂停 / 自动恢复"
metadata任意键值对,写入沙箱元数据,可在列表 / 详情接口中读出
env_vars注入沙箱进程的环境变量
allow_internet_access是否允许出公网;network 提供更细粒度的出站策略

Cube 不像托管 e2b 那样有严格的 24h/1h 单次运行上限。省略 timeout 时,实际空闲 TTL 由集群运维在服务端配置(见下文设计与运维要点)。

查询沙箱信息

python
info = sandbox.get_info()
print(info)
# {
#   "sandboxID": "iiny0783cype8gmoawzmx-ce30bc46",
#   "templateID": "rki5dems9wqfm4r03t7g",
#   "state": "running",
#   "startedAt": "2026-06-17T12:34:56Z",
#   "endAt":     "2026-06-17T12:39:56Z",
#   "metadata":  {...}
# }

endAt 表示按当前 timeout 估算的下一次超时时间。每次接收到新请求或调用 set_timeout(若有),endAt 会被刷新。对于永不超时的沙箱没有截止时间,因此响应中会省略 endAt,而不是把它渲染成等于 startedAt

列出运行中的沙箱

python
for sb in Sandbox.list():
    print(sb["sandboxID"], sb["state"])

显式销毁

python
sandbox.kill()

kill() 是不可逆的:与暂停不同,被 kill 的沙箱不能恢复。即便 lifecycle.on_timeout="pause",调用 kill() 仍然立即终止并丢弃快照。

删除暂停状态的沙箱

kill()DELETE /sandboxes/{sandboxID} 均可用于删除处于 runningpaused 状态的沙箱。

删除 paused 沙箱时,CubeSandbox 不会先 Resume/唤醒 MicroVM。控制面直接删除 paused tombstone、清理 pause 快照(catalog/CoW),并清除 pause 元数据。Plugin volume 的 refcount 已在 Pause 时调整,删除路径无需为销毁再挂载一遍。

接口仍保持同步语义:只有清理完成后才返回 204 No Content。该路径不是一次 Resume:

  • 不会触发 sandbox.resumed 生命周期事件;
  • 不会重置空闲超时;
  • 不需要节点容量准入(不再有「删除前恢复」)。

删除暂停状态的沙箱时,通常会遇到以下几类情况:

  • 删除成功时返回 204 No Content,表示沙箱、pause 快照及相关资源已经完成清理。

  • 如果沙箱正在进入暂停状态,或其他生命周期操作(pause/resume/delete)尚未完成并持有沙箱锁,会返回 503 Service Unavailable,并携带 Retry-After: 2。客户端应等待至少两秒后重试。

Retry-After 的单位为秒,仅用于提示客户端等待后重试。它不表示 CubeSandbox 会在后台继续删除,也不会启动后台重试任务。

404 Not Found408 Request Timeout 以及 running 沙箱的删除行为保持不变。

显式暂停 / 恢复

python
sandbox.pause()                       # 主动保存快照,释放 CPU/内存
# ... 一段时间过去 ...
sandbox.connect()                     # 从快照恢复
sandbox.run_code("print('back!')")    # 像没暂停过一样继续用

可参考示例:examples/code-sandbox-quickstart/pause.py

Resume 后的 CubeProxy 缓存

Resume 会重建 guest NIC/主机端口,并重写 Redis 沙箱代理路由。CubeMaster 随后 best-effort 调用 CubeProxy POST /admin/backend_cache/delete 清理 local_cache,避免流量仍打到 pause 前的旧 IP(同机 504)。

要使该清理成功,CubeMaster 与 CubeProxy 必须配置相同的 admin token

  • CubeMaster:cubeproxy.admin_token(请求头 X-Cube-Admin-Token
  • CubeProxy:nginx.conf 中的 $cube_admin_token(见 CubeProxy/lua/admin_phase.lua

若只配一侧或两端不一致,清理会返回 403:Redis 路由已正确,但 CubeProxy 可能继续使用过期缓存直至条目过期。使用 Resume 时请在部署/Helm 中对齐该 token。

平台自动暂停 / 自动恢复

很多 Agent 工作负载并不持续繁忙:用户敲一段代码 → 模型推理 → 沙箱执行 → 等待下一轮交互。在等待期间让沙箱自动暂停,下次请求来时再自动恢复,可以显著降低资源占用。

Cube 提供与 e2b lifecycle 完全一致的配置形态:

python
sandbox = Sandbox.create(
    template="<your-template-id>",
    timeout=300,                      # 5 分钟空闲后触发 on_timeout
    lifecycle={
        "on_timeout": "pause",        # 空闲超时后 → 暂停(而不是销毁)
        "auto_resume": True,          # 暂停后下一次请求 → 透明恢复
    },
)

行为说明

  • on_timeout="pause":沙箱空闲 timeout 秒后,平台调度暂停流程,state 变为 paused,VM 内存被冷藏到快照存储。
  • auto_resume=True:当再有任何请求路由到这个 paused 沙箱(HTTP 请求、run_code、文件读写等),平台自动唤醒它,调用方无需显式 connect();典型恢复时间在亚秒级到秒级。
  • 如果 auto_resume=False(或省略),沙箱暂停后必须显式 Sandbox.connect(sandbox_id=...) 才能再用 —— 适合"等用户决定"的场景。

自动恢复后的 timeout 重置

每次自动恢复成功后,沙箱获得一个全新的 timeout 计时窗口(与 e2b 同样语义),所以"恢复 → 短暂使用 → 再次空闲超时 → 再次暂停"的循环可以无缝持续。

何时算"活跃"

下列动作都会重置 idle 计时:

  • 通过 SDK 调用:sandbox.run_code(...)sandbox.commands.run(...)sandbox.files.read(...) / write(...)
  • 通过 HTTP 直连沙箱内的服务(例如 getHost() 返回的 URL)。

未配置 auto_pause / 不传 lifecycle 的沙箱默认行为是 on_timeout="kill":空闲超过 timeout 秒后,平台会主动销毁该沙箱。这与 e2b lifecycle.on_timeout="kill" 语义一致。若不希望被自动回收,可传 timeout=NEVER_TIMEOUT、省略 timeout(且服务端未设正数默认)、把 timeout 设得足够大,或通过定期活动刷新空闲计时。

端到端示例

平台提供两个互为镜像的端到端演示,对应 on_timeout 的两种取值:

  • examples/code-sandbox-quickstart/auto-resume.py —— on_timeout="pause" + auto_resume=True。创建沙箱、空闲触发自动暂停、再发请求触发自动恢复,最终对比"内核内存 + 文件系统"两层状态,验证全状态保留。
  • examples/code-sandbox-quickstart/auto-kill.py —— on_timeout="kill"(默认行为)。创建沙箱、空闲触发自动销毁、验证后续请求以 410 Gone 快速失败、Sandbox.list() 不再返回该沙箱,并通过创建一个对照沙箱排除集群整体故障。
bash
export CUBE_TEMPLATE_ID=<your-template>

# 自动暂停 + 自动恢复
python examples/code-sandbox-quickstart/auto-resume.py

# 自动销毁(不可恢复)
python examples/code-sandbox-quickstart/auto-kill.py

设计与运维要点

集群默认空闲超时(default_timeout_insec

客户端不传 timeout 时,由 CubeMaster 读取 CubeMaster/conf.yaml 中的 cubelet_conf.default_timeout_insec(one-click 安装路径:/usr/local/services/cubetoolbox/CubeMaster/conf.yaml)。

配置值客户端省略 timeout 时的效果
未配置或 <= 0不设集群级空闲 TTL —— 沙箱不会因空闲被自动回收
正整数 N默认空闲 N 秒后触发超时

仓库默认不配置集群级空闲超时default_timeout_insec: -1)。若希望集群自动回收未显式传 timeout 的沙箱,可改为正数(例如 300)。修改后需重启 cube-sandbox-cubemaster.service

同一段里的 create_timeout_insec 与空闲 TTL 无关,仅限制创建/调度 RPC 的截止时间。更多 CubeMaster 配置项见服务管理 — CubeMaster 配置项

  • 暂停的状态保真度:CPU 寄存器、进程内存、TCP 连接(无外部对端)、文件系统改动都会随快照保留;面向外部的连接(如 sandbox 主动建立的 outbound socket)会在暂停时断开,恢复后由应用层自行重连。
  • 集群一致性:自动暂停由部署在 control 节点上的 cube-lifecycle-manager 服务统一协调;它消费 CubeMaster 通过 Redis stream 发布的生命周期事件,通过 Redis 注册表实时发现所有在线的 CubeProxy 副本并广播状态。多副本环境下用 Redis SETNX 互斥锁确保同一沙箱不会被并发暂停或恢复。
  • 失败回退:自动恢复 RPC 失败时,CubeProxy 直接对客户端返回 503 + Retry-After,不会让用户卡在长超时上;当沙箱已经被销毁(killing / killed),则返回 410 Gone 让客户端立即停止重试。
  • 故障排查:控制节点上执行 docker logs cube-lifecycle-manager 查看运行日志,关键事件包括 create event appliedauto-paused sandboxauto-resumed sandboxtimeout-killed sandbox。每个 CubeProxy 副本额外提供 GET http://<node-ip>:8082/admin/healthz,其中 heartbeat_last_pushed_ms 表示该副本最近一次向 manager 上报心跳的时间戳。

暂停资源释放与节点调度配额

沙箱暂停后,其 CPU 和内存在物理上已被回收——但在默认情况下,节点资源计账仍然将暂停中(paused/pausing)的沙箱视为"已占用"调度配额。这意味着:即使大量闲置沙箱被暂停,宿主机上仍然没有"空位"来创建新沙箱。

为了解决这个问题,Cube 提供了一个节点级调节旋钮 host.quota.paused_resource_release_ratio(在 Cubelet/config/config.toml 中配置),值域 [0, 1],默认 0

行为适用场景
0.0暂停沙箱保留完整配额(与旧版本行为一致)。恢复始终有保障,不会因资源不足被拒绝。对可用性要求极高、不希望恢复失败的场景
1.0暂停沙箱的 CPU/内存配额全部释放给调度器。恢复变为尽力而为——节点资源不足时恢复会被拒绝。追求最大化部署密度、允许恢复偶尔失败的场景
0 < r < 1释放 r 比例,保留 (1-r) 作为余量。保留的配额仍会计入调度器的 CPU/内存使用量,因此暂停密集的节点会被自然降权,调度器不会在已有大量暂停沙箱的节点上继续堆积新沙箱。需要在可用性和高利用率之间做折中的场景

配置示例

toml
# Cubelet/config/config.toml
[host.quota]
paused_resource_release_ratio = 0.5   # 释放一半,保留一半

恢复准入检查

ratio > 0 时,恢复操作会触发本地实时准入检查——如果节点当前无法容纳该沙箱释放出去的资源量,恢复会被拒绝:

resume rejected by paused_resource_release_ratio policy: need 1024MB > quota 512MB

拒绝信息通过以下链路透传给客户端:Cubelet (130409 Conflict)CubeAPI (HTTP 409)WebUI(显示容量诊断)。409 是可重试的状态码——当其他沙箱被销毁或暂停、节点资源释放后,恢复可以重新尝试。

注意事项

  • 磁盘和 MvmNum 不受 ratio 影响——暂停快照始终占用存储空间,沙箱对象始终存在。
  • ratio=0 是零值安全的默认值:如果从未配置过此项,行为与旧版本完全一致,升级不会产生意外。
  • 此项为节点级配置,不同节点可以设置不同的比值,灵活应对异构硬件或分池部署的需求。
  • 当节点上一大批沙箱同时被唤醒、单节点无法承载时,控制面会返回 409 并给出具体配额数字。后续版本将支持跨节点恢复,让沙箱可以在集群内自由漂移,最大化整集群利用率。

下一步