跳转到内容

05 隔离后端选型

本章导读|预计 20 分钟

这一章做两件事:先选一条隔离边界,再看被它倒逼出来的四个配套机制。 读完你应该能回答:我的场景落在哪一类边界上,以及配套的四个机制各自要看什么。

5.1 两层结构:一条边界 + 四个配套机制

Section titled “5.1 两层结构:一条边界 + 四个配套机制”

先说一个容易被跳过的结论。

隔离不是一个从「弱」到「强」的滑块。旧版这一章把选型压成一条强度轴——Docker < gVisor < microVM < 完整 VM——这条轴有两处失真:

  1. 它抹掉了「OS 系统调用策略」这一整档:macOS Seatbelt、Linux bubblewrap、Landlock,以及 Anthropic 的 srt,是一条独立的路线,不是容器轴的某个刻度点;
  2. 它把 bubblewrap 误读成「弱隔离」。bwrap 的定位是「给已有进程加围栏」,和容器根本不在同一个维度上比——这一点下面单独讲。

更准的结构是两层。

第一层,五类互斥的隔离边界(选一个)。它们之间的区别是「边界画在哪」:

边界 边界画在
microVM 环境 ↔ hypervisor
用户态内核(gVisor) 应用 ↔ 用户态内核
容器 + 加固 进程视图 ↔ 宿主内核(同一个内核,靠命名空间与策略切分)
OS 系统调用策略 进程 ↔ 宿主内核能做的事(不上容器,只收窄这个进程的能力面)
WASM 代码 ↔ 语言运行时

第二层,四个配套机制(都要有):快照与分叉 / 网络控制 / 凭据隔离 / 编排与接入。

第二层不是可选装饰。它是被第一层倒逼出来的:选了 microVM,就欠下「启动重,需要预热池或快照来补偿」这笔账;选了 WASM,就欠下「没有原生文件与网络,能力必须靠 host 逐项授予」这笔账。选完边界,这四件事基本已经被规定。

下面先把五类边界逐一看清,再来看四个机制各自要看什么。

一句话先立住:这五类是互斥的,一个沙箱方案通常属于其中一类(可在边界之外叠加加固,但不是另一类)。代表项目与定位取自 2026-10-21 采集、2026-10-22 复验的 GitHub REST API 快照。

怎么隔离:每个沙箱一台轻量虚拟机,有独立的 guest 内核。Linux 上走 KVM,macOS 上走 Hypervisor.framework。宿主只暴露最小设备面(virtio 的块设备、网卡、串口),攻击面因此比通用 hypervisor 小得多。

代表项目:firecracker-microvm/firecracker(37.5k stars,microVM 事实标准)· libkrun 系(microsandbox 8.8k、smolvm 6.8k)· Cloud Hypervisor。承载它的沙箱产品有 E2B(14.5k)与 OpenSandbox(16.0k)。

代价:要 KVM(或 macOS 的 HVF)。取不到 KVM 的宿主就用不了——多数托管容器环境、部分 CI、嵌套虚拟化受限的云主机都属于这一类。此外每实例有 guest 内核的内存开销,GPU 直通麻烦(极简 microVM 通常不支持),启动路径比容器重。

「完整 VM」放哪:QEMU/KVM 完整 VM 属于这一档的重量级端——同一个隔离边界(独立 guest 内核 + hypervisor),只是设备面更全、支持 GUI 与 GPU,代价是启动到秒级。它不是第六类。

怎么隔离:runsc 用一个 Go 写的 Sentry 进程接管 guest 的系统调用,自己实现一套内核语义;宿主内核只看到 Sentry 发出的极少接口。也就是说,应用以为自己在对内核说话,其实在跟一个用户态进程说话。

代表项目:google/gvisor(19.7k)· Dormice(gVisor / Docker 双后端,打 E2B 兼容牌)· GKE Sandbox · Isola。

代价:syscall 兼容性。没实现的系统调用直接返回 ENOSYS(部分 ioctl 与特殊文件系统操作落在这一类),某些语言运行时或依赖会踩;ptrace 模式下 syscall 密集的负载明显变慢。哪几个 syscall 缺失、慢多少,取决于你的负载——调研里没有本地基准,这一项要自己实测(这是归纳,不是来源的说法:调研只记录了「官方宣称」级别的说法,没有实测数据)。

怎么隔离:共享宿主内核,靠 namespaces + cgroups v2 + seccomp-bpf + capabilities 裁剪 + 只读 rootfs + AppArmor/SELinux 把进程的视图与权限切分。注意最后一个词:加固。容器本身给的是「环境」,安全全部来自你叠上去的策略。

