news 2026/9/15 18:28:05

OpenSandbox Isolation Session 实战:用 bubblewrap 隔离会话在单个沙箱内并行跑多个互不干扰的任务

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenSandbox Isolation Session 实战:用 bubblewrap 隔离会话在单个沙箱内并行跑多个互不干扰的任务

OpenSandbox Isolation Session 实战:用 bubblewrap 隔离会话在单个沙箱内并行跑多个互不干扰的任务

【免费下载链接】OpenSandboxSecure, Fast, and Extensible Sandbox runtime for AI agents.项目地址: https://gitcode.com/GitHub_Trending/ope/OpenSandbox

在跑批量任务时,常见需求是:不想为每个任务都新起一个容器,但又不能让任务之间互相污染进程、环境变量和文件系统。OpenSandbox 的 Isolation Session 解决这个问题——它让沙箱内的execd为每个会话 fork 一个 bubblewrap(bwrap)子进程,bwrap建好 Linux namespace 后在其中exec一个长驻bash,每个会话拥有独立的 PID、mount、tmpfs 和 env namespace。一个沙箱 pod 因此可以承载多个互不隔离的短任务,会话创建约 100 ms,之后同一会话内的每次run开销接近于零。

适用环境(来自文档的组件版本要求):

  • execd>= 1.0.20,推荐 >= 1.0.21binds、List sessions、uid_mode: "userns"与默认可写白名单需要 1.0.21)
  • opensandbox-server>= 0.2.1(镜像声明bootstrap.execd.isolation时,server 会注入CAP_SYS_ADMINapparmor=unconfinedbwrap所需的 tmpfs mount)
  • Python SDK >= 0.1.14(isolation.run_once/isolation.session);JavaScript/TypeScript SDK >= 0.1.10;Kotlin >= 1.0.16;C# >= 0.1.4;Go >= 1.0.4
  • 主机要求:execd 镜像内自带bwrap二进制与受信任的 native workload gate、CAP_SYS_ADMIN、内核支持overlayfsoverlay工作区模式需要)。Linux 源码构建 execd 时,需先make build-session-gatesudo make install-session-gate,把辅助程序装到/opt/opensandbox/opensandbox-session-gate,且该路径必须 root-owned、不允许组/全局可写;缺少它时隔离能力探测 fail-closed

适合:RL rollouts、批量代码判分、多工具 agent 运行——一个 worker 一个沙箱,沙箱内跑大量互相隔离的任务。不适合:跨语言内核(用/code)、交互式 REPL(用/session)、对抗内核漏洞的硬信任边界(用 gVisor/Kata,见 Secure Container Runtime)。

先探测:确认当前沙箱支持隔离会话

所有接口挂在 execd 的/v1/isolated/下(execd 默认监听 44772 端口)。如果 execd 配置了 access token,请求要带X-EXECD-ACCESS-TOKEN头。

curl -s http://localhost:44772/v1/isolated/capabilities

文档示例输出:

{ "available": true, "isolator": "bwrap", "version": "0.9.0", "setpriv_available": true, "userns_available": false, "commit_supported": false, "diff_supported": false }

判断方法:

  • available: true说明可以创建隔离会话。available: false对应三种原因:native workload gate 缺失或不受信任、bwrap缺失、主机无法创建所需 namespace(缺CAP_SYS_ADMIN、user-ns sysctl 受限等)。
  • setpriv_available/userns_available分别表示uid_mode: "setpriv"(默认,真实 setuid/setgid 降权)和uid_mode: "userns"(user namespace 重映射)能否建会话。这两个字段是 execd v1.0.21 之后才有的,旧版 execd 不返回,客户端要容忍缺失。
  • commit_supported/diff_supported目前是 Phase 2 占位,当前返回503
  • 注意:缺少overlayfs不会翻转available,但workspace.mode: "overlay"的会话创建仍可能在运行时失败,依赖 overlay 模式的主机需自行验证overlayfs支持。

创建会话并执行第一个任务

最短主路径(curl,SSE 流返回stdout/error/complete事件):

# 创建会话:strict profile + overlay 工作区 SESSION=$(curl -s -X POST http://localhost:44772/v1/isolated/session \ -H "Content-Type: application/json" \ -d '{ "profile": "strict", "workspace": {"path": "/workspace", "mode": "overlay"}, "idle_timeout_seconds": 300 }' | jq -r .session_id) # 第一次 run curl -N -X POST "http://localhost:44772/v1/isolated/session/$SESSION/run" \ -H "Content-Type: application/json" \ -d '{"code": "export X=1; echo $X", "timeout_seconds": 30}' # 第二次 run 复用 shell 状态,会打印 1 curl -N -X POST "http://localhost:44772/v1/isolated/session/$SESSION/run" \ -H "Content-Type: application/json" \ -d '{"code": "echo $X"}' # 用完后销毁 curl -X DELETE "http://localhost:44772/v1/isolated/session/$SESSION"

bash是长驻的,所以同一会话内一次runexport X=1对下一次run可见——但只在同一会话内可见,另一个会话看不到。这就是"互不干扰"的核心:环境变量、进程和/tmpstrictprofile 下是私有 tmpfs)都按会话隔离。

