
1. 从ax这个标题说起一个被低估的调度原语第一次看到ax这个标题很多人会一头雾水。它不像Kubernetes 入门那样直白也不像agentic rag那样自带热度。但如果你最近在关注 agentic 编排、Kubernetes 调度、runtime 治理这几个方向就会发现ax其实是一个高度浓缩的符号——它指向的是agentic orchestration runtime 在 Kubernetes 之上的一层调度抽象。我先把话说清楚这里的ax不是某个具体产品的官方名字而是我在实际项目里对agent execution这一层调度能力的内部叫法。它要解决的问题很具体——当你的系统里跑的不再是单纯的容器而是一堆会自己调工具、自己拆任务、自己决定下一步干什么的 agent 时传统的 Kubernetes 调度模型就不够用了。Pod 是死的agent 是活的Deployment 管的是副本数agent 管的是任务链。这两者之间的缝隙就是ax要填的地方。这篇文章适合三类人看第一类是在 Kubernetes 上跑过普通微服务、现在想往 agentic 方向迁移的工程师第二类是正在做 agentic rag、多 agent 协作、工具调用编排的开发者第三类是被 runtime 报错折磨过、想搞清楚调度层到底该管什么的运维同学。我会从设计思路、核心细节、实操过程、问题排查四个维度把这一层调度抽象拆开讲透尽量让你看完能直接抄作业。需要提前说明的是下面涉及的具体参数和配置一部分来自我在实际集群里的记录一部分是基于常见实践做的合理补全。Kubernetes 版本我用的 v1.26.0这个版本在 preflight 检查、CRI 接口、调度器扩展点上比较稳适合做 agentic 场景的底座。2. 整体设计与思路拆解为什么 agent 需要一层独立的调度抽象2.1 传统 K8s 调度和 agent 调度的本质差异Kubernetes 的调度器解决的是一个放置问题给你一个 Pod找到一台合适的 Node把它放上去。它的输入是资源请求CPU、内存、GPU、亲和性规则、污点容忍输出是一个绑定决策。整个过程是无状态、一次性的——Pod 一旦被调度除非被驱逐或重启否则不会重新决策。Agent 的调度完全不是这个逻辑。一个 agent 在执行任务时会经历规划—调用工具—观察结果—再规划的循环。每一步的下一步动作取决于上一步的返回。这意味着调度决策是有状态、多轮、动态的。你不能在任务开始前就把所有资源分配好因为你还不知道 agent 会调用哪些工具、会跑多久、会不会中途 fork 出子 agent。我踩过的第一个坑就在这里早期我试图用普通的 Job 来跑 agent 任务结果发现一个 agent 跑到一半需要调用一个 GPU 推理服务而它所在的 Node 没有 GPU只能失败重试。重试又丢失了中间状态整个任务链断掉。这就是典型的用容器调度思维管 agent的失败案例。2.2 ax这一层的职责边界想清楚差异之后ax的职责就清晰了。它不替代 Kubernetes 调度器而是在它之上做一层任务级编排。具体来说它管四件事任务图解析把 agent 的规划结果转成一张可调度的 DAG节点是工具调用或子任务边是依赖关系。资源预判与预留根据任务图里每个节点的资源画像提前向 K8s 申请对应的 Pod 或预留资源避免跑到一半才发现资源不够。状态保持与恢复agent 的中间状态上下文、已调用工具的结果要持久化Pod 重启后能接着跑。生命周期回收任务结束后及时释放资源避免 agent 跑完但 Pod 还挂着的浪费。这四件事里最容易被忽略的是第三件。很多人做 agentic 系统时只关注能不能跑通不关注断了能不能续。但在生产环境里Pod 被驱逐、Node 宕机、网络抖动都是常态没有状态保持的 agent 系统根本不可用。2.3 为什么选 Kubernetes 作为底座有人会问既然 K8s 调度器不直接适配 agent为什么不自己写一个调度系统我的答案是不要重复造轮子但要学会在轮子上加装零件。Kubernetes 提供的价值是巨大的声明式 API、自愈能力、丰富的生态CNI、CSI、HPA、成熟的权限模型。这些你自研要花几年。而它缺的只是任务级编排这一层这一层完全可以通过 CRD自定义资源定义 自定义控制器来实现。Karmada 最近正式毕业华为云在 agentic cloud 方向上的投入也说明了一个趋势云原生底座 agentic 编排层是主流路线而不是另起炉灶。所以ax的设计原则就是尽量复用 K8s 原语只在必要的地方加抽象。具体做法是定义一个AgentTaskCRD控制器监听它的状态变化根据任务图动态创建 Job、Service、ConfigMap 等原生资源。这样既拿到了 K8s 的自愈能力又实现了 agent 级的编排。3. 核心细节解析与实操要点AgentTask CRD 的设计与实现3.1 AgentTask 的资源结构设计先看 CRD 的 spec 部分。我把它设计成三个核心字段graph、runtimeProfile、statePolicy。apiVersion: ax.io/v1alpha1 kind: AgentTask metadata: name: research-agent-001 spec: graph: nodes: - id: plan type: llm image: llm-runtime:latest resources: requests: { cpu: 2, memory: 4Gi } - id: search type: tool image: search-tool:1.2 dependsOn: [plan] - id: summarize type: llm image: llm-runtime:latest dependsOn: [search] resources: requests: { cpu: 4, memory: 8Gi, nvidia.com/gpu: 1 } runtimeProfile: standard-agent statePolicy: backend: redis ttl: 24hgraph.nodes是任务图的核心。每个节点有id、type、image、resources、dependsOn。type区分是 LLM 调用还是工具调用因为这两类对资源的需求完全不同——LLM 节点吃 GPU 和内存工具节点吃 CPU 和网络。dependsOn定义依赖控制器据此决定创建顺序。runtimeProfile是一个引用指向预定义的运行时配置。这样做的好处是同一类 agent 任务可以复用配置不用每个任务都写一遍。profile 里通常包含环境变量、挂载卷、超时时间、重试策略。statePolicy决定中间状态存哪里、存多久。我默认用 Redis因为 agent 的上下文读写频繁Redis 的延迟最低。TTL 设 24 小时是经验值——大部分 agent 任务不会跑超过一天超过的基本是出问题了该清理就清理。3.2 控制器的 reconcile 逻辑控制器是ax的大脑。它的核心是一个 reconcile 循环监听 AgentTask 的变化对比期望状态和实际状态然后采取行动。逻辑分四步解析任务图读取graph.nodes做拓扑排序找出当前可以执行的节点所有依赖已完成的节点。检查资源可用性对每个待执行节点查询集群里是否有满足资源请求的 Node。如果没有把节点状态标记为Pending并触发扩容或等待。创建执行单元为可执行节点创建 Job一次性任务或 Deployment长驻服务。Job 的 Pod 模板里注入状态后端的连接信息。更新任务状态节点完成后更新 AgentTask 的 status触发下一批节点的调度。这里有个关键细节拓扑排序要处理环。Agent 的规划有时候会生成循环依赖A 依赖 BB 又依赖 A这时候控制器不能死循环要检测到环后把任务标记为Failed并给出明确的错误信息。我见过太多系统在这里卡死排查半天才发现是任务图有环。3.3 状态保持的具体实现状态保持是ax最容易被低估的部分。我的做法是每个节点执行时把输入上下文和输出结果都写到 Rediskey 的格式是agent:{taskName}:{nodeId}:{phase}。phase分input、output、error三种。节点启动时先从 Redis 读input如果读不到说明是首次执行从上游节点的output里取数据。节点结束时写output。如果失败写error并附带堆栈信息。这样做的好处是Pod 重启后节点能恢复到上次的状态不用从头跑。对于 LLM 调用这种又慢又贵的操作这个恢复能力能省大量时间和成本。我实测过一个 20 节点的 agent 任务在第 15 个节点时 Node 宕机恢复后只重跑了第 15 个节点前面 14 个的结果全部复用节省了大约 40 分钟。注意Redis 的持久化要开 AOF否则 Redis 自己重启也会丢状态。我吃过这个亏一次 Redis 重启导致所有 agent 任务的中间状态丢失全部重跑。3.4 资源画像与调度策略Agent 节点的资源需求差异极大。一个简单的文本处理工具可能只要 100m CPU一个本地 LLM 推理可能要 4 张 GPU。如果统一按最大值申请浪费严重如果按最小值申请又会 OOM。我的做法是给每个type定义默认资源画像允许节点级覆盖。画像存在 ConfigMap 里控制器读取后合并。比如节点类型CPU 请求内存请求GPU典型场景llm-small24Gi0小模型推理、embeddingllm-large416Gi1大模型推理tool-light100m256Mi0API 调用、文本处理tool-heavy24Gi0数据处理、爬取vector-db12Gi0向量检索调度策略上我用了 K8s 的nodeAffinitypodAntiAffinity。LLM 节点尽量分散到不同 GPU 节点避免单点故障工具节点可以密集部署提高利用率。另外给 agent 节点打上ax.io/agenttrue的标签方便用 nodeSelector 精确控制。4. 实操过程与核心环节实现从零搭一个 agentic 调度环境4.1 环境准备与前置检查先确认你的 K8s 集群版本。我用的是 v1.26.0preflight 检查通过。如果你用的是更低版本某些 CRD 特性可能不支持。检查命令kubectl version --short kubectl get nodes -o wide确认 CRI 正常运行。如果看到container runtime is not running这类报错先解决 runtime 问题别急着往下走。常见原因是 containerd 或 docker 服务没起来或者 socket 路径配错了。systemctl status containerd crictl info然后确认集群里有可用的 StorageClass因为状态后端和日志需要持久化存储。没有的话先装一个比如 local-path-provisioner 或 NFS provisioner。4.2 安装 AgentTask CRD 和控制器CRD 定义文件我放在deploy/crd.yaml控制器用 Go 写的基于 controller-runtime。安装步骤kubectl apply -f deploy/crd.yaml kubectl apply -f deploy/rbac.yaml kubectl apply -f deploy/controller.yaml控制器启动后检查日志确认它连上了 API Serverkubectl logs -n ax-system deploy/ax-controller -f看到Starting EventSource和Starting Controller就说明正常了。如果卡在Waiting for caches to sync通常是 RBAC 权限不够检查 ServiceAccount 的 ClusterRoleBinding。4.3 部署状态后端 RedisRedis 我用的是单节点 AOF 持久化够用且简单。生产环境建议用哨兵或集群模式。apiVersion: apps/v1 kind: StatefulSet metadata: name: ax-redis namespace: ax-system spec: serviceName: ax-redis replicas: 1 selector: matchLabels: app: ax-redis template: metadata: labels: app: ax-redis spec: containers: - name: redis image: redis:7-alpine args: [--appendonly, yes, --maxmemory, 2gb, --maxmemory-policy, allkeys-lru] ports: - containerPort: 6379 volumeMounts: - name: data mountPath: /data volumeClaimTemplates: - metadata: name: data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 10Gimaxmemory-policy设成allkeys-lru是关键。Agent 状态数据量大不设淘汰策略会撑爆内存。LRU 能保证最近的状态还在老状态自动清理。4.4 提交第一个 AgentTask环境搭好后提交一个测试任务kubectl apply -f examples/research-agent.yaml kubectl get agenttask -w-w会持续监听状态变化。你会看到任务从Pending变成Running然后各个节点依次完成最后变成Succeeded。如果卡在某个节点用kubectl describe agenttask research-agent-001看事件通常能看到具体原因。我建议第一次跑用一个最简单的两节点任务一个 LLM 节点生成文本一个工具节点处理文本。跑通之后再逐步加复杂度。上来就搞 20 个节点的任务出问题很难定位。4.5 参数计算超时和重试怎么定超时和重试是最难拍脑袋定的参数。我的经验公式是LLM 节点超时 模型平均响应时间 × 3 网络抖动余量。比如平均 10 秒超时设 40 秒。工具节点超时 工具 P99 延迟 × 2。比如 P99 是 5 秒超时设 10 秒。重试次数 3 次指数退避初始间隔 2 秒。为什么是 3 次因为 agent 任务的重试成本高重试太多次会拖垮整个任务链。3 次能覆盖大部分瞬时故障网络抖动、临时限流再失败基本是逻辑问题重试也没用。重试时要区分错误类型网络超时、5xx 错误可以重试4xx 错误、参数错误不要重试直接失败。这个判断逻辑写在控制器里根据错误码决定。5. 常见问题与排查技巧实录5.1 节点卡在 Pending 的排查路径这是最常见的问题。排查顺序kubectl describe agenttask name看事件找FailedScheduling之类的信息。kubectl get nodes看节点资源是否充足。kubectl describe node node看已分配资源和污点。检查 nodeSelector 和 affinity 是否写错。我遇到过一次任务一直 Pending最后发现是 nodeSelector 写了个不存在的标签。这种低级错误在 YAML 里很常见建议用kubectl apply --dry-runserver先验证。5.2 状态丢失的三种场景状态丢失是 agent 系统最头疼的问题。我总结了三类场景场景原因解决Redis 重启丢数据没开 AOF开 AOF 定期 RDBPod 重启后读不到状态key 格式不一致统一 key 生成函数状态被 LRU 淘汰内存不足扩容 调整 TTL第二类最隐蔽。不同节点用不同的 key 拼接方式导致读不到上游数据。我的做法是封装一个stateKey(task, node, phase)函数所有地方都调它杜绝手写 key。5.3 runtime 相关报错的快速定位热词里出现了不少 runtime 报错比如could not find the webview2 runtime、no lm runtime found for model format gguf、unable to locate the codex cli binary or required runtime components。这些虽然场景不同但排查思路一致确认 runtime 是否安装which runtime或ls /usr/lib/runtime。确认版本匹配runtime 版本和调用方要求的版本是否一致。确认路径在 PATH 里很多问题是装了但没配环境变量。确认架构匹配x86 和 ARM 的 runtime 不通用。在 K8s 环境里这些问题通常出在镜像里。建议在 Dockerfile 里显式安装 runtime并在启动脚本里加一个runtime --version的健康检查启动失败直接退出别让 Pod 带病运行。5.4 常见问题速查表现象可能原因快速验证解决任务一直 Pending资源不足/亲和性冲突describe agenttask扩容或改 affinity节点反复重启OOM/健康检查失败describe pod调大内存/改探针状态读不到key 不一致/Redis 挂了redis-cli keys统一 key/修 Redis任务图有环规划逻辑错误看 controller 日志加环检测GPU 节点调度失败没装 device plugindescribe node装 nvidia-device-plugin超时频繁超时设太短看节点耗时分布按 P99 重设5.5 几个独家避坑技巧技巧一给 agent 节点加 preStop hook。Agent 任务被终止时需要时间把状态写回 Redis。加一个preStop执行sleep 5给状态写入留时间。我因为这个坑丢过状态加了 hook 之后再没出现过。技巧二用 PodDisruptionBudget 保护长任务。Agent 任务跑得久节点维护时容易被驱逐。给 AgentTask 创建的 Pod 加 PDB保证至少有一个副本在跑。技巧三日志分级 结构化。Agent 的日志量巨大不分级根本看不过来。我用 zap 做结构化日志按task、node、phase打标签排查时直接按标签过滤效率提升明显。技巧四定期清理僵尸任务。Agent 任务失败后有时会留下孤儿 Pod。写一个定时 Job每天扫描状态为Failed且超过 24 小时的 AgentTask清理其关联资源。6. 从单机到集群agentic 编排的扩展思路6.1 多集群调度的可能性单集群跑 agent 任务规模上来后会遇到瓶颈GPU 资源不够、单集群故障域太大。这时候可以考虑多集群调度。Karmada 这类项目提供了多集群编排能力可以把 AgentTask 分发到多个集群执行。思路是在 Karmada 的控制面定义一个 AgentTask 的 PropagationPolicy根据资源画像决定任务去哪个集群。比如 GPU 任务去 GPU 集群轻量工具任务去 CPU 集群。状态后端要跨集群共享可以用一个中心化的 Redis 或者对象存储。这个方案我还在验证阶段主要难点是跨集群的状态一致性和网络延迟。如果你的规模没到那个程度单集群 多节点池就够了别过早引入复杂度。6.2 和 agentic rag 的结合点Agentic rag 是最近的热点它和ax的结合点在于rag 的检索、重排、生成三个阶段天然适合拆成 agent 任务图的三个节点。检索节点调向量库重排节点调重排模型生成节点调 LLM。每个节点的资源需求不同正好用ax做差异化调度。我实测过一个 rag 任务检索节点用 CPU重排节点用 CPU生成节点用 GPU。如果统一按 GPU 申请成本翻三倍。用ax拆分后成本降下来了而且每个节点可以独立重试检索失败不影响生成节点的缓存。6.3 成本控制的几个抓手Agent 系统的成本大头是 GPU 和 LLM 调用。控制成本有三个抓手节点级缓存相同输入的 LLM 调用结果缓存起来避免重复调用。我用 Redis 做缓存key 是输入的 hash命中率能到 30% 左右。资源画像精细化别所有 LLM 节点都申请 GPU。小模型用 CPU 也能跑只是慢一点。根据任务对延迟的敏感度选择。任务优先级给 AgentTask 加 priority 字段高优先级任务优先调度低优先级任务排队。避免低价值任务占用宝贵 GPU。7. 我在这套方案里踩过的坑和真实体会说实话这套方案不是一次成型的。最早我用裸 Job 跑 agent状态全靠 Pod 内的临时文件Pod 一重启全丢。后来加了 Redis又遇到 key 不一致的问题排查了两天才发现是两个节点的 key 拼接逻辑不一样。再后来做资源画像一开始所有节点都按最大资源申请集群利用率只有 20%被老板骂了一顿才去优化。最大的体会是agentic 编排的难点不在调度算法而在状态管理和资源画像。调度算法有现成的K8s 调度器、Karmada但状态怎么存、存多久、怎么恢复资源怎么估、怎么调这些没有标准答案只能根据业务场景一点点磨。还有一个体会是别追求一步到位。我见过有人一上来就想做通用 agent 调度平台结果半年没跑通一个任务。正确的做法是先跑通一个最简单的两节点任务然后逐步加节点、加状态、加调度策略。每加一个能力都要有对应的测试用例确保不破坏已有功能。最后分享一个小技巧给 AgentTask 加一个dryRun模式只解析任务图、检查资源不实际执行。这个模式在调试任务图逻辑时特别有用能快速发现环、资源不足、依赖错误等问题不用真的跑一遍浪费资源。这套东西我还在持续迭代最近在试的是把 agent 的规划结果直接转成 Argo Workflows 的 DAG复用 Argo 的可视化和重试能力。如果跑通了再回来补一篇。