☰
Kubernetes 上 agentic 工作负载的调度与运行时编排:ax 抽象层实践
2026/9/28 17:22:57 网站建设 项目流程

1. 从"ax"这个标题说起:一个被低估的运行时调度命题

第一次看到"ax"这个标题,很多人会一头雾水——两个字母,没有上下文,没有正文,没有关键词,连摘要都是空的。但把相关热搜词摊开来看,脉络就清楚了:ax调度、agentic、orchestration、runtime、Kubernetes、Karmada、agentic cloud、agentic rag。这些词拼在一起,指向的是一个非常具体的技术命题:在 Kubernetes 之上,如何为 agentic 工作负载设计一套调度与运行时编排机制。

我先把结论摆在前面:ax 不是一个具体的开源项目名,而更像是一个抽象层(abstraction layer)的代号——它站在 Kubernetes 的调度器与上层 agent 编排框架之间,负责把"一个 agent 想干什么"翻译成"集群里哪些节点、哪些 runtime、哪些资源能承接这件事"。这个定位听起来有点虚,但落到实操上非常实在:它决定了你的 agent 任务是被均匀打散到整个集群,还是被死死摁在某几个节点上排队;决定了冷启动是 200ms 还是 8s;决定了 GPU 显存是被复用还是被浪费。

为什么这个话题现在值得认真聊?因为 agentic 负载和传统微服务负载的形态差异太大了。传统 Web 服务的请求是短平快的、无状态的、可随意水平扩展的;而一个 agent 任务往往是有状态的、长时运行的、需要多轮工具调用的、对上下文连续性有强依赖的。你拿一套为无状态 HTTP 服务设计的调度策略去跑 agent,结果就是:任务被反复迁移导致上下文丢失、工具调用链路被网络抖动打断、GPU 资源被单个长任务独占而其他任务饿死。

这篇内容适合三类人看:一是正在把 agent 应用往 Kubernetes 上迁的工程师;二是负责集群调度策略、被 agent 负载搞得焦头烂额的 SRE;三是想搞清楚"agentic cloud"到底在讲什么、值不值得投入的技术决策者。我会从调度模型、运行时隔离、状态管理、可观测性四个层面拆开讲,每个层面都给可落地的配置思路和踩坑经验。

2. ax 调度模型的核心矛盾:agent 要的是"粘性",K8s 默认给的是"漂移"

2.1 为什么默认调度器会把 agent 任务搞崩

Kubernetes 默认调度器的设计哲学是"最优放置":每次调度都重新评估所有节点的资源水位、亲和性、污点容忍,然后挑一个当前最合适的节点。这套逻辑对无状态服务是完美的——反正每个 Pod 都一样,放哪都行,均衡负载就是最优解。

但 agent 任务不是这样。一个 agent 在执行多轮推理时,会在本地缓存大量的中间状态:对话历史、工具调用的返回结果、向量检索的临时索引、甚至模型推理的 KV Cache。这些东西如果不在调度决策的考虑范围内,就会出现一个非常典型的现象——任务被驱逐后重新调度到新节点,所有缓存归零,agent 从"记得上下文"退化成"失忆重来"。

我实测过一个场景:一个带 RAG 的 agent 任务,单轮完整推理需要加载约 1.2GB 的检索索引到内存。默认调度下,节点资源紧张触发驱逐,任务漂移到新节点,索引重建耗时 4.7 秒。如果这个 agent 平均每 30 秒被调用一次,那 15% 的时间都花在重建索引上。这不是调度"不够聪明",而是调度器根本不知道这些状态的存在。

2.2 ax 层的解法:把"状态亲和性"变成一等调度约束

ax 调度模型的关键改动,是把 agent 的状态位置显式暴露给调度器。具体做法通常有三种,我按落地难度从低到高排:

方案实现方式适用场景代价
节点亲和性标签给承载状态的节点打 label,Pod 用 nodeAffinity 绑定状态节点固定、数量少扩展性差,节点故障即失联
本地 PV 绑定用 local PV 把状态钉在节点上,Pod 跟随 PV 调度状态量大、需要持久化PV 生命周期管理复杂
状态外置 + 会话粘性状态放 Redis/对象存储,调度层做 session affinity状态可序列化、要求高可用网络往返增加延迟

