1. 从"能跑"到"跑得住":云端智能体的基础设施缺口到底在哪
过去一年,我参与过三个不同规模的智能体项目落地,从最初在本地笔记本上跑通一个能查资料、能调工具的小助手,到后来把它塞进容器里、挂到集群上、接上真实业务流量,中间踩的坑几乎全部集中在同一个地方——基础设施。模型能力本身反而没怎么拖后腿,真正让人半夜爬起来看告警的,是运行时环境、沙箱隔离、资源调度和状态管理这些"脏活累活"。
这个现象其实很普遍。现在大家聊智能体,注意力大多集中在框架选型、提示词工程、工具编排上,这些确实重要,但它们解决的是"智能体能不能完成任务"的问题。而一旦你要让智能体在云端持续对外提供服务,面对的是另一类问题:它会不会因为一次工具调用超时把整个进程拖死?多个用户会话之间会不会互相污染上下文?一个失控的循环会不会把集群的 CPU 吃满?这些问题的答案,不在智能体框架里,而在它脚下的基础设施里。
我把这类问题统称为扩展瓶颈。注意,这里的"扩展"不只是横向加机器那么简单,它包含三个维度:单实例的稳定性扩展(从跑一次到跑一万次不出错)、并发维度的扩展(从单用户到多租户)、以及能力维度的扩展(从单一工具到复杂工具链)。这三个维度对基础设施的要求完全不同,但很多团队在早期只考虑了第一个,等到用户量上来才发现后两个才是真正的深水区。
这篇文章想做的事情,是把"云端智能体需要什么样的基础设施"这个问题拆开讲透。我会从运行时环境、沙箱隔离、编排调度、状态与可观测性几个层面,结合 Kubernetes 这类成熟基础设施的实践,讲清楚每个环节为什么需要、怎么做、以及我实际踩过的坑。目标读者是那些已经能让智能体跑起来、但正准备把它推向生产环境的工程师,如果你还在本地调试阶段,也可以先看看,提前避坑。
提示:本文讨论的"云端智能体"指的是以服务形式对外提供、需要处理真实并发请求的智能体系统,不包括纯本地运行的实验性脚本。两者的基础设施诉求差异极大。
2. 运行时环境:为什么"在我机器上能跑"在云端必然翻车
2.1 智能体运行时的特殊性:它不是一个普通的 Web 服务
普通 Web 服务的运行时行为是相对确定的:接收请求、查数据库、返回响应,整个链路的资源消耗和耗时基本可预测。智能体完全不是这个路子。一个智能体处理一次请求,内部可能经历"思考—调用工具—观察结果—再思考"的多轮循环,每一轮的耗时、内存占用、外部依赖都是动态的。更麻烦的是,这个循环的轮数在运行时才能确定,极端情况下可能因为工具返回异常而陷入反复重试。
这意味着智能体的运行时环境必须能容忍不确定的执行时长和不确定的资源峰值。我见过一个案例,某个智能体在处理一份复杂文档时,因为工具返回格式不符合预期,连续重试了四十多次,单次请求的 CPU 占用从正常的 5% 飙到 90%,持续了将近两分钟。如果这个进程和主服务共享资源,整个服务都会被拖垮。
所以第一件事,就是给智能体一个独立的、可限制的运行时边界。这个边界要能回答几个问题:这个智能体最多能用多少 CPU 和内存?它最多能跑多久?它崩溃了会不会影响别人?在 Kubernetes 的语境下,这些对应的是 ResourceQuota、LimitRange、Pod 的 resources 配置以及 activeDeadlineSeconds 这类机制。
2.2 依赖地狱:Python 版本、系统库与工具链的版本锁定
智能体项目对依赖的敏感度比普通服务高得多。原因在于它往往要同时集成多个来源的工具:有的工具依赖特定版本的 Python 库,有的需要调用系统命令行工具,有的要加载特定版本的模型推理库。这些依赖之间经常打架。
我印象最深的一次,是某个智能体需要同时用到一个较新的向量检索库和一个较老的文档解析库,前者要求 Python 3.10 以上,后者的某个底层依赖在 3.10 上有兼容问题。本地开发时因为环境是逐步搭建的,凑合能跑;一打成镜像部署到集群,直接起不来。
解决这类问题的核心思路是把运行时环境当成不可变制品来管理。具体做法:
- 用多阶段构建(multi-stage build)把构建依赖和运行依赖彻底分开,最终镜像只保留运行必需的内容。
- 所有依赖版本在锁文件里写死,包括间接依赖,不要依赖"最新版"。
- 系统级依赖(如某些命令行工具、字体、证书)显式安装并记录版本,不要假设基础镜像里有。
- 镜像构建后做一次"冷启动冒烟测试",在一个干净容器里跑一遍核心链路,确认没有隐藏的本地依赖。
这里有个容易被忽略的点:智能体经常需要动态安装或加载工具。有些框架支持运行时注册新工具,如果这个工具需要额外的系统依赖,而镜像里没有,就会在运行时失败。我的建议是,生产环境的智能体工具集应该是构建期确定的,运行时只做加载不做安装。如果业务上确实需要动态扩展,那也要走"预置一批可选工具镜像"的路子,而不是让智能体在运行时随意装东西。
2.3 冷启动与预热:别让第一个用户等太久
智能体服务有个特点:冷启动特别慢。加载模型、初始化向量库连接、预热工具链,这些加起来可能几十秒甚至几分钟。如果按普通 Web 服务的思路做弹性伸缩,用户请求打过来时 Pod 还在初始化,体验会非常糟糕。
我在一个项目里用过几种应对方式,各有取舍:
| 方式 | 做法 | 优点 | 代价 |
|---|---|---|---|
| 常驻最小副本 | 始终保持至少 N 个已预热副本 | 响应快 | 资源常驻成本 |
| 就绪探针精细化 | readinessProbe 检查真实可用性而非端口 | 避免流量打到未就绪实例 | 探针逻辑要写对 |
| 启动预热脚本 | 容器启动后主动跑一遍核心链路 | 提前暴露问题 | 延长启动时间 |
| 镜像分层缓存 | 把模型和依赖放在不常变的基础层 | 加速拉取 | 镜像管理复杂 |
实测下来,常驻最小副本 + 精细化就绪探针的组合最稳。就绪探针不要只检查端口通不通,要真正调用一次轻量级的健康检查接口,确认模型已加载、工具链可用。我见过太多因为探针写得太简单,导致流量打到半初始化实例上的事故。
注意:就绪探针的检查逻辑本身不能太重,否则会拖慢整个调度。建议用一个专门的轻量健康检查端点,只验证关键依赖的可用性,不要跑完整业务链路。
3. 沙箱隔离:智能体执行不可信代码的最后一道防线
3.1 为什么智能体必须要有沙箱
智能体和普通服务的根本区别在于,它会执行自己生成的代码或命令。这是它强大的地方,也是它危险的地方。一个能写代码、能调 shell 的智能体,如果没有任何隔离,理论上可以读取宿主机上的任意文件、发起任意网络请求、消耗任意资源。
这不是危言耸听。我在测试环境里就遇到过智能体因为理解错了任务,生成了一个递归删除文件的命令。幸好当时跑在隔离环境里,否则后果不堪设想。所以沙箱不是"锦上添花",而是生产环境的硬性要求。
沙箱要解决的核心问题有三个:文件系统隔离(它只能看到自己该看的)、网络隔离(它只能访问允许的地址)、资源隔离(它不能吃光宿主机资源)。这三个维度缺一不可。
3.2 从容器到微虚拟机:隔离强度的阶梯
很多人第一反应是用 Docker 容器做沙箱。容器确实方便,启动快、生态好,但它的隔离强度是有限的——共享内核意味着存在逃逸风险,对于执行完全不可信代码的场景,容器级别的隔离往往不够。
我把常见的隔离方案按强度排个序,供选型参考:
- 普通容器:隔离最弱,共享内核,适合执行相对可信的代码。启动最快,开销最小。
- 加固容器:配合 seccomp、AppArmor、只读根文件系统、非 root 用户等手段,隔离强度提升明显,是很多场景的性价比之选。
- 用户态内核:如 gVisor 这类方案,在应用和内核之间加一层,拦截系统调用,隔离强度接近虚拟机但启动比虚拟机快。
- 微虚拟机:如 Kata Containers 这类方案,每个沙箱一个轻量虚拟机,隔离强度最高,启动开销介于容器和传统虚拟机之间。
选哪个,取决于你的智能体要执行什么。如果只是跑一些受限的表达式求值,加固容器足够;如果要执行用户提交的任意代码,微虚拟机或用户态内核更稳妥。我在实际项目里的做法是分层:常规工具调用走加固容器,高风险代码执行走微虚拟机,两套运行时并存,按任务类型路由。
3.3 沙箱的生命周期管理:创建、复用与回收
沙箱不是创建完就完事了,它的生命周期管理是个精细活。创建太慢影响体验,复用不当导致状态污染,回收不及时浪费资源。
我总结的几个实践要点:
- 沙箱池化:预先创建一批空闲沙箱,请求到来时直接分配,避免每次现创建。池的大小根据并发峰值和创建耗时来定,一般保持在峰值并发的 1.2 到 1.5 倍。
- 一次性 vs 可复用:执行不可信代码的沙箱建议一次性使用,用完即销毁,杜绝状态残留;执行可信工具调用的沙箱可以复用,但要确保每次调用前清理工作目录和环境变量。
- 超时强制回收:每个沙箱都要有硬性超时,到点无论任务是否完成都强制销毁。这个超时要比业务预期的最长执行时间留出余量,但不能无限长。
- 资源配额硬限制:CPU、内存、磁盘、进程数、文件描述符都要设上限,防止单个沙箱拖垮节点。
这里有个坑我踩过:沙箱池化时,如果回收逻辑有 bug,会导致沙箱被"泄漏"——既不在池里也不被销毁,慢慢把节点资源吃光。解决办法是给每个沙箱打上创建时间戳,后台有个清理任务定期扫描超期未回收的沙箱,强制清理。
3.4 网络策略:默认拒绝,按需放行
沙箱的网络访问必须严格控制。默认应该是全部拒绝,只放行明确需要的地址。这一点在 Kubernetes 里可以用 NetworkPolicy 实现,但要注意它依赖 CNI 插件的支持,不是所有集群都默认生效。
具体策略上,我一般这样设计:
- 沙箱默认不能访问集群内部服务,防止它探测内网。
- 只允许访问明确的外部 API 地址,且最好通过一个代理层统一出口,方便审计和限流。
- 禁止沙箱访问云厂商的元数据服务地址,这是很多安全事件的入口。
- DNS 解析也要限制,避免通过 DNS 做数据外泄。
提示:网络策略配置完一定要实测验证,不要假设它生效了。我见过因为 CNI 插件不支持导致 NetworkPolicy 形同虚设的情况,测试方法很简单,从沙箱里尝试访问一个应该被拒绝的地址,看是否真的被拦。
4. 编排与调度:Kubernetes 能做什么,不能做什么
4.1 Kubernetes 是智能体基础设施的好底座,但不是开箱即用
Kubernetes 提供了容器编排的通用能力:调度、伸缩、自愈、服务发现、配置管理。这些能力对智能体服务同样适用,所以把它作为底座是合理的选择。但智能体有一些特殊诉求,Kubernetes 的原生能力覆盖不到,需要额外设计。
最典型的几个缺口:
- 任务级调度:智能体的执行单元往往是一次"任务"而非一个长期存活的进程,Kubernetes 原生更擅长管理长期服务,对短生命周期、高频创建销毁的任务支持需要借助 Job 或自定义控制器。
- 会话亲和性:多轮对话场景下,同一会话的请求最好落到同一个实例上,避免上下文反复加载。这需要会话粘性或者外部状态存储来配合。
- 异构资源:智能体可能同时需要 CPU、GPU、大内存等不同资源,调度策略要能表达这些约束。
- 快速弹性:智能体的负载波动可能很剧烈,原生 HPA 基于 CPU/内存的伸缩往往滞后,需要结合自定义指标。
4.2 用自定义资源定义智能体的"任务"抽象
在 Kubernetes 里管理智能体,一个有效的做法是用 CRD(自定义资源定义)把"智能体任务"抽象出来。比如定义一个 AgentTask 资源,包含任务类型、输入、资源需求、超时等字段,然后写一个控制器监听这些资源,负责创建对应的沙箱 Pod、监控执行、收集结果、清理资源。
这样做的好处是把智能体的业务语义和 Kubernetes 的编排能力解耦。业务侧只需要提交 AgentTask,不用关心底层是 Pod 还是别的什么;基础设施侧可以独立演进调度策略,不影响业务。
控制器的核心逻辑大致是:
- 监听 AgentTask 的创建事件。
- 根据任务类型和资源需求,从沙箱池里分配或新建一个执行环境。
- 把任务输入注入执行环境,启动执行。
- 监控执行状态,处理超时和异常。
- 收集结果写回 AgentTask 状态,清理执行环境。
这套模式我在两个项目里用过,扩展性和可维护性都不错。难点在于控制器的健壮性——要处理各种边界情况,比如执行环境创建失败、任务执行中途节点宕机、结果收集超时等。建议用成熟的控制器框架来写,不要从零造轮子。
4.3 调度策略:把合适的任务放到合适的节点
智能体的任务类型差异很大,有的吃 CPU,有的吃内存,有的需要 GPU,有的对网络延迟敏感。把它们无差别地丢给调度器,资源利用率会很差。
我的做法是给节点打标签,给任务设节点亲和性。比如:
- GPU 节点专门跑需要模型推理的任务。
- 大内存节点跑文档处理类任务。
- 普通节点跑轻量的工具调用任务。
同时配合污点和容忍度,防止不合适的任务被调度到专用节点上。这套组合用好了,资源利用率能提升不少。
另外,优先级和抢占也要考虑。交互式的用户请求应该比后台批处理任务优先级高,资源紧张时能抢占后者的资源。Kubernetes 的 PriorityClass 可以表达这个,但要配合调度器的抢占策略一起调。
4.4 弹性伸缩:别只盯着 CPU 利用率
前面提过,智能体的负载特征和普通服务不同,基于 CPU 的 HPA 经常反应迟钝。更合理的做法是结合业务指标做伸缩,比如待处理任务队列长度、平均任务等待时间、活跃会话数。
实现上可以用 KEDA 这类基于事件驱动的伸缩组件,它支持从消息队列、数据库等来源读取指标来驱动伸缩。我用它对接过任务队列,效果比原生 HPA 好很多,尤其是应对突发流量时。
但弹性伸缩有个前提:实例要能快速启动。如果冷启动要几分钟,伸缩再灵敏也没用。所以前面讲的预热和镜像优化,和弹性伸缩是配套的,不能分开看。
5. 状态管理:智能体的"记忆"该放在哪
5.1 无状态化是理想,但智能体天然有状态
普通 Web 服务可以做到完全无状态,会话数据放 Redis,实例随便扩缩。智能体要复杂一些,它的"状态"至少包含几类:对话历史、工具调用中间结果、长期记忆、任务执行上下文。这些状态有的生命周期很短(单次请求内),有的很长(跨会话)。
把所有状态都塞进实例内存是最省事的做法,但这样实例就无法随意扩缩,也无法在故障时快速恢复。我的建议是按状态的生命周期分层处理:
- 请求级状态:放在请求上下文里,随请求结束销毁,不持久化。
- 会话级状态:放外部存储(如 Redis),实例通过会话 ID 读写。
- 长期记忆:放向量数据库或专门的存储,按需检索。
- 任务执行状态:放数据库,支持断点续跑和故障恢复。
5.2 会话亲和性的取舍
如果会话状态放在外部存储,理论上不需要会话亲和性,任何实例都能处理任何请求。但实际中,加载会话上下文是有成本的,如果每次都从存储里拉全量历史,延迟和开销都不小。
所以很多团队会选择会话亲和性:同一会话的请求尽量落到同一实例,实例内存里缓存该会话的上下文。这能显著降低延迟,但代价是扩缩容和故障转移变复杂——实例下线时会话要迁移,扩容时新实例接不到已有会话。
我的经验是,交互式场景用亲和性,批处理场景不用。交互式对话对延迟敏感,值得为亲和性付出复杂度;批处理任务对延迟不敏感,无状态化更简单可靠。如果要用亲和性,记得设置合理的会话过期时间,避免实例被长期占用。
5.3 上下文窗口与状态压缩
智能体的上下文窗口是有限的,长对话迟早会超出。这时候需要做状态压缩:把历史对话摘要化,只保留关键信息。这个压缩逻辑放在哪执行,也是个基础设施问题。
放在实例内执行简单,但每个实例都要重复实现;放在独立的服务里执行,可以统一管理和优化,但增加了一次网络调用。我倾向于后者,尤其是当压缩逻辑需要调用模型时,独立服务更便于做限流和缓存。
6. 可观测性:智能体出问题时,你怎么知道发生了什么
6.1 传统监控指标不够用
CPU、内存、网络这些基础指标当然要监控,但对智能体来说远远不够。你还需要知道:每个任务的执行轮数、每轮的工具调用情况、模型调用的延迟和 token 消耗、任务的成功率和失败原因分布。这些是智能体特有的可观测性需求。
我的做法是在智能体的执行框架里埋点,把每个关键节点的事件结构化输出。比如任务开始、每轮思考开始结束、每次工具调用开始结束、任务结束,都打一条结构化日志。这些日志汇总起来,就能还原出任务的完整执行链路。
6.2 分布式追踪:把一次任务的全链路串起来
智能体的一次任务可能跨越多个服务:网关、调度器、沙箱、模型服务、工具服务。出问题时,你需要能把这条链路串起来看。分布式追踪在这里非常有用。
实现上,给每个任务分配一个全局 trace ID,在跨服务调用时透传。每个服务把自己的 span 上报到追踪系统,最后就能看到完整的调用树。我用这套方法定位过好几次性能问题,比如发现某个工具调用在特定条件下会卡住十几秒,从追踪图上一眼就能看出来。
6.3 日志的采集与脱敏
智能体的日志里往往包含用户输入、模型输出、工具返回等敏感内容。采集日志时要注意脱敏,尤其是涉及个人信息、凭证、内部数据的内容。这个工作最好在日志产生时就做,而不是等到存储后再处理。
另外,日志量可能非常大,尤其是高频调用的场景。要做好采样和分级,关键事件全量记录,常规事件按比例采样,避免日志系统被压垮。
7. 我踩过的几个真实坑与应对经验
7.1 沙箱泄漏导致节点资源耗尽
前面提过沙箱池化的坑,这里展开说。当时我们的沙箱回收逻辑依赖任务正常结束触发,结果有个任务因为工具调用卡死,既没正常结束也没触发超时(超时配置写错了单位),沙箱就一直挂着。几天后节点资源被吃光,新任务全部调度失败。
修复方案是加了一个独立的清理守护进程,定期扫描所有沙箱,凡是超过最大存活时间的,无论状态如何一律强制清理。同时把超时配置做了校验,防止单位写错。这个教训是:任何依赖"正常流程"的回收机制都不可靠,必须有兜底的强制清理。
7.2 模型服务限流引发的雪崩
智能体高频调用模型服务,如果模型服务有 QPS 限制,很容易触发限流。触发限流后,智能体如果直接重试,会加剧限流,形成雪崩。我们当时就遇到了这个情况,一个上游限流导致整个智能体服务大面积超时。
应对方法是在智能体侧做限流和退避。具体来说,给模型调用加一个令牌桶限流器,控制调用速率;失败重试要带指数退避和抖动,避免所有实例同时重试;再配合熔断机制,当失败率超过阈值时快速失败,给下游恢复的时间。
7.3 上下文污染导致的诡异 bug
多租户场景下,如果会话隔离没做好,A 用户的上下文可能泄漏到 B 用户的会话里。我们遇到过一次,用户反馈智能体"记得"自己没说过的话,排查后发现是会话 ID 生成有碰撞,导致两个会话共用了同一份上下文。
修复很简单,把会话 ID 换成全局唯一且不可预测的格式。但这个 bug 提醒我们:多租户隔离要在每一层都验证,不能假设某一层做了隔离其他层就安全了。存储层、缓存层、日志层都要检查。
7.4 镜像体积过大拖慢弹性伸缩
早期我们的智能体镜像里塞了完整的模型和所有依赖,体积好几个 G。结果弹性伸缩时,新节点拉镜像要几分钟,完全跟不上流量变化。后来做了镜像分层,把不常变的模型和基础依赖放底层,业务代码放上层,日常发布只更新上层,拉取时间大幅缩短。
这个经验是:镜像体积直接影响弹性能力,在智能体场景下尤其明显,因为镜像里往往包含模型这类大文件。设计镜像结构时要考虑更新频率,把变化频率不同的内容分层。
8. 面向未来的基础设施演进方向
8.1 从通用编排到智能体专用运行时
现在大家基本是用通用容器编排来跑智能体,未来可能会出现更专用的运行时。这类运行时会原生理解智能体的执行模型:多轮循环、工具调用、状态管理、沙箱隔离,把这些作为一等公民来支持,而不是让开发者自己拼装。
我观察到一些方向已经在探索,比如把沙箱生命周期管理和任务调度深度集成,把状态存储和会话管理做成运行时内置能力。这些如果成熟,会大幅降低智能体基础设施的搭建成本。
8.2 异构算力的统一调度
智能体对算力的需求是多样的:有的任务需要 GPU 做推理,有的需要大内存做数据处理,有的需要低延迟网络做实时交互。如何把这些异构资源统一调度,让合适的任务落到合适的硬件上,是个持续的挑战。
Kubernetes 在这方面有基础能力,但表达力和调度效率还有提升空间。尤其是当 GPU 这类稀缺资源需要细粒度共享时,现有的调度模型会显得笨重。
8.3 安全隔离的持续强化
随着智能体执行的任务越来越复杂、越来越接近真实系统操作,安全隔离的要求只会越来越高。微虚拟机、用户态内核这些技术的成熟和普及,会让高强度隔离的成本降下来,从而成为更普遍的选择。
同时,运行时的行为监控也会更重要。不只是隔离,还要能实时检测智能体的异常行为,比如异常的调用模式、异常的资源消耗,及时干预。
9. 给正在搭建智能体基础设施的团队的一些实操建议
如果你现在正准备把智能体从实验推向生产,我建议按这个顺序推进:
先把运行时环境做扎实。镜像分层、依赖锁定、就绪探针、资源限制,这些是地基,地基不稳后面全是坑。别急着上复杂的调度和隔离,先让单个实例能稳定跑起来。
然后解决沙箱隔离。哪怕初期用加固容器,也一定要有隔离,不要裸跑。网络策略默认拒绝,资源配额硬限制,超时强制回收,这三条是底线。
接着是状态管理。想清楚哪些状态放内存、哪些放外部存储,会话亲和性要不要用。这个决策会影响后面的扩缩容设计,早想清楚省事。
最后才是编排和可观测性的精细化。用 CRD 抽象任务、用业务指标驱动伸缩、用分布式追踪串链路,这些是锦上添花,但前提是前面几层已经稳了。
我个人在实际操作中的体会是,智能体基础设施的难点不在于某一项技术有多深,而在于多个层面的协同。运行时、沙箱、调度、状态、可观测性,每一层单独看都不算特别复杂,但要让它们配合好,需要反复调试和权衡。别指望一次设计到位,留出迭代空间,边跑边改,比一开始追求完美架构更实际。