
1. 从“ax”这个标题说起一个被低估的运行时编排切口“ax”这个标题乍看像某个命令行工具的缩写或者某个内部项目的代号。但把热搜词摊开来看——agentic、orchestration、runtime、Kubernetes——这几个词凑在一起指向的其实是一个非常具体的工程命题在 Kubernetes 之上为 agentic 工作负载构建一套可编排的运行时层。这不是又一个 CRD 封装也不是简单的 Operator 套壳而是要解决“智能体任务如何在集群里被调度、被隔离、被观测、被回收”这一整条链路的问题。我最早接触这类需求是在一个内部平台项目里。当时团队想把几个 LLM 驱动的自动化任务跑在现有的 K8s 集群上最初的想法很朴素写个 Deployment把 agent 的镜像塞进去配个 Service 就完事。结果上线第三天就出问题了——任务之间互相抢 GPU 显存一个长尾任务把节点拖垮日志散落在各个 Pod 里根本串不起来更别提任务重试和状态恢复了。那次之后我才意识到agentic 工作负载和传统无状态服务在运行时特征上完全是两码事。所以这篇博文我想围绕“ax”这个切口把 agentic orchestration runtime 在 Kubernetes 上的落地思路完整拆一遍。适合谁看如果你正在做 AI 平台、任务编排、或者想把 agent 类任务跑进 K8s但被调度、隔离、观测这些问题卡住那这篇内容应该能帮你少走一些弯路。我会从整体设计讲到具体实现包括参数选择、踩坑记录和排查技巧尽量做到可以直接抄作业。2. 整体设计思路为什么不能直接用原生 K8s 跑 agent2.1 agentic 负载的三个特殊之处传统微服务是无状态的、短请求、可水平扩展。agentic 负载不一样它有三个很明显的特征长时运行且状态敏感一个 agent 任务可能跑几分钟到几十分钟中间要维护对话历史、工具调用结果、中间推理状态。Pod 一重启这些状态就没了。资源需求动态且异构有的步骤吃 CPU有的步骤要 GPU有的步骤只是等外部 API 返回。资源画像在任务生命周期内是变化的。任务之间有依赖和编排关系一个 agent 可能派生多个子任务子任务之间还有先后顺序和数据传递。这不是简单的 Pod 副本数能表达的。我试过直接用 Job CronJob 来跑结果是 Job 的完成语义和 agent 的“可能还要继续”语义对不上。Job 认为容器退出就是完成但 agent 可能只是阶段性结束后面还要被唤醒。这个矛盾在早期版本里让我踩了不少坑。2.2 为什么选择在 K8s 之上做 runtime 层有人会问既然原生 K8s 不顺手为什么不直接自己写个调度器我的判断是K8s 在节点管理、网络、存储、RBAC 这些基础设施层面已经足够成熟重新造轮子的成本远高于在它之上做一层 runtime 抽象。而且团队已有的运维体系、监控告警、日志采集都是围绕 K8s 建的脱离它反而增加维护负担。所以“ax”这个项目的核心思路是把 agentic 任务抽象成一种新的运行时对象由一层 runtime controller 负责生命周期管理底层仍然复用 K8s 的调度和隔离能力。这样既拿到了 K8s 的稳定性又补上了 agent 场景需要的编排语义。2.3 分层架构的取舍整体分成三层层级职责关键技术点编排层任务图解析、依赖调度、状态机管理自定义 CRD controller运行时层容器生命周期、资源配额、状态持久化Pod PVC sidecar基础设施层节点调度、网络、存储、隔离原生 K8s这个分层的好处是每一层可以独立演进。编排层改调度策略不影响运行时层运行时层换容器运行时也不影响编排逻辑。代价是层间接口要设计得足够清晰否则调试时会很痛苦——我后面会讲怎么用 trace ID 把三层串起来。3. 核心细节解析runtime 层到底要解决什么3.1 任务状态机的设计agent 任务不能只有“运行中/完成/失败”三个状态。实际需要的是这样一组状态Pending已创建等待调度Provisioning正在拉镜像、挂载存储Running主容器在执行Waiting等待外部事件或子任务Checkpointing正在保存状态Succeeded/Failed/Cancelled终态这里最关键的是Waiting和Checkpointing。Waiting让 Pod 可以缩容到零但任务不结束Checkpointing保证状态能落盘。我最初没设计Waiting结果 agent 等外部 API 的时候 Pod 一直占着资源集群利用率很低。状态机的实现建议用有限状态机 事件驱动而不是在 controller 里写一堆 if-else。我用的是一个轻量的状态机库把每个状态的进入/退出动作注册成回调controller 只负责投递事件。这样后面加状态不用改核心逻辑。3.2 状态持久化的三种方案对比agent 状态怎么存是个绕不开的问题。我实际试过三种方案优点缺点适用场景EmptyDir 定期快照实现简单读写快Pod 漂移后恢复慢短任务、可重算PVC 挂载状态持久Pod 重建可恢复存储成本高跨节点挂载有延迟长任务、状态重要外部状态存储如 Redis/对象存储与 Pod 解耦恢复快需要额外组件网络依赖高可用要求我的选择是PVC 为主 关键检查点写对象存储。PVC 保证 Pod 重建时本地状态还在对象存储保证节点级故障时还能恢复。检查点频率不要太高我一般设成每 30 秒或每完成一个子任务写一次太频繁会拖慢主流程。3.3 资源隔离与配额控制agent 任务最容易出的问题就是资源互相干扰。我的做法是每个任务一个 ResourceQuota限制 CPU、内存、GPU 上限防止单个任务吃满节点。GPU 用 device plugin 显式申请不要用共享模式agent 任务对显存很敏感。网络用 NetworkPolicy 隔离默认拒绝所有出站按需放行。这里有个细节K8s 的 ResourceQuota 是按 namespace 算的如果每个任务一个 namespacenamespace 数量会爆炸。我的折中是按团队或按任务类型分 namespace任务级配额用 LimitRange 自定义 admission webhook 控制。webhook 里校验任务请求的资源是否超过该类型的上限超了直接拒绝创建。4. 实操过程从零搭一个最小可用的 ax runtime4.1 环境准备与前置检查假设你已有一个 K8s 集群版本 1.26 以上。先确认几个前置条件# 检查节点资源和 GPU 插件 kubectl get nodes -o wide kubectl get pods -n kube-system | grep -E device-plugin|nvidia # 检查存储类 kubectl get storageclass # 检查 admission webhook 是否可用 kubectl get validatingwebhookconfigurations如果 GPU 插件没装先装对应的 device plugin。存储类至少要有一个支持 ReadWriteOnce 的PVC 才能正常挂载。4.2 定义 CRDAgentTask核心 CRD 我命名为AgentTask关键字段如下apiVersion: ax.io/v1alpha1 kind: AgentTask metadata: name: demo-task spec: image: agent-runtime:latest command: [python, -m, agent.main] resources: cpu: 2 memory: 4Gi gpu: 1 checkpoint: intervalSeconds: 30 storageClass: fast-ssd dependencies: - taskRef: upstream-task timeoutSeconds: 3600dependencies字段是编排层用的controller 会等上游任务成功后才创建这个任务的 Pod。checkpoint控制检查点行为。timeoutSeconds是硬超时防止任务卡死。4.3 controller 的核心 reconcile 逻辑controller 的 reconcile 循环大致是这样获取 AgentTask 对象如果不存在就返回。检查是否有未满足的依赖有就更新状态为Pending并重新入队。检查是否已有对应的 Pod没有就创建。根据 Pod 状态更新 AgentTask 状态。如果任务进入终态清理关联资源PVC 可选保留。关键点是幂等。reconcile 可能被多次触发创建 Pod 前一定要先查是否已存在。我用的是ownerReference label selector 来关联避免重复创建。4.4 状态检查点的实现检查点逻辑放在 agent 容器内部通过一个 sidecar 暴露的 HTTP 接口触发import requests import pickle def save_checkpoint(state, path/checkpoint/state.pkl): with open(path, wb) as f: pickle.dump(state, f) # 通知 sidecar 上传到对象存储 requests.post(http://localhost:8080/checkpoint, json{path: path})sidecar 收到请求后把文件同步到对象存储并更新 AgentTask 的 annotation 记录最新检查点位置。Pod 重建时init container 先从对象存储拉最新检查点再启动主容器。4.5 观测与日志串联三层架构最大的调试痛点是日志散落。我的做法是统一 trace ID在 AgentTask 创建时生成一个 UUID注入到 Pod 的 annotation 和容器环境变量。日志采集按 trace ID 聚合用 Fluent Bit 采集时带上 trace ID 标签查询时按标签过滤。关键事件写 Eventcontroller 在每个状态转换时写 K8s Event方便kubectl describe查看。这样出问题时先kubectl describe agenttask看事件再用 trace ID 查日志基本能定位到具体环节。5. 常见问题与排查技巧实录5.1 任务卡在 Pending 不动最常见的原因是依赖没满足或资源不足。排查顺序kubectl describe agenttask name看 Events 里有没有FailedScheduling。检查依赖任务状态kubectl get agenttask看上游是否成功。检查 ResourceQuotakubectl describe quota -n ns。我遇到过一次是 webhook 把请求拒了但没写清楚原因后来在 webhook 里加了详细错误信息才定位到。5.2 Pod 反复重启agent 任务重启通常是因为 OOM 或检查点恢复失败。先看kubectl logs --previous拿上次崩溃日志。如果是 OOM调大 memory limit 或优化 agent 的内存使用。如果是恢复失败检查对象存储里的检查点文件是否完整init container 的拉取逻辑是否有超时。5.3 GPU 申请了但用不上这个坑我踩过。原因是节点上的 device plugin 没正确上报或者 Pod 的 resource limit 写成了nvidia.com/gpu: 1但节点标签不匹配。排查kubectl describe node node | grep -A5 Allocatable kubectl get pod pod -o jsonpath{.spec.containers[*].resources}确认节点有 GPU 资源且 Pod 正确申请。5.4 检查点写入慢导致任务超时检查点写对象存储如果网络不好会很慢。我的优化是异步写 本地先落盘。主流程只写本地 PVCsidecar 异步上传。如果上传失败下次检查点会覆盖不影响主流程。另外检查点文件不要太大只存必要状态别把整个内存 dump 进去。5.5 常见问题速查表现象可能原因排查命令Pending 不动依赖未满足/资源不足kubectl describe agenttaskPod 反复重启OOM/检查点恢复失败kubectl logs --previousGPU 不可用device plugin 异常kubectl describe node检查点超时对象存储慢查 sidecar 日志日志串不起来trace ID 未注入检查 Pod annotation6. 一些实操心得与后续扩展方向跑了一段时间后我最大的体会是agentic runtime 的难点不在调度而在状态管理和可观测性。调度有 K8s 兜底但状态怎么存、怎么恢复、怎么让运维看得懂这些才是真正花时间的地方。我建议早期就把 trace ID 和检查点机制做进去后面加功能会轻松很多。另外如果你的任务图比较复杂可以考虑引入 DAG 编排引擎但不要一上来就上重型框架。我见过团队为了跑几个 agent 任务引入了一整套工作流引擎结果运维成本比业务本身还高。先从 CRD controller 做起够用再扩。后续可以扩展的方向任务优先级和抢占、跨集群调度、agent 之间的消息传递。这些我还在摸索等有成熟经验再单独写一篇。