首页 / 资讯中心 / 文章详情

Agent容器化是算力陷阱?Serverless事件驱动才是正解

Agent容器化是算力陷阱?Serverless事件驱动才是正解 ★ FEATURED ARTICLE
去年做 Agent 托管平台的时候我第一版架构天真得像个笑话给每个 Agent 分配一个独立容器Kubernetes 集群一拉Agent 想跑哪个跑哪个互不干扰感觉特别“工业级”。结果上线不到两个星期账单和资源监控把我拉回现实——明明只有几千个活跃 Agent集群压力已经接近崩溃边缘。回头再算了一笔账我彻底明白了那句“Cloudflare全世界的算力不够给每个 Agent 发一个容器”并不是段子而是对资源模型最直观的嘲笑。这篇文章就从我踩过的这些坑讲起把“Agent 到底需不需要容器”“边缘算力能干多少活”“在有限算力下怎么设计 Agent 平台”这几个问题掰开揉碎说清楚。1. 从“一人一个容器”到“容器见鬼去”Agent 部署的真实算力账1.1 我最初的设计每个 Agent 一个隔离容器这个方案现在听起来很蠢但在当时是很多 Agent 平台的默认做法。我的设计逻辑是这样的每个 Agent 有自己独立的工具集、提示词模板、知识库索引和调用凭证如果不隔离Agent A 的工具可能会干扰 Agent B 的上下文甚至有安全问题。最稳妥的办法就是每个 Agent 一个容器镜像按需构建编排用 K8s配置用 Helm Chart每个 Agent 声明自己的 CPU、内存、环境变量。这样做的优势也很明显隔离彻底、部署粒度细、扩展时可以按 Agent 维度水平复制。尤其是做 To B 的 Agent 项目客户往往会要求“我的 Agent 跑在独立环境里”容器化的答案几乎不需要解释。我当时觉得这才是 AI 应用该有的“高级感”。结果呢一千个 Agent 只是小规模试点。每 Agent 分配 0.5 vCPU 和 512MB 内存已经是非常克制的配置毕竟一个 Agent 平时要支持多轮对话、工具调用和可能的内存态缓存。一千个 Agent 就需要 500 核 CPU 和 512GB 内存。这还没算系统预留、镜像存储、日志采集、网络组件。一个中型企业客户的数据中心恐怕都吃紧更别提公共云上每个月的账单。1.2 算力账算下来全世界的算力都不够我们可以把账算得更极端一点。假设未来真有百万级甚至千万级 Agent 在线每个 Agent 哪怕只分配 0.2 vCPU 和 256MB 内存一千万个 Agent 就需要两百万个 vCPU 和 256TB 内存。这个量级是什么概念一个大型云厂商的全球算力池可能都很难单独支撑。别说什么“全世界算力”就算全部数据中心都拿来做这件事通信开销、调度损耗、镜像分发、故障冗余一样会吃掉大量资源。这也是为什么 Cloudflare 这类边缘算力平台从设计上就走另一条路。以 Cloudflare Workers 为例它的默认模型是“每个请求分配一个隔离的 V8 isolate”请求处理完就销毁几乎不占常驻资源。你不可能在 Workers 上跑一个常驻容器因为 Workers 的设计本身就是为了事件驱动、短时执行、按请求计费。这句话不是我替 Cloudflare 做广告而是想说明一个事实真正的海量 Agent 服务应该建立在弹性、按需、无服务器化的资源模型上而不是建立在“每个 Agent 占一个坑”的容器模型上。1.3 容器数量与 Agent 活跃度的错配更深层的问题在于Agent 不是一个“一直跑”的服务。大多数 Agent 的真实状态是99% 的时间在等待1% 的时间在响应。用户发起一个任务Agent 开始解析意图、调用 LLM、执行工具、输出结果然后立刻进入等待。如果我们给每个 Agent 一个常驻容器相当于为每个用户都安排一个 24 小时站在门口的私人管家而管家真正干活的时间每天可能只有几分钟。这个错配会带来一系列连锁反应CPU 利用率极低、内存长期被占用、容器生命周期管理变得复杂。更尴尬的是为了让 Agent 及时响应你还要启动探针、保持连接、维持日志管道的通畅。每一样都在烧钱。我当时做过一个统计在“一 Agent 一容器”的模式下集群平均 CPU 利用率不到 8%内存利用率也只有 15% 左右但集群节点数量已经堆到了几十台。看到数据的瞬间我就知道这条路走错了。2. 为什么 Agent 不能简单套用传统容器化冷启动、并发与状态2.1 冷启动难题Agent 每次唤醒都要等容器起来传统后端服务容器化的思路是“服务常驻”流量进来直接打到对应的 Pod。但 Agent 是间歇性唤醒的如果每次唤醒都要先把一个容器从镜像仓库拉起来、初始化环境、加载依赖、建立连接那个延迟会直接摧毁用户体验。假设一个 Agent 任务需要跟用户实时交互用户发一条消息Agent 要在一个合理的响应窗口内给出结果。容器冷启动的典型耗时是几秒到十几秒光这一个等待就足够让用户怀疑系统挂了。有人可能会说那我加个预热池保持一批 Agent 容器常驻不就行了问题来了——这不又回到“每个 Agent 一个常驻容器”的烧钱模式了吗而且预热池只能解决“热”的问题一旦遇到新 Agent、新工具包、新配置冷启动仍然不可避免。所以正确的思路是尽量让 Agent 的执行单元变成“无状态函数”在请求到来时快速调度到一个已经预热好的通用执行环境里。环境本身是共享的Agent 的差异只是数据、配置、上下文。过去我们尊崇的“环境隔离”在很大程度上可以通过编排层和配置层实现而不一定非要每个 Agent 独占一个容器。2.2 AI Agent 的并发特性不是线性扩展另一个被低估的问题是 Agent 的流量模型。普通 Web 服务的并发增长还能用“扩容副本”来解决Agent 的并发增长则会瞬间撕裂你的资源预算。一个 Agent 在运行一个复杂任务时可能需要多次调用 LLM每次 LLM 调用之间还要穿插工具调用、知识库检索、代码执行。这一步没有完成下一步就无法开始但它又不是简单地“串行”——因为不同的用户会在不同节点上触发不同 Agent每个 Agent 还在异步等待外部 API。也就是说并发量不是以“在线用户数”来计算而是以“在途中间状态”的数量来计算的。举个例子我压测过一个简单的研究类 Agent它要做 10 个步骤每个步骤都要调一次 LLM平均耗时 2 秒。如果有 100 个用户同时发起系统里同时存在的执行步骤就可能超过 300 个。如果我为每个 Agent 固定分配一个容器容器里的进程只能串行处理单任务最终表现就是大量请求排队。反过来如果我把 Agent 的每一步建模成事件的“触发-处理-保存”就可以把负载打散到共享的执行资源池里谁的下一步先就绪就先处理谁整体吞吐量一下就能上来。2.3 状态管理记忆、会话与容器生命周期绑定 灾难Agent 跟普通微服务最大的区别是它有“记忆”。它需要持续跟踪多轮对话的上下文需要保存中间状态有时候还要记住用户偏好。很多人在设计容器化 Agent 的时候顺手就把记忆存进了容器本地磁盘或进程内存里理由是“快、不用走网络”。这听起来简单实际上是个定时炸弹。容器一旦重建所有记忆灰飞烟灭容器漂移到另一台机器本地文件就找不到了如果做水平扩展同一个 Agent 的多个实例共享不到记忆用户对话会断片。在有限算力下设计 Agent 平台记忆绝不能放进容器生命周期里必须外置到 Redis、对象存储或数据库。容器只负责“计算”不负责“存储”。这个转变看起来只是架构选择其实是成本模型的转折点一旦状态外置容器本身就变得可丢弃、可复用、可冷启动不再需要每个 Agent 独占一份。3. Cloudflare 的算力架构Workers、容器与边缘的适用边界3.1 Workers 不是容器但很多 Agent 不在乎这里要聊一个问题Agent 到底需要容器里的哪些能力认真拆解后发现大量 Agent 编排类工作根本不需要容器。以 Cloudflare Workers 为例。Worker 是一个在边缘节点上运行的 V8 isolate严格来说它不是容器没有完整的操作系统不能安装任意二进制但它能跑 JavaScript、TypeScript 和编译成 WASM 的代码。对于一个 Agent 来说如果它只是做意图解析、工具调用编排、外部 API 转发、状态读写Worker 的能力绰绰有余。你甚至可以把 Agent 的“大脑”抽象成一组工作流在 Worker 里定义好步骤每次触发就执行一步把结果写回 Durable Objects 或外部的状态存储。我当时尝试着把一个轻量论文阅读 Agent 搬到 Workers 上限制很明显每个 Worker 请求的 CPU 时间有限遇到大型数据处理会超时它没有本地文件系统处理大文件必须走对象存储如果 Agent 需要执行任意 Python 代码或者运行专用库Worker 也做不到。但这些限制恰好构成一个“资源边界”逼着我去思考哪些功能应该放在边缘编排层哪些功能必须下沉到真实容器里。3.2 边缘算力的真实上限与限制把 Cloudflare 说得太神也没意思得看它实际能承担的算力边界。边缘节点的主要优势是分布广、距离用户近、请求入口带宽大但单节点的计算能力是受限的。Cloudflare 自己也把 Workers 设计成短时、轻量、高并发的执行模型而不是重型计算平台。一旦 Agent 任务涉及模型推理、大规模数据处理、GPU 加速边缘节点反而是短板。很多团队踩过这个坑把 Agent 的 LLM 推理写在 Worker 里结果发现等待上游模型响应的时间太长一个 Worker 实例被长时间占用CPU 时间预算根本不够。更合理的边界是边缘算力负责“调度和编排”负责把 Agent 任务转化成一系列轻量事件负责做限流、鉴权、路由和状态读写。真正的重型计算放进中心化的容器集群或专用推理服务。这就像你把公司的前台放在写字楼门口把研发实验室放在后面的独立楼层两者各有分工没必要让前台同时当实验室。3.3 什么场景才真的需要容器我并不是要全盘否定容器。经过几轮迭代我总结出几种 Agent 场景仍然必须用容器Agent 需要执行用户上传的代码且要求安全沙箱和资源限制容器或 microVM 是更稳妥的隔离手段。Agent 依赖特定的二进制工具链比如某些行业软件的 CLI、专用 Python 包Workers 的无服务器环境根本装不上。Agent 要跑长时间后台任务例如批量数据处理、定时爬虫、持续监控这些任务需要常驻进程和稳定环境事件驱动的 Serverless 模型不适合。Agent 需要监听自定义端口或进行复杂的网络通信比如 WebSocket 服务端、本地调试环境这些在严格受限的 Worker 模型里非常别扭。在这些场景里容器依然是刚需。问题在于“用容器”不等于“每个 Agent 一个容器”。你完全可以准备一个通用的执行 Worker 池容器从池里动态分配任务的差异通过环境变量、挂载配置、工具镜像来体现。效果近似于“每个 Agent 一个容器”但底层资源是复用的、可释放的。4. 在有限算力下跑 Agent 的工程实践4.1 按需调度用函数计算模式代替常驻容器在经历了容器模式的翻车之后我把平台重构为“事件驱动 无状态执行”的模式。核心思想一句话Agent 不是常驻服务而是一组可恢复的状态机。具体实现上每一步 Agent 执行都被拆成一个事件。可以是用户消息、定时器触发、外部回调、子任务完成通知。事件进入队列后由无状态执行器从外部存储加载该 Agent 的当前状态执行一步再把新状态保存回去。一个简化版的伪代码是这样的async function handleAgentEvent(event: AgentEvent) { // 从外部状态存储加载 Agent 上下文 const state await kv.get(agent:${event.agentId}:state); // 调用模型决定下一步动作 const action await llm.complete({ messages: state.history.concat(event.payload), tools: state.toolList, }); if (action.type call_tool) { const result await executeTool(action.tool, action.arguments); state.history.push({ role: tool, content: result }); } else if (action.type respond) { state.history.push({ role: assistant, content: action.output }); } // 保存状态并等待下一事件 await kv.set(agent:${event.agentId}:state, state); return action.output; }这个模式的好处是Agent 空闲时不占用任何容器执行器只在事件积压时才会被触发。一个执行器可以顺序或并发处理大量 Agent 的“一小步”资源消耗与活跃度成正比而不是与 Agent 总量成正比。Cloudflare Workers、AWS Lambda 这类函数计算产品天然适配这种模型所以前文提到的“算力不够”问题在这个模式下基本被绕开了。4.2 共享基础镜像 隔离层折中的资源隔离如果你的 Agent 确实需要容器也不要立刻回到“一 Agent 一容器”。我的建议是“共享基础镜像 按需隔离层”。所谓共享基础镜像是指所有 Agent 共享同一个包含 Python/Node 运行时、常用依赖、调试工具的底层镜像。每个 Agent 的个性化工具包、知识库索引、配置文件通过外部挂载或构建层叠加。这样镜像仓库里的内容可以做到“一份基础层 N 个小增量层”存储占用大幅下降冷启动时基础层可以命中缓存增量层下载也更快。隔离层则有两种实现方式。一种是在同一容器内用多进程子进程和 cgroup 做资源限制适合信任度较高的内部场景另一种是用 microVM/安全容器方案比如把每个 Agent 任务丢进轻量虚机适合需要强隔离的客户场景。这两种方式都比“Agent 级别建容器”资源利用率高得多因为隔离粒度可以是“任务”而非“Agent”。4.3 队列、限流与水平扩展资源预算守护层算力有限不代表系统可以杀掉请求而是要把请求平滑地排到可用资源里。这一步是整个架构里最重要、也最容易忽略的。我设计了三个守护组件组件作用具体做法全局队列削峰填谷所有 Agent 事件先进队列消费端按当前资源水位拉取任务避免瞬时高并发打爆执行器令牌桶限流保护下游每个租户/Agent 配置每秒最大 LLM 调用次数、工具调用次数超出则排队或降级资源预算防止互相挤占记录每个 Agent 的累计消耗量设置日预算预算用尽后触发告警或暂停没有这些守护层系统会有一个可怕的行为一个异常死循环的 Agent 不停请求模型把集群资源全部耗尽其他 Agent 全部饿死。我们实测过一次一个没做限流的 Agent 任务在工具调用失败后没有退避策略导致执行器不断重试半小时内打出了几万次模型请求不仅账单爆炸还把同节点的其他任务拖垮了。后来把“失败重试 指数退避 全局队列 每 Agent 令牌桶”加进去同样规模的压测下系统稳定得多吞吐量虽然先降后升但整体效果明显更好。4.4 实测数据与成本对比重构前后的数据可以直观说明问题。为了公平对比我以 5000 个 Agent、平均活跃度 5% 的假设来测算旧方案每个 Agent 一个常驻容器需要 5000 个容器按每个 512MB 内存计算总内存需求约 2.5TBCPU 预留 2500 核。即便实际利用率很低也需要一整套 K8s 集群来承载。新方案事件驱动 共享执行池同时活跃的 Agent 只有 250 个左右执行器池按 300 个并发实例准备内存需求降到 150GB 左右CPU 需求降到 300 核以内且这些资源只在活跃时段占用。成本端的差异更夸张。常驻容器模式是“7×24 小时付费”哪怕 Agent 一天只运行十分钟你也要为全天买单。而按量函数计算模式是“调用次数 × 单次执行时长”空闲时间完全不产生费用。对我这个体量的项目来说账单直接降了一个数量级而且再也不用手动扩容容器了。5. Agent 平台的架构演进从容器崇拜到资源自治5.1 容器是好工具但不该成为 Agent 的代名词现在很多团队的 Agent 架构一上来就是 K8s、容器、Sidecar、Service Mesh听着很先进实际很多精力都花在了维护基础设施上。我想说的是容器是优秀的执行沙箱但它不应该成为 Agent 这个概念的核心。真正定义一个 Agent 的是什么是它的记忆、工具集、推理策略和外部交互协议。这些东西完全可以被建模成数据与配置跟执行环境解耦。那层执行环境可以是容器可以是函数甚至可以是一个远程调用的 API。把 Agent 的“规格”和“运行载体”分开之后算力模型才能灵活。你今天用容器跑明天想换到 Workers只需要把执行器替换掉Agent 本身不用改。5.2 Agent 资源调度的未来算力市场与竞价从成本模型的角度我觉得下一步可选的方向是“算力市场的竞价机制”。Agent 任务不像在线交易那样必须 10 毫秒内响应很多 Agent 任务天然允许延迟比如定时生成报告、批量数据处理、非实时研究辅助。这类任务完全可以优先使用空闲算力类似云厂商的 Spot 实例价格便宜但要接受被中断和排队。边缘算力在中间层会扮演一个有趣的“市场调度者”角色它以极低成本处理海量事件入口把真正需要重型计算的子任务按价格、延迟、可用区域转发给最合适的计算资源。计算资源可以来自中心云也可以来自边缘节点甚至可以来自用户自己的闲置设备。谁便宜、谁有空谁就接下来执行。对 Agent 而言它不关心底层的“容器”在哪里跑只关心“我的任务能不能在预算内完成”。5.3 给后来者的架构建议如果你现在正准备搭建 Agent 平台我的建议很直接先从 Serverless / 函数计算起步把 Agent 状态外置到 Redis 或数据库执行逻辑写成无状态函数。只在遇到“函数计算实在装不下”的场景时引入容器而且容器要作为共享 Worker 池不要给每个 Agent 一个。设计好限流、队列、预算机制这几样东西比容器编排重要十倍。把容器镜像看作“运行时模板”把 Agent 配置看作“数据”两者分离之后你才能自由切换底层算力不怕未来算力涨价或资源紧张。回到标题那句话全世界的算力够不够给每个 Agent 发一个容器从数学上说不够但从架构上来说根本不需要够。Agent 的宿命是活在一个按需调度、状态外置、资源自适应的世界里而不是活在一个永远空转的容器里。这是我踩过坑之后最真实的体会。
阅读完成 · 觉得有帮助?
咨询建站