我们如何大规模运行 Agent 沙盒

@LegitSeanSmith
英语2周前 · 2026年7月03日
319K
79
11
4
73

TL;DR

Adapt 首席技术官 Sean Smith 详细介绍了他们从 gVisor 迁移到 Firecracker microVM 的基础设施转型,该转型实现了数千个隔离的亚秒级沙盒,用于 AI Agent 的代码执行。

我们很早就下定决心,要赋予 LLM 运行任意代码的能力。这篇文章将探讨我们为何做出这一决定,以及要同时运行数千个这样的沙盒(随着用户开启和结束与 Agent 的对话而快速启动和销毁)需要付出怎样的努力。

用户与 Adapt Agent 的每一次对话都有其专属的计算机支持。这不仅仅是共享服务器上受限的容器,而是一个隔离的虚拟机(VM),模型可以随心所欲地对其进行操作:安装软件、编写并运行程序、浏览网页、调用 API。我们将这些称为沙盒,它们是 Adapt 构建的核心原语之一。

完全控制

LLM 是编程天才,而我的工作很大程度上就是为它们构建完美的开发环境。

将 AI 连接到外部世界的传统方式是手动构建集成,例如为 GitHub 构建一个定制连接器,为 HubSpot 构建另一个,为 Stripe 再构建一个,或者等待每个服务发布 MCP 服务器。这种方式无法扩展,而且我也不想日复一日地编写集成代码。

因此,我们没有自己动手,而是让模型来完成。任何提供 API 的服务都可以从 Adapt 访问,因为我们为 LLM 提供了编写与该 API 通信的脚本或程序所需的一切。当我们称 Adapt 为“水平智能”时,这就是其中的重要组成部分:它不绑定到固定的工具列表,而是可以根据需要即时构建所需的工具。

这一切的基础是让 LLM 拥有对沙盒的完全访问权限。我们没有给模型提供一套静态的语言和 CLI 工具以及受限的文件系统访问权限,而是赋予了它对一切的完全访问权。它以 root 身份运行。虽然我们的沙盒预装了 Node 和 Python 等常用运行时,但如果某个服务 API 的最佳 SDK 是用 Go 语言编写的呢?模型可以直接安装并运行它。

Sean Smith - inline image

LLM 需要编写一个 Go 程序吗?直接安装 Go 并运行即可。

那么,如果我们允许模型安装任何它想要的东西并执行未经人工验证的代码,我们该如何确保安全呢?幸运的是,我们并不是第一批需要运行不受信任代码的人。为此,有两个非常流行的安全运行时:gVisorFirecracker。到目前为止,我们的探索过程让我们对两者都非常熟悉。

从 gVisor 到 Firecracker

我们对 LLM 安全沙盒的首次尝试采用了“简单”的方法:在 GKE(Google Kubernetes Engine)之上使用 GKE Sandbox 运行每个 gVisor 沙盒。我们已经在 GKE 上运行了所有其他服务,因此这对我们来说是顺理成章的一步。

gVisor 位于容器和宿主机内核之间。它不会让程序直接向真实的 Linux 内核(你肯定不希望不受信任的代码触碰它)发起系统调用,而是会在其自己的用户空间内核中拦截这些调用并自行处理。你既能获得普通容器的大部分便利,又能拥有小得多的攻击面。GKE Sandbox 将这一切打包在一起。你只需部署 Pod(容器),它们就会在 gVisor 下透明地运行,而我们几乎不需要进行任何基础设施配置。

起初,这种方法效果很好。我们将“基础”沙盒定义为 Docker 镜像,并让 GKE 根据我们在任何给定时间所需的沙盒数量进行扩展。更新沙盒所搭载的软件只需更新 Dockerfile 并在清单中增加版本号即可。

Sean Smith - inline image

在 GKE Sandbox 下运行的数百个沙盒 Pod。

但正是这种让 gVisor 变得简单的抽象,成了我们不断抗争的对象。由于 gVisor 在用户空间重新实现了 Linux 系统调用接口,并非所有内容都能像在真实内核上那样运行,而我们的模型所构想的工作负载又是极其不可预测的。这种换取安全性的拦截机制,在系统调用和 I/O 密集型任务中会带来性能损耗。此外,依赖 GKE 来处理整个生命周期意味着我们最想控制的部分——启动时间、打包密度、网络以及我们回收机器的激进程度——反而是我们控制力最弱的部分。上面那个“OutOfcpu”的 Pod 就是当你过度压榨别人的调度器时会出现的情况。

