引言
Kubernetes 调度器(kube-scheduler)是整个容器编排平台的"大脑",它决定了 Pod 应该运行在集群的哪个节点上。看似简单的"放置"决策背后,融合了约束满足、资源优化、亲和性策略、优先级抢占等复杂机制。本文将从调度器架构出发,深入剖析调度框架(Scheduling Framework)、插件机制、优先级与抢占、以及自定义调度器的开发实践。
1. 调度器核心架构
1.1 调度的本质问题
从本质上说,K8s 调度是一个带约束的二维资源分配问题:给定一组 Pod(需求)和一组 Node(资源),找到满足所有硬约束(资源请求、节点选择器、亲和性)的最优放置方案,同时尽可能满足软约束(拓扑分布、负载均衡)。
问题的 NP-hard 特性在于:当 Pod 和 Node 规模增长时,穷举搜索的复杂度指数级上升。因此 K8s 采用了两阶段策略:
- 过滤阶段(Predicates):快速排除不满足硬约束的节点,缩小候选集
- 打分阶段(Priorities):对候选节点评分,选择最优节点
1.2 调度周期
每个调度周期(Scheduling Cycle)处理一个 Pod,包含以下步骤:
- 入队:Pod 通过 Informer Watch 机制进入调度队列
- 出队:调度器从活跃队列取出待调度 Pod
- 调度周期:执行 Filter → Score → Reserve → Permit → PreBind → Bind 全流程
- 绑定周期:异步执行 PreBind → Bind → PostBind,将调度结果持久化到 etcd
调度周期和绑定周期解耦,使得调度决策(同步)与持久化(异步)可以并行处理多个 Pod,提升吞吐。
2. 调度框架(Scheduling Framework)
自 K8s 1.18 起,调度器引入插件化的调度框架(Scheduling Framework),将原有硬编码的调度逻辑拆分为一系列可扩展的"扩展点"(Extension Points)。每个扩展点都有一组按顺序执行的插件。
2.1 扩展点全景
| 扩展点 | 时相 | 作用 |
|---|---|---|
| QueueSort | 队列排序 | 决定 Pod 在同一优先级下的出队顺序(FIFO/优先级) |
| PreFilter | 过滤前预处理 | 预计算信息或验证前置条件(如检查 PVC 是否已绑定) |
| Filter | 过滤(硬约束) | 排除不满足条件的节点(资源不足、节点选择器不匹配) |
| PostFilter | 过滤后 | 当所有节点被过滤后触发,用于抢占逻辑(找出可驱逐的低优先级 Pod) |
| PreScore | 打分前预处理 | 预计算打分所需的共享数据 |
| Score | 打分(软偏好) | 对候选节点打分(0-100),分数加权求和 |
| NormalizeScore | 分数归一化 | 将分数映射到统一范围后再加权聚合 |
| Reserve | 预留 | 在 Pod 绑定前预留资源(防止资源冲突) |
| Permit | 许可 | 决定 Pod 是否可以/延迟/拒绝绑定(实现 Coscheduling) |
| PreBind | 绑定前 | 执行绑定前的准备工作(如挂载网络卷) |
| Bind | 绑定 | 将 Pod 与 Node 的绑定关系写入 etcd |
| PostBind | 绑定后 | 绑定完成后的清理或日志记录 |
| Unreserve | 释放预留 | 绑定失败或 Pod 被删除时释放预留资源 |
2.2 内置插件作用对照
理解内置插件有助于理解默认调度行为:
- NodeResourcesFit(Filter/Score):检查 CPU/内存请求是否满足,按资源使用率打分(LeastAllocated/MostAllocated)
- NodeName(Filter):匹配 spec.nodeName(直接指定节点时)
- NodePorts(Filter/PreBind):确保节点上所需端口未被占用
- NodeAffinity(Filter/Score):实现 nodeSelector 和 requiredDuringScheduling 节点亲和
- PodAffinity(Filter/Score/PreScore):实现 Pod 间亲和与反亲和
- TaintToleration(Filter/Score):污点容忍匹配
- TopologySpreadConstraints(Filter/Score):拓扑域均匀分布
- VolumeBinding(Filter/PreBind/Reserve):CSI/持久卷绑定检查
- PrioritySort(QueueSort):按 PriorityClass 优先级排序出队
- DefaultBinder(Bind):默认绑定插件,写入 etcd
3. 优先级与抢占(Priority and Preemption)
当集群资源不足时,调度器需要在高优先级 Pod 无法调度时,通过驱逐低优先级 Pod 来"腾出空间"。
3.1 抢占流程
- PostFilter 扩展点触发抢占调度(
schedulingCycle中 Filter 全部失败后) - 计算 Pod 的所有可抢占节点(Potential Nodes),即驱逐若干低优先级 Pod 后资源可用的节点
- 执行 Dry-Run 预绑定(Assume),验证驱逐后是否能调度成功
- 选择"伤害最小"的节点(Minimum Victims,即驱逐的 Pod 数量最少、优先级最低的组合)
- 发送驱逐请求给 API Server,等待低优先级 Pod 优雅终止(Graceful Termination)
- 低优先级 Pod 终止后释放资源,高优先级 Pod 重新入队并调度
3.2 PDB(Pod Disruption Budget)保护
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: nginx-pdb
spec:
minAvailable: 2 # 或 maxUnavailable: 1
selector:
matchLabels:
app: nginx
PDB 限制了同一时间可被驱逐的 Pod 数量。抢占调度必须尊重 PDB 约束——即使为了高优先级 Pod,也不能违反 PDB 的 minAvailable/maxUnavailable 限制。
4. 高级调度策略
4.1 拓扑分布约束(Topology Spread)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-service
matchLabelKeys:
- pod-template-hash
nodeAffinityPolicy: Honor
nodeTaintsPolicy: Honor
maxSkew 控制不同拓扑域中 Pod 数量的最大差异。topologyKey 定义拓扑域(可用 zone、region、rack 等)。matchLabelKeys 用于排除滚动更新时新旧 Pod 属于同一副本集的 skew 计算。
4.2 亲和性调度进阶
K8s 提供了两套亲和机制:
- Node Affinity(K8s 1.4+):将 Pod 调度到匹配节点标签的节点,支持 required/Preferred 两级
- Inter-Pod Affinity/AntiAffinity(K8s 1.6+):基于 Pod 标签的关系约束,实现 Pod 间的"抱团"或"避让"
Pod AntiAffinity 的典型用法是高可用部署:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: database
topologyKey: kubernetes.io/hostname
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: database
topologyKey: topology.kubernetes.io/zone
4.3 多调度器
K8s 支持同时在集群中运行多个调度器,每个调度器有独立名称。通过 spec.schedulerName 指定使用哪个调度器:
apiVersion: v1
kind: Pod
metadata:
name: custom-scheduled-pod
spec:
schedulerName: my-custom-scheduler
containers:
- name: app
image: nginx
5. 自定义调度器开发
5.1 开发模式
自定义调度器有两种开发模式:
- 调度器扩展(Scheduler Extender):通过 Webhook 将 Filter/Score 远程调用到外部服务,适合渐进式集成
- 独立调度器(Out-of-tree Scheduler):基于 K8s 调度框架库(k8s.io/kube-scheduler)构建独立的二进制,拥有完全控制力
5.2 自定义调度器骨架
使用 k8s.io/kube-scheduler 框架构建自定义调度器:
package main
import (
"os"
"k8s.io/component-base/cli"
"k8s.io/kube-scheduler/config/v1beta3"
"k8s.io/kube-scheduler/scheduler/framework/runtime"
"k8s.io/kube-scheduler/scheduler/plugins/defaultbinder"
"k8s.io/kube-scheduler/scheduler/plugins/defaultpreemption"
)
func main() {
// 默认配置
cfg := v1beta3.KubeSchedulerConfiguration{}
// 注册自定义插件
rts, err := runtime.NewRuntime(componentConfig, registryWithCustomPlugins...)
// ...
cmd := app.NewSchedulerCommand(
app.WithPlugin("CustomScheduler", NewCustomScheduler),
)
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}
5.3 自定义 Scheduler Profile 配置
无需编写代码也可以通过 Scheduler Profile 微调内置插件行为:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
score:
disabled:
- name: NodeResourcesFit # 禁用默认资源评分
enabled:
- name: MyCustomScore # 启用自定义评分
weight: 2
preScore:
enabled:
- name: MyCustomPreScore
pluginConfig:
- name: NodeResourcesFit
args:
scoringStrategy:
type: MostAllocated # 反向评分:优先填满单节点
resources:
- name: cpu
weight: 1
- name: memory
weight: 3 # 内存权重更高
6. 性能调优与排障
6.1 调度器性能指标
调度器暴露 Prometheus 监控:
scheduler_scheduling_attempts_total:调度尝试成功/失败次数scheduler_scheduling_duration_seconds:调度延迟(p99 通常应 < 100ms)scheduler_pending_pods:各类队列中等待调度的 Pod 数scheduler_preemption_attempts_total:抢占次数及成功率
6.2 常见排障场景
Q: Pending Pod 为什么无法调度?
排查顺序:
- 检查
kubectl describe pod的 Events,查看 Filter 失败的节点和原因 - 检查节点资源容量 vs 请求量:
kubectl top nodes - 检查亲和性/污点配置是否过于严格
- 检查 PVC 是否已成功绑定(WaitForFirstConsumer 模式下 PVC 延迟绑定)
Q: 如何排查调度延迟过高?
- 调度器本身瓶颈:查看
scheduling_duration_seconds直方图,定位是 Filter 还是 Score 阶段慢 - API Server 压力大:调度频率过高(大量 Pod 同时创建/重启),考虑启用 PercentageOfNodesToScore 限制打分节点数
- etcd 延迟:绑定周期写 etcd 慢,影响调度吞吐
6.3 PercentageOfNodesToScore 调优
大规模集群(>5000 节点)中,默认对所有节点打分成本过高。调度器提供 PercentageOfNodesToScore 配置,限制打分的节点比例(按最快原则选择该百分比的随机节点打分):
apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration percentageOfNodesToScore: 10 # 仅对 10% 节点打分(最低 5%,默认 50%)
7. 总结与选型建议
| 场景 | 推荐策略 |
|---|---|
| 高可用部署 | Pod AntiAffinity(required) + TopologySpread(maxSkew=1) |
| 资源利用率最优 | NodeResourcesFit(LeastAllowed) + 紧凑打分 |
| 混合负载/批处理 | 低优先级 + 抢占 + PriorityClass |
| GPU 共享调度 | 自定义 Score 插件(Binpack/Spread 策略) |
| 边缘节点调度 | NodeAffinity + Taint/Toleration + 自定义拓扑约束 |
| 大规模集群(>5000 节点) | PercentageOfNodesToScore 调低 + 自定义调度器分区 |
| 延迟敏感型应用 | NodeResourcesFit(MostAllocated 留余量) + Preemption 禁用 |
K8s 调度器的核心价值在于声明式约束 + 自动化优化:开发者只需声明"需要什么资源"和"偏好什么位置",调度器自动找到最优放置。理解其内部机制,能帮助我们在生产环境中做出更合理的设计决策。

发表评论 取消回复