用 Python SDK 的话,同一任务可以写成:

from opensandbox import Sandbox from opensandbox.models.isolated import ( CreateIsolatedSessionRequest, IsolatedWorkspaceSpec, IsolatedRunOpts, ) async with (await Sandbox.create("python:3.11")) as sandbox: # 一次性执行 await sandbox.isolation.run_once( "python -c 'print(42)'", workspace="/workspace", profile="strict", ) # 长驻会话 async with sandbox.isolation.session( CreateIsolatedSessionRequest( workspace=IsolatedWorkspaceSpec(path="/workspace", mode="overlay"), profile="strict", idle_timeout_seconds=300, ) ) as session: await session.run("export STAGE=train") await session.run("python train.py", opts=IsolatedRunOpts(timeout_seconds=600))

客户端重启后可以用sandbox.isolation.attach(known_session_id)重新挂回已知会话。

并行跑多个互不干扰的任务

文档明确给出并行模型的边界:同一会话内的run是串行的,要做并行就创建多个会话。每个会话各自持有独立的 namespace 和 bash 进程,A 会话里 export 的变量、写/tmp的文件,B 会话都不可见。

# 两个相互隔离的会话 S1=$(curl -s -X POST http://localhost:44772/v1/isolated/session \ -H "Content-Type: application/json" \ -d '{"profile":"strict","workspace":{"path":"/workspace","mode":"overlay"},"idle_timeout_seconds":300}' \ | jq -r .session_id) S2=$(curl -s -X POST http://localhost:44772/v1/isolated/session \ -H "Content-Type: application/json" \ -d '{"profile":"strict","workspace":{"path":"/workspace","mode":"overlay"},"idle_timeout_seconds":300}' \ | jq -r .session_id) # 两个 run 同时在两个会话里执行 curl -N -X POST "http://localhost:44772/v1/isolated/session/$S1/run" \ -H "Content-Type: application/json" \ -d '{"code":"export TASK=a; sleep 3; echo task-a in $TASK"}' & curl -N -X POST "http://localhost:44772/v1/isolated/session/$S2/run" \ -H "Content-Type: application/json" \ -d '{"code":"export TASK=b; sleep 3; echo task-b in $TASK"}' & wait # 验证互不干扰:S1 看不到 S2 的 TASK 值 curl -N -X POST "http://localhost:44772/v1/isolated/session/$S1/run" \ -H "Content-Type: application/json" \ -d '{"code":"echo TASK=$TASK"}' # 输出 a;若输出 b 说明隔离失败

创建多个会话时的成本结构要心里有数:创建会话约 100 ms(execd 在bwrap启动后短暂等待以检测立即退出的子进程),之后同一会话内每次POST /run复用同一个 bash,开销可忽略。所以批量任务的设计原则是把创建成本摊薄到一个会话内的多次run上,而不是每跑一条命令就建删会话。

选择工作区模式:任务之间文件系统怎么隔离

workspace.mode决定会话看到的/workspace语义,与 profile 相互独立(省略时 execd 会把mode归一化为overlay):

模式语义
rw读写 bind-mount,写入持久化到宿主机
overlay(默认)overlayfs copy-on-write;写入落在每会话独立的 upper 目录,DELETE会话后消失
ro只读 bind-mount,写入报EROFS

并行任务的默认选择就是overlay:各会话写同一path互不影响,会话销毁后写入自动消失,也避免了一个池化沙箱的占用者把数据泄漏给下一个占用者。只有任务间需要交换文件时才用rw(写入落宿主机)或额外 bind。

额外暴露路径用两个字段,source 都会先做 symlink 解析再对照allowed_writable白名单(默认/workspace/mnt/media/data,含子路径;空白名单拒绝一切额外 bind):

{ "workspace": { "path": "/workspace", "mode": "rw" }, "extra_writable": ["/data/scratch"], "binds": [ { "source": "/data/in", "dest": "/mnt/in", "readonly": true }, { "source": "/data/out", "dest": "/mnt/out" } ] }

bindsdest必须已存在于沙箱镜像内(bwrap无法在只读根下新建挂载点),需要时打进镜像。

长任务:同一会话内的后台 run

如果希望某个任务跑着的同时还能在同一会话上继续其他工作,用 background run(文档中作为同一会话内的补充手段):

# 启动后台 run;202 返回 {"session_id", "run_id", "started_at"} RUN=$(curl -s -X POST "http://localhost:44772/v1/isolated/session/$SESSION/run" \ -H "Content-Type: application/json" \ -d '{"code": "sleep 5 && echo done", "background": true}' | jq -r .run_id) # 轮询状态直到 running=false curl -s "http://localhost:44772/v1/isolated/session/$SESSION/runs/$RUN" # 读输出(纯文本),EXECD-ISOLATED-TAIL-CURSOR 响应头给出增量读取的下一字节偏移 curl -s "http://localhost:44772/v1/isolated/session/$SESSION/runs/$RUN/logs?cursor=123"

