1. 从"ax"这个标题说起:一个被低估的运行时抽象层
第一次看到"ax"这个标题,很多人会一头雾水——两个字母,没有上下文,没有正文,没有关键词,连摘要都是空的。但如果你把相关热搜词摊开来看,脉络就清晰了:ax调度、agentic、orchestration、runtime、Kubernetes、Karmada、device plugin、container runtime……这些词拼在一起,指向的是一个非常具体的技术命题:在云原生体系里,如何为 agentic 工作负载构建一套可编排、可调度、可观测的运行时抽象层。
我把它简称为"ax"——不是某个具体产品的名字,而是一类架构思路的代称:agentic execution,或者说 agentic runtime abstraction。它要解决的问题是:当你的系统里不再只有无状态的 HTTP 服务,而是有一堆需要长时间运行、需要调用工具、需要维护上下文、需要动态扩缩的智能体进程时,传统的 Kubernetes 编排模型还够用吗?答案是不够,至少不够优雅。
这篇文章适合三类人看:第一类是做云原生基础设施、正在被 agentic 负载的调度问题折磨的工程师;第二类是想理解 Kubernetes 之上还能怎么抽象、怎么扩展的架构设计者;第三类是对 runtime、orchestration 这些概念有基本认知,但没想清楚它们怎么组合起来支撑智能体场景的开发者。我会从运行时抽象的必要性讲起,拆解调度层的设计取舍,聊 device plugin 和 container runtime 的边界,最后落到一套可复现的实操路径上。全程不堆概念,只讲我实际踩过的坑和验证过的做法。
2. 为什么 agentic 负载逼着我们要重新想 runtime 这件事
2.1 传统 Pod 模型在智能体场景下的三个失配
Kubernetes 的 Pod 模型是为"短生命周期、无状态、可随时重启"的服务设计的。这个假设在微服务时代非常成立:一个 HTTP 服务挂了,重启就行,状态在数据库里。但 agentic 负载完全不是这个形态。
第一个失配是生命周期。一个智能体任务可能跑几分钟,也可能跑几小时甚至几天——它在等外部工具返回、在等人工确认、在轮询某个资源。你用 Deployment 管它,重启策略怎么写?用 Job 管它,超时时间设多少?设短了任务被误杀,设长了资源被长期占着。
第二个失配是状态耦合。智能体的上下文、记忆、中间产物往往和进程绑定。Pod 一漂移,这些状态就丢了。你当然可以把状态外置到 Redis 或向量库,但那样每次工具调用都要走一次网络,延迟和一致性都是问题。
第三个失配是资源画像。传统服务是 CPU/内存二维的,智能体还要吃 GPU、要吃特定的模型推理 runtime、要吃某种加速卡。Kubernetes 原生的 resources 字段表达不了"我需要一个带特定 CUDA 版本的 GPU"这种诉求,得靠 device plugin 扩展。
提示:如果你现在的 agentic 负载还是用普通 Deployment 硬扛,先别急着上复杂方案。把生命周期和状态这两件事想清楚,比引入任何新框架都重要。
2.2 "ax"要抽象掉的到底是什么
我把 ax 这层抽象的目标总结成一句话:让智能体进程像函数一样被调度,但像服务一样被管理。
"像函数一样被调度"意味着:调度器看到的是一个声明式的任务描述——需要什么工具、需要什么模型、需要多少算力、优先级多高——而不是一个具体的 Pod spec。调度决策应该基于这些语义信息,而不是单纯的资源余量。
"像服务一样被管理"意味着:一旦调度下去,它就有健康检查、有日志、有指标、有优雅退出、有扩缩容。不能因为它是"智能体"就退化成裸进程。
这两句话听起来简单,落地的时候处处是取舍。比如健康检查,传统服务探的是端口,智能体探什么?探它是不是卡在某个工具调用上?探它的上下文是不是已经溢出?这些都需要在 runtime 层埋点,而不是在应用层各写各的。
2.3 一个具体的失配案例
我遇到过最典型的一个场景:一个做代码分析的智能体,需要调用编译工具链。它的工作模式是"拉取代码 → 编译 → 分析 → 生成报告",中间编译这一步可能耗时十几分钟,而且需要特定的工具链镜像。
最初我们用 Job 跑,问题来了:编译失败要重试,但重试的时候整个代码拉取又要重来一遍,因为 Job 的 Pod 是无状态的。后来改成 StatefulSet,又发现扩缩容极其别扭——每个副本的上下文是独立的,但任务队列是共享的,需要自己实现一套协调逻辑。
最后我们的做法是:把"任务"和"执行器"拆开。任务是一个 CRD(自定义资源),描述要做什么;执行器是一个长期运行的 agent runtime,它 watch 任务队列,领任务、执行、上报。这样生命周期问题解决了,状态问题也解决了——执行器自己维护上下文缓存。这个拆分思路,其实就是 ax 这层抽象的核心。
3. 调度层设计:从 Karmada 到自定义调度器的取舍
3.1 单集群调度够不够用
先说结论:如果你的 agentic 负载规模在几百个并发以内,单集群的默认调度器加上合理的亲和性配置,基本够用。不要一上来就上多集群。
单集群调度的关键是把"语义"翻译成"调度约束"。比如一个智能体需要 GPU,你不能只写nvidia.com/gpu: 1,还要考虑 GPU 型号、显存大小、驱动版本。这些在 Kubernetes 里通过 nodeSelector、affinity、toleration 组合表达,但组合起来很啰嗦。
我的做法是封装一层"资源画像"标签。给每个节点打上一组语义标签,比如accel-type=a100、accel-mem=80g、cuda=12.2,然后智能体的任务描述里只写它需要什么画像,由一个 admission webhook 把画像翻译成具体的调度约束。这样应用侧不用关心底层节点细节,运维侧改标签就能调整调度策略。
3.2 多集群场景下 Karmada 的角色
当你的负载跨多个集群——比如有的集群有 GPU,有的集群在边缘,有的集群专门跑推理——就需要多集群调度。Karmada 在这个位置上的价值是:它提供了一层"集群联邦"的抽象,让你可以用类似单集群的方式描述跨集群的部署和调度策略。
Karmada 刚正式毕业这件事,对做 agentic 基础设施的人来说是个信号:多集群编排的成熟度到了可以上生产的阶段。但要注意,Karmada 解决的是"把工作负载分发到多个集群"的问题,它不解决"智能体任务在集群内部怎么调度"的问题。这两层是叠加的,不是替代的。
我的实践是:Karmada 负责跨集群的粗粒度分发(比如"这个智能体类型只在有 A100 的集群跑"),集群内的细粒度调度还是交给原生调度器加自定义扩展。两层各司其职,不要试图用一层解决所有问题。
3.3 自定义调度器什么时候值得写
写自定义调度器是有成本的:你要维护调度框架的版本兼容、要处理抢占、要处理亲和性、要处理各种边界情况。所以我的判断标准是:当默认调度器的扩展点(scheduler framework plugin)表达不了你的调度逻辑时,才考虑写独立的调度器。
大多数 agentic 场景其实用 scheduler framework plugin 就够了。比如你想让"同一用户的智能体任务尽量调度到同一节点以复用缓存",写一个 Score plugin 就行,不需要独立调度器。只有当你的调度逻辑需要全局状态、需要跨任务的协调、需要复杂的队列管理时,独立调度器才有意义。
| 调度需求 | 推荐方案 | 理由 |
|---|---|---|
| 按资源画像调度 | nodeSelector + 标签 | 原生能力足够 |
| 同用户任务亲和 | scheduler framework Score plugin | 扩展点够用 |
| 跨集群分发 | Karmada | 成熟的多集群方案 |
| 全局队列与抢占 | 自定义调度器 | 需要全局状态 |
| 任务优先级动态调整 | 自定义调度器 + CRD | 需要外部输入 |
4. Runtime 层:container runtime、device plugin 与 agent runtime 的边界
4.1 container runtime 到底管什么
很多人把 container runtime 和"运行时"混为一谈。在 Kubernetes 语境里,container runtime(containerd、CRI-O 这些)只管一件事:把镜像变成进程,并管理这个进程的生命周期。它不管你的进程是 HTTP 服务还是智能体,不管你的进程需要什么外部工具,不管你的进程内部状态。
所以当你看到container runtime is not running这类报错时,问题一定在更底层——containerd 服务挂了、CRI 配置错了、socket 路径不对。这类问题和 agentic 负载本身无关,是基础设施问题。
我踩过的一个坑:某次节点上的 containerd 因为磁盘满了被 OOM killer 干掉,但 kubelet 没有正确上报,导致 Pod 一直处于 Running 状态但实际进程已经没了。排查的时候先看systemctl status containerd,再看crictl ps,最后看 kubelet 日志,三步定位。这个排查链路值得记下来,因为 agentic 负载往往跑得久,这类"假 Running"问题更容易积累。
4.2 device plugin 在 agentic 场景的特殊价值
device plugin 是 Kubernetes 暴露硬件资源的机制。对 agentic 负载来说,它的价值在于:把"我需要某种加速能力"这件事标准化。
传统做法是在 Pod spec 里写nvidia.com/gpu: 1,但这只表达了"我要一个 GPU",没表达"我要一个能跑特定模型的 GPU"。device plugin 可以扩展出更细粒度的资源类型,比如example.com/inference-a100-80g,调度器看到这个资源类型就知道该往哪调度。
但 device plugin 有个限制:它只负责"分配"和"上报",不负责"初始化"。也就是说,GPU 分给你了,但驱动版本对不对、CUDA 环境全不全,得靠镜像自己保证。我的做法是在镜像里固化工具链版本,然后用一个 init container 做运行时校验,校验不过就快速失败,避免任务跑到一半才发现环境不对。
4.3 agent runtime 应该承担什么职责
agent runtime 是夹在 container runtime 和业务逻辑之间的一层。它的职责边界,我总结为四条:
第一,任务生命周期管理。领任务、执行、上报结果、处理重试。这层逻辑不应该散落在每个智能体的业务代码里。
第二,工具调用的统一入口。智能体要调用外部工具(编译、搜索、数据库),这些调用应该经过 runtime 层,这样能做限流、能做审计、能做缓存。
第三,上下文管理。上下文的加载、裁剪、持久化,应该在 runtime 层统一处理,而不是每个智能体自己实现一套。
第四,可观测性埋点。token 消耗、工具调用次数、任务耗时、失败原因,这些指标在 runtime 层采集最自然。
注意:agent runtime 不要做成"大而全"的框架。我见过太多团队把 runtime 做成一个巨型 SDK,结果业务侧为了用它要改一堆代码。好的 runtime 应该是"透明"的——业务代码感知不到它的存在,但它的能力又实实在在。
5. 一套可复现的 ax 落地路径
5.1 环境准备与最小验证
假设你已经有一个可用的 Kubernetes 集群(1.28 以上),下面是搭一个最小 ax 验证环境的过程。
第一步,确认 container runtime 正常:
# 检查 containerd 状态 systemctl status containerd # 检查 CRI 连通性 crictl info # 检查节点状态 kubectl get nodes -o wide第二步,部署一个 device plugin(以通用设备插件为例,具体按你的硬件选):
kubectl apply -f https://raw.githubusercontent.com/example/device-plugin/deploy.yaml # 验证资源上报 kubectl get nodes -o json | jq '.items[].status.allocatable'第三步,定义一个任务 CRD:
apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: agenttasks.example.com spec: group: example.com versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: image: type: string resourceProfile: type: string priority: type: integer scope: Namespaced names: plural: agenttasks singular: agenttask kind: AgentTask第四步,写一个最简单的 runtime 控制器,watch 这个 CRD 并创建对应的 Pod。这一步用 client-go 或者 kubebuilder 都行,核心逻辑就是"读 CRD → 翻译成 Pod spec → 创建"。
5.2 资源画像标签的落地细节
资源画像标签不是随便打的,要有一套命名规范。我的规范是:
accel-type:加速卡类型,如a100、h100、noneaccel-mem:显存大小,如40g、80gruntime-class:运行时类别,如standard、inference、trainingzone:可用区,用于跨集群调度
打标签的命令:
kubectl label node node-1 accel-type=a100 accel-mem=80g runtime-class=inference然后在 admission webhook 里做翻译:当 AgentTask 的resourceProfile是inference-large时,翻译成nodeSelector: {accel-type: a100, accel-mem: 80g}。这样应用侧只写画像名,运维侧改标签就能调整。
5.3 从单集群到多集群的平滑过渡
不要一步到位上多集群。我的过渡路径是:
阶段一,单集群跑通,验证任务 CRD、runtime 控制器、资源画像这套机制。
阶段二,引入 Karmada,但只做"分发"不做"调度"。也就是把 AgentTask 的 CRD 定义分发到多个集群,但任务实际在哪个集群执行还是手动指定。
阶段三,让 Karmada 的调度策略接管分发决策,基于集群的标签(比如"这个集群有 A100")自动决定任务去哪。
阶段四,在集群内部引入自定义调度器,处理细粒度的队列和抢占。
每个阶段之间留出足够的观察期,不要跳步。我见过太多团队在阶段一还没跑稳的时候就上阶段三,结果问题定位不了——到底是 CRD 的问题、Karmada 的问题还是调度器的问题,全混在一起。
6. 那些文档里不会写的踩坑记录
6.1 "假 Running"状态的排查链路
前面提过 containerd 挂掉但 Pod 显示 Running 的问题。完整的排查链路是这样的:
先看 Pod 状态和事件:kubectl describe pod <name>,如果事件里没有异常,说明 kubelet 认为一切正常。然后上节点看容器实际状态:crictl ps -a | grep <container-id>,如果容器不在列表里,说明容器已经没了但 kubelet 没同步。再看 kubelet 日志:journalctl -u kubelet -n 200,通常会看到 CRI 调用超时或失败。最后看 containerd:journalctl -u containerd -n 200,定位到具体原因(磁盘满、OOM、配置错误)。
这个链路的关键是:不要相信 kubectl 显示的状态,要上节点验证。agentic 负载跑得久,这类状态漂移的概率比短任务高得多。
6.2 device plugin 分配了但用不了的坑
device plugin 上报了资源,调度器也分配了,但容器起来发现用不了。常见原因有三个:
一是驱动版本不匹配。节点上的驱动版本和镜像里期望的版本不一致,device plugin 不检查这个,得靠 init container 校验。
二是设备权限问题。容器里访问设备节点需要正确的权限,有时候需要 privileged 或者特定的 securityContext。
三是资源泄漏。device plugin 分配了资源但容器启动失败,资源没有正确释放,导致后续任务调度不上去。这个要靠 device plugin 的健康检查和 kubelet 的回收机制,但实际中经常出问题,需要监控 device plugin 的分配计数。
6.3 上下文溢出导致的静默失败
智能体跑着跑着上下文超了,但进程没崩,只是开始输出垃圾。这种静默失败最难排查,因为从外部看进程还活着,指标也正常。
我的做法是在 runtime 层加一个上下文水位监控:当上下文使用量超过阈值(比如 80%)时,主动触发裁剪或告警。裁剪策略要业务侧可配置,因为不同智能体对上下文丢失的容忍度不一样。
提示:上下文水位这个指标,比 CPU/内存更能反映智能体的健康状态。建议把它作为一等公民指标来采集。
6.4 多集群场景下的网络延迟陷阱
跨集群调度的时候,任务在 A 集群,但它依赖的数据在 B 集群,每次访问都要跨集群网络。延迟可能从毫秒级变成几十毫秒,对高频工具调用的智能体来说是灾难。
我的做法是:在任务描述里加一个"数据亲和性"字段,调度器优先把任务调度到数据所在的集群。如果做不到,就在任务启动时把数据预取到本地。这个预取逻辑放在 runtime 层,业务侧无感。
7. 我对 ax 这层抽象的一些个人判断
做了几个 agentic 基础设施项目之后,我越来越觉得 ax 这层抽象的价值不在于"新",而在于"稳"。它没有引入什么革命性的技术,就是把已有的 Kubernetes 能力——CRD、调度框架、device plugin、多集群编排——重新组合,去适配智能体这种新的负载形态。
组合的方式有很多种,没有标准答案。我见过用 Knative 做 agentic 调度的,也见过用 Nomad 的,甚至见过直接用 systemd 管的。关键不是选哪个框架,而是想清楚三件事:任务的生命周期怎么定义、状态放在哪里、资源怎么表达。这三件事想清楚了,用什么工具都能搭出来。
如果你现在正在被 agentic 负载的调度问题困扰,我的建议是先别急着引入新框架。拿一个真实的智能体任务,用最朴素的方式在单集群跑一遍,把生命周期、状态、资源这三个问题暴露出来,再决定要不要抽象、怎么抽象。很多时候,问题比你想的简单,只是被"agentic"这个词吓住了。
最后分享一个我常用的判断标准:如果一个方案需要业务侧改代码才能用,那它就不是好的基础设施方案。好的 ax 层应该像水电一样——业务侧只管用,不用关心它怎么来的。