← 返回 FEED
AGENT2026-05-16

为什么 Agent 沙箱正在转向 libkrun

Firecracker 是云端的王者,但本地 Agent 沙箱的答案不是它。Rohit Ghumare 在 iii 工程团队工作数月后,给出了一个清晰的判断:libkrun 才是 2026 年本地 Agent 基础设施的唯一选择。

问题变了

Firecracker 的假设是:Agent 和沙箱都运行在别人的数据中心里。这个模型确实支撑了大量云端 Agent 服务。

但正在吃掉市场的 Agent 形态——Claude Code、Codex、OpenCode、各种 IDE 内嵌 Agent——运行在开发者的 MacBook 上。Firecracker 依赖 /dev/kvm,KVM 是 Linux 内核模块,macOS 上不存在,也永远不会存在,因为 Hypervisor.framework 已经占住了那个架构位置。

你可以在一个 Linux VM 里跑 Firecracker 来间接在 Mac 上运行。但那是嵌套虚拟化:microVM 套在 UTM VM 里,套在 macOS 上。启动时间叠加、磁盘镜像叠加、网络栈叠加。演示可以,但无法支撑一个开发者每几秒就启动一个全新隔离环境的节奏。

2026 年的问题不再是"该包哪个 microVM monitor",而是"哪个 monitor 能在 Agent 实际运行的所有地方工作"。这个问题目前只有一个答案:libkrun。

libkrun 是什么

libkrun 最初是 Red Hat 为 podman-machine on macOS 开发的项目。它是一个动态库(Linux 上是 .so,macOS 上是 .dylib),把 VMM 暴露成一个可以链接的函数,而不是一个需要 exec 的进程。

你传给它一个内核(libkrunfw)、一个 rootfs(任意目录,透传挂载)、一个执行规格("用这些参数、环境变量、rlimit 跑 /init"),它就在进程内启动一个 microVM 并等待退出。

关键细节:libkrun 是 hypervisor-agnostic 的。Linux 上走 /dev/kvm(跟 Firecracker 一样),macOS Apple Silicon 上直接走 Hypervisor.framework。一个 API,一个设备模型,覆盖用户实际使用的两个操作系统。

比 Firecracker 更好的地方

libkrun 故意扔掉了 Firecracker 的一个默认设定:legacy block device。Firecracker 把 guest rootfs 暴露成 host 上 ext4 镜像 backed 的 virtio-block 设备。libkrun 直接把 host 上的一个目录通过 virtio-fs 挂载进 guest。不需要创建镜像,改文件不需要重建镜像,指向目录就行。

对于需要从 OCI registry 拉取任意 rootfs 的沙箱场景,这好得多:解压 OCI 镜像到目录,把目录交给 libkrun,guest 读的就是 host 磁盘上的同一份字节。没有 mkfs.ext4,没有 loopback,没有缓存失效后的重新打包。

iii-sandbox 的架构

iii-sandbox 是 iii 开源引擎中的一个 worker。iii 的设计哲学是:每个基础设施类别(HTTP 路由、队列、cron、Agent、沙箱)都是同一种东西——通过 WebSocket 连接到引擎、注册函数和触发器的 worker。

iii-sandbox 内部是一个守护进程,管理一组活跃的 microVM,磁盘上有三样东西:rootfs 缓存(每个镜像名一个解压后的 OCI 镜像)、per-sandbox overlay 树、每个沙箱一个 Unix socket 用于 shell 通道。

为了隔离崩溃风险,iii 采用 fork+exec 策略:父进程 fork 出子进程,子进程链接 libkrun。如果 guest kernel panic 或 libkrun segfault,父守护进程不会跟着死。

网络:没有 TAP,没有 iptables

libkrun 的网络模型是 smoltcp——一个纯 Rust 的用户态 TCP/IP 栈,通过共享内存队列桥接到 guest 的 virtio-net。host 侧没有 tap0,不需要改内核路由,不需要 iptables 规则。

在 macOS 上这意味着:网络直接工作,不需要 root 权限,不需要安装 Apple 正在弃用的 tuntap kext。

安全层面的意义也值得强调:smoltcp 是 guest 进入 host 的整个网络攻击面。它是 no-heap-allocation 的,小到可以审计。host 侧的桥接把 guest 的 outbound connect 翻译成 tokio::net::TcpStream。guest 里的 localhost:3000 会被重写到 gateway IP,让 Agent 命中 host 上实际运行的 dev server——这是 99% 场景下 Agent 想要的行为。

