引言
Kubernetes 作为云原生时代的操作系统,其调度器(kube-scheduler)是整个集群的"决策大脑"。当你执行 kubectl apply -f pod.yaml 时,背后发生的一系列精密计算往往被开发者忽视。本文将从源码级别深入剖析 Kubernetes Pod 调度的完整生命周期,涵盖调度框架、预置策略的底层实现,以及如何编写自定义调度器来满足生产环境的复杂需求。
1. 调度器架构总览
kube-scheduler 本质上是一个监听 API Server 中 spec.nodeName 为空 Pod 的控制器。其核心架构经历了从 "scheduler" 到 "scheduling framework" 的演进:
- v1.15 之前:单体调度逻辑,所有策略硬编码在
pkg/scheduler/目录 - v1.15+:引入 Scheduling Framework,将调度流程拆分为 Queue、Filter、Score、Bind 等可插拔阶段
- v1.22+:Plugin API 稳定,支持 Multi-Scheduling Profiles
一个完整的 Pod 调度经历以下阶段:
- SchedulingQueue:未调度 Pod 入队,按优先级排序(支持 Pod Priority 和 FIFO 子队列)
- Scheduling Cycle:为单个 Pod 选择最优节点,包括 Filter + Score + Select 三步
- Binding Cycle:将选定节点写入 Pod 的 spec.nodeName,异步执行以降低延迟
2. 调度框架的插件机制
Scheduling Framework 定义了 13 个扩展点(Extension Points),每个插件可注册到一个或多个扩展点:
| 扩展点 | 类型 | 作用 |
|---|---|---|
| QueueSort | InQueue | 排序调度队列中的 Pod |
| PreFilter | PreFilter | 预处理 Pod 信息(如计算 Pod 的资源需求) |
| Filter | Filter | 过滤不满足条件的节点(硬性约束) |
| PostFilter | PostFilter | 全部节点被过滤后的后置处理(如抢占逻辑) |
| PreScore | PreScore | 评分前的预处理 |
| Score | Score | 为通过过滤的节点打分 |
| Reserve / Unreserve | Bind | 预留/释放节点资源 |
| Permit | Permit | 延迟绑定(等待或拒绝绑定) |
| PreBind / PostBind | Bind | 绑定前后的钩子(如存储卷挂载通知) |
| Bind | Bind | 执行节点绑定(默认是 API Server 写入) |
这套插件机制使得我们可以用极小的代码量实现自定义逻辑。
3. 预置策略的源码级解析
3.1 NodeResourcesFit(资源适配)
这是最常用的 Filter 插件,位于 pkg/scheduler/framework/plugins/noderesources/fit.go。它实现了三种检查策略:
- LeastAllocated:选择剩余资源最少的节点(有利于资源集中,减少碎片)
- MostAllocated:选择剩余资源最多的节点(适合批处理作业,方便装箱)
- RequestedToCapacityRatio:基于请求/容量的综合比值打分
NodeResourcesFit 的 Score 函数实现非常优雅——它不是简单地用 used/total,而是使用线性归一化公式:
score = (1 - used/total) * maxScore
3.2 NodeAffinity(节点亲和性)
该插件实现了 nodeAffinity 的过滤和打分逻辑,对应 API 字段 spec.affinity.nodeAffinity。关键实现位于 pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go:
- Filter 阶段:只保留满足
requiredDuringSchedulingIgnoredDuringExecution的节点 - Score 阶段:匹配
preferredDuringSchedulingIgnoredDuringExecution中的权重规则加分
3.3 TaintToleration(污点容忍)
Taint 是节点主动"拒绝"Pod 的机制。TaintToleration 插件的 Filter 逻辑是:遍历 Pod 的所有 Toleration,检查是否能匹配节点的 Taint。这里有一个容易被忽略的细节:tolerationSeconds 不仅控制 Pod 在节点上的容忍时长,还影响调度器是否会将 running 的 Pod 驱逐——这个驱逐逻辑由 TaintManager controller 实现。
3.4 InterPodAffinity(Pod 间亲和/反亲和)
这是最复杂的预置插件之一。它实现了:
- 跨节点计数:计算同 Pod 标签匹配的节点数量,实现拓扑域(Topology Domain)约束
- 加权反亲和:通过
AntiAffinityTermsByTargetNode实现将副本分散到不同故障域 - 优化计算:使用
topologyPairToPodCounts缓存避免重复遍历所有 Pod
4. 自定义调度器实战
当预置策略无法满足需求时,有三种扩展方式(从简到繁):
方案一:Scheduler Extender(外部扩展器)
在 kube-scheduler 启动时通过 --config 指定 extender URL,调度器会在 Filter/Score 阶段将候选节点转发到外部 HTTP 服务。优点是不需要重新编译调度器,缺点是引入网络调用增加延迟。
方案二:Scheduling Profile + 自定义插件
编写一个实现 Plugin 接口的 Go 包,编译成独立的调度器二进制文件,以独立 Deployment 运行:
package main
import (
"context"
"k8s.io/kubernetes/pkg/scheduler/framework"
)
type CustomScheduler struct {}
func (cs *CustomScheduler) Name() string { return "CustomScheduler" }
func (cs *CustomScheduler) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
// 自定义过滤逻辑:只选择带有特定标签的节点
if nodeInfo.Node().Labels["region"] != "us-east-1" {
return framework.NewStatus(framework.Unschedulable, "node not in target region")
}
return framework.NewStatus(framework.Success)
}
然后注册到调度框架配置中:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: my-custom-scheduler
plugins:
filter:
enabled:
- name: CustomScheduler
disabled:
- name: NodeResourcesFit
方案三:独立调度器(完全自主实现)
对于最复杂的场景,可以完全放弃 kube-scheduler,自己实现一个调度器:
- 监听 API Server 获取待调度 Pod
- 执行自定义调度算法
- 通过 Patch Pod 的
spec.nodeName完成绑定
这种方式在机器学习训练场景(如 Volcano、Koordinator 等批调度器)中非常常见。
5. 调度性能优化实践
在大规模集群(5000+ 节点)中,调度器可能成为瓶颈。以下是一些生产级优化策略:
- PercentageOfNodesToScore:预置在 50%(可调整到 10%-100%),当节点数超过
thresholdNodesPercentage(默认 50 节点)时,只对一定比例的节点打分 - Parallelism:控制同一时刻并行执行的调度 Goroutine 数(默认 16,建议根据集群规模调整)
- Informer 优化:使用 SharedInformer 减少 API Server 压力,本地缓存 Node/Topology 数据
- 事件驱动重试:Node/Pod 变更事件入队时采用
AddAfter延迟而非立即重试,避免抖动
6. 总结
Kubernetes 调度器的设计体现了"渐进式暴露复杂性"的理念:默认情况下你只需要定义资源的 requests/limits,高级场景则可以通过 Pod Affinity、Topology Spread Constraints、自定义调度器等方式精确控制 Pod 的分布。理解 Scheduling Framework 的插件机制,是掌握 Kubernetes 调度的关键一步。
下一篇文章我们将深入 kube-scheduler 的抢占(Preemption)机制——当一个高优先级 Pod 因资源不足无法调度时,系统如何驱逐低优先级 Pod 来腾出空间。

发表评论 取消回复