☰
Kubernetes 上 agentic 工作负载的调度与编排:ax 原语实践
2026/9/28 16:50:43 网站建设 项目流程

1. 从“ax”这个标题说起:一个被低估的调度原语

第一次看到“ax”这个标题,很多人会以为是某个命令行工具的缩写,或者某个内部项目的代号。但把热搜词摊开来看——ax、agentic、orchestration、kubernetes、workspace——这几个词凑在一起,指向的其实是一个非常具体的技术命题:在 Kubernetes 之上,为 agentic 工作负载做一套调度与编排层。ax 在这里不是一个工具名,而是“agent execution”的缩写形态,是围绕 agent 执行单元构建的一层调度抽象。

我接触这个方向是从一个很实际的问题开始的:团队里跑了一批基于大模型的 agent 任务,每个 agent 需要独立的 workspace、独立的依赖环境、独立的生命周期管理。最开始用裸 Pod 硬扛,结果就是资源碎片化严重,一个 agent 卡住整个节点跟着遭殃,扩缩容全靠人肉盯。后来换成 Job + CronJob,又发现 agent 之间的依赖关系没法表达,A 的输出要喂给 B,B 又要等 C 的中间结果,这种 DAG 式的编排用原生工作负载描述起来极其别扭。

ax 要解决的就是这一层问题。它不是一个全新的调度器,而是在 Kubernetes 现有调度框架之上,加了一层面向 agent 的编排语义。你可以把它理解成:Kubernetes 负责“把容器跑起来”,ax 负责“让 agent 按正确的顺序、正确的依赖、正确的资源约束跑起来”。这个定位决定了它的核心能力集中在三块——workspace 隔离、agent 生命周期编排、以及跨节点的调度决策。

适合读这篇的人有三类:一是已经在 Kubernetes 上跑 AI 工作负载、但被编排问题折磨的工程师;二是正在评估 agentic 基础设施选型的技术负责人;三是对 orchestration 层设计感兴趣、想自己造轮子的开发者。不管你是哪一类,下面这些从实际踩坑里攒出来的东西应该都能用上。

2. 整体设计思路:为什么是在 Kubernetes 上做加法而不是重造

2.1 调度层的选型逻辑:复用还是自建

做 agentic orchestration,第一个要回答的问题就是:调度层是自己写还是复用 Kubernetes。我见过不少团队一上来就想自建调度器,理由是“Kubernetes 太重了,agent 任务很轻”。这个判断在早期看起来成立,但跑到一定规模就会翻车。

自建调度器最直接的代价是你要重新实现一遍资源模型、亲和性、污点容忍、优先级抢占这些东西。这些在 Kubernetes 里已经打磨了快十年,自己写一版能用的可能只要两周,但写到能扛住生产环境的边界情况,半年都打不住。ax 选择在 Kubernetes 上做加法,本质上是把“通用调度”和“agent 语义”拆开:通用部分交给 kube-scheduler,agent 特有的依赖编排、workspace 生命周期、执行单元分组交给 ax 自己的 controller。

这个拆分的收益在扩缩容场景下特别明显。agent 任务的资源画像和普通微服务完全不同——它可能是突发性的、短时的、资源需求波动极大的。Kubernetes 的 HPA 和 cluster-autoscaler 虽然不完美,但至少有一套成熟的指标采集和决策链路。ax 只需要把 agent 的队列深度、依赖就绪数这些自定义指标暴露出去,就能复用整套弹性能力。自己造的话,这套东西全得重来。

2.2 workspace 隔离方案:为什么不用裸容器

热搜词里反复出现 workspace,这不是偶然。agent 执行和普通容器最大的区别在于:agent 往往需要一个持久的、可读写的、带状态的工作目录。这个目录里可能有模型缓存、中间产物、临时文件、甚至是一整个代码仓库的 checkout。

裸容器的问题是文件系统隔离太彻底,每次重启状态全丢。用 emptyDir 又没法跨 Pod 共享。ax 的做法是引入一个 workspace 抽象层,底层可以是 PVC、可以是 hostPath、也可以是内存盘,但对 agent 暴露的接口是统一的。这样做的关键考量是:agent 的 workspace 生命周期和 Pod 生命周期解耦。Pod 可以因为调度、升级、故障而重建,但 workspace 里的状态要能保留下来,下次 agent 起来接着用。

这里有个实际踩过的坑:最开始我们用 PVC 做 workspace,结果发现大量小文件读写性能极差,agent 加载模型缓存要等好几分钟。后来换成 local PV 加节点亲和性,性能上来了,但调度灵活性下降了。ax 在这块的折中是支持多种 workspace backend,让用户按 agent 的类型选——无状态的用 emptyDir,有缓存需求的用 local PV,需要跨节点共享的用网络存储。没有银弹,只有权衡。