我个人的经验是:中小规模(50 节点以内)用本地 PV 绑定最省心,大规模集群必须走状态外置。原因很直接——本地 PV 的故障域就是单节点,节点挂了状态就没了,你得自己做副本;而状态外置虽然多一跳网络,但把"状态可用性"和"节点可用性"解耦了,运维复杂度反而下降。

这里有个容易被忽略的细节:session affinity 不能简单用源 IP 做哈希。agent 任务的调用方往往是另一个服务,源 IP 是固定的,哈希会导致所有请求打到同一个节点。正确做法是用 agent 的 session ID 或 conversation ID 做一致性哈希,并且哈希环要能感知节点上下线。

2.3 调度器的扩展点:别急着换调度器

很多人一遇到默认调度器不满足需求,第一反应是"换个调度器"或者"自己写一个"。我的建议是:先用调度器框架的扩展点,实在不行再动调度器本身。

Kubernetes 调度框架提供了几个关键扩展点,对 ax 场景特别有用:

  • PreFilter:在调度前检查 agent 任务的状态依赖是否满足,比如所需的状态分片是否已加载。
  • Filter:过滤掉不满足状态亲和性的节点。
  • Score:给"已经持有该 agent 状态"的节点加分,实现软亲和。
  • Reserve/Permit:在真正绑定前预留状态资源,避免并发调度冲突。

用 Score 扩展点做软亲和是最实用的。你可以给每个节点维护一个"状态命中度"分数,调度时优先选命中度高的节点,但不强制。这样既保证了大部分任务能复用状态,又不会因为状态节点满载而拒绝调度。

# 调度器配置片段:启用自定义 Score 插件 apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: ax-scheduler plugins: score: enabled: - name: AgentStateAffinity weight: 30 filter: enabled: - name: AgentStateFilter

注意:自定义调度器插件需要重新编译调度器二进制或使用调度器框架的 out-of-tree 插件机制。生产环境建议先在测试集群验证插件对调度吞吐的影响,Score 插件如果计算复杂,会显著拖慢调度周期。

3. 运行时隔离:agent 的"重"和容器的"轻"之间的拉扯

3.1 为什么标准容器运行时对 agent 不够用

标准容器运行时(containerd、CRI-O)的设计目标是"快速启动、轻量隔离"。一个容器从创建到可执行,理想情况是几百毫秒。但 agent 运行时往往需要:

  • 加载模型权重(几 GB 到几十 GB)
  • 初始化推理引擎(CUDA context、TensorRT engine)
  • 建立工具调用的连接池
  • 预热向量索引

这些操作加起来,冷启动动辄十几秒到几分钟。如果每次调度都触发一次完整冷启动,集群的吞吐会被彻底拖垮。

ax 层在运行时隔离上的核心思路是**"热池 + 快照"**:维护一批已经预热好的运行时实例,调度时直接把任务挂载到热实例上,而不是从零创建。这有点像数据库的连接池——连接建立很贵,但复用很便宜。

3.2 热池的三种实现路径与选型对比

我调研和实践过三种热池方案,各有取舍:

方案一:常驻 Pod 池 + 任务队列。维护一组常驻的 agent runtime Pod,每个 Pod 内部有一个任务队列,调度器把任务投递到队列而不是创建新 Pod。优点是实现简单,缺点是资源利用率低——空闲的 Pod 也占着内存和 GPU。

方案二:CRIU 检查点恢复。利用 CRIU(Checkpoint/Restore in Userspace)把预热好的运行时状态做检查点,需要时从检查点恢复。恢复速度比冷启动快一个数量级,但 CRIU 对 GPU 状态的支持一直是个坑,CUDA context 很难被完整检查点。

方案三:进程内快照 + fork。在同一个进程内维护多个 agent 执行上下文,通过 fork 或类似机制快速派生新上下文。这是性能最好的方案,但隔离性最弱,一个上下文崩溃可能影响同进程的其他上下文。

我的选型建议是:对延迟敏感、隔离要求不高的场景用方案三;对隔离要求高、能接受秒级启动的用方案一;方案二目前只适合 CPU-only 的 agent 场景。

3.3 资源超卖与 QoS:别让 agent 把节点吃干

agent 任务的资源画像和传统服务差异很大。传统服务通常是"稳定低占用 + 突发峰值",而 agent 任务是"长时间中等占用 + 阶段性高占用"——推理时 GPU 打满,工具调用时 GPU 空闲但网络和 CPU 忙。