代表项目:Docker / Podman · dify-sandbox · 多数「能跑就行」的方案。(旧版 Daytona 用 Sysbox 做加固,但该仓库 2026-06 起转闭源,见第 8 章。)

代价:共享宿主内核,内核漏洞即逃逸;强度取决于配置,默认不是安全的。三种典型失手:

  • --privileged:等于把宿主权限直接交给容器;
  • 挂载 docker.sock:容器能创建新容器,等于拿到宿主的控制权;
  • 内核 0-day:容器之间没有内核级边界,一个租户能攻击另一个。

单租户、可信代码,这一档够用且最省。多租户场景下,A 客户的代码不该有机会碰到 B 客户——容器给不了这个保证,microVM 可以:逃逸要同时攻破 guest 内核与 hypervisor 两层。

这是旧版整章缺失的一档,也是本章要补的关键空白。

怎么隔离:不上容器,直接给一个进程套策略,把它的能力面收窄:

  • macOS Seatbelt:系统自带的沙箱机制(sandbox_init / seatbelt profile),按 profile 限制进程能做的操作;
  • Linux bubblewrap:user namespace + bind mount + seccomp,bwrap 一条命令就能把进程关进一个受控的挂载与 syscall 视图;
  • Landlock:Linux 内核提供的 LSM,让进程(包括非特权进程)把自己声明过的文件系统访问权限收窄;
  • Anthropic srt(anthropics/sandbox-runtime,5.6k):把三者拼起来——macOS 用 Seatbelt、Linux 用 bubblewrap,再外加一个网络过滤代理,不依赖容器即可限制任意进程的文件系统与网络。

代表项目:Anthropic srt · nono(per-tool-call brokered sandboxing)· hivebox · yoloai。

代价:仍然是同一个宿主内核,只是把进程能做的事情收窄了。策略写错就是漏,而且难审计——它是声明式的,边界没有运行时可观测的形态。

它和容器档的区别(这一条最容易混):容器改变的是进程看到的世界(隔离的 rootfs、网络视图、pid 视图),OS 策略改变的是进程能做的事(哪些 syscall、哪些路径)。所以两者不是强度上的高低,而是形态上的不同,而且可以叠加——在 bwrap 里跑不跑容器都行。

定位纠正:bubblewrap 常被归进「弱隔离」那一列,这是误读。它这一档的主流用途就是「给已有 agent 加围栏、不上容器」:你已经在宿主机上跑着 Claude Code 一类的工具,只想给它加一层围栏,不想额外管理镜像与生命周期。这就是它存在的理由,也是它和容器不在同一档的原因。

怎么隔离:代码编译成 wasm,在 Wasmtime / V8 isolate 里执行,能力靠 host 显式授予(WASI 的能力模型:没拿到 fd 就碰不到文件与网络)。边界因此画在「代码 ↔ 语言运行时」之间。

代表项目:rivet-dev/agentos · Cloudflare 系方案。

代价:没有通用的 POSIX 环境,跑不了任意二进制。syscall 面不全,生态兼容性在五类里最差。把 Python / Node 生态搬进 WASM 的改造代价,经常高于隔离本身带来的收益——这一档要当心「轻量」这个词,它轻的是运行时,不一定是你的工作量。

5.3 常被混为一谈的一组:gVisor 与 microVM

Section titled “5.3 常被混为一谈的一组:gVisor 与 microVM”

两者都加强隔离,但走的是不同路线,一个是边界画在应用之下,一个是边界画在环境之外:

gVisor microVM(Firecracker)
思路 用户态内核:拦截并重新实现系统调用 独立内核:每个环境一个精简内核
隔离边界 应用 ↔ 用户态内核 环境 ↔ hypervisor
攻击面 gVisor 自身的实现(较大) hypervisor(较小)
兼容性 部分系统调用未实现 → 可能跑不起来 接近原生 Linux
资源开销 较低 每个 VM 有内核开销
启动 中 快(优化后 ~百 ms)
要 KVM 吗 不要 要(Linux)/ HVF(macOS)

怎么选:兼容性要求高、要求「像真机」→ microVM;想省内存、能接受偶发的 syscall 不兼容、又不想管 VM → gVisor。取不到 KVM 的宿主上,gVisor 往往是唯一能拿到「比裸容器强」的手段。

5.4 横向对照表:除隔离强度还要看什么

Section titled “5.4 横向对照表:除隔离强度还要看什么”

隔离强度只是第一眼。真正决定「这个后端能不能用」的是下面四列,按五类边界横向排开:

