news 2026/9/15 15:26:33

CubeSandbox 沙箱自动暂停/自动恢复协调服务 cube-lifecycle-manager 深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CubeSandbox 沙箱自动暂停/自动恢复协调服务 cube-lifecycle-manager 深度解析

CubeSandbox 沙箱自动暂停/自动恢复协调服务 cube-lifecycle-manager 深度解析

【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox

导读

本文以仓库中 cube-lifecycle-manager/README.md 为骨架,深入解析 CubeSandbox 中负责「沙箱自动暂停 / 自动恢复(auto-pause / auto-resume)协调」的独立服务cube-lifecycle-manager(下称 CLM)。CLM 横跨 CubeMaster、CubeProxy 与 Redis 三个组件,是控制面数据通路与数据面请求通路的胶水层。读完本文,你将掌握:CLM 消费的 Redis 生命周期事件模型、CubeProxy 实例发现与元数据广播机制、/internal/resume同步回调链路、全部CUBE_LCM_*环境变量的语义与默认值、镜像的构建发布流程,以及 Redis 主从式 leader election 与 warm standby 高可用设计。

CLM 在 CubeSandbox 中的定位

CubeSandbox 是一个面向 AI Agent 的即时、并发、安全、轻量级沙箱。Agent 场景下沙箱往往处于「频繁创建、长期空闲、按需唤醒」的状态,如果每个空闲沙箱都持续占用完整 VM 资源,成本与密度都不可接受。自动暂停 / 自动恢复因此成为核心资源治理能力:

  • 自动暂停:沙箱空闲超过阈值后,由 CLM 驱动 CubeMaster 将沙箱暂停(pause),释放 CPU / 内存等资源;
  • 自动恢复:暂停期间数据面收到对该沙箱的请求时,CubeProxy 通过 CLM 的/internal/resume同步回调驱动 CubeMaster 将其恢复(resume),再把请求转发进去。

从源码注释看(cmd/cube-lifecycle-manager/main.go),CLM 是「sits between CubeMaster, CubeProxy, and Redis」的独立服务,与历史版本的 sidecar 形态相比,它作为独立部署单元解耦了生命周期协调逻辑。

核心职责与三大数据通路

CLM 在运行时维护三方面职责,对应三条数据通路:

1. 生命周期事件消费(CubeMaster → Redis → CLM)

CLM 消费cube:v1:shared:sandbox:lifecycle:events事件流。CubeMaster 是事件的唯一写入方,CLM 是纯消费者。从 internal/lifecycle/schema.go 的注释可以看到,该 schema 是CubeMaster/pkg/lifecycle的本地镜像,两侧必须保持字节级兼容。

事件流上的操作码(Op)包括:

Op含义负载
create沙箱创建SandboxLifecycleMeta元数据
update沙箱元数据更新SandboxLifecycleMeta
delete沙箱删除仅 sandbox_id
stateCubeMaster 在 pause/resume RPC 成功后发出的终态迁移StatePayloadpaused/running

CLM 采用XREAD 广播消费而非XREADGROUP:每个副本都收到全部事件并维护自己的内存 registry,从而保证 warm standby 随时可接管。Read在 internal/redisstream/stream.go 中实现,消费游标由调用方持有。