如果按峰值来申请资源,集群利用率会低得可怜;如果按均值申请,又会在峰值时互相抢占。ax 层的做法通常是引入分级 QoS:

  • Guaranteed 级:给关键 agent 任务,资源独占,不超卖。
  • Burstable 级:给普通任务,允许在节点空闲时借用资源,但节点紧张时被压缩。
  • BestEffort 级:给批处理型 agent,随时可被驱逐。

关键是要给 agent 运行时加上资源使用反馈——让运行时能感知自己被压缩了,主动降低并发或切换到更小的模型。我见过太多集群因为 agent 运行时"不知道自己被限流了",还在拼命提交推理请求,结果全部超时。

# agent runtime 的资源声明示例 resources: requests: cpu: "2" memory: "8Gi" nvidia.com/gpu: "1" limits: cpu: "4" memory: "16Gi" nvidia.com/gpu: "1" # 配合 QoS 分级,建议用 Guaranteed 保证 GPU 不被超卖

提示:GPU 资源在 Kubernetes 里默认不支持超卖,一个 GPU 只能被一个 Pod 独占。如果要做 GPU 共享,需要上 MIG(Multi-Instance GPU)或时间片调度方案,这会显著增加运维复杂度,建议先评估是否真的需要。

4. 状态管理:agent 的"记忆"到底该放在哪

4.1 三层状态模型:会话态、工具态、模型态

agent 的状态不是一个整体,我习惯把它拆成三层:

会话态:对话历史、用户偏好、当前任务目标。特点是读写频繁、体积小、生命周期与会话一致。适合放 Redis 或内存数据库。

工具态:工具调用的连接、认证凭证、临时文件、中间结果。特点是生命周期短、与具体工具绑定。适合放本地临时存储或 sidecar 容器。

模型态:KV Cache、LoRA 适配器、推理引擎的编译产物。特点是体积大、重建成本高、与模型版本强绑定。适合放本地高速存储(NVMe)或共享内存。

这三层的生命周期和访问模式完全不同,如果混在一起管理,必然顾此失彼。ax 层的价值就在于给每一层提供独立的存储抽象和生命周期策略。

4.2 会话态的持久化:别用 etcd,也别用本地盘

会话态最常见的错误是放 etcd。etcd 是为集群元数据设计的,写入放大严重,会话数据的高频写入会直接把 etcd 打爆。我见过一个集群因为把 agent 会话写进 etcd,导致整个集群的 watch 延迟从毫秒级涨到秒级。

正确做法是用专门的会话存储。Redis 是最常见的选择,但要注意:

  • 开启 AOF 持久化,但不要用 always 模式,用 everysec 就够。
  • 会话数据要设 TTL,避免无限增长。
  • 如果会话需要跨集群共享,用 Redis Cluster 或兼容协议的其他方案。

本地盘的问题是节点故障即丢失。如果会话态可以容忍丢失(比如用户重新开始对话),本地盘 + 定期快照是可以接受的;如果不能容忍,必须外置。

4.3 模型态的复用:共享内存与只读挂载

模型态是体积最大、复用价值最高的一层。一个 7B 模型的权重约 14GB(FP16),如果每个 agent 实例都独立加载一份,10 个实例就是 140GB 显存,完全不现实。

ax 层的典型做法是模型权重只读共享 + KV Cache 独立:

  • 模型权重放在共享存储(NFS、对象存储挂载、或节点本地缓存),多个实例通过只读挂载共享同一份。
  • KV Cache 是每个会话独立的,放在显存或共享内存里,按会话生命周期管理。

这里有个实操细节:模型权重的加载速度直接决定冷启动时间。如果从对象存储拉取,14GB 在千兆网络下要 2 分钟;如果节点本地有缓存,加载时间可以降到 10 秒以内。所以节点本地模型缓存是 ax 部署的标配,可以用 DaemonSet 预热,或者用类似镜像预拉取的机制。

# 节点本地模型缓存预热示例(DaemonSet 思路) # 在每个节点上维护 /var/cache/models 目录 # 通过 initContainer 检查并拉取所需模型 initContainers: - name: model-prefetch image: model-syncer:latest command: ["/sync", "--model", "llama-7b", "--dest", "/var/cache/models"] volumeMounts: - name: model-cache mountPath: /var/cache/models

5. 可观测性:agent 调度出问题时,你该看什么

5.1 传统指标为什么不够用

