4. kube-scheduler 调度策略:默认调度框架、调度队列、抢占与优先级、自定义调度器扩展点

kube-scheduler 是 Kubernetes 控制面中负责 Pod 与 Node 匹配的核心组件。本章深入解析其内部调度策略,涵盖默认调度框架的流水线、调度队列的排序机制、基于优先级的抢占逻辑,以及通过扩展点实现自定义调度行为的工程方法。

4.1 默认调度框架:调度周期与绑定周期

Kubernetes 自 v1.19 起将调度器重构为可扩展的“调度框架”(Scheduling Framework)。每个调度周期分为两个阶段:调度周期(Scheduling Cycle)和绑定周期(Binding Cycle)。调度周期为 Pod 选择一个合适的 Node,绑定周期将决策结果持久化到 API Server。

关键概念:调度框架将调度流程拆解为多个扩展点(Extension Points),每个扩展点可以插入自定义插件。默认调度器内置了超过 20 个插件,覆盖从队列排序到节点预留的全流程。
kube-scheduler 调度框架扩展点流水线 调度框架扩展点流水线 调度周期(Scheduling Cycle) QueueSort PreFilter Filter PostFilter PreScore Score NormalizeScore Reserve Permit 绑定周期(Binding Cycle) PreBind Bind PostBind 说明: 调度周期为每个 Pod 选择一个 Node; 绑定周期将决策持久化到 etcd。

4.2 调度队列:三种队列与排序策略

调度器内部维护三种队列来管理待调度 Pod:

队列名称 数据结构 作用 出队条件
activeQ 堆(Heap) 存放所有等待调度的 Pod,按优先级排序 调度器每次从中取出优先级最高的 Pod
backoffQ 堆(Heap) 存放调度失败的 Pod,等待退避重试 退避时间到期后移回 activeQ
unschedulableQ Map + 集合 存放因资源不足、污点/容忍等不可调度 Pod 集群拓扑变化(如新 Node 加入)时重新评估
性能要点:activeQ 使用最小堆实现,Pod 优先级越高(Priority 值越大),出队越早。backoffQ 的退避时间呈指数增长(base=1s,max=10s),避免频繁重试导致调度器 CPU 飙升。

4.3 抢占与优先级:PodPriority 与抢占策略

当高优先级 Pod 无法调度时,调度器会触发抢占(Preemption),驱逐低优先级 Pod 以释放资源。抢占流程分为四个步骤:

  1. 筛选候选节点:找出所有可能通过驱逐 Pod 来满足高优先级 Pod 需求的 Node。
  2. 选择受害者:在候选节点上选择优先级最低的 Pod 作为驱逐目标,优先驱逐优先级低于抢占者的 Pod。
  3. 执行驱逐:通过 API Server 删除受害者 Pod,触发优雅终止(Graceful Shutdown)。
  4. 重新调度:高优先级 Pod 进入 activeQ 头部,等待调度到释放资源的 Node。
注意:抢占不是万能的。如果集群中所有 Node 都无法满足 Pod 的硬约束(如 nodeSelector、亲和性),抢占也不会成功。此外,频繁抢占会导致低优先级 Pod 被反复驱逐,影响服务稳定性。
抢占与优先级调度流程 抢占与优先级调度流程 高优先级 Pod 入队 PriorityClass=1000 调度失败 资源不足 触发抢占 PostFilter 插件 选择受害者 最低优先级 驱逐低优先级 Pod 调用 DELETE API,优雅终止 资源释放,重新调度 高优先级 Pod 进入 activeQ 头部 关键指标: 抢占延迟 < 5s 受害者数量最小化

4.4 自定义调度器扩展点:插件开发与配置

调度框架允许通过实现特定扩展点接口来开发自定义插件。常见的扩展点包括:

扩展点 接口方法 典型用途
Filter Filter(ctx, state, pod, node) *Status 实现自定义资源过滤(如 GPU 型号匹配)
Score Score(ctx, state, pod, node) (int64, *Status) 实现自定义打分策略(如网络延迟感知)
Reserve Reserve(ctx, state, pod, nodeName) *Status 预留资源,防止并发调度冲突
Permit Permit(ctx, state, pod, nodeName) *Status 实现调度准入控制(如配额检查)
PreBind PreBind(ctx, state, pod, nodeName) *Status 绑定前执行操作(如挂载存储卷)

以下是一个自定义 Filter 插件的代码示例:

package myplugin

import (
    "context"
    "k8s.io/kubernetes/pkg/scheduler/framework"
)

type MyFilter struct{}

func (m *MyFilter) Name() string {
    return "MyFilter"
}

func (m *MyFilter) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
    // 检查 Node 是否包含特定标签
    if _, exists := nodeInfo.Node().Labels["accelerator"]; !exists {
        return framework.NewStatus(framework.Unschedulable, "Node missing accelerator label")
    }
    return framework.NewStatus(framework.Success, "")
}

// 注册插件
func New(_ runtime.Object, _ framework.Handle) (framework.Plugin, error) {
    return &MyFilter{}, nil
}
配置方式:自定义插件通过 KubeSchedulerConfiguration 的 profiles[].plugins 字段启用。可以指定插件在哪个扩展点生效,以及是启用(enabled)还是禁用(disabled)。多调度器 Profile 支持为不同 Pod 使用不同的调度策略。

4.5 性能验证:调度延迟与吞吐量

验证调度策略性能时,重点关注以下指标:

  • 调度延迟(Scheduling Latency):从 Pod 创建到调度器完成绑定的时间,目标 < 100ms(P99)。
  • 调度吞吐量(Throughput):每秒调度的 Pod 数量,目标 > 100 Pods/s(1000 Node 集群)。
  • 抢占成功率:高优先级 Pod 触发抢占后成功调度的比例,目标 > 95%。
  • 队列深度:activeQ 和 backoffQ 的瞬时长度,避免队列积压导致调度饥饿。
验证工具:使用 kubectl get events --field-selector reason=FailedScheduling 查看调度失败事件;使用 kube-scheduler --v=5 开启详细日志;使用 Prometheus 监控 scheduler_scheduling_algorithm_duration_seconds 和 scheduler_e2e_scheduling_duration_seconds 指标。