找回密码
立即注册
搜索
发回帖 发新帖

6738

积分

0

好友

856

主题
发表于 昨天 22:31 | 查看: 2| 回复: 0

本次调研对象为 E2B、CubeSandbox、OpenSandbox 与 agent-sandbox,均到源码级。前四篇偏平台侧,覆盖创建、生命周期、存储和网络;本篇把视角切到沙箱内侧,看 Agent 发命令、读文件、拿输出时,请求实际落到哪套接口。

一、问题

这一层拆开看,主要是五件事:

  1. 「Agent 位置与通道」:daemon 在 guest / 容器里还是宿主上;请求走公网域名,还是控制面带外直连。
  2. 「进程执行」:Agent / SDK 调 API,让沙箱内 daemon fork 出进程跑命令,再回传 stdout / stderr 与退出码。这里不是 Jupyter 那种有状态代码执行。细节包括同步或流式、stdin / PTY、信号、进程组、超时、孤儿清理,以及 kill 是打单个 PID 还是整组。
  3. 「文件 API」:base64 塞进 JSON,还是裸字节流;路径圈禁是词法检查,还是解析 symlink 真实落点。
  4. 「有状态代码执行」:Jupyter kernel 焊进 daemon,还是留给模板;MCP(Model Context Protocol,给 Agent 接工具的协议)这类端点放哪。
  5. 「安全模型」:daemon 常以 root 跑,API 本身就是边界;token 存原值还是只存 digest;默认开不开鉴权。

envd 已经是事实标准。Cube 直接装进 guest,49983、Connect-RPC、Basic user 头均沿用;agent-sandbox 的 runtime KEP 把它列为两大范式之一,并给出了「envd 当 runtime」的示例。不过当前 E2B 源码里的 envd 已重写为 chi + Connect,「Jupyter 网关已删除」。也就是说,标准指的是进程 / 文件协议;Cube 的 49999 Jupyter 属于旧血统遗留。

第二节按这五个维度逐家拆开,第三节再做横向对照。

二、四家怎么做

每家统一按:位置与通道 → 进程 → 文件 → 有状态代码 → 安全;产品特有能力如 MCP、热升级、版本耦合,收在末尾。

1. CubeSandbox

「Agent 位置与通道」

guest 里有两个 Agent,分工严格:

Agent 通道 职责
「envd」(用户面) 49983 Connect-RPC,走 CubeProxy 通配域名 进程 / 文件 / PTY / Jupyter / 拉起 MCP
「cube-agent」(基础设施面) VM 内 PID 1(init);vsock 接 CubeShim(ttrpc) 把 OCI 镜像在 guest 里跑成容器:Create/Start/Exec/Kill/Delete、cgroup、指标

「「VM 套容器」是什么」,这块容易和「沙箱在 Pod 里」搞混:

宿主机 KVM
 └── MicroVM(沙箱的硬件边界;是宿主的直接客户机,不是跑在 Pod 里面)
      └── cube-agent(PID 1)
           └── OCI 容器(用户镜像)
                └── 其中再跑 envd 等

沙箱创建在宿主机之上。K8s 模式下 cube-node Pod 里跑的是 CubeShim 等管理面;VM 本身始终是宿主 KVM 客户机,不是嵌套虚拟化,也不是「Pod 里再套一台虚拟机」。

为什么 guest 里还要按容器跑,而不是像 E2B 那样摊成盘直接跑进程?隔离要 VM,生态要容器:对外接 containerd / Shim v2,镜像、Exec、sidecar、按容器 cgroup 采指标都得成立。内层容器只是同内核里的 namespace / cgroup / 挂载再 exec,不是再套一层虚拟机;相对 MicroVM 本身,这点开销通常不是瓶颈。换成「直接跑」模型更简单,但会丢掉运行时 OCI 语义——那是产品选型,不是因为容器更快。

「cube-agent」 就是改过的 Kata agent + rustjail:宿主 CubeShim 经 vsock 下发 OCI 生命周期,一 VM ≈ 一 Pod。它负责把镜像变成环境;Agent 日常 run command 仍走 envd。SDK 自写 Connect-JSON,不依赖 e2b 包。

「进程执行」

