1. 从“ax”这个标题说起一个被低估的运行时编排命题第一次看到“ax”这个标题很多人会一头雾水。它不像“Kubernetes 入门”那样直白也不像“Agentic RAG 实战”那样自带场景。但把热搜词摊开来看脉络就清楚了ax、agentic、orchestration、runtime、Kubernetes这几个词放在一起指向的是一个非常具体的工程命题——在 Kubernetes 之上如何为 agentic 工作负载构建一套可编排、可观测、可复现的运行时层。我先把结论摆在前面“ax”在这里不是一个产品名而是一类架构模式的代号。你可以把它理解成“agent execution”的缩写也可以理解成“a-x”这条从 agent 到 execution 的链路。它要解决的问题是当你的系统里不再只有无状态的 HTTP 服务而是有一堆会自己规划、自己调工具、自己决定下一步干什么的 agent 时传统的 Deployment Service 那套东西就不够用了。你需要一层专门管“谁在什么时候执行什么、执行到哪一步了、失败了怎么重试、多个 agent 之间怎么协调”的运行时。这篇文章适合三类人看。第一类是做平台工程的手里有 K8s 集群想在上面跑 agent 类负载但发现 pod 老是莫名其妙卡住第二类是做 AI 应用的写了不少 agent 逻辑但一到生产环境就发现状态管理一团糟第三类是对 orchestration 和 runtime 这两个词一直似懂非懂想找个具体场景把它们串起来的人。我会尽量用我实际踩过的坑来讲不堆概念。2. 为什么 agentic 负载不能直接塞进普通 K8s 工作负载2.1 普通 Deployment 的假设在 agent 场景下全部失效Kubernetes 最核心的设计假设是工作负载是无状态的、可替换的、幂等的。一个 pod 挂了ReplicaSet 拉起一个新的流量切过去用户无感知。这套模型对 Web 服务、API 网关、批处理任务都工作得很好。但 agent 不一样。一个正在执行任务的 agent它的“状态”不只是内存里的变量还包括它已经调用了哪些工具、每个工具返回了什么、它当前的推理链走到哪一步、它对外部世界产生了什么副作用比如已经发了一封邮件、已经写了一条数据库记录。这些东西如果 pod 一重启就没了那这个 agent 就是不可用的。我见过太多团队一开始的做法是把 agent 逻辑写成一个 FastAPI 服务打个镜像扔进 Deployment配个 HPA。跑 demo 没问题一上量就出问题。典型症状是agent 执行到一半 pod 被驱逐任务丢失或者两个副本同时处理同一个任务重复调用外部 API再或者 agent 之间的依赖关系完全靠应用层自己维护K8s 完全不知道谁在等谁。2.2 orchestration 层要补的四个缺口所以“ax”这类运行时编排层本质上要补四个缺口。第一个是执行状态的持久化。agent 的每一步执行结果不能只放在内存里必须有一个地方能记录“这个任务执行到哪了”。这个东西可以是数据库可以是 etcd也可以是专门的状态存储但必须独立于 pod 生命周期。第二个是任务级调度而非请求级调度。普通 K8s 调度的是 pod它不关心 pod 里跑的是什么任务。但 agent 场景下你需要的是“把这个任务分配给那个 agent 实例”这是一个更高层的调度决策。K8s 的 scheduler 管不了这个你得自己在上面搭一层。第三个是agent 之间的协调原语。多个 agent 协作时谁等谁、谁给谁传数据、超时怎么处理、一个失败了其他要不要回滚这些都需要运行时提供原语。靠应用层用消息队列硬凑最后会变成一团乱麻。第四个是可观测性的粒度。普通服务的监控看 QPS、延迟、错误率就够了。agent 的监控要看“这个任务走了几步、每步花了多久、调了哪些工具、哪一步是瓶颈”。这个粒度比传统 APM 细得多。2.3 一个具体的对比普通 Job 和 agent task 的差异我用一个表格把差异说清楚这样你在做架构决策时可以直接对照。维度普通 K8s JobAgent Task执行单元一次性容器多步骤、有状态的执行链状态无状态或外部存储执行中间态必须持久化重试语义整个 Job 重跑从失败步骤续跑调度粒度Pod 级任务级 步骤级依赖关系基本无Agent 之间有等待、传递、编排超时处理整体超时每步独立超时 整体预算可观测性容器指标步骤级 trace 工具调用记录这张表是我自己在做平台选型时整理的每次有团队想把 agent 直接塞进 Job 里我就把这张表甩过去。不是说不能跑而是你会在第三个迭代周期开始还技术债。3. ax 运行时的核心架构拆解3.1 控制面与数据面的分离“ax”这类运行时的第一个架构决策是控制面和数据面必须分开。控制面负责“决定谁执行什么”数据面负责“实际执行”。这个分离不是为了好看而是为了两个很实际的原因。第一控制面需要全局视图。它要知道当前有哪些 agent 在线、每个 agent 的能力是什么、有哪些任务在排队、哪些任务正在执行。这个信息必须是全局一致的不能分散在各个 pod 里。第二数据面需要独立扩缩容。执行 agent 的 pod 可能很吃资源比如要加载模型、要跑浏览器而控制面很轻。如果混在一起扩缩容就会互相干扰。具体实现上控制面通常是一个 Deployment跑几个副本前面挂 Service。数据面是一组 worker pod通过 gRPC 或消息队列跟控制面通信。控制面维护任务队列和状态机worker 从控制面拉任务、执行、回报结果。这里有个坑我要提前说控制面的状态存储不要用 K8s 的 CRD 硬扛。我见过有人把任务状态写成 CRD用 informer 监听。小规模能跑任务一多 etcd 就扛不住了而且 CRD 的 watch 机制在高频更新下延迟很感人。正确做法是用独立的存储比如 PostgreSQL 或者 RedisCRD 只用来做声明式的配置管理。3.2 任务状态机的设计要点Agent 任务的状态机比普通任务复杂得多。一个典型的状态流转是这样的PENDING - SCHEDULED - RUNNING - (STEP_COMPLETED - RUNNING)* - COMPLETED \- FAILED - RETRYING - RUNNING \- CANCELLED关键点在于STEP_COMPLETED这个中间态。普通任务只有“跑完”和“没跑完”但 agent 任务是分步的每一步完成后都要落盘。这样 pod 挂了之后新 pod 可以从最后一个完成的步骤继续而不是从头再来。状态机的实现我建议用事件溯源的思路不存当前状态存状态变更事件。每次 agent 完成一步就追加一条事件。当前状态由事件回放得出。这样做的好处是审计友好、调试方便而且天然支持“回到某一步重放”。坏处是事件表会涨得很快需要定期做快照压缩。3.3 与 Kubernetes 的集成边界“ax”运行时跑在 K8s 上但不是所有东西都要交给 K8s 管。这个边界划清楚很重要。交给 K8s 管的worker pod 的生命周期、资源配额、网络策略、镜像分发、节点调度。这些是 K8s 的强项没必要自己造。不交给 K8s 管的任务调度、agent 状态、步骤编排、重试逻辑、agent 间通信。这些是 ax 运行时的职责K8s 原生原语表达不了。我见过两种极端。一种是全部自己造连 pod 调度都自己写最后发现是在重新实现 K8s。另一种是全部交给 K8s用 Job 套 Job用 Init Container 做步骤编排最后 YAML 复杂到没人敢改。正确的做法是中间路线K8s 管基础设施ax 管业务编排。4. 实操从零搭一个最小可用的 ax 运行时4.1 环境准备与依赖选型我假设你手里有一个能用的 K8s 集群版本 1.26 以上。本地开发可以用 kind 或者 minikube生产环境建议用托管集群省心。核心依赖我推荐这几个状态存储PostgreSQL 14。任务状态、事件流、agent 注册信息都放这里。别用 etcd 存业务状态它是给 K8s 自己用的。消息队列NATS 或者 Redis Stream。控制面给 worker 派任务用。NATS 更轻Redis Stream 如果你已经有 Redis 就更省事。可观测性OpenTelemetry Collector Jaeger。agent 的步骤级 trace 全靠它。控制面框架Go 或者 Python 都行。Go 的并发模型更适合控制面Python 开发快。我自己的选择是 Go 写控制面Python 写 worker 侧的 agent 逻辑。这里有个选型心得不要一上来就上 Temporal 或者 Argo Workflows。这两个都是好东西但它们的抽象层次跟 agent 场景不完全匹配。Temporal 的 workflow 是代码定义的agent 的步骤是运行时动态决定的硬套会很别扭。Argo 更适合 CI/CD 和数据处理流水线。ax 这类运行时需要的是更轻、更贴近 agent 语义的编排层。先用最小实现跑通等真的遇到瓶颈再考虑引入重型框架。4.2 控制面的最小实现控制面我拆成三个模块API Server、Scheduler、State Manager。API Server 对外暴露 gRPC 接口接收任务提交和 worker 注册。任务提交的接口大概长这样service AxControl { rpc SubmitTask(SubmitTaskRequest) returns (SubmitTaskResponse); rpc RegisterWorker(RegisterWorkerRequest) returns (stream TaskAssignment); rpc ReportStep(ReportStepRequest) returns (ReportStepResponse); } message SubmitTaskRequest { string agent_type 1; string input_payload 2; int32 max_steps 3; int32 timeout_seconds 4; mapstring, string metadata 5; }Scheduler 是一个后台循环每隔一小段时间扫一次待调度任务根据 agent_type 匹配在线的 worker把任务分配出去。分配的时候要写一条SCHEDULED事件并且把任务标记为“已分配但未确认”。worker 收到任务后回一个 ack才转成RUNNING。这个 ack 机制是为了处理 worker 在收到任务后立刻挂掉的情况。State Manager 封装所有状态读写。对外只暴露“追加事件”和“查询当前状态”两个方法。内部实现事件溯源定期做快照。4.3 Worker 侧的执行循环Worker 的逻辑相对简单就是一个循环注册自己 - 等待任务 - 执行任务 - 回报步骤 - 等待下一个任务。执行任务的时候agent 的每一步都要调ReportStep把这一步的输入、输出、耗时、工具调用记录都报上去。控制面收到后追加事件并返回“是否继续”的指令。如果控制面发现这个任务已经被取消或者超预算了就告诉 worker 停止。这里有个细节步骤回报要幂等。worker 可能因为网络问题重发同一个步骤的回报控制面要能识别重复并忽略。实现上给每个步骤一个唯一 ID控制面记录已处理的步骤 ID 集合。4.4 部署到 Kubernetes 的清单要点控制面的 Deployment 没什么特别的注意配好 liveness 和 readiness 探针副本数至少 2 个避免单点。Worker 的部署有几个要点。第一不要用 Deployment用 StatefulSet 或者自定义控制器。因为 worker 需要稳定的身份控制面要能追踪“哪个 worker 在执行哪个任务”。第二worker 的优雅退出很重要。收到 SIGTERM 后worker 要先把当前步骤执行完、回报完再退出。这需要配terminationGracePeriodSeconds默认 30 秒可能不够agent 的一步可能跑几分钟。第三worker 的资源请求要按 agent 类型区分。跑浏览器的 agent 和跑纯推理的 agent资源画像完全不同最好用不同的 StatefulSet。网络策略上worker 只需要能访问控制面和外部工具 API不需要暴露任何入站端口。控制面需要能被 worker 访问同时对外暴露 API 给任务提交方。5. 踩坑实录那些文档里不会写的问题5.1 任务重复执行的三种成因Agent 任务重复执行是最常见也最恶心的问题。我遇到过三种成因。第一种是调度器的重复分配。Scheduler 扫到任务 A分配给 worker 1但在写状态之前 Scheduler 重启了重启后又扫到任务 A分配给 worker 2。两个 worker 同时执行。解决办法是分配动作和状态写入要在同一个事务里或者用乐观锁。第二种是worker 的 ack 丢失。控制面分配了任务worker 执行完了但 ack 在网络上丢了。控制面以为任务没被接重新分配。解决办法是 worker 回报步骤时带上任务 ID 和步骤 ID控制面做去重。第三种是外部工具的副作用。Agent 调了一个发邮件的工具邮件发出去了但回报步骤时网络断了。控制面重试这一步邮件发了两遍。这个最难解只能靠工具本身做幂等比如发邮件时带一个唯一 ID收件方去重。5.2 状态存储的膨胀与压缩事件溯源跑一段时间后事件表会变得巨大。一个跑了 10 万步的 agent 任务就有 10 万条事件。查询当前状态要回放 10 万条慢得没法用。解决办法是定期做快照。每 N 条事件做一次快照快照存当前完整状态。查询时先找最近的快照再从快照之后的事件回放。N 的取值要权衡太小快照太多占空间太大回放太慢。我的经验值是 N100配合每天一次的全量压缩。快照的另一个用途是调试。线上出问题时把某个任务的事件流导出来本地回放能精确复现当时的执行路径。这个能力在排查 agent 逻辑 bug 时非常有用。5.3 常见问题速查表症状可能原因排查方向解决手段任务卡在 RUNNING 不动worker 挂了但没上报查 worker pod 状态和最后心跳加心跳超时超时后重新调度任务重复执行调度重复或 ack 丢失查事件流里是否有两条 SCHEDULED事务化分配 步骤去重步骤回报延迟高控制面写入瓶颈查 PostgreSQL 慢查询加索引、批量写入、读写分离worker 频繁重启资源不足或 OOM查 pod 的 resource 使用调大 limit或拆分 agent 类型trace 断链context 没传递查 OTel 的 span 关联在 gRPC metadata 里传 trace context任务超时但没终止超时检查没生效查控制面的超时扫描逻辑加独立的超时扫描协程这张表是我自己运维时攒下来的基本覆盖了 80% 的线上问题。剩下的 20% 通常是业务逻辑 bug得具体看。6. 从能跑到好用几个进阶优化方向6.1 步骤级缓存Agent 的很多步骤是重复的。比如同一个 agent 类型前几步的初始化逻辑完全一样。如果能把步骤结果缓存起来命中缓存直接跳过能省不少时间和成本。缓存的 key 是“agent_type 步骤序号 输入 hash”。缓存的值是步骤输出。注意只有幂等的步骤才能缓存。有副作用的步骤比如写数据库不能缓存否则会跳过副作用。6.2 优先级与抢占生产环境里任务是有优先级的。用户交互触发的 agent 任务要优先于后台批处理任务。这需要调度器支持优先级队列并且支持抢占——高优先级任务来了可以暂停低优先级任务把资源让出来。抢占的实现要小心。被抢占的任务不能直接杀掉要让它执行完当前步骤保存状态然后暂停。恢复的时候从暂停点继续。6.3 多集群与容灾单集群跑 agent 任务集群挂了就全挂了。进阶做法是多集群部署控制面全局一份worker 分布在多个集群。控制面根据集群的健康状况和负载决定把任务派到哪个集群。这个架构的复杂度会上升一个量级主要是状态同步和网络分区处理。我的建议是除非业务真的要求跨地域容灾否则先别碰。单集群做好备份和快速恢复性价比更高。7. 关于 ax 运行时我个人的几点体会做这类运行时最深的体会是不要把 agent 当成特殊的服务也不要把 agent 当成普通的服务。它介于两者之间。它有状态但状态是任务级的不是服务级的它需要调度但调度粒度比 pod 细它需要编排但编排逻辑是运行时动态决定的不是编译期写死的。另一个体会是可观测性要前置。我一开始觉得 trace 是锦上添花后来发现没有步骤级 trace排查问题基本靠猜。现在我的做法是任何 agent 步骤不管多小都要打 trace。宁可多存点数据也不要出问题时两眼一抹黑。最后一个建议先用最小实现跑通一个真实场景再考虑抽象和通用化。我见过太多团队一上来就设计一个“通用 agent 运行时”结果抽象层次没找对做出来的东西既不好用也不通用。正确的路径是先让一个具体场景跑起来把坑踩一遍再从踩过的坑里提炼抽象。这样出来的设计才是接地气的。如果你现在手里正好有一个 K8s 集群和一堆 agent 逻辑不知道往哪放我建议你先别急着上框架。拿一个最简单的任务用 PostgreSQL 存状态用 NATS 派任务用 OTel 打 trace手动搭一个最小闭环。跑通之后你会发现很多所谓的“复杂问题”其实在最小实现里就已经暴露出来了而且解决起来并不难。难的是你一开始就想一步到位。