3. 调度框架详解:核心扩展点

Kubernetes 调度器从 v1.19 开始引入了可扩展的调度框架(Scheduling Framework),将调度流程拆解为多个独立的扩展点(Extension Points)。每个扩展点对应调度周期中的一个特定阶段,允许通过插件机制注入自定义逻辑。理解这些扩展点的执行顺序、输入输出以及典型用途,是进行调度性能调优和故障排查的基础。

核心概念: 调度框架将调度过程分为两个周期:调度周期(Scheduling Cycle)和绑定周期(Binding Cycle)。调度周期为 Pod 选择一个节点,绑定周期将选择结果持久化。扩展点分布在两个周期中,部分扩展点支持等待(Wait)和拒绝(Reject)语义。

3.1 扩展点全景图

调度框架共定义了 11 个扩展点,按执行顺序排列如下。其中 PreFilter、Filter、PostFilter、PreScore、Score、NormalizeScore 属于调度周期;Reserve、Permit、PreBind、Bind、PostBind 属于绑定周期。

K8s 调度框架扩展点全景流程 调度框架扩展点全景流程 调度周期(Scheduling Cycle) 绑定周期(Binding Cycle) PreFilter Filter PostFilter PreScore Score NormalizeScore Reserve Permit PreBind Bind PostBind 调度周期特点: • 为 Pod 选择一个最优节点 • 可并行执行 Filter/Score • 失败时可触发 PostFilter 抢占 绑定周期特点: • 将调度结果持久化到 etcd • 支持等待/拒绝语义(Permit) • 失败时由 Reserve 回滚资源

3.2 扩展点详解

每个扩展点都有明确的职责和输入输出。下表总结了各扩展点的核心语义、典型用途和性能影响。

扩展点 所属周期 核心职责 输入 输出 典型用途
PreFilter 调度 预处理 Pod 和集群状态,生成过滤所需的数据 Pod, NodeInfo PreFilterResult / Status 检查 Pod 的 QoS、拓扑约束预处理
Filter 调度 过滤掉不满足条件的节点 Pod, Node Status(通过/不通过) 节点资源检查、污点容忍、节点选择器
PostFilter 调度 所有节点 Filter 失败后执行,尝试抢占 Pod, FailedNodes PostFilterResult / Status 抢占调度、日志记录、指标上报
PreScore 调度 为 Score 阶段准备评分数据 Pod, FilteredNodes PreScoreResult / Status 预计算亲和性权重、加载缓存
Score 调度 对过滤后的节点进行评分 Pod, Node Score(0-100) 资源均衡、亲和性评分、自定义打分
NormalizeScore 调度 对多个 Score 插件的评分结果进行归一化 Pod, NodeScores NormalizedScores 权重调整、分数范围统一
Reserve 绑定 在选定节点上预留 Pod 资源 Pod, Node Status 资源预留、IP 分配、存储卷预挂载
Permit 绑定 批准或拒绝 Pod 的绑定操作 Pod, Node Status(批准/等待/拒绝) 配额检查、多租户准入、全局限流
PreBind 绑定 执行绑定前的准备工作 Pod, Node Status 存储卷绑定、网络配置、Sidecar 注入
Bind 绑定 将 Pod 绑定到节点(写入 etcd) Pod, Node Status 默认绑定、自定义绑定策略
PostBind 绑定 绑定完成后的清理和通知 Pod, Node (无返回值) 日志记录、指标上报、事件通知

3.3 关键扩展点深入分析

3.3.1 PreFilter 与 Filter:调度准入的第一道关卡

PreFilter 在 Filter 之前执行,用于预处理 Pod 和集群的全局信息。例如,NodeResourcesFit 插件在 PreFilter 阶段计算 Pod 的请求资源总量,避免在 Filter 阶段重复计算。PreFilter 返回的 PreFilterResult 可以传递给后续的 Filter 插件使用。