用户面与 E2B 同范式:调 guest 里的 envd,经 CubeProxy 到 49983,不走 cube-agent。

  1. 「接命令」 — process.Process/Start;命令固定 /bin/bash -l -c(login shell,拿完整 PATH);Connect-Timeout-Ms 硬墙钟截止,按真实时间到点就停。
  2. 「起进程」 — 仍由 envd fork / exec,属于旧血统实现。
  3. 「定身份」 — Basic user 头表达以哪个用户跑,默认偏 root。
  4. 「回传输出」 — 事件流里 stdout / stderr 仍是 「base64 塞进 JSON」,兼容旧 envd,大文件更吃亏;E2B 主线已改裸流。
  5. 「信号 / PTY」 — 走同一 process.Process 服务;信号用字符串枚举,如 SIGNAL_SIGKILL。

「文件 API」

双通道:

  • /files HTTP:读写大文件。
  • Filesystem Connect-RPC:ListDir / Stat / Remove / Move / MakeDir / WatchDir(目录监听)。

「有状态代码执行」

  • Jupyter 仍留在 49999,旧 envd 血统;E2B 主线已删。
  • MCP 网关不进平台、进模板:SDK 经 envd 以 root 起 mcp-gateway;平台只做校验、启动、路由,沙箱里跑什么由模板决定。

「安全模型」

  • X-Access-Token + Basic user 头。后者无密码,只用来表达以哪个用户跑。
  • 默认用户偏 root,兼容旧 envd。

「版本耦合」

模板内存快照固化了 guest 里的 cube-agent,节点升级不等于沙箱升级;彻底升级只能删了重建。兼容的是已停更的旧 envd,包含 base64 与 Jupyter;E2B 主线这两样已经淘汰。

2. E2B

「Agent 位置与通道」

  • envd 单端口 0.0.0.0:49983;orchestrator 以 slot IP 直连,不经公网入站。
  • 控制面与用户面混布同一端口:靠 x-internal 标记 + 节点代理对控制路径一律 404。隔离靠路由,不靠端口分拆。
  • 已重写为 chi + Connect;旧 JSON-over-HTTP / Jupyter 网关已不存在。

和 Cube 的对照:E2B 构建期也吃 Docker / OCI,拉镜像 → 抽成 ext4 rootfs → 注入 envd → 打快照,但那只是 「文件从哪来」;运行时 guest 里是普通 Linux + envd,「没有」 Docker / containerd / 内层 OCI 运行时。租户隔离交给整台 MicroVM;envd 只做在这台小机器上起进程,包括 fork/exec、切用户、进 cgroup,不是简化版容器。namespace、多容器生命周期这些,因为有了 VM,就不必在 guest 里再演一遍。

「进程执行」

SDK 调 commands.run("python clean.py"),底层是 process.Process/Start。guest 里的 envd 大致走这几步:

  1. 「接命令」 — Connect-RPC 收到请求;冻结期间直接拒绝。fork 进已冻结 cgroup 会卡在 exec 前,拖死 Go 的 GC STW,连 thaw 都救不回来。
  2. 「起进程」 — wrapper 先写 oom_score_adj=100,OOM 时优先杀用户进程、保住 daemon;再 exec 换成真正命令,shell 被顶替,kill 该 pid 即 kill 用户命令。minimal 镜像缺 ionice / nice 时优雅降级。
  3. 「定身份 / cgroup」 — syscall.Credential 直设 uid/gid,不走 sudo;CLONE_INTO_CGROUP 让子进程生而进对应 cgroup,没有「先 fork 再迁移」的窗口。
  4. 「回传输出」 — stdout / stderr 流式推;结束后退出码保留约 30s,断连重连还能查结局;无人订阅时输出直接丢弃,不能回放。
  5. 「结束 / kill」 — 目前只杀单个 PID,不做进程组。孙进程若还握着管道写端,读端会一直等不到 EOF,这是源码注释里的失败模式。

「文件 API」

  • 目录走 RPC;大文件走裸 HTTP,全程无 base64。
  • ServeContent 支持 Range 断点续传;copy_file_range 做内核零拷贝 compose。
  • /files 另有签名 URL,凭证编进 URL 哈希,token 本身不上 wire。

「有状态代码执行」

