ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Kubernetes的Agentic工作负载编排:ax运行时架构设计与实践

基于Kubernetes的Agentic工作负载编排:ax运行时架构设计与实践 1. 从“ax”这个标题说起一个被低估的运行时编排命题第一次看到“ax”这个标题很多人会一头雾水。它不像“Kubernetes 入门”那样直白也不像“agentic rag”那样自带热度。但把热搜词摊开来看脉络就清楚了ax、agentic、orchestration、runtime、Kubernetes这几个词放在一起指向的是一个非常具体的工程命题——在 Kubernetes 之上如何为 agentic 工作负载构建一套可编排、可观测、可复现的运行时层。我接触这类需求是从一个内部工具链整合项目开始的。当时团队已经有了一套基于 Kubernetes 的服务编排但新来的 agentic 任务多步骤推理、工具调用、状态回传跑在上面非常别扭Pod 生命周期和 agent 的会话生命周期对不上重试逻辑和 agent 的幂等设计打架日志散落在各个容器里根本串不起来。于是我们开始琢磨能不能抽一层薄薄的 runtime专门管 agent 的编排而把调度、网络、存储这些脏活继续交给 Kubernetes。“ax”在我的理解里就是这层 runtime 的一个代号。它不是一个具体的开源项目名至少目前不是而更像一个架构代号a 代表 agenticx 代表 orchestration 里的交叉调度与执行。你完全可以把它当成“agentic runtime on Kubernetes”的缩写来理解。这篇文章要聊的就是这层 runtime 到底该怎么设计、怎么落地、踩过哪些坑以及为什么它和传统的微服务编排不是一回事。适合谁看如果你正在把 LLM 驱动的 agent 往生产环境搬或者你已经在 Kubernetes 上跑了一些实验性的 agent 任务但被状态管理搞得焦头烂额那这篇内容会对你有直接帮助。如果你只是听说过 agentic 这个词想搞清楚它和普通服务编排的区别也能从里面拿到一个完整的工程视角。2. 为什么 agentic 工作负载需要独立的 orchestration runtime2.1 传统 Kubernetes 编排和 agent 编排的根本差异Kubernetes 的编排模型是为无状态、短生命周期、可水平替换的服务设计的。一个 Deployment 管一组 PodPod 挂了就重建请求打到哪个副本都行。这套模型跑 Web 服务、跑批处理任务都非常成熟。但 agentic 工作负载有几个特性直接和这套模型冲突会话是有状态的。一个 agent 在执行多步任务时中间会产生大量上下文工具调用的返回值、推理链的中间结果、用户的历史交互。这些状态不能随便丢也不能随便换一个副本继续。执行路径是动态的。传统服务的调用链在代码里写死了agent 的下一步动作往往是根据上一步的输出动态决定的。你没法在部署时就把 DAG 画好。单步耗时差异极大。一次 LLM 调用可能 200ms也可能 30s一次外部工具调用可能瞬间返回也可能超时重试好几轮。用统一的 liveness probe 和 timeout 去管要么误杀要么卡死。失败语义不同。微服务里一个请求失败重试通常是无害的。但 agent 的某一步失败后重试可能会重复执行一个有副作用的工具调用比如已经发了一封邮件。我实测下来最直接的感受是把 agent 当成普通 Pod 来管你会花 80% 的时间在处理状态和重试的边界情况上而不是在 agent 本身的逻辑上。这就是为什么需要一层专门的 runtime。2.2 “ax” runtime 的定位薄编排层而非重平台这里有个关键的设计取舍。市面上有两种做法一种是做一个大而全的 agent 平台把调度、状态、工具注册、模型网关全部包进来。这种方案上手快但和现有 Kubernetes 基础设施的整合会很痛而且容易被平台锁定。另一种是做一个薄编排层只解决 agent 特有的问题会话状态、动态步骤调度、幂等重试、执行追踪。底下的调度、网络、存储、密钥管理继续用 Kubernetes 原生能力。“ax”走的是第二条路。它的核心抽象只有三个抽象对应 Kubernetes 概念职责Session无直接对应用 CRD 表达管理一次 agent 会话的完整生命周期和状态StepJob 的增强版表示 agent 的一个执行步骤支持动态生成Runtime SidecarSidecar 容器负责状态同步、追踪上报、幂等控制这个设计的逻辑是Kubernetes 已经解决了 90% 的分布式系统问题runtime 只需要解决剩下 10% 的 agent 特有问题。不重复造轮子也不强行把 agent 塞进不合适的模型里。2.3 选型背后的三个关键考量为什么不是直接用 Argo Workflows 或者 Tekton 这类工作流引擎我试过有几个绕不过去的点第一工作流引擎的 DAG 是静态定义的。你可以在运行时传参但没法在步骤执行过程中动态追加新步骤。agent 的典型模式是“执行一步看结果决定下一步”这需要 runtime 支持动态步骤生成。第二工作流引擎的状态管理是面向完成度的不是面向会话的。它关心的是“这个 workflow 跑完了没有”而 agent runtime 关心的是“这个会话的上下文现在是什么状态能不能从中断点恢复”。第三追踪粒度不一样。工作流引擎追踪的是步骤级别的成功失败agent runtime 需要追踪的是推理链级别的因果关系为什么走了这一步上一步的哪个输出触发了这个决策。这对调试和审计至关重要。所以“ax”的选型结论是用 CRD 扩展 Kubernetes API 来表达 Session 和 Step用 controller 模式做编排用 sidecar 做运行时支撑。这套组合既保留了 Kubernetes 的运维生态又给了 agent 足够的表达空间。3. 核心细节拆解Session、Step 与 Runtime Sidecar 的实现要点3.1 Session CRD 的状态机设计Session 是整个 runtime 的核心。它需要表达的状态比普通 Kubernetes 资源复杂得多。我们最终设计的状态机是这样的Pending - Initializing - Running - WaitingForInput - Running - ... - Completed | | v v Failed Cancelled几个关键设计点WaitingForInput 是一等状态。很多 agent 任务需要人工介入或者等待外部事件。如果把这个状态藏在 Running 里面外部系统就没法感知 agent 在等什么。单独拎出来之后可以配合 Kubernetes 的 Event 机制做通知。状态转换必须幂等。controller 会不断 reconcile同一个状态转换可能被触发多次。我们在 Session 的 status 里加了一个transitionVersion字段每次转换递增controller 只在版本匹配时才执行副作用。状态和上下文分离存储。Session 的 status 只存轻量级的状态信息真正的会话上下文消息历史、工具调用记录存在单独的 ConfigMap 或者外部存储里。这样做的好处是 Session 对象不会因为上下文膨胀而变得巨大影响 etcd 的性能。注意Session CRD 的 status 子资源一定要开启否则并发更新时会出现冲突。我们早期没开结果在高并发场景下丢了不少状态更新。3.2 Step 的动态生成与依赖解析Step 的设计是整个 runtime 里最烧脑的部分。传统 Job 是你提交一个 spec它跑完就结束。但 agent 的 Step 是在执行过程中动态产生的。我们的做法是Step 本身也是一个 CRD但它有一个parentSession字段和一个sequence字段。controller 监听 Session 的状态变化当 Session 进入 Running 且没有活跃 Step 时调用 agent 的决策逻辑可以是一个 webhook也可以是一个内置的 planner生成下一个 Step。这里有个关键问题怎么保证动态生成的 Step 不会无限循环我们加了三道防线每个 Session 有一个maxSteps字段默认 50超过就强制进入 Failed 状态。每个 Step 有一个deadline超过就标记为 Timeout由 controller 决定是重试还是终止会话。决策逻辑本身要有终止条件不能依赖 runtime 兜底。依赖解析方面我们支持两种模式串行模式上一步完成才生成下一步和并行模式一次生成多个无依赖的 Step用dependsOn字段表达依赖关系。并行模式在工具调用场景下特别有用比如同时查三个数据源。3.3 Runtime Sidecar 的职责边界Sidecar 是 runtime 和 agent 容器之间的桥梁。它的职责必须严格限定否则会变成一个什么都管的“上帝容器”。我们最终给它划了四条职责状态同步定期把 agent 容器的执行状态通过本地 HTTP 接口暴露同步到 Session CRD 的 status 里。追踪上报收集 agent 产生的 trace span批量上报到追踪后端。幂等控制为每个有副作用的工具调用生成幂等键在重试时复用。优雅终止收到 SIGTERM 后通知 agent 容器保存检查点等待其完成后再退出。Sidecar 不负责的事情也很明确不做模型调用、不做工具注册、不做权限校验。这些要么在 agent 容器里做要么在更上层的网关做。实操心得Sidecar 和 agent 容器之间的通信一定要用 localhost不要走 Service。走 Service 会引入额外的网络跳数和 DNS 解析开销在频繁通信的场景下延迟很明显。我们用 localhost Unix domain socket延迟从平均 3ms 降到了 0.2ms。3.4 与 Kubernetes 原生能力的对接方式“ax” runtime 不重新实现调度、网络、存储而是通过标准接口对接调度Session 和 Step 都通过标准的 Pod spec 表达资源需求交给 kube-scheduler。我们额外加了一个nodeAffinity的约定让有 GPU 的 Step 调度到 GPU 节点。网络Step 之间的通信走 Kubernetes Service但 Session 内部的 Step 之间用 headless service 做直连避免不必要的负载均衡。存储会话上下文用 PVC 挂载保证 Pod 重建后上下文不丢。临时数据用 emptyDir。密钥工具调用的凭证用 Secret 挂载通过 volume 注入不走环境变量环境变量容易在日志里泄露。这套对接方式的好处是运维团队不需要学新东西。他们继续用 kubectl、Prometheus、Grafana 这些工具只是多了一组 CRD 要关注。4. 实操过程从零搭建一个最小可用的 ax runtime4.1 环境准备与前置检查在开始之前确认你的集群满足以下条件# 检查 Kubernetes 版本建议 1.26 及以上 kubectl version --short # 检查 CRD 支持 kubectl api-resources | grep customresourcedefinition # 检查是否有默认 StorageClass kubectl get storageclass # 检查节点资源 kubectl top nodes我们踩过的一个坑是集群版本太低1.22 以下时CRD 的status子资源在某些场景下行为不一致导致状态更新丢失。建议至少 1.26。另外如果你打算用 sidecar 模式确认集群开启了SidecarContainers特性门控1.29 之后默认开启。早期版本需要用 init container 模拟体验差很多。4.2 定义 Session 和 Step 的 CRD先写 Session 的 CRD 定义。这里只展示核心字段apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: sessions.ax.example.com spec: group: ax.example.com versions: - name: v1alpha1 served: true storage: true subresources: status: {} schema: openAPIV3Schema: type: object properties: spec: type: object properties: maxSteps: type: integer default: 50 agentImage: type: string contextPVC: type: string status: type: object properties: phase: type: string enum: [Pending, Initializing, Running, WaitingForInput, Completed, Failed, Cancelled] transitionVersion: type: integer currentStep: type: string scope: Namespaced names: plural: sessions singular: session kind: SessionStep 的 CRD 类似关键字段是parentSession、sequence、dependsOn、deadline。写完 CRD 之后用kubectl apply应用然后检查kubectl get crd | grep ax.example.com kubectl explain session.speckubectl explain能正常输出说明 CRD 注册成功。4.3 Controller 的核心 reconcile 逻辑Controller 用 client-go 或者 controller-runtime 写都可以。我们用 controller-runtime核心 reconcile 逻辑大概是这样func (r *SessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var session axv1alpha1.Session if err : r.Get(ctx, req.NamespacedName, session); err ! nil { return ctrl.Result{}, client.IgnoreNotFound(err) } switch session.Status.Phase { case Pending: return r.initializeSession(ctx, session) case Running: return r.advanceSession(ctx, session) case WaitingForInput: return r.checkInput(ctx, session) default: return ctrl.Result{}, nil } }advanceSession是核心中的核心。它的逻辑是查询当前 Session 下所有 Step 的状态。如果有 Step 在运行等待。如果所有 Step 都完成调用 planner 生成下一个 Step。如果 planner 返回终止信号把 Session 置为 Completed。如果生成的 Step 数量超过maxSteps置为 Failed。这里有个细节planner 调用必须是幂等的。因为 reconcile 可能被重复触发同一个状态下可能多次调用 planner。我们的做法是在 Session 的 status 里记录lastPlannedStepSequence只有当前 sequence 大于记录值时才调用 planner。4.4 Sidecar 注入与状态同步配置Sidecar 注入用 MutatingWebhook 实现。当创建带有ax.example.com/inject: true标签的 Pod 时webhook 自动注入 sidecar 容器。Sidecar 的核心配置- name: ax-runtime-sidecar image: ax/runtime-sidecar:v0.1.0 ports: - containerPort: 9090 name: state-sync env: - name: SESSION_NAME valueFrom: fieldRef: fieldPath: metadata.labels[ax\.example\.com/session] - name: AGENT_ENDPOINT value: http://localhost:8080 volumeMounts: - name: session-context mountPath: /var/ax/context resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi状态同步的间隔我们设的是 2 秒。太频繁会给 API Server 压力太慢会导致状态滞后。2 秒是实测下来比较平衡的值。注意Sidecar 的资源 limits 一定要设。我们早期没设结果某个 agent 疯狂产生 tracesidecar 内存涨到 2G 被 OOM kill连带 agent 容器也挂了。4.5 一次完整的 agent 会话执行记录下面是一次真实的执行记录脱敏后[00:00:00] Session created, phasePending [00:00:01] Controller initialized session, phaseInitializing [00:00:03] PVC mounted, agent container started, phaseRunning [00:00:05] Step-1 generated: search_knowledge_base [00:00:08] Step-1 completed, result: 3 documents found [00:00:09] Step-2 generated: summarize_documents [00:00:15] Step-2 completed, result: summary generated [00:00:16] Step-3 generated: call_external_api [00:00:45] Step-3 timeout, retry with idempotency key [00:01:10] Step-3 completed after retry [00:01:12] Planner returned terminal signal [00:01:13] Session phaseCompleted整个会话耗时 1 分 13 秒其中 Step-3 的重试占了 25 秒。如果没有幂等控制这次重试会导致外部 API 被调用两次产生重复数据。5. 常见问题与排查技巧实录5.1 状态不同步Session 卡在 Running 不动这是最常见的问题。表现是 Session 的 phase 一直是 Running但没有任何 Step 在跑。排查顺序先看 controller 的日志确认 reconcile 有没有被触发。如果没触发检查 controller 的 watch 配置。如果 reconcile 触发了但没进展看 planner 的调用日志。大概率是 planner 返回了错误但被吞掉了。检查 Session 的transitionVersion有没有在递增。如果不递增说明状态更新被 API Server 拒绝了通常是 resourceVersion 冲突。我们遇到过一次原因是 planner 的 webhook 超时了但 controller 没有设置超时导致 reconcile 一直挂着。后来给所有外部调用都加了 5 秒超时。5.2 Step 重复执行幂等键没生效表现是同一个 Step 被执行了多次产生了重复的副作用。根因通常是幂等键的生成逻辑有问题。我们的幂等键是sessionID stepSequence toolName的哈希。早期版本漏了stepSequence导致同一个会话里不同步骤调用同一个工具时幂等键冲突第二次调用被误判为重复而跳过。修复方法很简单但发现过程很痛苦。建议在 sidecar 里加一个调试接口能查询任意幂等键的状态。5.3 Sidecar 资源竞争导致 agent 变慢表现是 agent 的执行时间比预期长很多但 CPU 和内存看起来都不高。这种情况通常是 sidecar 和 agent 容器在争抢同一个 CPU 核。Kubernetes 默认的 CPU 分配是共享的如果 sidecar 在做密集的状态同步会挤占 agent 的 CPU 时间。解决办法是给 sidecar 设置cpu: 100m的 request 和cpu: 200m的 limit并且开启 CPU manager 的 static 策略让 agent 容器独占核心。5.4 常见问题速查表现象可能原因排查方法解决方案Session 卡在 Runningplanner 超时或返回错误查 controller 日志和 planner 调用记录给外部调用加超时错误要显式处理Step 重复执行幂等键生成逻辑有误查 sidecar 的幂等键调试接口修正幂等键生成规则agent 执行变慢sidecar 资源竞争查容器的 CPU throttling 指标设置合理的 request/limit开启 static 策略状态更新丢失resourceVersion 冲突查 API Server 的审计日志开启 status 子资源用乐观锁重试Sidecar OOMtrace 数据积压查 sidecar 内存曲线设置内存 limit加 trace 采样率PVC 挂载失败StorageClass 不匹配查 PVC 的 event确认 StorageClass 存在且可动态供应5.5 几个独家避坑技巧技巧一给 Session 加一个dryRun模式。在这个模式下Step 会被生成但不实际执行只记录执行计划。这在调试 planner 逻辑时非常有用能快速验证决策路径是否正确而不用真的跑一遍完整的 agent。技巧二trace 数据一定要采样。我们早期全量上报结果追踪后端被打爆。后来改成按 Session 采样10% 的会话全量上报其余只上报关键 span。调试问题时可以临时提高采样率。技巧三Step 的 deadline 要留足余量。我们一开始把 deadline 设成预期耗时的 1.5 倍结果 LLM 调用偶尔抖动就超时。后来改成 3 倍超时率从 8% 降到了 0.5%。代价是失败检测变慢但整体体验更好。技巧四Session 的上下文要定期压缩。长会话的上下文会越来越大最终撑爆 PVC 或者拖慢 planner。我们的做法是每 10 个 Step 做一次上下文摘要把历史消息压缩成一段总结。这个逻辑放在 sidecar 里做对 agent 透明。6. 这套 runtime 后续可以怎么扩展跑通最小可用版本之后我们陆续加了几个扩展效果都不错。第一个是多 agent 协作。在 Session 层面支持subSessions字段一个主 Session 可以派生出多个子 Session各自独立执行最后汇总结果。这比在单个 agent 里做多角色模拟要清晰得多因为每个子 Session 有独立的资源和状态。第二个是执行回放。因为所有 Step 的输入输出都记录在案可以按时间顺序回放整个会话。这在排查“为什么 agent 做了这个决策”时特别有用。回放功能我们做成了一个 CLI 工具输入 Session ID 就能看到完整的执行链路。第三个是成本追踪。每个 Step 记录消耗的 token 数和工具调用次数汇总到 Session 级别。这样能清楚地看到一次会话花了多少钱哪个步骤是大头。对于需要控制成本的场景这个功能是刚需。如果你也在做类似的事情我的建议是先把最小闭环跑通一个 Session、一个 Step、一个 sidecar能完整跑一次 agent 任务就行。不要一上来就追求多 agent、回放、成本追踪这些高级功能。基础的状态机和幂等控制做扎实了后面的扩展都是水到渠成的事。
返回列表