ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kubernetes 上 agentic 工作负载的调度与编排:ax 原语实践

Kubernetes 上 agentic 工作负载的调度与编排:ax 原语实践 1. 从“ax”这个标题说起一个被低估的调度原语第一次看到“ax”这个标题很多人会以为是某个命令行工具的缩写或者某个内部项目的代号。但把热搜词摊开来看——ax、agentic、orchestration、kubernetes、workspace——这几个词凑在一起指向的其实是一个非常具体的技术命题在 Kubernetes 之上为 agentic 工作负载做一套调度与编排层。ax 在这里不是一个工具名而是“agent execution”的缩写形态是围绕 agent 执行单元构建的一层调度抽象。我接触这个方向是从一个很实际的问题开始的团队里跑了一批基于大模型的 agent 任务每个 agent 需要独立的 workspace、独立的依赖环境、独立的生命周期管理。最开始用裸 Pod 硬扛结果就是资源碎片化严重一个 agent 卡住整个节点跟着遭殃扩缩容全靠人肉盯。后来换成 Job CronJob又发现 agent 之间的依赖关系没法表达A 的输出要喂给 BB 又要等 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-scheduleragent 特有的依赖编排、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任务已提交依赖未满足依赖满足后转 PreparingPreparing依赖已满足workspace 准备中workspace 就绪后转 SchedulingScheduling等待 kube-scheduler 分配节点节点分配后转 RunningRunning容器已启动agent 执行中成功转 Succeeded失败转 FailedSucceededagent 正常退出触发下游依赖Failedagent 异常退出或超时按重试策略处理这个状态机看起来简单但实际运维中最容易出问题的是 Preparing 和 Scheduling 之间的过渡。workspace 准备可能因为存储后端慢而卡很久这时候如果调度器已经把 Pod 分配出去了就会出现 Pod 一直 Pending 等挂载的情况。ax 的做法是把 workspace 准备放在调度之前用独立的 controller 串行处理避免资源被无效占用。另一个细节是 Failed 状态的重试策略。agent 任务和普通 Job 不同它的失败可能是确定性的比如代码 bug也可能是瞬时的比如依赖服务抖动。ax 支持按退出码区分非零退出码如果是 137OOM 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 componentkube-scheduler -o yaml | grep -A5 plugins # 检查节点 local PV 容量 kubectl get nodes -o custom-columnsNAME:.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-interval300s - --agent-timeout-default3600s - --scheduler-plugin-enabledtrue 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 pipelinefeature-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) didnt 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 pipelinexxx看上游 agent 的状态再看 workspace 是否 Readykubectl 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 检查 RBAC6. 几个从生产环境攒下来的经验第一个经验是关于 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 看一眼十次里有三次能直接定位到问题。
返回列表