Jupyter 已删;所谓 code interpreter 也只是普通 command + Python,和上面的进程执行是同一条路,不是独立的有状态 kernel。runtime 收窄到进程与文件本体。

「安全模型」

鉴权三通道:

  1. 常规:X-Access-Token。memguard 在内存里加密持有,降低明文落盘风险。
  2. /files:签名 URL。
  3. /init:MMDS 做 hash 校验,走 Firecracker 元数据口,不经普通出站网络。

升级后 fail-closed:新 envd 在 token 恢复前只放行 /init 和 /health。

硬伤在于,token 未设置时签名校验直接放行。也就是说,/init 之前 guest 内任何进程可用 root 文件 API。daemon 以 root 跑;root fallback 经 X-Envd-Defaults 打指标,属于异常监控,不是设计目标。

「热升级」

冻结 → 序列化进程表 → fd 挪到固定槽位 → syscall.Exec 同 PID 换二进制 → pidfd re-adopt。成功标志是连接被重置,旧进程不会正常回包。

3. OpenSandbox

「Agent 位置与通道」

daemon 叫 「execd」,始终跑在沙箱里面,和 E2B 的 envd 同级。三种底层投放方式不同,但目的一致:让沙箱里出现一个可打的 HTTP 服务。

底层 execd 怎么进沙箱
「Docker」 先起临时容器,从专用镜像把 execd 二进制拷进目标目录,再改入口脚本后台拉起,不是运行时反复 docker exec
「K8s」 用 initContainer 把 execd 等文件拷进共享卷,业务容器起来后就能用
「fsb」(Firecracker 快路径) 构建模板时就把 execd 写进镜像,恢复出来自带

共同点:单端口 「44772」;命令一律 HTTP → 沙箱内 execd → fork/exec。控制面只解析「这个沙箱的 IP/端口是啥」,SDK 拿到后 「直连 execd」,控制面不在数据路径上。

「进程执行」

含义与 E2B 相同:SDK 发命令,execd 在沙箱内起进程,回传输出与退出码。例如 python clean.py → POST /command → execd fork → SSE 推日志。相对 envd,实现几乎处处反向:

  1. 「接命令」 — 协议是 SSE,不是 Connect-RPC。头惰性提交,首个事件写出前还没定成 SSE,执行前的同步错误仍能返回普通 JSON。命令可以是一段 shell,也可以是原生 argv;没有 bash 则回退 sh。
  2. 「起进程」 — Setpgid 把自己和孩子放进同一进程组,方便整组杀掉。
  3. 「定身份」 — 请求可带 uid/gid,syscall.Credential 直设。
  4. 「回传输出」 — 先写入私有目录 /tmp/opensandbox-execd/,权限 0700,防止 workload 预置恶意 symlink,再经 SSE 增量推给客户端;保留约 24h。完成后多 sleep 200ms,给客户端与 sidecar 一点收敛时间。
  5. 「结束 / kill」 — Kill(-pid),负号表示打整组;kill 前检查 done channel,防 pid 被内核复用后误杀新进程。PTY 另有单 holder(可写)+ 多 viewer(只读),takeover=1 可抢占。

「文件 API」

  • /files + /directories 全套。
  • 另有身份前缀版 /v1/filesystem/{uid}/{gid}/...:每请求 fork 独立 worker,带所选凭证执行,不继承 execd 的补充组。

「有状态代码执行」

和进程执行分开:

  • 「Jupyter」(/code):四家里唯一还活着的内置实现。每语言一个持久 kernel,变量与导入跨调用保留;sql 直连沙箱内 MySQL。
  • 「bash session」:无 TTY,但 env / cwd 跨调用保持,介于一次性 command 与 Jupyter 之间。

「安全模型」

  • 每请求可带 uid/gid;same-identity 必须走无凭证路径。即便 uid 不变,设 Credential 也会触发 setgroups,能力已 drop 的沙箱里必然 EPERM。
  • 鉴权机制在、默认关:本仓库 server 不调用 /internal/init 注入 token;token 空即全放行。
  • Docker publish_host 默认 0.0.0.0,即绑所有网卡。机制存在,不等于被调用。

4. agent-sandbox