2.3 agentic 编排的语义设计:DAG 还是状态机

agent 之间的依赖关系怎么表达,这是 orchestration 层的核心设计决策。主流有两种思路:DAG 和状态机。DAG 适合表达“A 完成后 B 和 C 并行,B 和 C 都完成后 D 执行”这种静态依赖。状态机适合表达“根据 A 的输出决定走 B 还是 C”这种动态分支。

ax 的选择是两者结合:顶层用 DAG 描述 agent 之间的粗粒度依赖,每个 agent 内部用状态机描述细粒度的执行流转。这个设计的原因是,纯 DAG 在 agent 场景下很快就不够用——agent 的输出往往是不确定的,下一步走哪条路要看模型返回什么。纯状态机又太灵活,缺乏全局视角,调度器没法提前做资源预留。

实际用下来,这个混合模型在大多数场景下够用。但有一个边界情况要注意:当 DAG 的某个节点是动态展开的(比如一个 agent 要 fan-out 出 N 个子 agent),调度器需要在运行时才能知道总资源需求。ax 的处理方式是引入一个“预留-确认”两阶段机制,先按预估上限预留资源,实际展开后再调整。这个机制在资源紧张时会导致利用率下降,但至少不会出现死锁。

3. 核心细节拆解:workspace、调度、生命周期三件套

3.1 workspace 的创建与挂载:从 PVC 到 local PV 的实操

workspace 的创建流程在 ax 里是一个独立的 controller 负责的。当你提交一个 agent 任务时,ax 会先创建一个 Workspace CRD,然后根据 spec 里的 backend 类型去准备实际存储。以 local PV 为例,整个链路是这样的:

apiVersion: ax.io/v1 kind: Workspace metadata: name: agent-ws-001 spec: backend: local size: 50Gi nodeAffinity: required: - matchLabels: ax.io/node-pool: agent-pool retentionPolicy: retain

这个 CRD 提交后,ax 的 workspace controller 会做几件事:首先检查目标节点上有没有可用的 local PV,没有就动态创建;然后创建一个 init container 做目录初始化和权限设置;最后把 workspace 的挂载信息注入到 agent Pod 的 spec 里。

这里有几个实操要点。第一,local PV 的 nodeAffinity 必须和 agent Pod 的 nodeAffinity 对齐,否则会出现 workspace 在 A 节点、Pod 调度到 B 节点的情况,直接挂载失败。ax 在 admission webhook 里做了校验,但如果你手动改 Pod spec 绕过 webhook,这个坑还是会踩。第二,retentionPolicy 设成 retain 的话,任务结束后 workspace 不会自动清理,需要配一个 GC 策略定期回收,否则节点磁盘很快会被打满。我们线上就出过一次因为没配 GC,三天内把节点盘写满导致整个 pool 不可调度的事故。

3.2 agent 生命周期编排:从 Pending 到 Succeeded 的完整状态流转

ax 里一个 agent 执行单元的状态比普通 Pod 要复杂,因为它多了依赖等待和 workspace 准备两个阶段。完整的状态流转是这样的:

状态触发条件下一步
Pending任务已提交,依赖未满足依赖满足后转 Preparing
Preparing依赖已满足,workspace 准备中workspace 就绪后转 Scheduling
Scheduling等待 kube-scheduler 分配节点节点分配后转 Running
Running容器已启动,agent 执行中成功转 Succeeded,失败转 Failed
Succeededagent 正常退出触发下游依赖
Failedagent 异常退出或超时按重试策略处理

这个状态机看起来简单,但实际运维中最容易出问题的是 Preparing 和 Scheduling 之间的过渡。workspace 准备可能因为存储后端慢而卡很久,这时候如果调度器已经把 Pod 分配出去了,就会出现 Pod 一直 Pending 等挂载的情况。ax 的做法是把 workspace 准备放在调度之前,用独立的 controller 串行处理,避免资源被无效占用。

另一个细节是 Failed 状态的重试策略。agent 任务和普通 Job 不同,它的失败可能是确定性的(比如代码 bug)也可能是瞬时的(比如依赖服务抖动)。ax 支持按退出码区分:非零退出码如果是 137(OOM Kill)就自动重试并提升内存 limit,如果是 1(业务错误)就不重试直接标记失败。这个策略需要根据你的 agent 类型调,没有通用解。

3.3 调度决策的注入点:怎么让 kube-scheduler 理解 agent 语义

ax 不替换 kube-scheduler,而是通过 scheduler framework 的扩展点注入 agent 特有的调度逻辑。具体来说,它实现了两个 plugin:一个是 PreFilter,用来检查 agent 的依赖是否就绪、workspace 是否可用;另一个是 Score,用来给节点打分时考虑 agent 的亲和性偏好。