Prometheus 那套 CPU、内存、网络指标,对 agent 调度问题的定位帮助有限。因为 agent 的问题往往不是"资源不够",而是"资源用错了地方"。比如:

  • GPU 利用率 90%,但任务吞吐很低——可能是显存带宽瓶颈,不是算力瓶颈。
  • 节点 CPU 不高,但任务排队严重——可能是某个 agent 持有了锁,其他任务在等。
  • 网络流量正常,但工具调用超时——可能是 DNS 解析慢或连接池耗尽。

这些问题的共同点是:传统指标只能告诉你"哪里不对",不能告诉你"为什么不对"。ax 层需要补充的是语义级指标。

5.2 必须采集的四类 agent 专属指标

我建议至少采集这四类:

调度延迟分解:从任务提交到开始执行,中间经历了排队、状态加载、运行时预热几个阶段,每个阶段耗时多少。这能直接告诉你瓶颈在哪。

状态命中率:任务调度到节点时,所需状态已经在本地命中的比例。命中率低说明调度策略有问题,或者状态分布不合理。

工具调用链路:每个工具调用的耗时、成功率、重试次数。agent 的失败往往不是推理失败,而是某个工具调用卡住了。

上下文长度分布:agent 的上下文长度直接决定显存占用和推理延迟。如果上下文长度分布出现长尾,说明有任务在无限累积历史,需要做截断或摘要。

5.3 用 OpenTelemetry 串起 agent 的完整调用链

agent 的调用链比传统服务复杂得多:一次用户请求可能触发多次模型推理、多次工具调用、多次状态读写。用 OpenTelemetry 把这些串起来,是定位问题的关键。

关键是要在调度层注入 trace context,让任务从提交那一刻起就有完整的追踪链路。具体做法是在任务元数据里带上 trace ID,运行时在执行每个阶段时创建 span。

# agent 运行时埋点示例(OpenTelemetry) from opentelemetry import trace tracer = trace.get_tracer("ax.runtime") def execute_agent_task(task): with tracer.start_as_current_span("agent.execute") as span: span.set_attribute("agent.session_id", task.session_id) span.set_attribute("agent.model", task.model_name) with tracer.start_as_current_span("state.load"): state = load_state(task.session_id) with tracer.start_as_current_span("inference"): result = run_inference(task, state) with tracer.start_as_current_span("tool.call"): tool_result = call_tool(result.tool_name, result.tool_args) return tool_result

注意:agent 的 trace 数据量可能非常大,尤其是多轮对话场景。建议对 trace 做采样,但采样策略要按 session 而不是按请求——同一个 session 的 trace 要么全采要么全不采,否则链路是断的。

6. 从 Karmada 到 agentic cloud:多集群调度的现实考量

6.1 单集群调度到多集群调度的跃迁

当 agent 规模超过单集群承载能力时,就要考虑多集群调度。Karmada 这类多集群编排工具最近热度很高,核心能力是把工作负载分发到多个集群,并做故障转移。

但 agent 负载的多集群调度有个特殊难点:状态跟着任务走。传统无状态服务分发到哪个集群都行,agent 任务分发时必须考虑目标集群是否有该会话的状态。如果状态是外置的(比如全局 Redis),这个问题就简化成网络延迟问题;如果状态是本地的,就必须做状态迁移或会话重定向。

我的经验是:多集群 agent 部署,状态必须外置。本地状态在多集群场景下是运维噩梦,迁移成本高、一致性难保证。把状态外置后,多集群调度就退化成"选一个离状态存储近、资源充足的集群",逻辑简单很多。

6.2 故障转移时的状态一致性

多集群故障转移最怕的是状态分裂:主集群挂了,任务转移到备集群,但备集群的状态是旧的,导致 agent"记错事"。

解决思路有两种:

强一致方案:状态写入必须同步到多个集群,任务转移前确认状态已同步。代价是写入延迟高,跨集群网络抖动会直接影响 agent 响应。

最终一致方案:状态异步同步,任务转移时接受短暂的状态不一致,通过重放或补偿来修复。代价是逻辑复杂,需要处理各种边界情况。

对大多数 agent 场景,我推荐最终一致 + 会话级锁:同一个会话同时只能在一个集群活跃,转移时先释放锁再获取锁,避免并发写冲突。这样既保证了会话内的一致性,又不需要全局强一致。

6.3 agentic cloud 的底座到底需要什么