定位和其他三家不同:「不卖自己的 runtime 产品」,而是在 K8s 上游做标准化,即 KEP-539.2。它规定进程 / 文件 API 该长什么样,并给一个参考实现 「sandboxd」。灵感来自 OpenSandbox 的 REST 与 E2B 的 envd。协议按操作拆:进程走 gRPC,长连接流式推输出;文件走 REST,避免大文件再包 base64。

「Agent 位置与通道」

sandboxd 跑在沙箱 「Pod 里面」,命令实际执行在托管 sandboxd 的那个容器里。对外两个端口:

端口 协议 干什么
:9090 gRPC ProcessService——起进程、推输出、发信号
:8080 HTTP FilesystemService——读写文件、健康检查、metadata

怎么让客户端连上它:复用 K8s 已有能力,比如 kubectl port-forward、Pod IP 或 headless Service,「没有」 自研公网代理。注意,官方 sandbox-router 上游禁了 HTTP/2,「带不了 gRPC」,所以 runtime 流量不能指望走 router。

sandboxd 怎么进 Pod,有两种常见拓扑:

拓扑 做法 效果
「A. 专用 runtime 容器」 Pod 里一个极简容器专门跑 sandboxd 隔离强;要加工具得重建这个镜像
「B. 二进制注入」 initContainer 把 sandboxd 静态二进制拷进业务镜像的共享目录 免重建;命令跑在 「你的」 业务镜像里,能直接用里面的 python 等

踩坑:若把 sandboxd 挂成 「sidecar」 却只共享 volume,共享的是文件,不是「能看见对方镜像里的工具」。看起来像 B,实际命令仍跑在 sidecar 自己的根文件系统里,业务镜像里的软件找不到。

另外,官方示例支持直接把 「E2B 的 envd 当 runtime」 挂上,标准 Linux 容器即可。也就是说,标准接口与参考实现可以拆开用。

「进程执行」

含义仍是:客户端发命令 → sandboxd 在 Pod 内起进程 → 流式回传输出与退出码。例如 gRPC Start 跑 python clean.py。相对 envd / execd,sandboxd 刻意做最小集:

  1. 「接命令」 — Start 是 server-stream:先 InitEvent(带上 daemon 自己发的 process_id),再若干段 stdout/stderr,这里均为 「裸 bytes,不 base64」,最后恰好一个 ExitEvent。另有一元 Execute:跑完再一次性返回。可选 PTY。
  2. 「起进程」 — 身份等于 sandboxd 所在容器的用户,「不做」 请求级 uid 切换;也 「没有」 API 级 timeout,超时交给调用方自己管。
  3. 「会话 ID」 — 虚拟 PID registry:daemon 内存里发单调递增 ID,不绑 Linux 真实 PID,只要求当前活着的进程之间不撞号。
  4. 「回传输出」 — 流式即焚,不落盘、不保留。对比 execd 的 24h、envd 退出码 30s。
  5. 「结束 / kill」 — SendSignal 用 Kill(-pid) 打进程组。daemon 自己要退出时:SIGTERM 广播给孩子 → 宽限 10s → SIGKILL 兜底清理。

「文件 API」

  • GET/PUT/DELETE /v1/files/{path}:文件回原始字节流,目录回 JSON。同一 URL,按类型变响应。
  • PUT 原子写:先写临时文件再 rename,读端要么全旧要么全新。
  • 路径圈禁较严:已存在路径先 EvalSymlinks 再检查仍在根内;新文件则解析最近已存在的祖先目录,防止用 symlink 把写入拐出沙箱根。

「有状态代码执行」

KEP 把 Jupyter 列在 Full 能力级,但 「sandboxd 参考实现未做」,需要自己当普通进程在 workload 里起。

「安全模型」

  • 默认不鉴权:相信 K8s 信任链,例如 NetworkPolicy 限制谁能连到 Pod,port-forward 要过 apiserver。不过 「脱离集群后这个默认不成立」。
  • 可选 --auth-token-sha256:Pod 里只放 token 的 digest,不放原值。命令行、环境变量、子进程 env 就算被抄走,也推不出原 token。
  • token 是 NetworkPolicy 旁边的纵深,不是替代;验的是 「集群外驱动客户端」,不是沙箱里的代码,因为沙箱内代码本来就够得着本机 API。
  • /v1/metadata:只下发允许前缀的环境变量;名字里带 TOKEN / SECRET / PASSWORD 等标记的一律不下发。