PreFilter 的逻辑很关键。如果依赖没就绪,直接返回 Unschedulable,这样 Pod 不会占用调度队列。但这里有个坑:如果依赖永远不就绪(比如上游 agent 挂了),Pod 会一直卡在 Unschedulable 状态,不会触发失败。ax 的做法是加一个超时机制,超过阈值后强制标记失败并触发告警。

Score 插件的打分维度包括:节点上已有的同组 agent 数量(倾向于打散还是聚集)、workspace 的本地性(local PV 必须和 Pod 同节点)、以及节点的资源水位。这几个维度的权重是可配的,默认配置下本地性权重最高,因为 workspace 跨节点挂载的性能损失太大。

4. 实操过程:从零搭一个 ax 编排环境

4.1 环境准备与依赖检查

搭 ax 环境的前提是一个能用的 Kubernetes 集群,版本建议 1.26 以上,因为用到了一些较新的调度框架特性。集群的 preflight 检查项包括:API server 是否开启了 CRD 支持、调度器是否允许自定义 plugin、节点上是否有足够的 local PV 容量。

# 检查集群版本 kubectl version --short # 检查调度器配置是否支持自定义 plugin kubectl get pod -n kube-system -l component=kube-scheduler -o yaml | grep -A5 "plugins" # 检查节点 local PV 容量 kubectl get nodes -o custom-columns=NAME:.metadata.name,CAPACITY:.status.allocatable.ephemeral-storage

这几步看起来简单,但实际部署时最容易卡在调度器配置上。很多托管集群不允许改 kube-scheduler 的启动参数,这时候 ax 的 Score plugin 就没法注入,只能退化成用 nodeAffinity 做粗粒度调度。如果你的集群是这种情况,建议先确认能不能拿到调度器配置的控制权,拿不到的话 ax 的价值会打对折。

4.2 ax controller 的部署与配置

ax 的 controller 本身是一个标准的 Kubernetes operator,用 Deployment 部署,通过 leader election 保证高可用。核心配置项包括:

apiVersion: apps/v1 kind: Deployment metadata: name: ax-controller namespace: ax-system spec: replicas: 2 template: spec: containers: - name: controller image: ax/controller:v0.8.3 args: - --workspace-gc-interval=300s - --agent-timeout-default=3600s - --scheduler-plugin-enabled=true resources: requests: cpu: 500m memory: 512Mi

这里有几个参数值得展开说。workspace-gc-interval控制 workspace 回收的扫描间隔,设太短会增加 API server 压力,设太长会导致磁盘回收不及时,300 秒是个比较稳的折中。agent-timeout-default是 agent 的默认超时,超过这个时间没完成就标记失败,这个值要根据你的 agent 平均执行时长来调,设太小会误杀长任务,设太大又会让卡死的任务占用资源太久。

部署完之后用kubectl get crd | grep ax确认 CRD 注册成功,然后用一个最小的 agent 任务做冒烟测试。

4.3 一个完整的 agent 编排示例

下面这个例子展示了一个三阶段的 agent 流水线:第一个 agent 做数据预处理,第二个和第三个并行做特征提取,第四个做汇总。

apiVersion: ax.io/v1 kind: AgentPipeline metadata: name: feature-pipeline spec: agents: - name: preprocess image: myrepo/preprocess:latest workspace: backend: local size: 20Gi resources: requests: cpu: 2 memory: 4Gi - name: extract-a image: myrepo/extract-a:latest dependsOn: [preprocess] workspace: backend: local size: 10Gi - name: extract-b image: myrepo/extract-b:latest dependsOn: [preprocess] workspace: backend: local size: 10Gi - name: aggregate image: myrepo/aggregate:latest dependsOn: [extract-a, extract-b] workspace: backend: local size: 5Gi

提交之后可以用kubectl get agentpipeline feature-pipeline -o yaml看整体状态,用kubectl get agents -l pipeline=feature-pipeline看每个 agent 的详细状态。实测下来,这个流水线在 8 核 16G 的节点上跑,从提交到完成大概 12 分钟,其中 preprocess 占 5 分钟,两个 extract 并行各 4 分钟,aggregate 占 3 分钟。瓶颈在 preprocess,如果要优化就把它拆成更细的并行单元。

5. 常见问题与排查技巧实录

5.1 workspace 挂载失败的三类原因

workspace 挂载失败是最高频的问题,排查下来基本归为三类。第一类是 nodeAffinity 不匹配,workspace 创建在 A 节点但 Pod 调度到了 B 节点。排查方法是kubectl describe pod看 Events 里有没有node(s) didn't match node affinity的报错。解决方式是确保 Workspace 和 AgentPipeline 里的 nodeAffinity 配置一致。