边界 取 KVM / GPU 能力 syscall 兼容性 内核漏洞面 运维复杂度
microVM 需要 KVM 或 HVF;GPU 直通麻烦,极简 microVM 通常不支持 → 要 GPU 就上完整 VM / Kata / 托管 GPU 沙箱 最好(guest 内核就是 Linux) 最小:逃逸要跨 guest 内核 + hypervisor 两层 最高:镜像、VM 编排、池化与回收
用户态内核(gVisor) 不需要 KVM;不支持特殊 ioctl,GPU 场景基本排除 中:未实现的 syscall 返回 ENOSYS 小:宿主内核面窄,但 gVisor 自身实现即攻击面 中:作为 RuntimeClass 接入现有容器工具链
容器 + 加固 不需要 KVM;GPU 可直通(nvidia-container-toolkit) 最好(原生内核) 最大:与宿主共享内核 最低:生态与工具链最成熟
OS 系统调用策略 不需要 KVM;策略只约束进程能力,与 GPU 无关 最好(仍是宿主内核,只是能力被收窄) 同容器:共享内核,但进程权限面更小 低到中:单进程起步很容易,跨平台差异(Seatbelt ↔ bwrap/Landlock)要分别维护
WASM 不需要 KVM;没有 GPU 概念 最差:只有 WASI 子集 最小(边界在语言运行时层) 中:要重编译 / 移植生态

两条读表的要点:

  • 「隔离强度 ↔ 兼容性」的反向关系对容器、gVisor、WASM 成立(拦得越多、越可能跑不起来),有两处例外:microVM 靠独立内核拿到强隔离、又保住接近原生的 syscall 兼容,这是它成为生产多租户首选的主要原因;OS 系统调用策略用的是真内核,兼容性不受影响,被收窄的是进程的能力面。
  • 「运维复杂度」和「安全上限」的方向常常相反。小团队选托管方案不算偷懒,它是在买运维这一列。

可信代码 → 容器 + 加固;不可信 / 多租户 → microVM 或 gVisor;只要轻量函数 → WASM;要完整电脑 → 完整 VM;只想给已有进程加围栏 → bwrap / Seatbelt。

更细一点的判断顺序:

要跑任意代码吗?
├─ 否 → 不需要沙箱(见第 9 章)
└─ 是
├─ 需要完整 OS / GUI / 桌面应用吗?
│ ├─ 是 → 完整 VM(computer-use)
│ └─ 否
│ ├─ 只是短小、确定性的函数吗?
│ │ ├─ 是 → WASM
│ │ └─ 否
│ │ ├─ 用户之间互不信任 / 面向公众吗?
│ │ │ ├─ 是 → microVM(Firecracker)或 gVisor
│ │ │ └─ 否 → 容器 + 加固够用;agent 已经在宿主上跑、不想上容器 → bwrap / Seatbelt

一个补充分支:取不到 KVM 时(托管容器、受限 CI),上不到 microVM,就在「加固容器 + 严格 egress 代理」和 gVisor 之间选。

5.6 两条升级路径,不是一个方向

Section titled “5.6 两条升级路径,不是一个方向”

旧版只写了「容器 → gVisor → microVM → 完整 VM」这一条。它其实是强度路径的一半,另外还有一条形态路径,两条轴不要混:

路径一 · 强度路径——边界有多硬:

本地开发 / 可信代码 容器 + 加固(+ 默认断网)
│ 出现多租户 / 不可信输入
小规模上线 gVisor(不想管 VM)或 加固容器 + egress 代理
│ 安全成为产品生命线
生产多租户 microVM(Firecracker)+ 预热池
│ 需要完整 OS / GUI / GPU
完整 VM microVM 的放大版(同一档,设备面更全)

路径二 · 形态路径——agent 要不要放进盒子里:

agent 在沙箱外(沙箱只当工具执行器)
│ agent 需要自己的 shell / 文件系统 / 长期状态,或要放任它在仓库里跑
agent 在沙箱内(容器或 microVM 里装 harness + 凭据)
│ 只差「给这个进程加围栏」,不想为它管理镜像与生命周期
OS 系统调用策略(bwrap / Seatbelt / Landlock)直接套在已有进程上

要点:不要一步到位。先用加固容器把产品跑通,等出现多租户 / 不可信需求时再沿强度路径升级。升级时控制面接口不变,只换数据面实现——这正是第 4 章 4.2 讲「控制面与数据面分离」的价值。

形态路径则是另一件事,它决定出网要求、观测点和凭据模型(第 7 章 7.1 会从场景角度展开这条分叉)。bwrap 出现在形态路径的末端而不是强度路径的起点,这正是它容易被误读成「弱隔离」的原因——它解决的不是「边界够不够硬」,而是「agent 要不要进盒子」。