三、横向对比

1. Agent 位置与通道

  • E2B:guest 内只有 envd;orchestrator 带外直连;控制面 / 用户面混布单端口。OCI 止于构建期,运行时不跑容器。
  • Cube:双 Agent。envd 走 CubeProxy 承接用户命令;cube-agent 走 vsock 承接 OCI 生命周期。沙箱是宿主上的 MicroVM,VM 内再套容器接生态。
  • OpenSandbox:execd 在沙箱内;控制面解析端点,SDK 直连。
  • agent-sandbox:sandboxd 在 Pod 内;通道靠 K8s 原语。

只做执行与文件时,端点解析 + 直连够用;要注入 env、挂卷、冻结、原地升级,协议会变重。E2B / Cube 都用 VM 做强隔离;分叉在 guest 内还要不要保留容器运行时:要则换 OCI / containerd 兼容,不要则路径更短。

2. 进程与文件

  • kill:OpenSandbox / sandboxd 做进程组 + pid 复用防御;E2B 单 PID 是已知短板。
  • 命令包装:exec 替换、无 shell 残留。
  • 输出生命周期:即焚 / 30s 只留 exit / 24h 全量,属于产品选择。
  • 退出码:全员 128+signal,信号致死的惯例写法,死后可查。
  • 超时:envd 当 API 契约;sandboxd 外包给调用方。
  • 文件编码:E2B 新版 / sandboxd / OpenSandbox 裸流;Cube 仍 base64-in-JSON。
  • 路径圈禁:sandboxd 的 symlink 解析最完整。

3. Jupyter / MCP

E2B 删了 → Cube 因兼容还留 → OpenSandbox 内置 → KEP 未落地。代码执行在往 runtime 之上的可选服务挪;kernel 本质是长命进程。MCP 一致收窄:不进平台、进模板。

4. 鉴权

  • 事实开放:OpenSandbox 的 server 不注入 token;E2B /init 前的 /files 同样开放。
  • 静态 token / 签名 URL:E2B。
  • digest-only:sandboxd。
  • Cube:token + Basic,默认用户偏 root。

形态上都有解,默认启用普遍偏弱。验收要看默认路径有没有真正调用鉴权,不能只看代码在不在。

四、自研建议

  1. 协议分治,且兼容 envd:进程走 Connect / gRPC 流,文件走 REST 裸流;提供 49983 + Basic user 兼容层。
  2. 进程组 kill + pid 复用防御是底线。
  3. 命令包装抄 E2B:exec 替换 + OOM 偏置 + CLONE_INTO_CGROUP。
  4. 身份分层,root fallback 可观测;same-identity 走无凭证路径;sudo 是模板能力,不是 API 能力。
  5. 鉴权抄 sandboxd digest;OpenSandbox、E2B 两个事实开放案例进评审清单;文件下载加签名 URL。
  6. 控制面与用户面分端口或显式分权;混布单端口至少公网不可达 init。
  7. 输出保留做成产品决策;退出码保留窗口必备。
  8. 热升级 v1 可不做,接口留白:进程表可序列化、升级窗口 fail-closed。
  9. Jupyter 不进 runtime;MCP 抄 Cube,平台只做校验 / 启动 / 路由。
  10. 可观测性搭 init 便车;若做 pause,冻结期间拒绝 spawn。

五、小结

Agent 循环消费的是:命令边界清楚,包括进程组与 OOM 偏置;结局可查,退出码有窗口;产物可取回,文件可流式传输。断连、超时、暂停、daemon 升级,都不该逼它重跑上一步。

envd 协议是生态入口;进程组 kill 与 digest 鉴权是底线;Jupyter / MCP 进模板不进平台。四家默认鉴权都不理想,「机制被默认调用」要写进验收。相关讨论也可以到 云栈社区 继续交流。

下一篇:编排调度与资源管理。




上一篇:Rust 从零实现 Raft 共识算法:选主不难,难在这些安全性约束
下一篇:Agent安全与成本设计:提示词注入四道防线和全链路成本账
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-11-6 17:04 , Processed in 0.080633 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表