这正是促使我们转向 Firecracker 的原因。

Firecracker 微型虚拟机(microVM)是真正的虚拟机,每个都有自己的客户机内核,通过硬件虚拟化运行,但经过精简,启动仅需几分之一秒,且开销仅有几兆字节。AWS 正是利用同样的技术将海量的 Lambda 和 Fargate 工作负载打包到共享硬件上。它为我们提供了比共享内核更强的隔离边界,启动速度快到几乎感觉不到,而且体积小巧,足以在单台主机上打包运行大量实例。

代价是 Firecracker 只提供一个虚拟机,除此之外别无他物。没有 GKE 那样的层来负责调度、网络和生命周期编排。所以我们自己构建了一个,并将其命名为 orc。

根文件系统只是一个镜像

在放弃容器的过程中,我们不想放弃的一点是:将沙盒定义为纯粹的 Dockerfile。容器让这一点变得简单;而虚拟机传统上并非如此,因为微型虚拟机启动的是根文件系统,而不是 OCI 镜像。

因此,orc 充当了两者之间的桥梁。当它被要求创建一个虚拟机时,它会获取一个普通的 Docker/OCI 镜像,并即时生成虚拟机的根文件系统,同时缓存结果,以便后续启动同一个镜像时能够快速完成。我们的基础沙盒仍然只是一个 Dockerfile,而 orc 会在请求时将其转换为可启动的根文件系统。

这使得我们的工作流程与 GKE 时代保持一致:编辑 Dockerfile,发布新沙盒,同时在底层的真实虚拟机上运行。这也开启了一扇我们才刚刚开始探索的大门。由于任何 OCI 镜像都可以成为微型虚拟机,我们可以从默认镜像以外的镜像启动沙盒。想要一个已经内置了 Postgres 和 pgvector 的虚拟机吗?只需将 orc 指向该镜像,你就能得到一个独立的机器。沙盒不再是一个单一的固定环境,而变成了“任务所需的任何镜像,以其自身的虚拟机形式启动”。

大规模执行

这就是让问题变得真正困难的地方:每一次对话都有其专属的沙盒。每个对话对应一台机器。在任何给定时刻,我们都有数千个沙盒处于活跃状态,而且这个数字一直在变动。每当有人开启对话,沙盒就必须出现;每当对话静止,沙盒就必须消失,这样我们才不用为此付费。我们一直在不断地启动和销毁沙盒。

有两个数字决定了一切:我们准备好一个沙盒的速度,以及我们能在单台主机上容纳多少个沙盒。

启动延迟。 Firecracker 微型虚拟机在几百毫秒内即可启动。这速度快到我们根本不需要保留预热池,这是切换到该方案带来的最令人欣慰的收获之一。在 GKE 下,我们必须保留备用容量来掩盖启动时间。有了 orc,一个新的沙盒在你察觉之前就已经准备好了,所以我们只需在对话开始时按需创建一个,并在对话结束时将其销毁。再也不需要维护或支付闲置资源池的费用了。

密度。 由于每个微型虚拟机都很小,我们可以在一台物理主机上打包运行大量实例。我们根据每个沙盒的实际需求来分配 CPU 和内存,而不是过度配置,这正是我们能够经济高效地运行数千个沙盒的原因。

orc 本身被刻意设计得很小。它是一个控制平面,使用简单的 API:从给定的镜像创建一个具有 N 个 vCPU 和 M 兆字节内存的虚拟机,向其中流式传输命令,读取和写入其中的文件,为其添加标签以便日后查找,并在完成后将其删除。每个客户机都运行一个微小的 init 进程作为 PID 1,并拥有自己独立的网络。这就是它的全部。它的魔力不在于任何巧妙的技巧,而在于这些原语足够枯燥且足够快,足以支撑起整个机群的运行。

所有这些底层工作的回报就是我们最初的目标:一个能够安装任何东西、编写程序、调用 API 并将答案交还给你的模型,而这一切都运行在真实的计算机上。

一键保存

使用 YouMind AI 深度阅读爆款文章

保存原文、追问细节、总结观点,并在一个 AI 工作空间里把爆款文章沉淀成可复用笔记。

了解 YouMind
写给创作者

把你的 Markdown 变成干净的 𝕏 文章

图片上传、表格、代码块,往 𝕏 上手动重排太痛苦。YouMind 把整篇 Markdown 一键转成干净、可直接发布的 𝕏 文章草稿。

试试 Markdown 转 𝕏

更多可拆解样本

近期爆款文章

探索更多爆款文章