后台 run 的边界:timeout_seconds只对前台 run 生效,后台 run 不限时;后台 run 活跃期间该会话的 idle GC 挂起;ro工作区会因无可写日志位置而拒绝后台 run(400),rw/overlay可以;每次logs最多返回 16 MiB,单 run 日志保留上限也是 16 MiB,超出部分在 run 结束时丢弃,需要更多时要增量拉取。

验证隔离是否按预期工作

结合上面的步骤,核对点是:

  1. 能力探测/v1/isolated/capabilities返回available: trueisolator: "bwrap"
  2. 会话状态隔离:在会话 A 里export的变量,会话 B 里查不到(SSE 输出里应为空或旧值),同一会话内的第二次run能看到第一次的export结果。
  3. 工作区隔离:两个overlay会话对/workspace各写一个文件,互相ls不应看到对方的写入;DELETE任一会话后,其 overlay 写入消失。
  4. 会话清单GET /v1/isolated/sessions列出当前活跃会话;GET /v1/isolated/session/{id}返回完整状态并回显创建参数,无状态客户端可据此重建句柄。
  5. 销毁DELETE /v1/isolated/session/{id}后,对已销毁会话的run会返回IsolatedErrorsession process has exitedErrContextNotFound)。

失败处理与限制

非正常路径的行为文档有明确定义,处理时可以照表判断:

现象行为处理
前台runtimeout_seconds到期execd 取消 run context,向 bwrap 进程组发SIGINT,发IsolatedErrorSSE 事件;会话本身存活换更短的命令分段或调大超时后重跑
客户端在 SSE 中途断开请求 context 取消,execd 对运行中命令发SIGINT;run不会转入后台继续需要跑完就改用background: true
bwrap进程退出下一次run返回IsolatedErrorsession process has exitedDELETE后重建会话
idle 超时到达GC 执行与DELETE相同的拆除idle_timeout_seconds设为0可禁用 idle GC,改为显式DELETE

限制项(直接决定这套方案能用到哪里):

  • diff/commit是 Phase 2 占位,当前返回503(见 OSEP-0013)。
  • 没有硬件级保证:只有 namespace + seccomp,需要对抗内核漏洞时配合 gVisor/Kata 安全运行时。
  • 仅支持 Linux,非 Linux 构建返回available: false
  • 同一会话内 run 串行,并行必须多会话。
  • share_net省略时默认共享沙箱网络 namespace;沙箱级的 egress 与 Credential Vault 策略仍然生效。私有网络会话(share_net: false)无法跨越 execd 重启恢复,启用了它的部署要把 execd 视为沙箱关键进程,execd 退出时整个沙箱需要重建。
  • 会话状态只存在于 execd 内存中,execd 重启后所有会话状态不保留;upper_root下残留的 overlay upper 目录会在启动时被回收。

服务端配置(可选)

隔离行为由 execd 读取的可选 TOML 控制,通过--isolation-config或环境变量EXECD_ISOLATION_CONFIG指定,完整示例见 isolation.example.toml:

# 每会话 overlay upper 目录的父目录 upper_root = "/var/lib/execd/isolation" # 所有会话 upper 目录的总大小硬上限(字节),默认 8 GiB;0 表示禁用配额 upper_max_bytes = 8589934592 # extra_writable / binds 允许的 source 前缀(symlink 解析后检查) # 默认 ["/workspace", "/mnt", "/media", "/data"];置空则拒绝所有额外 bind allowed_writable = ["/workspace", "/mnt", "/media", "/data"]

多会话并行写放大时,upper_max_bytes是每个沙箱内全部会话共享的配额,会话可能因超配额而创建失败,需要按并发任务数和工作集大小调整。

进一步阅读:execd 组件文档、OSEP-0013 Isolated Execution API、Secure Container Runtime。

【免费下载链接】OpenSandboxSecure, Fast, and Extensible Sandbox runtime for AI agents.项目地址: https://gitcode.com/GitHub_Trending/ope/OpenSandbox

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

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

如何用 PDFPatcher 无损提取 PDF 图片并自定义输出文件命名掩码?

如何用 PDFPatcher 无损提取 PDF 图片并自定义输出文件命名掩码? 【免费下载链接】PDFPatcher PDF补丁丁——PDF工具箱,可以编辑书签、剪裁旋转页面、解除限制、提取或合并文档,探查文档结构,提取图片、转成图片等等 项目地址: …

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

Matlab频谱与Bode图绘制:从FFT到频响验证的完整指南

简介:面向信号处理、通信工程与控制系统领域的初学者及工程师,提供一套实用的MATLAB频谱分析与Bode图绘制解决方案,专门解决两大高频需求:一是利用pwelch函数完成功率谱密度估计,涵盖数据读取、滤波去噪、窗函数选择与…

作者头像 李华
网站建设 2026/9/15 18:21:51

用MySQL和ODBC构建Cadence CIS统一元器件库管理系统

搞硬件设计的兄弟应该都有这种体会:原理图库和PCB封装库要是乱起来,那真是灾难。一个项目里同一个电阻,有人用R0603,有人用RESC1608,还有人直接画个矩形框当电阻用,到了做BOM的时候,采购拿着Exc…

作者头像 李华