Kubernetes 调度器深度实战:从调度框架到自定义调度器开发
调度器是 Kubernetes 集群的"决策大脑",它决定了 Pod 应该运行在哪个 Node 上。本文深入剖析 kube-scheduler 的内部机制、调度框架设计、核心算法,并通过 Go 代码实战演示如何开发自定义调度器与高级调度策略。
一、调度全景:从 Pod 创建到绑定
理解调度器,首先要看清 Pod 生命周期中的调度路径。当用户执行 kubectl apply -f pod.yaml 时,背后发生的是:
用户请求 → API Server (准入/校验) → etcd 持久化
↓ (Pod 状态为 Pending)
调度器 Watch API Server → 发现未绑定 Pod → 调度周期 → Bind
↓
目标节点 kubelet → CRI 创建容器 → 上报 Running
关键洞察:kube-scheduler 不直接与 kubelet 交互。它只负责通过 API Server 将 Pod 的 spec.nodeName 更新为目标节点,kubelet 监听到该字段变更后才会启动容器。这种基于声明式的间接通信解耦了调度逻辑与执行逻辑,是整个调度架构灵活性的基石。
一个常被误解的点是:调度器处理的不仅是新创建的 Pod。Node 故障驱逐、手动迁移、抢占(preemption)、重调度(descheduler)都会重新进入调度 pipeline。因此调度器实际上是一个持续运行的调和循环(reconciliation loop)。
二、调度框架:可扩展的插件架构
从 Kubernetes 1.15 开始,kube-scheduler 引入了调度框架(Scheduling Framework),将调度过程拆解为一系列可扩展的阶段。
2.1 调度周期与绑定周期
每个调度周期(Scheduling Cycle)是互斥的、单 Pod 的处理过程。绑定周期(Binding Cycle)则处理 Node 绑定的异步验证:
Scheduling Cycle(串行)
├── QueueSort // 排序等待队列中的 Pod
├── PreFilter // 预检:校验 Pod 与集群状态兼容性
├── Filter // 过滤:淘汰不满足硬约束的 Node
├── PreScore // 打分前置:标准化 Node 元数据
├── Score // 打分:对候选 Node 量化评估
├── NormalizeScore // 归一化分数到 [0, 100]
├── Reserve // 预留:标记目标 Node 资源
├── Permit // 准入:等待/拒绝/允许
└── PostBind // 绑定后置:通知插件绑定完成
Binding Cycle(异步,可与下一个调度周期并行)
├── PreBind // 绑定前置:创建 PVC 等依赖资源
├── Bind // 执行绑定:调用 API Server 更新 nodeName
└── PostBind // 绑定后置:日志/指标上报
注意 Permit 阶段的精妙设计:它可以返回 Wait 让 Pod 在队列中等待固定超时,这实现了类似"批处理准入"的能力——例如等待多个 Pod 同时就绪后再一起调度,这正是 Gang Scheduling 的基础。
2.2 核心源码结构
追踪调度框架的实现,核心入口在 pkg/scheduler/scheduler.go:
// 调度器主循环的核心绑定逻辑
func (sched *Scheduler) scheduleOne(ctx context.Context) {
// 1. 从优先队列中取出 Pod
podInfo := sched.NextPod()
// 2. 执行 Scheduling Cycle
scheduleResult, err := sched.SchedulePod(ctx, podInfo.Pod)
// 3. 异步执行 Binding Cycle
go func() {
err := sched.BindPod(ctx, scheduleResult)
}()
}
而插件注册的机制在 pkg/scheduler/framework/runtime/framework.go:
type Framework interface {
// QueueSort 插件:决定 Pod 调度顺序
QueueSortPlugin
// 过滤阶段:淘汰不满足约束的 Node
FilterPlugin
// 打分阶段:为 Node 评分 [0, MaxNodeScore]
ScorePlugin
// 预订阶段:预留资源
ReservePlugin
// 准入控制:决定 Pod 是否/何时允许绑定
PermitPlugin
}
三、核心调度算法详解
3.1 过滤阶段:谓词判定
过滤阶段内置的预置谓词函数是最小的准入集,典型实现包括:
// predicates 插件的关键过滤逻辑(K8s 1.29 迁移至 framework 插件)
// NodeResourcesFit: 校验 CPU/Memory/Ephemeral Storage 是否充足
func nodeResourcesFit(pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
// 计算节点可分配资源
allowedPodNumber := nodeInfo.AllowedPodNumber()
if len(nodeInfo.Pods) 1

发表评论 取消回复