第二类是权限问题,local PV 的目录属主和容器内运行用户的 UID 不匹配。这个在 Events 里表现为permission denied。解决方式是在 workspace 的 init container 里做 chown,或者用 fsGroup 做组权限映射。

第三类是容量不足,local PV 的实际可用空间小于 spec 里声明的大小。这个最隐蔽,因为 PV 创建时不会校验实际容量,只有写入时才报no space left on device。排查方法是df -h看节点实际磁盘使用,解决方式是配一个容量告警,在磁盘用到 80% 时就提前介入。

5.2 agent 卡在 Pending 状态的排查路径

agent 卡 Pending 的排查要按顺序走:先看依赖是否就绪,kubectl get agents -l pipeline=xxx看上游 agent 的状态;再看 workspace 是否 Ready,kubectl get workspace看状态字段;最后看调度器日志,kubectl logs -n kube-system kube-scheduler-xxx看有没有 Unschedulable 的记录。

我遇到过最诡异的一次是依赖和 workspace 都正常,但 agent 就是不动。查了半天发现是 ax controller 的 leader election 出了问题,两个副本都在抢锁,导致 reconcile 循环卡死。解决方式是重启 controller 并检查 RBAC 配置,确保 lease 资源的权限正确。

5.3 资源利用率低的优化方向

ax 环境跑久了,资源利用率低是普遍问题。优化方向有三个:一是把 workspace 的 retentionPolicy 从 retain 改成 delete,任务结束就回收,避免磁盘碎片;二是调整 agent 的资源 request 和 limit 比例,agent 任务的资源使用往往波动大,request 设太高会导致调度不出去,设太低又会被 OOM Kill,建议用 VPA 先跑一段时间采集实际用量;三是开启调度器的 bin-packing 策略,让 agent 尽量集中到少数节点,把空闲节点缩容掉。

问题现象可能原因排查命令解决方式
workspace 挂载失败nodeAffinity 不匹配kubectl describe pod对齐 Workspace 和 Pod 的亲和性配置
agent 卡 Pending依赖未就绪kubectl get agents检查上游 agent 状态
agent 频繁 OOMmemory limit 过低kubectl describe pod提升 limit 或优化 agent 内存使用
workspace 磁盘满GC 未配置df -h配置 retentionPolicy 和 GC 策略
controller 不响应leader election 异常kubectl logs ax-controller重启 controller 检查 RBAC

6. 几个从生产环境攒下来的经验

第一个经验是关于 workspace 的容量规划。不要按 agent 的平均用量来设,要按峰值来设,而且留 30% 余量。我们最开始按平均 10Gi 设,结果遇到一个 agent 处理大文件时写到 15Gi 直接爆盘,连累整个节点上的其他 agent 全部失败。后来改成按峰值加余量,虽然磁盘利用率看起来低了,但稳定性上了一个台阶。

第二个经验是关于 agent 的超时设置。默认超时不要设成全局统一值,要按 agent 类型分。预处理类 agent 通常快,超时设 10 分钟就够;模型推理类 agent 可能跑很久,超时要设到小时级。ax 支持在 AgentPipeline 里按 agent 单独配 timeout,这个一定要用起来,全局统一超时是运维噩梦。

第三个经验是关于调度器的 Score plugin 权重。默认配置下本地性权重最高,这在大多数场景下是对的,但如果你的集群节点数少、local PV 分布不均,会导致某些节点过载而其他节点空闲。这时候要适当降低本地性权重,让调度器有更多腾挪空间。权重的调整没有公式,只能根据集群的实际负载分布来试,建议先用 0.5 的本地性权重跑一周,看节点负载是否均衡再微调。

第四个经验是关于监控。ax 暴露的指标里,最值得盯的是 agent 的 Pending 时长和 workspace 的准备时长。这两个指标一旦上涨,说明调度链路或存储链路有瓶颈。我们配的告警是 Pending 时长 P99 超过 5 分钟就报警,workspace 准备时长 P99 超过 2 分钟就报警。这两个阈值是根据实际业务容忍度定的,你可以根据自己的场景调。

最后分享一个排查小技巧:当 agent 行为异常但又没有明显报错时,先看 workspace 里的实际文件状态。很多时候问题不在调度层,而在 agent 自己的逻辑——比如它期望的输入文件没生成,或者中间产物写到了错误的路径。ax 的 workspace 是持久化的,任务失败后可以直接进去看现场,这比看日志猜要高效得多。我现在的习惯是,任何 agent 失败先kubectl exec进 workspace 看一眼,十次里有三次能直接定位到问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询