学习库/ AI Infra 转型/ 06 · 06 K8s 调度器原理
🧠 AI Infra 转型 · 第 7 / 17 篇

06 K8s 调度器原理

3108 字· 阅读约 7 分钟· 2026-08-23 更新

06 K8s 调度器原理

资源层把 GPU 注册好了,接下来是调度层:Pod 来了,谁来决定它跑在哪张卡上?这一章吃透 kube-scheduler——你作为云原生工程师的看家本领,要在这里深化成「AI 感知」的调度能力。

💡 给你的话:这章对你应该是复习+深化。你已经懂 K8s 调度,重点是理解「为什么 AI 负载让默认调度器力不从心」,引出 Volcano。但本章会把调度器的每个阶段、抢占机制、拓扑感知都讲透——不要跳过,这是 07/08 的基础。

📖 本章导读与前置依赖

  • 前置(你已在 04/05 学过):GPU 已被 device-plugin 注册为可调度资源(04)、可切分(05);kube-scheduler 基本流程(你的老本行)。本章深化成「AI 感知」调度。
  • 本章解决什么:Pod 来了怎么决定跑哪张卡——kube-schedulerScheduler Framework 扩展点、Scheduler Extender、以及为什么默认调度器搞不定 AI 负载(Gang 问题、拓扑无知、队列弱)。
  • 学习目标:能基于 Scheduler Framework 写一个自定义 Plugin(如按 GPU 型号打分);理解 Gang Scheduling 为什么是 AI 训练刚需(引出 07);理解拓扑感知调度为什么影响训练性能;知道 Extender 的性能局限。
  • 怎么读:6.1-6.4 复习+深化框架,6.5 是核心(AI 调度四大痛点),6.6 看拓扑感知深度,6.7 看队列与抢占,6.8 看进阶路线。

6.1 调度器做什么

kube-scheduler 接收「待调度 Pod」,根据资源、约束、策略从集群选出最优节点并绑定。目标是:满足约束的前提下,集群资源利用最优、任务尽快运行。

💡 类比:调度器像「出租车调度中心」——乘客(Pod)报目的地和需求(要几张卡、什么型号),调度中心从所有空车(节点)里挑一辆最合适的派过去。

对 AI 场景,调度器还得多想一层:GPU 是昂贵稀缺资源,且分布式训练要「整组卡同时到位」,否则会死锁或空等。默认调度器没这能力。


6.2 默认调度流程(Scheduler Framework)

Scheduler Framework 是 K8s 1.19+ 把调度器拆成的一组「扩展点(插件点)」,让你能在调度各阶段插入自定义逻辑。

Scheduler Framework 扩展点

① 入队 SchedulingQueue
② QueueSort 排序(谁先调度)
③ PreFilter  预过滤(快速排除)
④ Filter     过滤(节点是否满足资源/亲和/污点等硬约束)
⑤ PostFilter 抢占(若无节点满足,能否抢占他人?)
⑥ Score      打分(满足约束的节点打分,越优越高)
⑦ Reserve    预留资源(防止并发冲突)
⑧ Permit     许可(可插件放行/拒绝)
⑨ PreBind/Bind 绑定到节点
⑩ PostBind   收尾

名词解释

  • Filter(过滤):硬约束筛选,如「节点有没有足够 GPU」「污点是否容忍」,不满足直接淘汰。
  • Score(打分):在通过 Filter 的节点里选最优,如「资源均衡度」「亲和性得分」。
  • 抢占(Preemption):高优先级 Pod 没资源时,驱逐低优先级 Pod 腾位置。

每个阶段对 AI 负载的意义

阶段 默认行为 AI 场景的不足
QueueSort 按 priorityClass 排序 无队列/配额/公平份额 → 一个团队占满集群
Filter 检查 nvidia.com/gpu 够不够 不检查「整组卡是否同时到位」→ Gang 死锁
Score 资源均衡度打分 不考虑 GPU 拓扑 → 可能打散到跨节点
PostFilter 抢占低优 Pod 不支持「整组抢占」

6.3 Scheduler Framework 扩展点(⭐面试核心)

扩展点 阶段 你能做什么
QueueSort 排序 自定义优先级队列
PreFilter 过滤前 预计算、快速剪枝
Filter 过滤 自定义「节点是否可放」规则(如 GPU 型号亲和)
PostFilter 抢占 自定义抢占逻辑
Score 打分 自定义「多合适」评分(如 NVLink 拓扑优先)
Reserve 预留 预留资源/状态
Permit 许可 等待/批准/拒绝
PreBind/Bind 绑定 绑定前处理、执行绑定
PostBind 收尾 状态清理

重点:你之前对话里问「需不需要精通调度器」——对竞争性面试,至少要能基于 Scheduler Framework 写一个自定义 Plugin(比如「按 GPU 型号打分」的 Score 插件)。这是你云原生背景的直接兑现点。

