尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Kubernetes 上 agentic 工作负载的调度与工作区编排实践

发布时间:2026/9/28 17:32:23

资讯中心
01
ARTICLE

Kubernetes 上 agentic 工作负载的调度与工作区编排实践

Kubernetes 上 agentic 工作负载的调度与工作区编排实践
1. 从“ax”这个标题说起一个被低估的调度原语第一次看到“ax”这个标题很多人会以为是某个命令行工具的缩写或者某个前端框架的别名。但把热搜词摊开来看——ax调度、agentic、orchestration、kubernetes、workspace——这几个词凑在一起指向的其实是一个非常具体的工程命题在 Kubernetes 之上如何为 agentic 工作负载设计一套轻量、可组合、可观测的调度与工作区编排层。我最早接触这类需求是在做多租户 AI 任务平台的时候。当时团队把一堆推理任务、数据预处理任务、评测任务全塞进一个 K8s 集群用原生 Deployment 和 Job 硬扛。结果很快就撞墙了任务之间需要共享中间产物但 Pod 的临时存储生命周期和任务生命周期对不齐不同任务对 GPU、内存、网络的需求差异极大默认调度器经常把大任务和小任务混排导致资源碎片更麻烦的是每个任务都需要一个“工作区”来挂载代码、数据、缓存和日志而 K8s 原生并没有 workspace 这个抽象。“ax”在这个语境下我理解它代表的是一种面向 agentic 场景的调度与工作区抽象层。它不是要替代 Kubernetes而是在 K8s 之上做一层薄薄的编排把“任务调度”和“工作区管理”这两件事从业务代码里抽出来变成平台能力。这篇文章我会从设计思路、核心抽象、实操落地、问题排查四个维度把这套东西拆开讲清楚。如果你正在做 AI 任务平台、多租户工作区、或者 agentic 应用的底层编排这篇内容应该能帮你少走一些弯路。2. 为什么原生 Kubernetes 扛不住 agentic 工作负载2.1 agentic 任务的三个特殊之处传统微服务的调度模型是“长期运行、状态外置、副本对等”。一个服务起三个副本每个副本无状态请求打到哪个都行。但 agentic 任务完全不是这个模型。第一任务是有向无环图里的节点。一个 agent 执行一次任务可能先调检索、再调模型、再调工具、最后写回结果。每个步骤是一个短生命周期的工作单元步骤之间有数据依赖。用 K8s 的 Job 来表达单个步骤没问题但步骤之间的依赖关系、中间产物的传递、失败重试的边界原生 Job 管不了。第二工作区是任务的一等公民。每个 agent 任务都需要一个隔离的文件系统视图代码从哪来、数据挂在哪、缓存写到哪里、日志落到哪。这个工作区的生命周期通常比 Pod 长——Pod 重启了工作区里的缓存不应该丢任务结束了工作区可能还要保留一段时间供调试。K8s 的 emptyDir 随 Pod 销毁hostPath 又缺乏隔离PVC 挂载粒度太粗。第三资源画像高度异构。有的步骤是 CPU 密集的文本处理有的是 GPU 密集的推理有的是 IO 密集的数据拉取。默认调度器基于 request/limit 做 bin packing但在 agentic 场景下任务的实际资源消耗往往在运行前无法准确预估导致要么过度申请浪费配额要么申请不足被 OOM kill。2.2 调度层需要补的三个能力基于上面三个特点我在设计“ax”这层编排时重点补了三个能力。能力一工作区生命周期与任务生命周期解耦。工作区不再绑定到单个 Pod而是作为一个独立资源存在。任务运行时挂载工作区任务结束后工作区可以保留、可以快照、可以传给下一个任务。实现上可以用一个轻量的工作区控制器为每个工作区维护一个 PVC 或者一个节点本地目录再通过 init container 做挂载准备。能力二调度决策引入数据局部性。如果任务 A 的输出是任务 B 的输入那 B 最好调度到 A 工作区所在的节点避免跨节点拉数据。原生调度器的 NodeAffinity 可以表达这个约束但需要平台层在创建 Pod 时动态注入 affinity 规则。我在实现时用一个调度插件在 PreFilter 阶段读取任务依赖图把上游工作区位置转成节点偏好。能力三资源预估的动态修正。与其让用户拍脑袋写 request不如让平台记录每个任务历史运行的峰值消耗用滑动窗口算出一个推荐值。第一次运行给保守值后续运行逐步收敛。这个逻辑放在一个 sidecar 或者 admission webhook 里都行我倾向 webhook因为不侵入业务容器。2.3 为什么是“薄层”而不是“重平台”这里有一个选型上的关键判断到底是在 K8s 上做一层薄编排还是自建一套调度系统我见过一些团队选择后者用 Go 或者 Rust 写一个独立的调度器自己管节点、管容器、管网络。短期看很灵活长期看是在重新发明 K8s 已经解决好的问题证书轮换、网络插件、存储插件、节点健康检查、滚动升级。这些事情的维护成本极高而且和云厂商的托管服务脱节。“ax”这层薄编排的定位是只做 K8s 不擅长的那部分其余全部复用。具体来说复用 K8s 的节点管理、网络、存储、RBAC、审计自己实现的是工作区抽象、依赖感知调度、资源画像修正。这样既拿到了 K8s 的生态红利又补上了 agentic 场景的短板。代价是要接受 K8s 的一些约束比如 Pod 是最小调度单元不能在一个 Pod 里做更细粒度的资源隔离。但对大多数 agentic 任务来说这个粒度够用。3. 核心抽象设计Workspace、Task、Flow 三层模型3.1 Workspace可挂载、可快照、可传递的工作区Workspace 是这套编排里最基础的抽象。一个 Workspace 本质上是一个有名字的目录具备三个能力可被任务挂载、可被快照、可在任务之间传递。实现上我选择用PVC 节点本地缓存的混合方案。每个 Workspace 对应一个 PVC保证跨节点可访问同时在每个节点上维护一个本地缓存目录任务运行时优先读写本地缓存后台异步同步到 PVC。这样既避免了纯 PVC 的网络延迟又保证了工作区在节点故障时不丢数据。Workspace 的元数据里记录几个关键字段所有者、创建时间、最后访问时间、大小配额、快照列表。快照用 CSI 的 VolumeSnapshot 能力实现不自己造轮子。传递则通过一个简单的引用计数任务 A 声明输出到 Workspace W任务 B 声明从 Workspace W 读取平台层在调度 B 时确保 W 已经就绪。注意Workspace 的清理策略一定要显式配置。我踩过的坑是默认不清理结果测试环境跑了两个月PVC 把存储配额吃满了。后来改成“最后访问时间超过 N 天自动回收”并且回收前发通知。3.2 Task带依赖和资源画像的调度单元Task 是对 K8s Job 的封装但增加了三样东西依赖声明、资源画像、工作区绑定。依赖声明用的是一个简单的 DAG 描述每个 Task 可以声明dependsOn列表。平台层在创建 Task 时会检查依赖是否满足不满足就挂起。依赖满足后再注入数据局部性相关的 affinity 规则。资源画像分两部分用户声明的 request/limit和平台记录的历史峰值。调度时取两者的较大值作为实际预留但允许在节点资源紧张时降级到用户声明值。这个降级策略要谨慎我一般只在非关键任务上开启。工作区绑定通过 volume 挂载实现。Task 的 Pod spec 里会注入一个 init container负责把 Workspace 的本地缓存准备好再把主容器的 volumeMount 指过去。3.3 Flow把 Task 串成可观测的执行流Flow 是 Task 的集合代表一次完整的 agentic 执行。一个 Flow 有唯一的 ID所有属于这个 Flow 的 Task 都带上这个标签。这样在排查问题时可以通过 Flow ID 一次性拉出所有相关 Pod、事件、日志。Flow 还负责聚合状态。单个 Task 失败不一定代表 Flow 失败可能有重试或者降级路径。Flow 的状态机是Pending - Running - Succeeded / Failed / PartiallySucceeded。PartiallySucceeded 这个状态很实用表示主路径成功但某些非关键分支失败业务上可以接受。3.4 三层模型的关系与边界这三层的关系可以用一句话概括Flow 管编排Task 管执行Workspace 管数据。边界也很清晰Flow 不关心 Task 内部怎么跑只关心依赖和整体状态Task 不关心数据从哪来只关心挂载点Workspace 不关心谁在用只关心生命周期和一致性。这种职责分离的好处是每一层都可以独立演进。比如后来我们想支持跨集群的 Flow只需要改 Flow 控制器的调度逻辑Task 和 Workspace 层几乎不动。4. 调度器扩展从默认调度到依赖感知调度4.1 为什么默认调度器不够用K8s 默认调度器的核心逻辑是过滤和打分。过滤阶段筛掉不满足硬约束的节点打分阶段给剩余节点排序。这个模型对无状态服务很好用但对有依赖的 agentic 任务有两个盲区。盲区一不知道任务之间的数据关系。默认调度器看到的是一个个独立的 Pod不知道 Pod B 需要读 Pod A 写的文件。结果 B 可能被调度到离 A 很远的节点跨节点读数据延迟高、带宽成本大。盲区二不知道工作区的本地缓存状态。如果 Workspace W 在节点 N1 上有完整缓存在 N2 上没有那把使用 W 的任务调度到 N1 明显更优。默认调度器没有这个信息。4.2 调度插件的工作流程我实现的是一个Scheduler Framework 插件挂在 PreFilter、Filter、Score 三个阶段。PreFilter 阶段做两件事读取 Task 的依赖列表解析出上游 Workspace 的位置信息读取 Workspace 的缓存分布生成一个“偏好节点集合”。Filter 阶段在默认过滤的基础上增加一条硬约束如果 Task 声明了requireLocalWorkspace: true则只保留有完整缓存的节点。这个约束默认关闭因为会降低调度成功率只在 IO 敏感任务上开启。Score 阶段给节点打分打分公式是score w1 * cacheHitRatio w2 * (1 - nodeLoad) w3 * affinityScore其中 cacheHitRatio 是节点上 Workspace 缓存的命中比例nodeLoad 是节点当前资源使用率affinityScore 是默认调度器的打分归一化值。权重 w1、w2、w3 可以通过 ConfigMap 配置默认是 0.5、0.3、0.2。4.3 缓存预热与失效处理依赖感知调度有一个前提Workspace 的缓存分布是已知的。但缓存会失效节点会下线所以需要一个缓存状态同步机制。我的做法是每个节点上跑一个轻量的 agent定期上报本地 Workspace 缓存的元数据workspace ID、版本号、大小、最后访问时间。调度器插件维护一个内存中的缓存索引定期从 agent 拉取更新。索引更新有延迟所以调度决策是“尽力而为”的不保证 100% 命中。缓存失效的处理策略是如果调度到的节点缓存不完整init container 会从 PVC 拉取缺失部分。这个过程是阻塞的但可以设置超时。超时后任务仍然启动只是首次读取会慢一些。实操心得缓存 agent 的上报频率不要太高否则 etcd 或者你的元数据存储会被写爆。我一开始设成 10 秒一次结果 200 个节点每秒 20 次写入元数据存储直接扛不住。后来改成 60 秒一次并且只在缓存有变化时才上报压力小了很多。4.4 调度器的可观测性调度决策如果不可观测排查问题就是黑盒。我在插件里加了几个关键指标调度尝试次数、过滤失败原因分布、打分分布、缓存命中率。这些指标通过 Prometheus 暴露配合 Grafana 看板能快速定位是资源不足、依赖未满足还是缓存缺失导致的调度失败。另外每次调度决策都会写一条事件到 Task 的 status 里记录候选节点、过滤原因、最终得分。这样用户在自己的 Task 详情里就能看到“为什么被调度到这个节点”。5. 工作区实操从创建到挂载到回收5.1 创建一个 Workspace创建 Workspace 有两种方式声明式和命令式。声明式是写一个 Workspace CRD命令式是通过 CLI 或者 API 直接创建。我推荐声明式因为可以纳入 GitOps 流程。一个典型的 Workspace 定义长这样apiVersion: ax.io/v1alpha1 kind: Workspace metadata: name: agent-task-001-ws spec: owner: team-nlp quota: 10Gi retentionPolicy: ttl: 168h onExpiry: snapshotAndDelete snapshotPolicy: schedule: 0 2 * * * keep: 3这里几个字段值得展开说。quota是软限制超过会告警但不阻断。retentionPolicy.ttl是最后访问后保留时长168 小时就是 7 天。onExpiry有两个选项delete直接删snapshotAndDelete先快照再删。snapshotPolicy是定时快照每天凌晨 2 点一次保留 3 份。5.2 挂载到 TaskTask 挂载 Workspace 通过 volume 声明apiVersion: ax.io/v1alpha1 kind: Task metadata: name: preprocess-001 labels: ax.io/flow: flow-abc spec: workspaceRefs: - name: agent-task-001-ws mountPath: /workspace mode: rw dependsOn: - name: fetch-data-001 template: spec: containers: - name: main image: my-preprocess:latest command: [python, preprocess.py]平台层在创建 Pod 时会把workspaceRefs转成实际的 volume 和 volumeMount。具体来说会注入一个 init container它负责检查本地缓存目录是否存在不存在就从 PVC 拉取检查缓存版本是否匹配不匹配就增量同步最后把缓存目录准备好主容器直接挂载。5.3 工作区在任务间的传递工作区传递的核心是引用计数和就绪检查。当 Task B 声明dependsOn: [Task A]且两者共享同一个 Workspace 时平台层在调度 B 之前会确认A 已经成功结束且 A 对 Workspace 的写入已经 flush 到 PVC。flush 这个动作很关键。如果 A 的容器直接写本地缓存缓存异步同步到 PVC那 A 结束后可能有部分数据还在同步队列里。我的做法是A 的容器退出前由一个 preStop hook 触发一次强制同步同步完成才允许 Pod 终止。这样 B 启动时读到的数据一定是完整的。5.4 回收与快照回收由 Workspace 控制器负责。控制器定期扫描所有 Workspace检查最后访问时间。超过 TTL 的进入回收流程先根据onExpiry决定是否快照然后删除 PVC 和本地缓存最后删除 Workspace 资源本身。快照用 CSI VolumeSnapshot。这里有一个坑不是所有存储插件都支持快照。如果你的集群用的是不支持快照的存储类snapshotAndDelete会失败。所以我在控制器里加了一个能力探测启动时检查默认存储类是否支持快照不支持就降级为delete并告警。注意快照本身也占存储。我见过一个团队开了每小时快照保留 24 份结果快照占的空间比原始数据还大。快照策略要根据数据变化频率来定变化不频繁的数据每天一次就够了。6. 资源画像让调度决策有数据支撑6.1 为什么用户声明的 request 不可靠让用户写 request 有两个问题。一是用户倾向于高估怕被 OOM kill结果申请 8Gi 实际用 2Gi浪费 75%。二是 agentic 任务的资源消耗和输入数据强相关同一个任务处理 1MB 输入和 1GB 输入内存消耗差两个数量级用户没法用一个固定值表达。6.2 历史峰值采集与滑动窗口我的方案是平台记录每个 Task 模板的历史运行峰值用滑动窗口算推荐值。具体来说每个 Task 模板有一个画像记录包含最近 N 次运行的 CPU 峰值、内存峰值、GPU 峰值、运行时长。推荐值取 P95 分位数而不是最大值避免被偶发尖峰带偏。采集方式有两种一是从 cAdvisor 或者 metrics-server 拉容器指标二是从业务容器自己上报。我倾向后者因为业务容器最清楚自己的关键指标。上报通过一个 sidecar 或者直接写一个本地文件平台层定期收集。6.3 推荐值的应用策略推荐值怎么用有三种策略策略行为适用场景保守取 max(用户声明, 推荐值)关键任务不能 OOM平衡取推荐值用户声明作为上限大多数任务激进取推荐值的 80%允许超卖非关键、可重试任务默认用平衡策略。用户可以在 Task 上通过 annotation 覆盖策略。6.4 画像的冷启动问题新 Task 模板没有历史数据画像为空。这时候只能回退到用户声明值。为了加速冷启动我做了两件事一是允许用户从相似 Task 模板继承画像二是第一次运行时用保守策略运行结束后立即更新画像第二次运行就能用上推荐值。相似度判断用的是 Task 模板的镜像、命令、资源声明等特征的向量化比较。这个做得比较粗糙但实际用下来效果还行至少比完全冷启动强。7. 常见问题与排查技巧实录7.1 工作区挂载失败现象Task Pod 一直卡在 Init 状态事件里报mount failed: workspace not ready。排查思路先看 Workspace 资源的状态是不是还在 Pending。如果 Workspace 本身没就绪看它的 PVC 有没有绑定成功。PVC 绑定失败常见原因是存储类不存在或者配额不足。如果 Workspace 就绪但挂载失败看 init container 的日志通常是本地缓存目录权限问题或者磁盘空间不足。解决权限问题在 init container 里加chown或者chmod。磁盘空间不足需要清理节点上的旧缓存我写了一个定时任务每天清理超过 3 天未访问的本地缓存。7.2 调度一直 Pending现象Task 创建后一直 Pending没有 Pod 被调度。排查思路先看 Task 的 status有没有dependsOn未满足。如果依赖满足看调度器事件是不是所有节点都被过滤掉了。常见过滤原因资源不足、节点亲和性不匹配、污点未容忍。解决资源不足就扩容或者降低 request。亲和性不匹配检查 Workspace 的缓存分布如果所有缓存节点都满了可以临时关闭requireLocalWorkspace。污点问题检查节点 taint 和 Task 的 toleration。7.3 工作区数据不一致现象Task B 读到的数据不是 Task A 写的最新版本。排查思路检查 A 的 preStop 同步有没有执行成功。看 A 的 Pod 事件preStop hook 有没有超时。如果超时可能是数据量太大同步时间不够。解决增大 preStop 的超时时间或者改成 A 结束后由控制器触发同步不依赖 preStop。后者更可靠但需要控制器有权限访问节点本地缓存。7.4 缓存命中率低现象调度器日志显示缓存命中率长期低于 30%。排查思路看缓存 agent 的上报是否正常元数据索引是不是过期了。看任务分布是不是任务太分散每个节点上的 Workspace 都不完整。解决如果是上报问题检查 agent 的健康状态。如果是任务分散考虑引入 Workspace 亲和性让使用同一 Workspace 的任务尽量调度到同一批节点。7.5 常见问题速查表问题可能原因快速检查解决方向Pod 卡 InitWorkspace 未就绪kubectl get ws检查 PVC 绑定调度 Pending依赖未满足Task status检查上游 Task调度 Pending资源不足调度器事件扩容或降 request数据不一致同步未完成preStop 日志增大超时或改控制器同步缓存命中低上报异常agent 健康重启 agent快照失败存储不支持存储类能力降级为 delete8. 一些踩过的坑和最后的经验这套东西从第一版跑通到相对稳定大概花了四个月。中间踩的坑不少挑几个有代表性的说说。第一个坑是过度依赖节点本地缓存。一开始为了性能所有读写都走本地缓存PVC 只做冷备。结果节点故障时没同步的数据全丢了。后来改成写操作同步到 PVC读操作走本地缓存牺牲一点写性能换可靠性。这个取舍在 agentic 场景下是值得的因为中间产物往往比最终结果更难重建。第二个坑是调度器的打分权重拍脑袋定。一开始 w1、w2、w3 设成 0.5、0.3、0.2跑了一段时间发现缓存命中率上不去。后来用历史数据做了一轮回归发现缓存权重要更高调到 0.7 才有效果。权重这东西没有万能值得根据自己的任务分布调。第三个坑是Workspace 的 TTL 设得太短。一开始设 24 小时结果跨天调试的任务经常发现工作区被清了。后来改成 7 天并且加了“正在被 Task 引用时不允许回收”的保护。如果让我给正在做类似事情的团队一个建议那就是先把 Workspace 的生命周期管好再去做调度优化。工作区是数据的地基地基不稳上面的调度再聪明也是空中楼阁。调度优化可以慢慢迭代但数据丢了就是真丢了。另外这套东西不要一开始就追求大而全。我见过一些团队一上来就想支持跨集群、多租户、细粒度配额结果半年没跑通一个完整流程。我的做法是先在一个集群、一个租户、一个 Flow 上跑通再逐步扩展。每扩展一个维度都确保前一个维度是稳定的。这样虽然看起来慢但实际落地速度反而更快。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。