Filter 是调度器中最核心的扩展点,负责逐节点检查 Pod 是否满足调度条件。Filter 插件可以并行执行,每个节点独立判断。常见的 Filter 插件包括:

  • NodeResourcesFit:检查节点剩余 CPU、内存是否满足 Pod 请求
  • NodeName:检查 Pod 的 spec.nodeName 是否匹配
  • NodeUnschedulable:检查节点是否被标记为不可调度
  • TaintToleration:检查 Pod 是否能容忍节点的污点
  • NodeAffinity:检查节点亲和性规则
  • PodTopologySpread:检查拓扑分布约束
性能调优要点: Filter 阶段是调度器 CPU 消耗的主要来源。当集群节点数超过 1000 时,建议启用 nodeResourceFitScoringStrategy 和 nodeAffinityScoringStrategy 的缓存机制,避免每次调度都重新计算节点资源。同时,合理配置 percentageOfNodesToScore 参数(默认 50%),控制参与 Filter 的节点比例。

3.3.2 PostFilter:调度失败的兜底机制

当所有节点都未通过 Filter 检查时,调度器会触发 PostFilter 扩展点。PostFilter 的核心职责是尝试通过抢占(Preemption)为 Pod 腾出调度空间。抢占逻辑会:

  1. 找出优先级低于当前 Pod 的候选 Pod
  2. 模拟删除这些 Pod 后节点是否满足条件
  3. 选择最优的抢占目标并驱逐

PostFilter 插件也可以用于记录调度失败日志、上报监控指标,或者执行自定义的降级策略。

注意: 抢占操作会删除正在运行的 Pod,导致服务中断。在生产环境中,应谨慎配置 Pod 优先级(PriorityClass),避免频繁触发抢占。建议为关键业务 Pod 设置高优先级,并为抢占操作配置 disablePreemption 参数。

3.3.3 Score 与 NormalizeScore:节点择优

Score 扩展点对通过 Filter 的节点进行评分,分数范围 0-100。调度器会运行多个 Score 插件,每个插件独立打分。常见的 Score 插件包括:

  • NodeResourcesBalancedAllocation:优先选择资源分配更均衡的节点
  • NodeResourcesLeastAllocated:优先选择剩余资源最多的节点
  • ImageLocality:优先选择已缓存 Pod 所需镜像的节点
  • InterPodAffinity:根据 Pod 间亲和性/反亲和性打分
  • NodeAffinity:根据节点亲和性权重打分

NormalizeScore 在 Score 之后执行,用于将多个 Score 插件的分数进行归一化处理。默认情况下,调度器会将所有 Score 插件的分数加权求和。NormalizeScore 允许插件调整自己的分数范围,或者修改其他插件的分数权重。

Score 与 NormalizeScore 处理流程 Score 与 NormalizeScore 处理流程 Filtered Nodes Score 插件 1 Score 插件 2 Score 插件 3 NormalizeScore 最优节点 关键逻辑: 每个 Score 插件独立打分(0-100)→ NormalizeScore 合并分数(加权求和)→ 选择总分最高的节点作为最优节点

3.3.4 Reserve 与 Permit:绑定周期的关键控制点

Reserve 是绑定周期的第一个扩展点,用于在选定节点上预留 Pod 所需的资源。预留操作包括:

  • 更新节点的资源使用量(CPU、内存、存储)
  • 分配 IP 地址(如果使用静态 IP 分配)
  • 预挂载存储卷

Reserve 是幂等的,如果绑定失败,调度器会调用 Unreserve 回滚预留的资源。

Permit 是绑定周期中唯一支持等待语义的扩展点。Permit 插件可以返回三种状态:

状态 含义 后续行为
批准(Approved) 允许继续执行绑定 进入 PreBind 阶段
等待(Wait) 暂停绑定,等待外部条件满足 调度器定期检查,超时后拒绝
拒绝(Denied) 禁止绑定,调度失败 触发 Unreserve 回滚,重新调度
实战场景: Permit 扩展点常用于实现全局配额控制。例如,当集群中同时调度大量 Pod 时,Permit 插件可以限制同时绑定的 Pod 数量,避免 etcd 写入压力过大。等待超时时间可通过 permitWaitingTimeoutSeconds 配置(默认 10 秒)。

3.3.5 PreBind、Bind 与 PostBind:绑定执行与收尾

PreBind 在绑定之前执行,用于执行绑定前的准备工作。典型的 PreBind 插件包括:

  • VolumeBinding:等待存储卷挂载完成
  • NodePorts:分配 NodePort 端口
  • InterPodAffinity:检查 Pod 间亲和性约束

Bind 是调度器的最终决策点,负责将 Pod 绑定到节点。默认的 Bind 插件会调用 Kubernetes API 创建 Pod 的 NodeName 字段。自定义 Bind 插件可以实现多集群调度、边缘节点绑定等高级功能。

PostBind 是调度周期的最后一个扩展点,用于执行绑定后的清理和通知工作。PostBind 插件没有返回值,即使执行失败也不会影响调度结果。典型用途包括:

  • 记录调度延迟指标
  • 发送调度事件到监控系统
  • 清理临时缓存数据

3.4 扩展点执行顺序与错误处理

调度框架定义了严格的执行顺序和错误处理规则:

  1. 顺序执行:扩展点按 PreFilter → Filter → PostFilter → PreScore → Score → NormalizeScore → Reserve → Permit → PreBind → Bind → PostBind 的顺序执行
  2. 短路机制:如果某个扩展点返回错误,调度器会立即停止当前周期的执行,并触发回滚
  3. 回滚路径:如果绑定周期失败(Permit 拒绝、PreBind 失败、Bind 失败),调度器会调用 Unreserve 回滚 Reserve 阶段预留的资源
  4. 重试策略:调度失败后,Pod 会被重新放入调度队列,等待下一次调度尝试
故障排查要点: 当 Pod 调度失败时,可以通过以下步骤定位问题:
1. 查看 Pod 的 Events:kubectl describe pod <pod-name>
2. 检查调度器日志:kubectl logs -n kube-system <scheduler-pod>
3. 分析调度框架的扩展点执行记录(启用 --v=5 日志级别)
4. 使用 SchedulingFailure 事件追踪失败原因

3.5 扩展点配置示例

以下是一个自定义调度器配置的 YAML 示例,展示了如何启用和配置多个扩展点插件:

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: custom-scheduler
    plugins:
      # 调度周期插件
      preFilter:
        enabled:
          - name: NodeResourcesFit
          - name: NodePorts
      filter:
        enabled:
          - name: NodeResourcesFit
          - name: NodeName
          - name: TaintToleration
      postFilter:
        enabled:
          - name: DefaultPreemption
      preScore:
        enabled:
          - name: InterPodAffinity
      score:
        enabled:
          - name: NodeResourcesBalancedAllocation
            weight: 2
          - name: ImageLocality
            weight: 1
      normalizeScore:
        enabled:
          - name: DefaultNormalizeScore
      # 绑定周期插件
      reserve:
        enabled:
          - name: VolumeBinding
      permit:
        enabled:
          - name: CustomQuotaPlugin
      preBind:
        enabled:
          - name: VolumeBinding
      bind:
        enabled:
          - name: DefaultBinder
      postBind:
        enabled:
          - name: MetricsReporter
    pluginConfig:
      - name: CustomQuotaPlugin
        args:
          maxConcurrentBindings: 50
          permitTimeoutSeconds: 15
性能警告: 每个扩展点都会增加调度延迟。在生产环境中,建议只启用必要的插件,避免在 Score 阶段注册过多插件导致评分计算过慢。对于大规模集群(节点数 > 5000),建议将 Score 插件数量控制在 5 个以内,并启用评分缓存。