5.7 四个配套机制:按边界选型时各看什么

Section titled “5.7 四个配套机制:按边界选型时各看什么”

这一节只回答一个问题:**选完边界之后,这四个机制各自要看哪一项。**四项的机制实现都在第 4 章(4.6–4.11,第四项见 4.11);这一节讲的是同一件事在选型时的判据。

1. 快照与分叉。 microVM 系强依赖它。缺了会怎样:microVM 的「重启动」没有补偿手段,只能靠预热池硬扛闲置成本。选型时要看两项:这一档支不支持「存内存状态」(microVM 支持得最完整,容器侧只能靠 CRIU),以及 fork 上限够不够(E2B 文档写的是 100 份)。怎么搭见第 4 章 4.6,快照与预热池的区别见第 4 章 4.7。

2. 网络控制。 所有边界都需要,但通道要按档各自搭:WASM 一档没有原生网络,能力靠 host 逐项授予(见 5.2.5)。缺了会怎样:第 6 章那条「默认断网 + 白名单」的防线落不了地,出网只能全通或全断。选型时要看:这一档支不支持域名级 egress 白名单,以及能不能给每个沙箱配入站子域名。通道与过滤怎么搭见第 4 章 4.9。

3. 凭据隔离。 agent 在沙箱内时依赖最强。缺了会怎样:注入一旦成功,凭据就是现成的外泄物,第 6 章的头号风险直接兑现。选型时要看:凭据在哪个环节注入,沙箱里能不能读到真值。真 key 不进沙箱的搭法(vault、代理注入、占位符)见第 4 章 4.10。

4. 编排与接入。 边界复杂度决定这一层的重量。microVM 最依赖编排——CRD + controller 管生命周期、RuntimeClass 选后端、镜像懒加载(overlaybd / Nydus)压冷启动;WASM 依赖的是编译与部署管线,不是 VM 编排。缺了会怎样:microVM 的池化与回收做不起来,闲置环境堆积、账单失控。接入侧各档通用:REST/gRPC + E2B 兼容 API(已成事实标准)、MCP server、PTY over WebSocket、浏览器用 CDP、桌面用 VNC/RDP。这一项怎么搭见第 4 章 4.11;生命周期与回收的机制面见 4.4、4.5,编排与接入的产品落点见第 8 章 8.1。

场景 推荐边界 理由
内部团队的数据分析 Agent 容器 + 加固(+ 默认断网) 可信用户、代码大体可控,够用且便宜
面向公众的在线代码执行平台 Firecracker microVM 不完全信任用户,安全 = 产品生命线
浏览器自动化 / computer-use 完整 VM(或容器 + Playwright) 需要完整 OS 与 GUI
边缘轻量函数 WASM 启动极快、资源极小
本地教学 / 原型 bwrap(OS 系统调用策略) 零依赖、无需 root、不用容器
已经在宿主上跑的 coding agent,只想加围栏 srt / bwrap / Seatbelt 形态路径:不搬进容器,只收窄进程能力面
取不到 KVM 又要多租户 gVisor microVM 上不去时,这是比裸容器强的一档
误区 纠正
「隔离是一条强度轴:Docker < gVisor < microVM < 完整 VM」 那是把「边界画在哪」和「边界有多硬」压成了一维。先按五类边界定位,再看 5.4 的四列比强度
「bubblewrap 就是弱隔离」 bwrap 属于 OS 系统调用策略档:形态是给进程加围栏,不是给环境装容器。它常和容器叠加使用,两者可同时在场
「上了 microVM 就不用加固了」 只读根、去 capabilities、seccomp、默认断网,在任何一档都要做。microVM 只是把逃逸的代价抬高
「快照 = 预热池」 一个存状态(用于回溯与分支),一个省冷启动。只有在时刻对得上时才能互相替代,细节见第 4 章 4.7
「WASM 是免费的轻量隔离」 代价是把生态搬进 WASM:跑不了任意二进制,改造工作量常在隔离收益之上
「装了 gVisor 就一定比容器安全」 syscall 兼容缺口会让某些负载直接失败。兼容性问题常常先于攻击出现

给下面三个场景各选一类边界,并说明理由:

  1. 内部团队用的数据分析 Agent
  2. 面向公众的在线代码执行平台
  3. 浏览器自动化 Agent

再进阶两步:

  1. 给「既想省钱、又要多租户」的团队设计一条强度路径上的分阶段升级方案。
  2. 换一个已经在宿主机上跑的 coding agent:它只需要一层围栏、不值得搬进容器。说出你走的是哪条路径、为什么,以及这一档的两个代价。