💡 类比:Scheduler Framework 就像你熟悉的 Admission Webhook(准入控制钩子)——都是在请求生命周期里插入自定义逻辑的扩展点。Webhook 管「能不能创建对象」,Framework 管「Pod 调到哪个节点」。


6.4 Scheduler Extender(HTTP 扩展)

当 Plugin 不够(如需要外部系统决策),可用 Scheduler Extender:一个外部 HTTP 服务,kube-scheduler 在 Filter/Score 阶段调用它。

kube-scheduler ──HTTP──> Extender(如 qGPU 调度器)
                 返回可调度节点 / 打分
  • 优点:解耦,可用任意语言实现复杂逻辑。
  • 局限:同步 HTTP 调用,吞吐受限;扩展性不如原生 Plugin。
  • 典型用例:腾讯 qGPU 作为 Extender 实现细粒度算力/显存调度(见 08)。

⚠️ 内幕:Extender 的同步 HTTP 是性能瓶颈——大集群(万卡)调度吞吐跟不上,这也是字节 Gödel 改用「乐观并发」的原因(见 08)。小集群用 Extender 没问题,大规模要换路子。


6.5 GPU 调度中默认调度器的局限(引出 Volcano)

默认 kube-scheduler 天生不为 AI 设计,痛点:

Gang 调度问题

  1. Gang 问题:8 卡训练任务,若只抢到 4 卡就启动 → 其余 4 卡永远等不到 → 死锁/空转。默认调度器没有「整组同时起」语义。
    • 名词解释Gang Scheduling(成组调度) 指一组 Pod 必须同时拿到全部资源才一起启动,否则全部等待。
  2. 拓扑无知:不懂 NVLink 比跨节点网络快,可能把任务分散到高延迟节点。
  3. 队列/配额弱:多团队共用集群,默认调度器没有「队列 + 公平份额 + 配额」的批处理语义。
  4. 多厂商亲和弱:难表达「这个任务只要 A100、那个只要 MI300X」。

这些局限 → 引出 Volcano(批处理调度) 与工业级扩展(07/08)。

重点:理解「Gang 为什么必要」是 AI 调度的核心。分布式训练的 8 个 worker 是一个整体,少一个都训不动——所以必须「要么 8 个一起起,要么一个都不起」,避免 4 个起了空等死锁。这是你 K8s 思维的延伸:原生调度器管「单个 Pod」,AI 要管「一组 Pod」。


6.6 拓扑感知调度深度(AI 大规模训练必懂)

前面提了一句「拓扑无知」,这里讲透——这是大模型训练性能的隐形命脉,也是字节火山引擎/AI Infra 岗位的高频考点。

GPU 拓扑感知调度

6.6.1 为什么拓扑这么重要

同一个 8 卡训练,拓扑放对了走 NVLink(~900GB/s),放错了走跨节点以太网(~10GB/s),通信速度差 90 倍——训练 MFU 可能从 50% 掉到 15%。这就是为什么大厂调度器都重金投拓扑感知。

6.6.2 GPU 拓扑发现

一个 8 卡节点内,8 张 GPU 不是「全对称」的——它们通过 NVLink/NVSwitch 互联,但有「同 NVSwitch 域」和「跨域」之分,还有 NUMA 亲和(GPU 挂在不同 CPU 下)。用 nvidia-smi topo -m 能看到节点内 GPU 的拓扑矩阵(哪几张卡间走 NVLink、哪几张跨 NUMA)。

6.6.3 NVSwitch 域

NVSwitch 让节点内 8 卡全互联无阻塞(任意两卡间带宽一致)。没有 NVSwitch 的节点,卡间走 PCIe 或部分 NVLink,拓扑不对称。

6.6.4 拓扑感知打分算法

调度器在 Score 阶段,对同一 Job(Gang)的多个 Pod,优先把它们调到:

  1. 同一节点(节点内 NVLink 最快,~900GB/s)
  2. 同一 NVSwitch 域(次快)
  3. 同一 NUMA(内存亲和,避免跨 CPU 内存访问)

避免把一个 8 卡训练打散到 8 个节点(全走跨节点 IB/以太网,慢几倍)。

6.6.5 HNT(异构网络拓扑)

HNT(Heterogeneous Network Topology,异构网络拓扑):把「卡间 NVLink + 机间 IB/RoCE 拓扑」统一建模,调度器据此选最优放置策略。这是大规模训练调度的前沿方向(字节/阿里都在做)。

6.6.6 实现

Volcano 的 numa-topology/topology-aware 插件、Koordinator 的拓扑感知、Gödel 都做这件事。本质是 Scheduler Framework 的 Score 插件读节点的拓扑标签(GFD 会打 nvidia.com/gpu.replicas、NVSwitch 域信息等)来打分。

