
1. 从“ax”这个标题说起一个被低估的调度入口第一次看到“ax”这个标题很多人会以为是某个命令行工具的缩写或者某个内部项目的代号。但把热搜词摊开来看——ax、agentic、orchestration、kubernetes、cli——这几个词凑在一起指向的其实是一个非常具体的场景用一条极简的命令行入口把 agentic 工作负载调度到 Kubernetes 集群上跑起来。我最早接触这类需求是在做一个多智能体协作的自动化流水线时。当时团队里有人用脚本拼凑有人用现成的编排框架结果就是每个 agent 的启动方式都不一样日志散落在各处扩缩容全靠手动改 YAML。后来我们决定收敛到一个统一的 CLI 入口名字就叫ax。它要解决的问题很朴素让 agentic 任务的编排像kubectl apply一样简单但比裸写 Kubernetes 资源清单更贴近 agent 的语义。这篇文章不是官方文档的复述而是我把这套东西从零搭起来、踩过坑、调过参数之后的一份实操记录。适合两类人看一类是已经在用 Kubernetes 跑服务、想进一步把 agentic 工作流搬上去的工程师另一类是对 agentic orchestration 感兴趣、但被 Kubernetes 的复杂度劝退、想找一个更轻入口的开发者。全文会围绕ax这个 CLI 的设计思路、核心命令、调度细节、以及实际排障过程展开尽量把“为什么这么设计”讲透而不是只列命令。需要先说明一点ax在这里是一个概念性的 CLI 入口名称代表“agentic execution”这一类工具的设计范式。市面上已经有若干同类思路的工具比如 codex cli、claude cli 这类面向 agent 的命令行客户端它们解决的是“怎么跟 agent 对话”而ax这类工具解决的是“怎么把 agent 当成工作负载调度到集群里”。两者是互补关系不是替代关系。理解这一点后面的内容才不会跑偏。2. 为什么 agentic 工作负载需要专门的编排层2.1 传统 Kubernetes 编排和 agentic 任务的错配Kubernetes 原生的编排模型是围绕“长期运行的服务”设计的。一个 Deployment 管一组无状态 Pod一个 Service 给它们做负载均衡一个 HPA 根据 CPU 或自定义指标扩缩容。这套模型跑 Web 服务、跑 API 网关、跑消息消费者都很顺但一碰到 agentic 任务就开始别扭。agentic 任务有几个很鲜明的特征。第一生命周期短且不规则。一个 agent 可能跑 3 秒就结束也可能因为要等外部工具返回而挂起几分钟。第二状态是会话级的。同一个 agent 在多轮交互中要保持上下文不能像无状态 Pod 那样随便被替换。第三资源需求波动大。推理阶段吃 GPU工具调用阶段几乎不吃算力用固定的 resource request 去申请资源要么浪费要么不够。第四任务之间有依赖。一个 orchestrator agent 要等若干个 worker agent 返回结果才能继续这种 DAG 式的依赖用原生 Job 表达起来非常啰嗦。我试过直接用 Job ConfigMap 硬拼结果是每个 agent 都要写一份几乎一样的 YAML改一个超时参数要动十几个文件。后来换成用脚本生成 YAML又陷入了“脚本本身成了新维护负担”的循环。这就是ax这类工具存在的理由在 Kubernetes 之上加一层薄薄的、面向 agent 语义的抽象把重复的样板代码收进 CLI把真正需要差异化的部分留给用户。2.2 “薄抽象”和“厚框架”的取舍做编排层有两条路。一条是“厚框架”比如引入一套完整的 CRD自定义资源定义定义 Agent、Task、Workflow 等一堆新对象用户学一套新 API。另一条是“薄抽象”CLI 只负责把用户输入翻译成标准 Kubernetes 资源不引入新的 API 对象用户随时可以用kubectl接管。ax走的是第二条路。原因很实际团队里已经有人熟悉 kubectl再让他们学一套新 CRD 的调试方式成本太高。薄抽象的好处是出问题时你可以直接kubectl describe pod看底层状态不用先搞懂框架自己的状态机。坏处是一些高级的编排语义比如 agent 之间的消息传递需要靠约定而不是靠 API 强制容易写错。我的取舍标准是能用标准资源表达的绝不引入新 CRD只有标准资源表达起来会严重失真的才考虑加一层。比如 agent 的会话保持用 StatefulSet 的稳定网络标识就能近似实现没必要自己造一个 Session 对象。而 agent 之间的依赖关系用 Job 的dependsOn注解加一个轻量的控制器来解析也比引入完整的 Workflow CRD 更划算。2.3 热搜词里的信号agentic rag 和 karmada 毕业说明了什么热搜里出现了“agentic rag”和“karmada 正式毕业”这两个词其实透露了行业走向。agentic rag 指的是把检索增强生成RAG从“一次性问答”升级成“多步推理 工具调用”的 agent 流程这对编排的要求从“跑一个 Pod”变成了“跑一个有状态的、可能跨多轮的流程”。karmada 毕业则说明多集群调度正在成为基础设施的标配能力。把这两件事放在一起看ax这类 CLI 的定位就清楚了它要能在单集群里把 agentic 任务跑顺同时保留向多集群扩展的接口。我在设计时特意把“目标集群”作为一个显式参数而不是写死在配置里就是为了后面接多集群调度时不用大改。这个决定在当时看起来有点过度设计但后来做灰度发布时确实省了事。3. ax CLI 的核心命令设计与实操3.1 命令结构为什么是 run、ls、logs、rm 这四个ax的命令集我刻意压到最小只有四个核心动词run、ls、logs、rm。这不是偷懒而是遵循“一个 CLI 只做一件事”的原则。run负责把 agentic 任务提交到集群ls看当前有哪些任务在跑logs拉某个任务的输出rm清理任务及其关联资源。为什么不做scale、update这类命令因为 agentic 任务的扩缩容语义和普通服务不一样。一个 agent 任务要么在跑要么结束了很少需要“把副本数从 3 改成 5”。如果真需要并发跑多个相同 agent那应该在run的时候就用参数指定并发度而不是事后 scale。这个设计决定来自一次教训早期版本有scale命令结果用户把正在处理会话的 agent 强行扩容导致同一个会话被两个 agent 同时处理上下文直接错乱。后来干脆把scale砍掉改成run时用--parallel参数一次性声明。update同理。agent 的镜像或参数变了正确做法是停掉旧的、起新的而不是原地更新。原地更新会让正在处理的会话中断而且新旧版本的 agent 可能对同一份上下文的理解不一致。所以ax只提供rmrun的组合逼用户显式地做替换。3.2 run 命令的参数拆解与默认值选择run是使用频率最高的命令参数设计直接决定了好不好用。我把参数分成三类必填的身份类、常用的行为类、少用的调优类。身份类只有两个--name和--image。--name是任务名同时用作 Kubernetes 资源的名字前缀所以要求符合 DNS-1123 规范小写字母、数字、连字符。--image是 agent 的容器镜像。这两个必填是因为没有它们根本无法定位一个任务。行为类里最重要的是--parallel默认值是 1。这个默认值是有讲究的agentic 任务大多是有状态的默认串行执行最安全。如果用户明确知道任务无状态、可以并发再显式传--parallel N。另一个是--timeout默认 600 秒。为什么是 600 而不是 300 或 1800因为实测下来一个带工具调用的 agent 任务P95 耗时在 400 秒左右留一倍余量到 600 秒比较稳妥。超时太短会误杀正常任务太长会让卡死的任务占着资源不放。调优类包括--cpu、--memory、--gpu。这些默认都不设让 Kubernetes 用命名空间的默认值。只有用户明确知道 agent 需要 GPU 推理时才传--gpu 1。这里有个坑不要给 agent 设 CPU request 的默认值。因为 agent 在等待外部工具返回时几乎不耗 CPU设了 request 会导致节点上明明有闲置 CPU 却调度不上去。我一般建议用户对 agent 只设 memory requestCPU 用 limit 兜底就行。# 最简用法跑一个 agent串行600 秒超时 ax run --name summarizer --image registry.example.com/agent-summarizer:v1.2 # 带并发和 GPU 的用法 ax run --name batch-embedder --image registry.example.com/agent-embed:v2 \ --parallel 4 --gpu 1 --memory 8Gi --timeout 12003.3 ls 和 logs可观测性的最小闭环ls的输出我改过三版。第一版只列任务名和状态结果用户反馈“不知道跑了多久”。第二版加了启动时间又有人问“还剩多少时间”。第三版定稿为五列NAME、STATUS、AGE、PARALLEL、IMAGE。STATUS 只有四个值Pending、Running、Succeeded、Failed。不做更细的状态区分是因为更细的状态应该去kubectl describe看CLI 只给概览。logs的设计有个细节默认拉取的是聚合日志也就是把同一个任务下所有并行 Pod 的日志按时间戳合并。这个功能用kubectl logs加 label selector 也能做但需要用户自己拼命令。ax logs把它封装成一条命令并且默认--tail 100避免一次性拉出几万行把终端刷爆。如果要持续跟踪加--follow。提示ax logs的聚合是按 Pod 启动时间排序的不是严格的事件时间顺序。如果 agent 之间有跨 Pod 的因果依赖看聚合日志可能会误判先后关系。这种情况建议加--pod参数单独看某个 Pod 的日志。3.4 rm 的清理边界删到什么程度rm最容易出问题的地方是“删不干净”。早期版本只删 Job结果关联的 ConfigMap、Secret、PVC 都留在集群里跑几个月后命名空间里全是垃圾。后来改成默认删除任务关联的所有资源用 labelax-taskname来识别。但 PVC 是个例外默认不删 PVC因为里面可能有 agent 产生的中间数据误删代价太大。要删 PVC 必须显式加--purge-data。这个设计来自一次真实事故有个用户的任务名起得和另一个任务很像rm的时候手滑删错了把另一个任务正在用的 PVC 也清了导致那个任务的所有中间结果丢失。从那以后PVC 的删除就变成了显式操作。宁可让用户多打一个参数也不要让误操作不可逆。4. 调度到 Kubernetes 的关键实现细节4.1 从 CLI 参数到 Kubernetes 资源的映射ax run在底层做的事情是把 CLI 参数翻译成一组 Kubernetes 资源。核心映射关系如下表CLI 参数Kubernetes 资源字段说明--nameJobmetadata.name加ax-前缀避免冲突--imageJobspec.template.spec.containers[0].image直接透传--parallelJobspec.parallelism同时运行的 Pod 数--timeoutJobspec.activeDeadlineSeconds超时后 Job 被标记 Failed--memoryJobresources.requests.memory只设 request不设 limit--gpuJobresources.limits.nvidia.com/gpu需要节点有 GPU 标签--envConfigMapdata环境变量单独存便于复用这里有个关键选择用 Job 而不是 Pod 或 Deployment。Job 天然适合“跑完就结束”的任务而且自带重试和完成计数。agentic 任务虽然可能跑很久但本质上还是“有终态”的用 Job 语义最贴切。Deployment 会一直维持副本数任务结束后 Pod 被删了还会被拉起来完全不对。另一个选择是环境变量走 ConfigMap 而不是直接写在 Job 里。这样做的好处是同一份环境变量配置可以被多个任务复用改一处就全生效。坏处是多了一个资源要管理。我的判断是如果环境变量超过 5 个就值得抽成 ConfigMap少于 5 个直接写在 Job 里更省事。ax默认走 ConfigMap但提供--inline-env开关让用户选择内联。4.2 会话保持用 headless Service 还是 StatefulSetagentic 任务如果需要多轮交互就得保证同一个会话总是落到同一个 Pod 上。Kubernetes 里实现这个有两条路headless Service 客户端侧的一致性哈希或者 StatefulSet 稳定网络标识。我选的是StatefulSet 的简化版当--parallel 1且任务声明了--session时ax会创建一个带serviceName的 StatefulSet而不是 Job。这样 Pod 的名字是稳定的ax-name-0网络标识也是稳定的。客户端只要记住这个标识就能一直找到同一个 Pod。为什么不直接用 headless Service因为 headless Service 只解决“找到 Pod”不解决“Pod 重建后还是同一个身份”。StatefulSet 的podManagementPolicy: Parallel加上稳定的 PVC能保证 Pod 重建后挂载同一份数据会话上下文不会丢。这个细节在官方文档里不会强调但实际做有状态 agent 时非常关键。注意StatefulSet 的 Pod 重建后 IP 会变但 DNS 名不变。所以 agent 之间通信一定要用 DNS 名不要缓存 IP。我见过有人图省事在代码里硬编码 Pod IP结果 Pod 一重建整个流程就断了。4.3 资源申请的计算过程以 GPU agent 为例假设你要跑一个带 GPU 推理的 agent模型是 7B 参数用 FP16 加载。显存占用怎么算模型权重7B × 2 字节 14 GB。KV cache假设 batch size 为 4序列长度 2048层数 32头数 32头维度 128那么 KV cache 约为 4 × 2048 × 32 × 32 × 128 × 2 × 2 字节 ≈ 4.3 GB。再加上推理框架本身的开销约 2 GB总计约 20 GB。所以--gpu 1对应的节点显存至少要 24 GB留一点余量。CPU 和内存方面agent 的 Python 进程本身吃 1-2 GB 内存工具调用的子进程再吃 1 GB所以--memory 4Gi是起步值。CPU 不设 request只设 limit 2 核防止某个 agent 死循环把节点拖垮。这套计算不是拍脑袋而是每次上新模型时都要重新算一遍。我一般会先用一个--dry-run模式把资源清单打出来人工核对一遍再真正提交。ax的--dry-run会输出完整的 YAML方便你检查。4.4 多集群调度的预留接口虽然当前ax只调度到单集群但我在设计时留了--context参数对应 kubeconfig 里的 context 名。默认用当前 context指定后可以切到另一个集群。这个参数现在看起来多余但等你要做灰度发布——比如 10% 的 agent 跑在新集群、90% 跑在老集群——就会发现它很有用。karmada 毕业带来的多集群能力理论上可以通过--context逐个指定来实现但更优雅的方式是接一个多集群调度器。ax目前没做这层因为大多数团队的单集群还没跑满过早引入多集群只会增加复杂度。我的建议是先把单集群的调度跑稳等真的遇到单集群容量瓶颈或隔离需求时再考虑多集群。5. 实操全流程从零跑通一个 agentic 任务5.1 环境准备与前置检查在跑ax之前有几项前置条件必须确认。第一kubeconfig 指向的集群版本不低于 v1.26.0。为什么是 1.26因为ax用到了 Job 的activeDeadlineSeconds和 StatefulSet 的podManagementPolicy的一些行为在 1.26 之前有细微差异。第二集群里要有可用的 StorageClass否则带--session的任务会因为 PVC 无法绑定而卡在 Pending。第三如果要用 GPU节点上要装好对应的设备插件并且有nvidia.com/gpu这个资源可调度。检查命令很简单# 看集群版本 kubectl version --short # 看 StorageClass kubectl get storageclass # 看 GPU 资源 kubectl get nodes -o json | jq .items[].status.allocatable[nvidia.com/gpu]如果这三项都正常就可以装ax了。ax本身是一个静态编译的二进制下载后放到 PATH 里即可不需要额外的运行时依赖。这一点和 codex cli 那类工具不同——后者通常需要 Node 或 Python 运行时装的时候容易遇到“unable to locate the codex cli binary or required runtime components”这类报错。ax刻意做成无依赖的就是为了避开这类环境问题。5.2 第一个任务跑一个最简单的 echo agent先用一个最简单的镜像验证链路通不通。这个镜像只做一件事打印环境变量睡 5 秒退出。ax run --name hello-ax \ --image registry.example.com/agent-echo:v1 \ --env GREETINGhello \ --timeout 60提交后立刻ax ls应该能看到hello-ax处于 Pending 或 Running。等 5 秒后再看变成 Succeeded。然后ax logs hello-ax应该能看到GREETINGhello这行输出。这一步看起来简单但能验证四件事CLI 到集群的网络通不通、镜像能不能拉下来、Job 能不能正常调度、日志能不能聚合。任何一环出问题都会在这一步暴露。我建议每个新环境都先跑这个 hello 任务别一上来就上复杂的 agent。5.3 带会话的 agent验证状态保持第二个任务验证会话保持。用一个会读写本地文件的 agent第一次运行写入一个计数器第二次运行读取并加一。# 第一次运行 ax run --name counter --image registry.example.com/agent-counter:v1 \ --session --timeout 120 # 等它 Succeeded 后再跑一次同名任务 ax run --name counter --image registry.example.com/agent-counter:v1 \ --session --timeout 120因为用了--sessionax会创建 StatefulSet 而不是 JobPVC 会保留。第二次运行时Pod 重建但挂载同一个 PVC所以能读到第一次写入的值。ax logs counter应该显示计数从 1 变成 2。这里有个容易踩的坑第二次run同名任务时如果第一次的 StatefulSet 还没被清理会冲突。ax的处理方式是检测到同名资源存在时先删旧的再建新的但 PVC 保留。这个行为要记牢否则会以为任务没跑起来。5.4 并行任务与结果聚合第三个任务验证并行。用一个把输入切成 N 份、每份独立处理的 agent。ax run --name parallel-demo --image registry.example.com/agent-chunk:v1 \ --parallel 4 --env INPUT/data/big.txt --timeout 300--parallel 4会让 Job 的parallelism设为 4同时起 4 个 Pod。每个 Pod 处理输入的一部分。处理完后ax logs parallel-demo会把 4 个 Pod 的日志聚合在一起。如果每个 Pod 输出一行结果聚合后就是 4 行。并行任务的关键是结果怎么汇总。ax本身不做汇总它只负责把日志聚合起来。真正的汇总逻辑要写在 agent 里比如每个 Pod 把结果写到一个共享的 PVC或者写到一个外部存储。我一般建议写到外部对象存储因为 PVC 的 ReadWriteMany 支持取决于 StorageClass不是所有集群都有。6. 常见问题与排查技巧实录6.1 任务一直 Pending 的排查路径Pending 是最常见也最让人抓狂的状态。排查顺序应该是先看事件再看资源最后看调度约束。# 第一步看 Pod 事件 kubectl describe pod -l ax-taskname # 第二步看节点资源 kubectl describe nodes | grep -A 5 Allocated resources # 第三步看是否有污点或亲和性限制 kubectl get pod -l ax-taskname -o json | jq .items[].spec.affinity八成的情况是资源不够。特别是 GPU 任务如果节点上 GPU 已经被占满新 Pod 就会一直 Pending。这时候要么等要么加节点。还有一种情况是 PVC 绑不上事件里会写waiting for first consumer to be created这是正常的延迟绑定等 Pod 调度后就会绑上。6.2 任务超时被杀的几种原因--timeout到了之后Job 会被标记 FailedPod 被终止。但超时不一定意味着任务真的卡死了可能是agent 在等一个永远不返回的外部调用agent 陷入了重试循环资源不足导致处理速度极慢区分方法是看日志的最后几行。如果最后一行是“waiting for tool response”那就是外部依赖问题如果是“retrying attempt N”那就是重试逻辑有问题如果日志根本没输出到超时点那就是资源问题。我的经验是给 agent 加一个内部的软超时比依赖 Job 的硬超时更好。软超时到了之后agent 可以优雅地保存中间状态再退出而不是被 SIGKILL 直接杀掉。ax的--timeout是硬超时作为最后兜底软超时要在 agent 代码里自己实现。6.3 日志聚合乱序的应对前面提过ax logs的聚合是按 Pod 启动时间排序的。如果任务之间有严格的因果顺序这个排序会误导你。应对方法是给每条日志加一个单调递增的序列号聚合后按序列号重排。# agent 里输出日志时带上序列号 import time seq int(time.time() * 1000) print(f[seq{seq}] processing chunk {i})然后在本地用sort -t -k2 -n重排。这个技巧在调试多 agent 协作时特别有用能还原出真实的事件顺序。6.4 常见问题速查表现象可能原因排查命令解决方式Pending 超过 5 分钟资源不足 / PVC 未绑定kubectl describe pod加节点或减小 request日志为空镜像里没输出到 stdoutkubectl logs pod检查 agent 的日志配置任务反复重启镜像启动命令有误kubectl get pod -w检查 entrypointGPU 任务调度失败节点无 GPU 或插件未装kubectl describe node装设备插件或换节点rm 后资源残留PVC 默认不删kubectl get pvc -l ax-task加--purge-data同名任务冲突旧 StatefulSet 未清理ax ls先ax rm再ax run6.5 几个我踩过的坑第一个坑是镜像 tag 用 latest。有一次 agent 镜像更新了但 tag 还是 latest结果新起的 Pod 拉到了新镜像行为和老 Pod 不一致导致同一批任务里有的成功有的失败。从那以后ax强制要求--image带明确的 tag不允许 latest。第二个坑是环境变量里有特殊字符。有个 agent 的 API key 里带了$直接写在--env里被 shell 解释了。后来改成从文件读--env-file参数就是那时候加的。第三个坑是并行任务的输出互相覆盖。4 个 Pod 同时往同一个文件写结果内容交错。解决办法是每个 Pod 写自己的文件最后再合并。这个逻辑ax不管得在 agent 里做。7. 和 codex cli、claude cli 这类工具的配合方式7.1 定位差异客户端 vs 调度器codex cli 和 claude cli 是面向“人机对话”的客户端你在终端里跟 agent 交互它帮你调工具、写代码。ax是面向“任务调度”的入口你把 agent 当成一个工作负载提交到集群它帮你管生命周期。两者不在一个层面上不存在谁替代谁。实际用法是用 codex cli 或 claude cli 做开发和调试用ax做批量执行和调度。比如你要跑 1000 个代码审查任务不可能在终端里一个个对话这时候就把审查逻辑封装成 agent 镜像用ax run --parallel 20批量跑。7.2 把 cli 的配置复用到 ax 任务里codex cli 和 claude cli 通常有自己的配置文件里面存了模型端点、API key、超时设置。这些配置在ax任务里也要用。我的做法是抽出一个共享的 ConfigMap两边都挂载同一份。apiVersion: v1 kind: ConfigMap metadata: name: agent-shared-config data: MODEL_ENDPOINT: https://model.example.com/v1 TIMEOUT_SECONDS: 300ax run时用--config agent-shared-config挂载。这样改一处cli 和集群任务同时生效不会出现“本地能跑、集群跑不了”的配置漂移。7.3 安装环节的避坑codex cli 安装时常见的报错是“unable to locate the codex cli binary or required runtime components”这通常是 PATH 没配好或者运行时版本不对。ax因为是静态二进制不会有这个问题。但如果你在 agent 镜像里同时装了 codex cli就要注意镜像的基础层里要有对应的运行时。我的建议是agent 镜像尽量精简只装任务真正需要的东西。如果任务只是调模型 API就不需要装完整的 cli 工具链。镜像越小拉取越快启动越稳。8. 后续可以扩展的方向ax目前只做了最核心的调度功能但有几个方向值得往下走。第一个是任务依赖现在只能靠用户自己控制提交顺序未来可以加--depends-on参数让ax自动解析 DAG 并按序提交。第二个是结果回传现在结果只能通过日志或外部存储拿未来可以加--output参数把结果直接写到指定位置。第三个是成本追踪给每个任务打上成本标签跑完后能算出这次任务花了多少资源。不过这些都是后话。我的原则是先把当前这四个命令用熟等真的遇到瓶颈了再扩展。过早加功能只会让 CLI 变复杂而复杂是可用性最大的敌人。我在实际使用中发现80% 的场景用runlslogs就够了rm都很少用因为任务跑完 Job 会自动清理 Pod只有 PVC 需要手动清。所以如果你刚开始用先把这三个命令练熟比什么都强。