前言:为什么需要理解K8s调度器?
在Kubernetes集群中,kube-scheduler 是负责将 Pending 状态的 Pod 调度到合适 Node 上的核心组件。对于大多数用户而言,调度是一个透明的"黑盒"——Pod 创建后会被自动分配到某个节点上运行。但当集群规模扩大、工作负载变得复杂时,理解调度器的内部机制就变得至关重要:你需要解释为什么某个 Pod 始终 Pending、如何控制亲和性分布、如何实现自定义调度策略,以及优化大规模集群的调度性能。
本文将从调度器的完整生命周期出发,深入剖析 Scheduler Framework 的 13 个扩展点、优先级抢占机制、Volume 拓扑约束,以及如何基于 Scheduler Framework 构建自定义调度器。
1. Scheduler 总体架构
Kubernetes Scheduler 本质上是一个控制循环组件,其核心职责可以简化为:为每一个 Pending 的 Pod 选择一个最优的 Node。完整架构包含以下关键模块:
- Informer Watch 机制:通过 Informer 监听 API Server 上的 Pod 和 Node 变更事件
- 调度队列(SchedulingQueue):三层队列结构 — activeQ(待调度队列)、unschedulableQ(不可调度重试队列)、backoffQ(退避队列)
- 调度缓存(Scheduler Cache):维护集群节点的内存快照 NodeInfo,避免每次调度都访问 API Server
- 调度框架(Scheduler Framework):标准化的扩展点插件架构
- 调度 Profile:支持多调度器配置,为不同工作负载设置不同调度策略
从 Kubernetes 1.15 之后,Scheduler Framework 从一个 alpha 特性稳定到今天,已经完全替代了老旧的 Policy 配置方式,成为扩展调度器行为的标准手段。
2. 双周期调度:Scheduling Cycle 与 Binding Cycle
K8s 调度器将 Pod 调度分为两个独立的循环(Cycle),这种关注点分离的设计使得调度和绑定可以独立扩展:
2.1 Scheduling Cycle(调度周期)
调度周期负责为 Pod 选择最合适的 Node。调度周期是串行执行的,确保同一时刻只有一个 Pod 在经历决策过程。核心输出为 ScheduleResult(Node Name)。
2.2 Binding Cycle(绑定周期)
绑定周期负责将调度决策同步到 API Server(创建 Binding 对象)。绑定周期是异步并行执行的,多个绑定操作可以同时进行。这种设计意味着:决策缓慢不会阻塞 API Server,绑定缓慢不会阻塞新的调度决策。
两个周期之间的纽带是 Permit 插件 —— 它可以在调度结果确定后、绑定开始前介入,执行等待(Wait)、拒绝(Reject)或允许(Allow)操作,为调度器提供"事后审批"能力。
3. Scheduler Framework 13 个扩展点全解析
Scheduler Framework 通过在调度和绑定管线的 13 个位置(Extension Points)插入插件来实现能力扩展。理解这 13 个扩展点的执行顺序和职责是掌握调度的关键:
3.1 QueueSort(队列排序)
执行时机:Pod 进入调度队列时
作用:决定 activeQ 中 Pod 的排序顺序。默认插件 PrioritySort 根据 Pod 的 Priority 值(priorityClassName 对应的整数值)降序排列,高优先级 Pod 优先被调度。
3.2 PreFilter(预过滤)
执行时机:Filter 之前
作用:预检查 Pod 的集群状态,计算可能影响调度的元数据并写入 CycleState。典型用途包括检查 Pod 请求的资源是否超过集群总量,以及为后续插件缓存中间结果。
3.3 Filter(过滤/断言)
执行时机:核心过滤阶段
作用:排除不满足硬性约束的节点。只有全部 Filter 插件都返回 Success 的 Node 才会进入下一阶段。内置 Filter 包括:
NodeResourcesFit:检查 CPU/Memory 资源是否足够NodeName:匹配 spec.nodeNameNodeUnschedulable:过滤掉 spec.unschedulable=true 的节点TaintToleration:检查 Pod 的 toleration 是否匹配节点 taintNodeAffinity:实现 nodeSelector 和 requiredDuringScheduling 逻辑EBSLimits、GCEPDLimits:检查云厂商存储卷限制
3.4 PostFilter(后过滤)
执行时机:Filter 阶段没有找到可用 Node 时
作用:当 Pod 无法调度到任何节点时触发。抢占逻辑(Preemption)的 PostFilter 插件在此处执行,分析哪些节点上的哪些低优先级 Pod 可以被驱逐来腾出空间。
3.5 PreScore(预评分)
执行时机:Score 之前
作用:预计算 Score 阶段需要的状态,写入 CycleState 供 Score 插件使用。
3.6 Score(评分/优先)
执行时机:候选节点评分阶段
作用:对通过 Filter 的候选节点进行打分(0-100 分),各 Score 插件的权重(weight)加权聚合后得到最终分数。最高分节点即为最佳调度目标。内置 Score 包括:
NodeResourcesFit(LeastAllocated/MostAllocated/RequestedToAllocatedRatio):基于资源利用率选择最优节点ImageLocality:偏好已有容器镜像的节点(减少镜像拉取)InterPodAffinity:实现 preferredDuringScheduling 的 Pod 亲和/反亲和NodeAffinity:实现 preferredDuringScheduling 的节点亲和TaintToleration:偏向容忍度更少的节点TopologySpread:确保 Pod 在拓扑域间均匀分布
3.7 NormalizeScore(归一化评分)
执行时机:Score 之后、最终排名之前
作用:将各个 Score 插件的原始分数归一化到统一范围,确保不同评分维度的分数可比。K8s 1.23+ 引入。
3.8 Reserve(预留)
执行时机:Permit 返回 Allow 后、绑定前
作用:为 Pod 在目标 Node 上预留资源。多个 Reserve 插件可以协作。VolumeBinding 插件使用此扩展点绑定 PVC。
3.9 Permit(审批)
执行时机:调度结果确定后
作用:对最终调度决策进行"放回"式审批。返回值有三种:
Allow:立即放行,进入 Reserve/Bind 阶段Wait:等待一段时间(可配置超时),超时后转为 RejectReject:拒绝调度,Pod 退回到队列重新排队
典型应用:等待目标节点上的同组 Pod 全部就绪后再绑定,实现 Gang Scheduling。
3.10 PreBind / 3.11 Bind / 3.12 PostBind / 3.13 Unreserve
PreBind:绑定前的前置检查(如确认 PVC 绑定状态)
Bind:创建 Binding 对象同步到 API Server
PostBind:绑定后的清理/日志钩子
Unreserve:绑定失败时释放预留资源
4. Pod Priority 与 Preemption 抢占机制
当高优先级 Pod 无法找到合适的 Node 时,抢占机制会驱逐目标节点上的低优先级 Pod。
4.1 PriorityClass 配置
PriorityClass 是集群级别资源,定义 Pod 的优先级,value 字段决定优先级高低(数值越大越高)。K8s 默认提供 system-cluster-critical(2000000000)和 system-node-critical(2000001000)。
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: production-critical
value: 1000000
globalDefault: false
description: "生产环境关键服务"
4.2 抢占执行流程
- 模拟节点过滤:对每个节点,模拟移除优先级低于当前 Pod 的所有 Pod
- 可行性检查:检查移除后当前 Pod 是否能调度到该节点
- 受害者选取:选择"代价最小"的节点(受害者最少、优先级差距最小)
- 优雅驱逐:发送 Delete 请求,kubelet 执行优雅终止
抢占过程同时验证 PreemptionPolicy(默认 PreemptLowerPriority)和 PDB 约束,确保驱逐不违反服务可用性保障。
5. Volume Topology 与存储约束
Stateful 应用依赖可用区绑定的块存储卷,K8s 通过 Topology 机制处理这种约束。
- StorageClass AllowedTopologies:限制 PV 可创建的拓扑域
- WaitForFirstConsumer 延迟绑定:根据 Pod 调度结果动态选择可用区创建 PV
- VolumeBinding 多扩展点协作:PreFilter 检查模式 → Filter 匹配拓扑 → Reserve 预绑定 → Unreserve 失败释放
- Node 拓扑标签:由 kubelet --cloud-provider 自动标注(如 topology.kubernetes.io/zone)
6. 自定义调度器与 Scheduler Profile
通过 Scheduler Profile 可以在不修改源码的情况下为不同工作负载配置不同调度策略:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
plugins:
score:
enabled:
- name: NodeResourcesFit
weight: 2
- name: ImageLocality
weight: 1
disabled:
- name: "*"
permit:
enable:
- name: GangScheduling
postFilter:
enabled:
- name: DefaultPreemption
7. 调度性能优化实践
在大规模集群(5000+ Node)中,调度性能优化的关键手段:
- PercentageOfNodesToScore:控制 Filter 后评分节点比例,建议:100节点以下100%、100-1000节点50-80%、1000+节点10-30%
- 调度 Cache Snapshot:NodeInfo 内存快照,避免每次请求 API Server,延迟稳定在毫秒级
- Pod 反亲和性优化:优先用 preferred 替代 required,用 topologySpreadConstraints 替代 Pod 反亲和以降低复杂度
8. 总结
Kubernetes Scheduler 通过标准化框架和 13 个扩展点,平衡了通用可用与深度可定制之间的矛盾。掌握双周期调度架构、优先级抢占、Volume 拓扑约束三大核心机制,不仅能解决日常调度问题,还能为构建面向领域的高性能调度系统奠定基础。
三条实用建议:
- 生产环境尽量使用 Scheduler Framework 而非自定义调度器(降低维护成本)
- 通过 Priority + PDB 组合保证关键服务稳定性,而非无限抢占
- 合理使用 WaitForFirstConsumer 延迟绑定模式平衡 Stateful 应用的可用区分布

发表评论 取消回复