为什么重要:同样是 8 卡训练,拓扑放对了走 NVLink(~900GB/s),放错了走跨节点以太网(~10GB/s),通信速度差 90 倍——训练 MFU 可能从 50% 掉到 15%。这就是为什么大厂调度器都重金投拓扑感知。


6.7 调度队列与抢占机制 + 拓扑感知调度

6.7.1 调度队列与抢占(AI 场景常考)

待调度的 Pod 进 SchedulingQueue(默认按优先级排序)。当高优 Pod 找不到资源时,PostFilter 阶段会触发抢占——从低优 Pod 占用的节点上驱逐一个或多个低优 Pod,腾位给高优 Pod。被驱逐的 Pod 重新进队列。AI 训练任务常设高 priorityClass,调度器会为它抢占低优推理/批处理 Pod 的 GPU。

6.7.2 拓扑感知调度(为什么对 AI 重要)

  • 同一个 8 卡节点的卡间走 NVLink(数百 GB/s),跨节点走 IB/以太网(慢得多)。
  • 默认调度器不懂「这 8 个 GPU Pod 最好落在同一个节点」,可能把它们打散到 8 个节点 → 多卡训练通信全走慢网络,性能暴跌。
  • 解法:用 Scheduler Framework 的 Score 插件做拓扑亲和打分——优先把同一 Job 的 Pod 调到同一节点/同一 NVSwitch 域。Volcano 的 numa-topology 插件、Koordinator 的拓扑感知都在做这件事。

这就是为什么 06 要引出 Volcano/Gödel:原生调度器没做 GPU 拓扑感知,AI 训练必须靠扩展调度器保证「整组卡 + 同节点/同 NVSwitch 域」。


6.8 扩展方向(你的能力进阶路线)

层级 做法 适合
用现成 直接 kube-scheduler + 标签亲和 简单异构
写 Plugin Scheduler Framework 自定义 Filter/Score 轻度定制、性能佳
用 Extender HTTP 外部调度逻辑 复杂隔离(qGPU)
换调度器 Volcano / Gödel / Kueue 大规模 AI 训练


6.8 实战:观察调度器 Filter/Score 与抢占

6.8.1 观察节点亲和(Filter 阶段)

# 复用 kind 集群,给节点打标签(模拟不同 GPU 型号)
kubectl label node gpu-lab-worker gpu-type=a100 --overwrite
kubectl label node gpu-lab-worker2 gpu-type=l40s --overwrite

# 提交带节点亲和的 Pod(只要 A100)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: affinity-test}
spec:
  containers: [{name: t, image: busybox:1.36, command: [sleep, 300]}]
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions: [{key: gpu-type, operator: In, values: [a100]}]
EOF

kubectl get pod affinity-test -o wide
# NODE 应显示带 a100 标签的节点

6.8.2 观察抢占(PostFilter 阶段)

# 先占满资源(低优)
kubectl run hog --image=busybox --command -- sleep 600 --limits=cpu=3 --priority=low

# 高优 Pod 抢占
cat <<EOF | kubectl apply -f -
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata: {name: high-pri}
value: 1000
---
apiVersion: v1
kind: Pod
metadata: {name: important}
spec:
  priorityClassName: high-pri
  containers: [{name: t, image: busybox:1.36, command: [echo, got-resources], resources: {limits: {cpu: 3}}}]
EOF

kubectl get pods -w
# 低优 Pod 被驱逐,高优 Pod 启动

目的:亲眼看到抢占和亲和——这就是调度器 Filter/Score/PostFilter 的直观效果。


6.9 自测题

  1. 说出 Scheduler Framework 的主要扩展点(至少 5 个)。
  2. Filter 和 Score 的区别?PreFilter 和 Filter 的关系?
  3. 什么是 Scheduler Extender?它和 Plugin 比,主要优劣?
  4. Scheduler Framework 和 Admission Webhook 在设计思想上有何共性?
  5. 分布式训练为什么需要 Gang Scheduling?默认调度器为什么做不到?
  6. ⭐ 画图说明 Gang 调度的死锁场景:默认调度器起了 4/8 卡会发生什么?
  7. 为什么「按 NVLink 拓扑亲和」能提升训练效率?放错拓扑 MFU 会怎样?
  8. 给你一个需求「按 GPU 型号打分优先」,你应该写哪种扩展?
  9. ⭐ 拓扑感知打分算法的优先级是什么?(同节点 > 同 NVSwitch 域 > 同 NUMA)
  10. ⭐ HNT 是什么?它解决什么问题?
  11. ⭐ 调度队列的抢占机制怎么工作?高优训练 Pod 怎么抢低优推理 Pod 的 GPU?
  12. Extender 在大规模集群为什么有性能瓶颈?

答上即可进 07——亲手用 Volcano 解决 Gang 问题。

← 返回专栏