Host-guest 通信:virtio-console 替代 vsock

Firecracker 和大多数 microVM 用 vsock 做 host-guest RPC:一种不经过 IP 的内核 socket。iii 团队先试了 vsock,但 macOS Hypervisor.framework 上的 vsock 支持是半成品,libkrun 的公开 API 也没暴露。

替代方案是 virtio-console 的 named ports。host 上开一个 socketpair(AF_UNIX),一端交给 libkrun 作为 named guest port(iii.control, iii.exec),guest 里的 init 从 /dev/vport0p1 读取。传输层是双向字节流,上层叠一个 line-framed JSON 协议。

听起来是降级,确实稍微笨重一点。但关键的安全属性保住了:guest 无法访问 host 上 127.0.0.1:port unless host 在启动 VM 时显式 wired 了那个端口。同样的安全模型,只是传输层稍厚。

真正花时间的地方:PID 1

iii 团队投入最多时间的不是 libkrun,而是 iii-init——他们打进每个 guest 的 PID 1 二进制。

microVM 没有 systemd,用户空间极简。guest kernel 启动后 exec VMM 指定的 init。这个二进制是 PID 1:它退出 kernel panic,它阻塞整个 VM 阻塞,它泄漏 fd 或不回收子进程就导致进程表腐败。

systemd 太大(1.5M,为笔记本启动设计),tini 太小(12K,只做容器里的信号转发和子进程回收)。microVM 里的 PID 1 需要做的事情比 tini 多、比 systemd 少。

iii-init 做五件事:

  1. pivot_to_tmpfs_root:把 / 从 virtio-fs root pivot 到 tmpfs,再用 bind mount 重新暴露 rootfs 条目。因为 libkrun 的 virtio-fs root 有 readdir bug,在 <1GB RAM 的 guest 上 ls / 会 OOM-kill。

  2. mount_filesystems + mount_virtiofs_shares

  3. override_proc_meminfo:伪造 MemTotal 到 per-worker RAM cap。因为 bun 的 allocator 直接读 MemTotal 而忽略 cgroup v2 的 memory.max。

  4. raise_nofile:把 RLIMIT_NOFILE 提到 1M。因为 node 的 fs.promises 和 Python asyncio 在并行测试时打开的 fd 会炸掉默认的 1024 限制。

  5. configure_network:从环境变量读 IP/GW/CIDR/DNS,配置 eth0 和 /etc/resolv.conf。没设这些变量就什么都不做,network: false 的沙箱真正是无网络的。

  6. exec_worker:supervisor 循环,打开 virtio-console control port,处理 Restart/Shutdown/Ping/Status RPC,exec 用户指定的命令。

Ghumare 的核心论断:如果你要建类似的东西,microVM monitor 是商品,kernel 是商品,真正 load-bearing 的工程在 init binary 里。iii-sandbox 能干净启动,是因为他们写了一个 Rust musl PID 1,知道每个预期托管的运行时的具体 bug 和 edge case。

Sandbox-as-worker,不是 Sandbox-as-product

iii 的设计跟所有"沙箱即服务"都不一样。其他产品里,沙箱是独立产品:你注册、拿 API key、调 Sandbox.create(),然后你的 Agent 系统得想办法跟它对接。沙箱活在不同的信任域、不同的身份模型、不同的可观测面、不同的限流策略里。

在 iii 里,沙箱只是众多 worker 中的一种。同一个引擎既托管 HTTP 路由也托管沙箱守护进程。同一个 trace ID 从 Agent 的 researcher 函数穿透 sandbox::create 和 sandbox::exec,出现在 Agent worker 和沙箱 worker 的同一个 OpenTelemetry 视图里。

一个 Agent worker 可以注册一个函数:启动沙箱、运行代码、捕获输出、关闭沙箱——这个复合函数本身又是其他 worker 可以调用的函数。沙箱不是系统边缘的一个层,而是一个可以跟其他所有东西组合的基础原语。

结论

  • 如果你在做托管沙箱服务给别人用:Firecracker 仍是正确答案
  • 如果你在做一个本地优先的 Agent(CLI、IDE 插件、任何会被 npm install 或 brew install 的东西):libkrun 是唯一能在 macOS 上给 Firecracker 级隔离的开源路径
  • 无论你做什么,真正花时间的都在 init binary,不在 VMM
  • 最被低估的是组合形态:沙箱作为 worker 跟其他基础设施统一,而不是作为独立产品被 bolt-on