"agentic cloud"这个词最近被提得很多,但剥开概念,底座需求其实很朴素:

  • 弹性调度:能根据 agent 负载动态调整资源,而不是固定分配。
  • 状态抽象:给会话态、工具态、模型态提供统一的存储接口。
  • 运行时复用:热池、快照、共享权重,降低冷启动成本。
  • 可观测性:语义级指标和完整调用链,能定位 agent 特有的问题。
  • 多集群能力:能跨集群调度和故障转移,且状态一致。

这五件事没有一件是全新的技术,难的是把它们组合成一个对 agent 友好的整体。ax 这个抽象层的价值,就在于它试图定义这个组合的接口和边界。

7. 实操中踩过的坑与几条硬经验

7.1 坑一:把 agent 当微服务调度,上下文全丢

最早我把 agent 任务按 Deployment 部署,副本数设 3,以为能自动负载均衡。结果 agent 的会话状态在 Pod 本地,请求被轮询到不同 Pod,每个 Pod 都只有部分上下文,agent 的回答前后矛盾。

修复方式:改成 StatefulSet + 会话粘性,或者把状态外置。我选了后者,因为 StatefulSet 的扩缩容太慢,不适合 agent 的弹性需求。

7.2 坑二:GPU 显存碎片化,大任务永远调度不上

集群总显存 800GB,但一个需要 80GB 显存的大模型任务死活调度不上,因为显存被一堆小任务碎片化了。Kubernetes 的 GPU 调度是整卡分配,不做碎片整理。

修复方式:给大任务预留专属节点池,用 taint 隔离;小任务走共享池。或者上 MIG,把一张卡切成多个实例,但 MIG 的切分是静态的,灵活性差。

7.3 坑三:工具调用超时拖垮整个 agent

一个 agent 任务卡在某个外部工具调用上,超时设置是 30 秒,结果整个任务链被拖了 30 秒。更糟的是,这个任务占着 GPU 不放,其他任务在排队。

修复方式:给工具调用设置独立的超时和熔断,超时后 agent 要能降级处理(比如返回"工具暂时不可用"而不是一直等)。同时给 agent 运行时加"最大执行时间",超时强制释放资源。

7.4 几条硬经验

经验一:调度策略要可回滚。任何调度策略的调整都可能引发连锁反应,上线前一定要有快速回滚的机制。我习惯用 feature flag 控制调度策略,出问题一键切回默认。

经验二:状态命中率是核心指标。如果状态命中率低于 60%,说明调度策略有问题,优先优化这个指标,比优化推理速度收益大得多。

经验三:别追求 100% 资源利用率。agent 负载的突发性很强,留 20% 的缓冲资源比追求满负载更稳。我见过太多集群为了省资源把水位压到 95%,结果一个突发流量就雪崩。

经验四:冷启动优化要分层做。模型加载、引擎初始化、状态预热,每一层的优化手段不同,要分别度量、分别优化。别指望一个方案解决所有冷启动问题。

经验五:多集群不是银弹。多集群能提升可用性,但也引入了状态一致性和网络延迟问题。如果单集群能满足可用性要求,别急着上多集群。

8. 一个可落地的最小 ax 调度原型

如果你现在就想动手验证,我给一个最小原型的搭建思路,不依赖任何特定商业产品:

第一步:状态外置。起一个 Redis 存会话态,一个对象存储存模型权重,节点本地 NVMe 做模型缓存。

第二步:自定义调度器插件。用调度器框架写一个 Score 插件,给持有会话状态的节点加分。先用软亲和,跑通了再考虑硬约束。

第三步:运行时热池。用 Deployment 维护一组常驻 runtime Pod,每个 Pod 暴露一个任务接收接口。调度器不直接创建 Pod,而是把任务投递到热池。

第四步:可观测性。接入 OpenTelemetry,至少埋调度延迟、状态命中率、工具调用耗时三个指标。

第五步:压测验证。用真实 agent 负载压测,重点看状态命中率和 P99 延迟。如果命中率低于 60%,回去调调度策略。

这个原型不追求生产级完备,但能让你在一天内跑通核心链路,验证 ax 调度思路是否适合你的场景。跑通之后再逐步补故障转移、多集群、QoS 这些能力。

我在实际搭建这个原型时最大的体会是:调度策略的调优是个迭代过程,没有一劳永逸的最优解。你的 agent 负载特征、集群规模、状态分布都在变,调度策略也得跟着变。所以别追求一步到位,先把可观测性做好,让数据告诉你该往哪个方向调。

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

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

立即咨询