ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Kubernetes 上的 Agentic 运行时编排:从 CRD 设计到多智能体协作实践

Kubernetes 上的 Agentic 运行时编排:从 CRD 设计到多智能体协作实践 1. 从“ax”这个标题说起一个被低估的运行时编排命题“ax”这两个字母放在一起第一眼看上去像是一个缩写、一个代号甚至像某个内部项目的临时命名。但如果你把最近围绕它出现的热词串起来看——agentic、orchestration、runtime、Kubernetes、agentic rag、Karmada 毕业、华为云 agentic cloud——就会发现“ax”大概率指向的是一个面向智能体时代的运行时编排层。它不是某个具体框架的名字而更像是一类问题的代号当多个智能体agent需要在 Kubernetes 集群上协同工作时谁来决定它们怎么调度、怎么通信、怎么共享上下文、怎么在失败时恢复我之所以对这个方向感兴趣是因为过去一年里我陆续在几个内部项目里尝试把 agentic 工作流搬到 K8s 上跑。一开始觉得不就是把容器镜像换成一个带 LLM 调用的服务吗结果踩了一连串坑agent 之间的状态怎么传递、工具调用怎么隔离、长任务怎么断点续跑、多个 agent 同时抢一个外部 API 怎么限流。这些问题单独看都不难但叠在一起就变成了一个典型的运行时编排问题。而“ax”这个标题恰好可以作为一个切入点把 agentic orchestration runtime 在 Kubernetes 上的核心设计思路拆开来讲。这篇文章适合谁看如果你已经在用 Kubernetes 跑普通微服务现在想进一步跑 agentic 工作负载或者你在做 RAG 系统发现单 agent 不够用想上多 agent 协作又或者你只是对“agentic cloud”这个概念好奇想知道底层到底需要什么运行时能力——那这篇内容应该能给你一些可直接参考的思路。我会尽量把每个设计选择背后的“为什么”讲清楚而不是只丢一堆 YAML 和概念。提示本文讨论的“ax”是一个泛指的项目代号代表一类 agentic orchestration runtime 的工程实践不指向任何特定商业产品或内部系统。2. 核心思路拆解为什么 agentic 工作负载不能直接套用普通微服务编排2.1 普通 K8s 编排和 agentic 编排的本质差异Kubernetes 最擅长的场景是无状态、短生命周期、请求-响应式的服务。一个 Pod 起来处理完请求返回结果然后继续等下一个请求。即使是有状态服务通常也是通过 PVC 或外部存储把状态外置Pod 本身尽量保持“可替换”。这套模型跑 Web 服务、API 网关、批处理任务都很成熟。但 agentic 工作负载有几个根本性的不同。第一执行路径是动态的。一个 agent 在收到任务后可能会根据中间结果决定下一步调用哪个工具、是否要拆解成子任务、要不要唤起另一个 agent。这个决策过程在运行时才确定没法在部署时写死在 YAML 里。第二状态是长生命周期的。一个多 agent 协作任务可能跑几分钟甚至几小时中间涉及多轮 LLM 调用、工具执行、人工确认。如果某个 Pod 挂了不能简单重启了事得从断点恢复。第三通信模式是多方交互的。普通微服务通常是 A 调 B、B 调 C 的链式或扇出结构而 agent 之间可能是协商、竞争、投票、共享黑板等多种模式。这就解释了为什么热词里同时出现了“agentic orchestration”和“runtime”。Orchestration 解决的是“谁在什么时候做什么”的调度问题runtime 解决的是“做的时候需要什么环境”的执行问题。两者在 agentic 场景下必须一起设计不能像传统微服务那样拆成两个独立层。2.2 为什么选 Kubernetes 作为底座而不是自建调度器有人可能会问既然 K8s 的模型和 agentic 工作负载不匹配为什么不自己写一个调度器我一开始也这么想过但实际评估下来自建调度器的成本远高于在 K8s 上做扩展。原因有几个。K8s 已经解决了资源隔离、网络策略、服务发现、密钥管理、可观测性接入这些基础设施问题。你自建调度器这些全都要重新做一遍而且很难做得比 K8s 生态更稳。更重要的是agentic 工作负载并不是完全排斥 K8s 模型它只是需要在 K8s 之上加一层面向 agent 的抽象。比如用 Custom Resource DefinitionCRD来定义 Agent、Task、Session 这些概念用 Operator 来实现 agent 生命周期管理用 K8s 的 Job/CronJob 来跑工具执行容器。这样既复用了 K8s 的成熟能力又补上了 agentic 特有的编排逻辑。Karmada 最近正式毕业这件事也侧面印证了这个方向。Karmada 解决的是多集群编排问题而 agentic cloud 的一个核心诉求就是跨集群、跨地域的 agent 调度。华为云在这个方向上的投入说明业界已经认可“K8s 多集群编排 agentic 运行时”这条技术路线。2.3 “ax”可能代表的运行时分层模型结合热词里的 runtime、agentic rag、orchestration我倾向于把“ax”理解为一个四层运行时模型层级职责对应 K8s 原语接入层接收任务、鉴权、限流Ingress / Gateway API编排层agent 调度、任务拆解、状态机CRD Operator执行层LLM 调用、工具执行、RAG 检索Job / Pod / Sidecar状态层会话上下文、中间结果、向量存储PVC / ConfigMap / 外部存储这个分层不是拍脑袋想的而是我在实际项目中反复调整后稳定下来的结构。最早我把编排和执行混在一起结果 agent 逻辑一改就要重新打镜像迭代极慢。后来把编排逻辑抽到 Operator 里执行层只负责“给定输入、跑工具、返回输出”迭代速度才上来。状态层单独拆出来是因为 agent 的上下文往往比普通服务的状态大得多而且需要支持向量检索和结构化查询两种访问模式。3. 核心细节解析agentic runtime 在 K8s 上的关键实现点3.1 Agent 的 CRD 设计把 agent 当成一种资源在 K8s 里扩展能力第一步通常是定义 CRD。对于 agentic runtime我建议至少定义三个 CRDAgent、Task、Session。Agent描述一个 agent 的能力画像比如它能用哪些工具、走哪个模型、有什么系统提示词。Task描述一个具体要执行的工作单元包括输入、期望输出格式、超时时间。Session描述一次多 agent 协作的上下文把多个 Task 和 Agent 关联起来。这样设计的好处是agent 的配置和 agent 的运行实例解耦了。你可以定义一个Agent叫 “researcher”它绑定搜索工具和某个模型然后创建多个Task引用这个 Agent每个 Task 有自己的输入。Operator 监听到 Task 创建后会为每个 Task 生成一个 JobJob 里的容器读取 Agent 配置和 Task 输入执行完后把结果写回 Session 的状态里。这里有个细节值得注意Agent 配置里不要直接写 API Key。我见过有人在 CRD 里明文写模型服务的密钥这是大忌。正确做法是用 K8s Secret 存密钥Agent CRD 里只引用 Secret 的名字。Operator 在创建 Job 时把 Secret 以环境变量或 Volume 的形式注入到 Pod 里。3.2 状态传递为什么不能用 K8s 原生机制硬扛Agent 之间的状态传递是 agentic runtime 最棘手的问题之一。普通微服务可以用 Service 互相调用每次调用带上必要的参数就行。但 agent 协作往往需要共享一个不断增长的上下文比如一个 agent 检索到的文档另一个 agent 要基于这些文档做推理第三个 agent 要基于推理结果生成最终答案。这个上下文可能很大而且需要支持并发读写。我试过几种方案。第一种是用 ConfigMap 存上下文但 ConfigMap 有大小限制1MB而且更新有延迟不适合频繁写入。第二种是用 PVC 挂载共享文件系统但多个 Pod 同时读写同一个文件需要加锁实现复杂。第三种是用外部存储比如 Redis 或向量数据库把上下文存在外面Pod 里只保留一个引用 ID。实测下来第三种最稳也最容易扩展。具体做法是Session CRD 里记录一个contextRef指向外部存储里的一个 key。每个 Task 的 Pod 启动时从环境变量拿到contextRef然后去外部存储读取自己需要的部分。执行完后把新产生的上下文追加写回去。为了避免并发冲突可以用 Redis 的 List 或 Stream 结构每个 agent 往自己的 Stream 里写需要读全量上下文时再合并。注意外部存储的选型要考虑持久化和备份。Agent 的上下文往往包含重要的中间推理结果丢了就得重跑成本很高。3.3 工具执行的隔离与限流Agent 调用工具是 agentic 工作负载的另一个特点。工具可能是 HTTP API、数据库查询、代码执行、文件操作等。在 K8s 上跑工具执行最大的风险是一个 agent 的工具调用把整个集群的资源吃光。比如某个 agent 决定调用一个代码执行工具跑了一个死循环把节点 CPU 打满。我的做法是给每个工具执行单独设一个 ResourceQuota 和 LimitRange。具体来说在命名空间级别限制总 CPU 和内存在 Pod 级别设置 requests 和 limits。对于代码执行这类高风险工具额外加一个activeDeadlineSeconds超时直接杀掉。另外工具执行的 Pod 最好跑在独立的节点池上和编排层、状态层隔离开避免互相影响。限流方面如果多个 agent 同时调用同一个外部 API很容易触发对方的速率限制。我通常会在工具执行层加一个令牌桶代理所有对外调用都经过这个代理由代理统一控制速率。这个代理可以是一个 Sidecar 容器也可以是一个独立的 Deployment。用 Sidecar 的好处是每个 Pod 自己管自己的限流不依赖外部服务缺点是配置分散。用独立 Deployment 的好处是集中管理缺点是增加了一跳网络延迟。我一般选 Sidecar因为 agent 的数量通常不会太多配置分散的问题可以用 Helm chart 统一模板化解决。3.4 RAG 在 agentic runtime 里的位置热词里出现了 “agentic rag”这其实是一个很重要的信号。传统 RAG 是“检索-拼接-生成”的固定流程而 agentic RAG 是把检索本身也交给 agent 决策。Agent 可以决定要不要检索、检索什么、检索几次、要不要基于检索结果再检索。这就要求 runtime 把 RAG 能力做成一种可被 agent 调用的工具而不是写死在流程里。在 K8s 上实现 agentic RAG我建议把向量检索服务单独部署成一个 StatefulSet用 PVC 存向量索引。Agent 通过一个轻量级的客户端库调用检索服务客户端库负责把查询转成向量、发请求、解析结果。这样 agent 的代码里不需要关心向量数据库的具体实现换后端只需要改客户端库的配置。这里有个坑向量检索的延迟波动很大。有时候几十毫秒有时候几秒。如果 agent 的推理流程里同步等待检索结果整体延迟会被拉高。我的做法是在 agent 侧加一个超时和降级逻辑如果检索超过 2 秒还没返回就先用已有上下文继续推理把检索结果作为异步补充。这样虽然可能损失一点准确性但整体响应时间可控。4. 实操过程从零搭一个最小可用的 agentic runtime4.1 环境准备与基础组件选型假设你有一个能用的 K8s 集群1.26 以上接下来需要准备几样东西。第一是 Operator 框架我用的是 Kubebuilder因为它生成的脚手架比较干净CRD 定义和 Reconcile 逻辑分离得清楚。第二是外部状态存储我选 Redis因为它的 Stream 结构天然适合多 agent 追加写入。第三是向量检索服务我用的是 Milvus 的单机版够用且部署简单。第四是模型服务这个取决于你的实际情况可以是自部署的推理服务也可以是外部 API。选型逻辑说一下。Operator 框架选 Kubebuilder 而不是 Operator SDK是因为 Kubebuilder 更贴近原生 controller-runtime出问题容易排查。Redis 选 Stream 而不是 List是因为 Stream 支持消费者组多个 agent 可以各自读自己的部分不会互相阻塞。Milvus 选单机版而不是集群版是因为初期 agent 数量少单机版够用等规模上来了再换集群版也不迟。4.2 定义 CRD 与编写 Reconcile 逻辑先定义 Agent CRD。关键字段包括modelRef引用模型服务配置、tools工具列表、systemPrompt系统提示词、maxConcurrency最大并发任务数。然后定义 Task CRD关键字段包括agentRef、input、outputFormat、timeoutSeconds。最后定义 Session CRD关键字段包括contextRef、taskRefs、status。Reconcile 逻辑的核心是监听 Task 创建事件检查对应的 Agent 是否存在且未超过并发限制然后创建一个 Job。Job 的 Pod 模板里注入 Agent 配置、Task 输入、Session 的 contextRef。Job 完成后Reconcile 逻辑读取 Job 的日志或退出码更新 Task 的 status并把结果写回 Session 的 contextRef。这里有个实现细节Job 的 Pod 模板里不要直接挂载 Agent CRD 的完整内容而是挂载一个 ConfigMapConfigMap 里只放当前 Task 需要的字段。这样做是为了避免 Pod 启动时拉取过多无关配置也方便后续做配置热更新。4.3 编写 Agent 执行器的容器镜像Agent 执行器是一个容器它的职责很单一读取环境变量里的 Agent 配置和 Task 输入调用模型服务执行工具把结果写到标准输出或外部存储。我通常用 Python 写这个执行器因为 Python 的 LLM 生态最成熟。执行器的主循环大概是这样的先从环境变量拿到AGENT_CONFIG_PATH和TASK_INPUT_PATH读取配置和输入。然后初始化模型客户端和工具客户端。接着进入推理循环调用模型解析模型返回的工具调用请求执行工具把工具结果追加到上下文再次调用模型直到模型返回最终答案或达到最大轮数。最后把最终答案写到OUTPUT_PATH退出。工具调用的实现要注意错误处理。工具执行失败时不要把异常直接抛给模型而是把错误信息作为工具结果返回给模型让模型决定是重试、换工具还是放弃。这样 agent 的行为更鲁棒。4.4 部署与验证跑一个多 agent 协作的例子部署顺序是先部署 Redis 和 Milvus再部署 Operator然后创建 Agent CRD 实例最后创建 Session 和 Task。验证时我通常会跑一个简单的例子一个 “researcher” agent 负责检索文档一个 “writer” agent 负责基于检索结果写摘要。Session 里先创建一个 researcher 的 Task等它完成后再创建一个 writer 的 Taskwriter 的输入里引用 researcher 的输出。这个例子里researcher 的 Task 完成后Operator 会把结果写到 Session 的 contextRef 里。writer 的 Task 创建时Operator 会检查 contextRef 里是否有 researcher 的输出如果有就把它作为 writer 的输入的一部分。这样两个 agent 就通过 Session 的上下文实现了协作。实测下来这个最小可用版本的端到端延迟在 10 到 30 秒之间取决于模型服务的响应速度和检索的耗时。对于演示和初期验证来说够用了。如果要上生产还需要加监控、告警、重试策略、成本控制等。5. 常见问题与排查技巧实录5.1 Agent Pod 启动失败镜像拉取和配置注入问题最常见的问题是 Agent Pod 起不来。排查顺序是先看 Pod 的 Events确认是镜像拉取失败还是配置注入失败。镜像拉取失败通常是镜像仓库的 Secret 没配好或者镜像 tag 写错了。配置注入失败通常是 ConfigMap 或 Secret 不存在或者挂载路径写错了。我踩过的一个坑是ConfigMap 更新后已经挂载的 Pod 不会自动重新加载。如果 Agent 配置改了需要手动删除 Pod 让它重建。后来我在 Operator 里加了一个逻辑监听 ConfigMap 变化如果某个 ConfigMap 被某个 Agent 引用就自动触发相关 Pod 的重建。这个逻辑用 controller-runtime 的Watches机制实现不算复杂但很实用。5.2 任务卡住不结束超时和死锁排查Agent 任务卡住不结束通常有两个原因。一是模型服务响应慢或挂了agent 一直在等。二是工具执行死锁比如两个 agent 互相等对方的结果。排查方法是看 Pod 的日志确认卡在哪一步。如果是模型服务的问题加超时和重试如果是死锁需要在编排层加检测逻辑比如 Session 级别的超时超时后强制终止所有相关 Task。我遇到过一次比较隐蔽的死锁两个 agent 通过 Redis Stream 通信一个 agent 在等另一个 agent 写数据但另一个 agent 因为并发限制没被调度起来。后来我在 Operator 里加了一个检查如果 Session 里有 Task 处于等待状态超过一定时间就临时提高并发限制让等待的 Task 先跑起来。5.3 资源竞争与限流失效多个 agent 同时跑的时候容易出现资源竞争。比如同时调用模型服务把模型服务的并发打满导致所有请求都变慢。解决办法是在模型服务前面加一个队列agent 的请求先入队由队列控制并发。K8s 里可以用一个简单的 Deployment 实现这个队列或者用 Knative 的自动扩缩容能力。限流失效的另一个原因是限流配置没有覆盖所有出口。比如 agent 直接调用外部 API绕过了 Sidecar 代理。解决办法是在网络策略层面限制 Pod 的出站流量只允许它访问 Sidecar 代理和必要的内部服务。这样即使 agent 代码里写了直接调用外部 API也会被网络策略拦住。5.4 常见问题速查表现象可能原因排查方法解决思路Pod 一直 Pending资源不足或调度约束kubectl describe pod看 Events调整 requests 或节点池Pod CrashLoopBackOff配置错误或依赖缺失kubectl logs看启动日志检查 ConfigMap/Secret 挂载任务超时模型慢或工具卡住看 Pod 日志定位卡点加超时和重试上下文丢失外部存储写入失败检查 Redis/Milvus 连接加重试和持久化并发超限并发限制配置过小看 Operator 日志调整 maxConcurrency限流失效出口未走代理检查网络策略限制出站流量提示排查 agentic runtime 问题时日志的粒度很关键。建议在 agent 执行器里对每个关键步骤都打日志包括模型调用开始/结束、工具调用开始/结束、上下文读写。这样出问题时能快速定位。6. 一些实操心得和后续扩展方向我在实际项目里最大的体会是agentic runtime 的复杂度不在单个 agent 的实现而在 agent 之间的协调。单个 agent 的推理循环其实很简单无非是调模型、执行工具、循环。但一旦多个 agent 要协作状态管理、并发控制、失败恢复这些问题就会指数级放大。所以如果你刚开始做建议先从单 agent 跑通再逐步加多 agent 协作不要一上来就设计一个复杂的多 agent 系统。另一个心得是不要把编排逻辑写进 agent 执行器里。我早期犯过这个错误把“什么时候调用哪个 agent”的逻辑写在了 agent 的代码里结果每次调整协作流程都要重新打镜像。后来把编排逻辑全部抽到 Operator 里agent 执行器只负责“给定输入、跑推理、返回输出”迭代效率高了很多。后续如果要扩展我觉得有几个方向值得尝试。一是跨集群调度用 Karmada 把 agent 分布到多个集群按数据 locality 或成本来调度。二是agent 市场的概念把 Agent CRD 做成可共享、可组合的模块不同团队可以复用别人的 agent 定义。三是成本感知调度在编排层记录每个 agent 的 token 消耗和工具调用成本根据预算来决定调度策略。这些方向目前都还在早期但我觉得是 agentic cloud 真正落地时绕不开的问题。最后分享一个小技巧在开发阶段可以用一个 mock 模型服务替代真实模型返回固定的工具调用序列。这样可以在不消耗 token 的情况下快速验证编排逻辑是否正确。等编排逻辑稳定了再切换到真实模型做端到端测试。这个做法帮我省了不少调试时间。
返回列表