2. CubeProxy 实例发现与元数据广播(CLM → CubeProxy /admin/*)

CLM 通过cube:v1:shared:cube_proxy:{registry,heartbeat}实时发现每个存活的 CubeProxy 副本,并把沙箱元数据 + 运行状态广播到它们的/admin/*端点。这样 CubeProxy 数据面(Lua 层)就能根据本地 dict 判断某个沙箱是pausedrunning还是正在迁移,从而决定放行还是触发 resume。

发现逻辑实现在 internal/discovery/redis.go:周期性扫描 Redis 中的注册 Hash 与心跳 Sorted Set,维护内存中的活跃端点集合;HeartbeatTTL用于判定离线(默认 3 × 5s = 15s),OnJoin回调负责对新加入的副本做全量元数据回放(replay)。

3. 同步恢复回调(CubeProxy → CLM → CubeMaster)

当 CubeProxy 在数据面发现「沙箱已暂停但收到请求」时,通过内部 sub-location 调用 CLM 的POST /internal/resume?sandbox_id=...&request_id=...。CLM 完成恢复协调后返回,CubeProxy 再决定转发请求。这条链路是同步的,因此 HTTP 超时预算的设定非常讲究(见下文 HTTP API 部分)。

本地开发与测试

仓库提供 Makefile(cube-lifecycle-manager/Makefile)封装常用操作:

make test # go test ./... make build # local image, tag: cube-lifecycle-manager:v0.7.1-<arch>

make build会基于本机架构生成镜像,tag 形如cube-lifecycle-manager:v0.7.1-amd64ARCH变量默认取宿主架构(uname -m映射),也可显式指定:

make build ARCH=amd64 make build ARCH=arm64

此外还提供make fmtgo fmt ./...)与make clean

镜像发布流程(多架构)

README 明确说明发布流程镜像CubeEgress/Makefile,对cube-sandbox-int.tencentcloudcr.comcube-sandbox-cn.tencentcloudcr.com两个仓库同时生效。完整流程为:

# 在 amd64 主机上: make build push ARCH=amd64 # 在 arm64 主机上: make build push ARCH=arm64 # 在任意一台主机上(两个架构镜像必须先已推送): make manifest

push目标把本架构镜像同时 tag 并推送到两个仓库;manifest目标则对每个仓库的每个 tag($(IMAGE_TAG)latest)创建并推送多架构 manifest list,将-amd64-arm64两个镜像合并为多架构镜像。

可用的覆盖变量:

  • IMAGE_TAG=<tag>—— 覆盖发布 tag(默认v0.7.1),同时影响本地镜像与推送 tag;
  • V=1—— 打印详细的 docker 命令(verbose 模式)。

CUBE_VERSIONCUBE_COMMITCUBE_BUILD_TIME作为--build-arg传入构建过程,最终写入运行镜像的/etc/cube/version.json(见 Dockerfile)。

配置体系:全部通过环境变量

CLM 的配置全部经由环境变量注入,统一使用CUBE_LCM_前缀,权威清单见 internal/config/config.go。Load()的规则是:未设置的环境变量回落到内置默认值;只有「设置了但解析失败」才报错。Validate()则检查字段组合是否自洽。

环境变量速查表

环境变量默认值说明
CUBE_LCM_REDIS_ADDR127.0.0.1:6379Redis 地址(与 CubeMaster 写入的是同一实例)
CUBE_LCM_REDIS_PASSWORDRedis 密码
CUBE_LCM_REDIS_DB0Redis 逻辑库
CUBE_LCM_REDIS_MASTER_NAME设置后启用 Sentinel 模式,需同时配置 SENTINEL_NODES,使用 go-redis FailoverClient
CUBE_LCM_REDIS_SENTINEL_NODESSentinel 节点列表,逗号分隔;裸主机名/裸[ipv6]自动补默认端口 26379
CUBE_LCM_REDIS_SENTINEL_PASSWORD认证 Sentinel 实例的密码(非 master 密码;master 密码仍走 REDIS_PASSWORD)
CUBE_LCM_PROXY_ADMIN_URLShttp://127.0.0.1:8082CubeProxy admin 端点,逗号分隔可配多个
CUBE_LCM_ADMIN_TOKEN可选共享密钥,作为X-Cube-Admin-Token头发送
CUBE_LCM_CUBEMASTER_URLhttp://127.0.0.1:8089CubeMaster 内部 HTTP 地址,CLM 调用POST <url>/cube/sandbox/update,action=pause|resume
CUBE_LCM_LISTEN_ADDR0.0.0.0:8083CLM HTTP 监听地址(/internal/resume等);单机开发可改为 loopback
CUBE_LCM_DEFAULT_IDLE_TIMEOUT5m沙箱 lifecycle meta 缺省TimeoutSeconds时的空闲阈值
CUBE_LCM_STREAM_READ_BLOCK5sXREAD 的 BLOCK 参数
CUBE_LCM_LAST_ACTIVE_POLL5sGET /admin/last_active轮询周期
CUBE_LCM_IDLE_SWEEP_INTERVAL5ssweeper 扫描周期
CUBE_LCM_BOOTSTRAP_WARMUP30s重启后对 HGETALL 引导加载的沙箱暂缓暂停的宽限窗口,等待 last_active 回填
CUBE_LCM_STATE_LOCK_TTL60s暂停/恢复状态锁(SETNX TTL),需大于 HTTP_TIMEOUT 才能通过校验
CUBE_LCM_CONSUMER_NAME主机名默认取os.Hostname();作为 leader election 的身份前缀
CUBE_LCM_HTTP_TIMEOUT10s对 CubeMaster / CubeProxy 出站调用的 HTTP 超时
CUBE_LCM_USE_STATIC_FLEETfalse(接受1/true/yes为 true 时禁用 Redis 服务发现,以 PROXY_ADMIN_URLS 为权威副本列表(单机开发/集成测试)
CUBE_LCM_HEARTBEAT_TTL15sCubeProxy 心跳超过该时长视为离线(建议为代理心跳周期的 3 倍)
CUBE_LCM_DISCOVERY_REFRESH3sRedis 心跳扫描周期
CUBE_LCM_EVENTBUS_ENABLEDtrue启用 Pub/Sub 跨副本唤醒提示;为 false 时状态写入退回旧式 Set/Del 路径,resume 等待走 100ms 轮询
CUBE_LCM_LEADER_ELECTION_ENABLEDfalse是否启用 leader election;Kubernetes 部署显式开启,非 K8s 单实例保持关闭
CUBE_LCM_LEADER_LEASE_TTL10sleader 租约 TTL
CUBE_LCM_LEADER_RENEW_INTERVAL3s租约续期周期,必须小于租约 TTL 的一半
CUBE_LCM_LEADER_RETRY_INTERVAL1s争抢/失败后的重试间隔,必须大于 0 且小于租约 TTL

需要注意:CUBE_LCM_CONSUMER_GROUP为兼容上一版本保留(默认cube-proxy-sidecar),当前广播式 XREAD 已不使用消费组;保留该值是为了原地升级时延续同一 pending-entries 列表,避免历史重放。

配置校验的关键约束

Validate()里有两类值得关注的硬约束:

  • Sentinel 模式REDIS_MASTER_NAME一旦设置,REDIS_SENTINEL_NODES必须非空,否则直接报错;
  • 锁与超时的相对关系StateLockTTL <= HTTPTimeout时报错——锁必须活得比一次 CubeMaster RPC 长,才能为「慢 RPC + 崩溃副本」提供安全边界;LeaderRenewInterval * 2 >= LeaderLeaseTTL也会报错,防止续期间隔逼近租约导致频繁掉主。

高可用设计:Redis 主从式 leader election 与 warm standby

README 明确了 Kubernetes 部署下的运行模型:两个 warm 副本,配合 Redis-backed leader election。

角色划分

  • 两个副本都消费生命周期事件、维护内存 registry、轮询 last_active、提供/internal/resume服务(standby 也能处理 resume);
  • 只有 leader执行单例维护工作:空闲扫描(idle sweep / kill)与过期 CubeProxy 修剪。

从 cmd/cube-lifecycle-manager/main.go 的reconciledLeader实现可以看到,standby 的「可写」能力被精确 gate:IsLeader()只有在「租约生成号匹配 + 已完成追赶调和 + 当前仍是 leader」三者同时满足时才为真。

无 Lua 的租约实现

Leader election 刻意不用 Lua/EVAL,只用SET NX PX+ 单 keyWATCH事务(见 internal/leader/lease.go)。原因在 schema 注释里说得很清楚:所有租约事务只触碰cube:v1:shared:lock:lifecycle-manager:leader这一个 key,天然兼容 Redis Cluster 单槽位约束。

每次成功获取租约后Generation()递增;续期通过 WATCH 校验 token 后PExpire;释放通过 WATCH 校验后Del。本地还维护deadline(TTL 减去续期间隔的余量),即使 Redis 续期瞬时失败,只要未过本地 deadline 也不会立即掉主。

新 leader 的追赶与排干协议

README 描述的接管序列,对应 main.go 的reconcileOnLeadership

  1. 新 leader 获取租约后,先把 XREAD 游标**追赶(catch-up)**到事件流高水位;
  2. 等待一个 CubeProxy HTTP 超时(默认CUBE_LCM_HTTP_TIMEOUT10s),排干上一任 leader 在途写入 CubeProxy 的请求;
  3. 再次追赶事件流(防止排干窗口内产生新事件);
  4. 调和共享 Redis 状态 key(reconcileSharedState,带 CAS),
  5. markReconciled(generation)放开单例工作闸门。

值得一提的是resolvePromotionState(main.go)的保守策略:当本地终态与共享 key 冲突且无法判断先后时,paused——理由是「错误的 paused 最多多触发一次自动恢复,错误的 running 则会把流量路由进一台已停机的 VM」。

每沙箱状态锁作为 fencing 边界

单例动作的 fencing 由每沙箱状态锁提供:SET NX(带 TTL)写在cube:v1:shared:sandbox:lifecycle:state:<id>。谁拿到锁谁就拥有本次 pause/resume/kill 迁移的所有权;锁 TTL(默认 60s)比一次慢 CubeMaster RPC 长,又比崩溃副本的恢复周期短,兼顾安全与自愈。状态迁移的 CAS 事务同样只用单 keyWATCH,见 redisstream/stream.go 的acquireIf

引导追赶与缓存调和

Hydrating CubeProxy 的本地 dict 是best-effort的:hydrateFleet以 goroutine 异步执行,不阻塞 leader 就绪(README 明确「does not gate leadership」)。同理,当 XREAD 游标因MAXLEN(100000 条)裁剪而失效时(ErrCursorTrimmed),副本会从权威的 meta Hash 重建 registry,并保留已学的LastActiveMs/RuntimeState,然后restoreIfSameGeneration恢复单例工作——见 main.go。

非 Kubernetes 部署

Host Docker / systemd 单实例部署时,LeaderElectionEnabled默认关闭。Lease.New在未启用时直接令IsLeader()恒为 true(lease.go),保持历史单实例行为,无需任何额外配置。

源码级原理:四个核心循环

main()启动后并行运行多个后台循环,首个出错即取消整体(main.go)。下面逐一展开。

1. 事件消费循环(consumeStream)

维护内存 registry 并随 create/delete/update 事件把增量推送给 CubeProxy。handleEvent的行为(main.go):

  • create / update:写入 registry(update 同时重置 LastActive),若当前为 leader 则推送元数据到 CubeProxy;
  • delete:从 registry 删除并推送删除——所有副本都推,因为删除是终态、沙箱 ID 不复用,晚到的副本也无法翻转它,即使 leader 失败也能保证代理侧清理;
  • state:交给statesync.Handle调和外部驱动的暂停/恢复(如 SDKconnect())。

create 事件在 info 级别打日志,作为「CubeMaster → Redis → CLM 链路已打通」的心跳信号。

2. last_active 轮询(pollLastActive)

LastActivePoll周期对每个 CubeProxy 调GET /admin/last_active,把活跃时间戳合并进 registry(MergeLastActive)。sweeper 消费的是合并后的视图。关键细节是水位推进:用各响应中now最小值推进since水位,保证即便某台 CubeProxy 时钟偏慢,也不会遗漏(since, next_since]区间内的任何记录(main.go)。

3. 空闲扫描器(sweeper)

internal/sweeper/sweeper.go 实现自动暂停/超时杀。每次扫描:

  • 跳过pausing/resuming/killing/killed等迁移标记(skipSweepState),不叠加到在途迁移上;
  • 空闲基线的计算规则:baseline = max(LastActiveMs, CreatedAt),都缺失时回退到FirstSeenAt
  • 超时判定:TimeoutSeconds为 nil 时用DefaultIdleTimeout负值表示永不超时(跳过);其余按秒换算;
  • AutoPause=truetryPause(先SET NX "pausing"→ 预推送pausing状态到 CubeProxy → 调 CubeMaster.Pause → 写paused终态并推送);
  • AutoPause=false(即on_timeout=kill)→tryKillAcquireKill拿到killing锁 → 预推送killing→ CubeMaster.Kill(reason=timeout) → 写killed终态、删除代理侧元数据、从 registry 逐出)。

tryKill的终态不可恢复,killing/killed会让数据面 Lua 门直接返回 410 Gone,让在途客户端请求快速失败而不是挂在无望的重试上。

错误语义方面:CubeMaster 返回的「sandbox 不存在」(IsNotFound)会触发本地逐出;「已在目标状态」(IsAlreadyInState)视为成功继续收尾;而传输/超时类错误的结果未知——CLM 不会清锁广播running(避免把流量放进可能已被暂停的 VM),而是保留迁移标记到歧义窗口(AmbiguityTTL),等后续重试。

BootstrapWarmup的设计动机也很典型:CLM 刚启动时,HGETALL 引导的沙箱LastActiveMs=0,若不等待,首轮扫描就会把所有历史沙箱当超时空闲处理。引导条目FirstSeenAt被回填为启动时刻,配合 30s 宽限窗口即可区分「引导加载」与「启动后经流到达」两类条目。

4. 恢复协调器(resumer)

internal/resumer/resumer.go 是/internal/resume的请求侧实现。核心能力是并发合并(coalescing):同一沙箱的并发 resume 请求共享一个 in-flight call(calls map[string]*call),只有第一个到达者真正发起 CubeMaster RPC,其余阻塞等待结果,避免请求风暴打爆控制面。

acquireResumeOwnership用单 key WATCH 事务解决一个微妙的双义问题——同一个状态 key 既承载终态标记(paused/running)又承载在途迁移锁(pausing/resuming),SETNX本身无法区分「我在恢复这个沙箱」和「这个沙箱停在 paused 等人恢复」,必须先用 WATCH 事务窥视当前值再 CAS 写入resuming

  • paused或 key 过期 → 原子写入resuming,赢家驱动 RPC;
  • running→ 无操作成功(会顺手向代理重申running以修复 dict 漂移);
  • pausing/resuming→ 等待对端完成(waitForRunning);
  • killing/killed→ 返回「沙箱已被杀」,让 CubeProxy 映射为 410。

waitForRunning有两种实现:EventBus 关闭时退化为 100ms 轮询;开启时先注册 Pub/Sub 监听再读 Redis,轮询仅作丢消息兜底。AutoResume的检查刻意放在拿到resuming之后:这样「用户已通过 CubeMaster 手动恢复、状态事件同步器已把 key 翻成 running」的场景不受AutoResume=false阻拦,数据面请求照常放行。

成功收尾的三次 best-effort 写也值得注意:Redis state →running(跨副本可见)、CubeProxy dict →running(关掉 rewrite 门)、registryLastActiveMs→ now(否则 5 秒后 sweeper 就会对刚唤醒的沙箱误报「idle threshold exceeded」)。

HTTP API 与观测端点

CLM 对外暴露三个 HTTP 端点(internal/httpapi/server.go):

端点方法说明
/internal/resumePOST查询参数sandbox_id(必填)、request_id(可选);成功返回{"ok":true,"sandbox_id":...},失败返回 503 及原因
/healthzGETliveness,恒返回ok
/readyzGETreadiness,JSON 返回role(leader/standby)、leader_election_enabledregistry_lenfleet_size

超时预算的设计是理解这条链路的关键:

  • CubeProxy 侧的proxy_read_timeout为 30s,因此 CLM 用 25s 的请求级超时(server.go),确保自己总是能在 nginx 放弃之前刷出 5xx 响应体——否则 nginx 只会返回无 body 的 504,丢掉诊断信息;
  • http.ServerWriteTimeout设为 35s,保证 CubeProxy 的子请求不会被 CLM 提前切断。

/readyz刻意不因 standby 角色而失败:warm standby 必须留在 Service endpoints 里(WithLeaderStatus只改变role字段,不改变就绪状态),这样 leader 故障时流量能立即打到可服务的 standby。

部署形态速览

CLM 的 Dockerfile 是标准的多阶段构建:golang:1.25-alpine编译出CGO_ENABLED=0的静态二进制,运行在gcr.io/distroless/static-debian12:nonroot之上,USER nonroot:nonroot,暴露 8083 端口(Dockerfile)。手动运行示例:

docker build -t cube-lifecycle-manager:one-click . docker run --network=host \ -e CUBE_LCM_REDIS_ADDR=127.0.0.1:6379 \ -e CUBE_LCM_CUBEMASTER_URL=http://127.0.0.1:8089 \ cube-lifecycle-manager:one-click

部署时注意三件事:

  1. Redis 必须是 CubeMaster 写入的同一实例,事件流、meta Hash、状态 key、leader 租约全部在其中;
  2. Kubernetes 下开CUBE_LCM_LEADER_ELECTION_ENABLED=true跑两个副本;单机 Docker/systemd 保持默认关闭即单实例模式;
  3. 静态副本列表与动态发现二选一:设置CUBE_LCM_PROXY_ADMIN_URLS+CUBE_LCM_USE_STATIC_FLEET=1时彻底跳过 Redis 发现(fleet == nil分支),适合单机开发与集成测试;生产默认走 Redis 心跳发现。

总结

cube-lifecycle-manager的价值在于把「何时暂停、如何恢复、谁来做」这套分布式协调问题收敛到一个独立、可观测、可横向扩展的服务里:

  • 事件驱动:CubeMaster 写入事件流,CLM 广播消费,任何副本都能保持内存视图新鲜;
  • 租约保护:无 Lua 的SET NX PX+WATCH事务实现 Redis Cluster 友好的 leader election,配合追赶-排干-再追赶协议杜绝脑裂双写;
  • 锁即状态:每沙箱状态 key 一身二职(终态 + 迁移锁),配合 CAS 事务把并发 pause/resume/kill 串行化;
  • 保守错误语义:结果未知时宁可不清理,也绝不把流量引入可能已暂停的 VM。

对于想深入了解的读者,建议按此顺序研读源码:先从 schema.go 建立 Redis 数据结构全貌,再看 main.go 的主流程编排,随后分别深入 sweeper.go、resumer.go 与 statesync/handler.go 三条业务主线,最后对照 config.go 完成部署参数的全量校验。

【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 15:26:01

GUI智能体UI-TARS实操指南:让它替你点击与输入

GUI智能体UI-TARS实操指南&#xff1a;让它替你点击与输入 【免费下载链接】UI-TARS Pioneering Automated GUI Interaction with Native Agents 项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS 想让电脑替你点鼠标填表单&#xff0c;却被自动化脚本劝退&am…

作者头像 李华
网站建设 2026/9/15 15:25:02

EIP-7792 可验证日志:用链上日志累积器让 eth_getLogs 响应可验证

EIP-7792 可验证日志&#xff1a;用链上日志累积器让 eth_getLogs 响应可验证 【免费下载链接】EIPs The Ethereum Improvement Proposal repository 项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs 本指南以仓库中的 EIPS/eip-7792.md 为蓝本&#xff0c;系统…

作者头像 李华
网站建设 2026/9/15 15:24:35

基于51单片机的智能滴灌控制系统设计与实现

简介&#xff1a;基于51单片机的滴灌控制系统资料包&#xff0c;面向单片机学习者、课程设计与毕业设计人群&#xff0c;解决温室或农田场景下依据温湿度自动滴灌的工程问题。系统通过PT100测温&#xff0c;配合模拟量湿度传感器或仿真电位器监测湿度&#xff0c;用户可按键设定…

作者头像 李华
网站建设 2026/9/15 15:24:23

微信外卖点餐小程序模板二次开发:从zip导入到购物车订单联调

简介&#xff1a;美食餐饮外卖点餐微信小程序模板&#xff0c;是为餐饮商户与小程序开发者打造的一站式解决方案&#xff0c;帮助快速搭建外卖点餐与门店展示平台&#xff0c;无需从零编写代码&#xff0c;适合预算有限或希望快速试水的团队。压缩包共68个文件&#xff0c;其中…

作者头像 李华