3. 调度框架详解:PreFilter、Filter、PostFilter、PreScore、Score、NormalizeScore、Reserve、Permit、PreBind、Bind、PostBind
Kubernetes 调度器从 v1.19 开始引入调度框架(Scheduling Framework),将调度过程拆解为一系列可扩展的插件化阶段。每个阶段对应一个或多个扩展点(Extension Point),调度器按固定顺序依次执行这些扩展点。理解这 11 个核心扩展点,是掌握调度器工作原理和开发自定义调度插件的基础。
3.1 调度框架整体流程
下图展示了调度框架中 11 个扩展点的执行顺序。整个调度周期分为调度周期(Scheduling Cycle)和绑定周期(Binding Cycle)两个阶段。
3.2 调度周期扩展点详解
调度周期负责为 Pod 选择一个合适的节点。它包含 7 个扩展点,按顺序依次执行。
3.2.1 PreFilter(预过滤)
作用:在正式过滤节点之前,对 Pod 和集群状态进行预处理和检查。常用于解析 Pod 的调度约束、检查 Pod 是否满足基本条件。
- 典型用途:检查 Pod 的 NodeSelector、NodeAffinity、PodAffinity 等调度约束是否合法;预处理 Pod 的资源请求。
- 执行结果:返回
PreFilterResult或错误。如果返回错误,调度周期提前终止。 - 插件示例:NodeResourcesFit 插件在此阶段检查 Pod 的资源请求是否超过集群总容量。
3.2.2 Filter(过滤)
作用:对集群中所有节点进行过滤,排除不满足 Pod 调度要求的节点。这是调度器最核心的筛选阶段。
- 典型用途:检查节点资源是否充足、节点端口是否冲突、节点是否满足 Pod 的污点容忍条件。
- 执行方式:每个 Filter 插件对每个节点独立执行。所有 Filter 插件都通过的节点进入下一阶段。
- 插件示例:NodeUnschedulable 插件过滤掉标记为
Unschedulable的节点;TaintToleration 插件检查 Pod 是否能容忍节点的污点。
3.2.3 PostFilter(后过滤)
作用:当 Filter 阶段没有找到任何可用节点时,PostFilter 插件有机会执行补救措施或生成诊断信息。
- 典型用途:尝试抢占(Preemption)低优先级 Pod 以释放资源;记录调度失败原因供后续分析。
- 执行条件:仅在 Filter 阶段后可用节点数为 0 时触发。
- 插件示例:DefaultPreemption 插件在此阶段尝试抢占其他 Pod。
3.2.4 PreScore(预评分)
作用:在正式评分之前,对 Filter 阶段通过的节点列表进行预处理,为 Score 阶段准备数据。
- 典型用途:计算节点间的拓扑关系、预加载评分所需的缓存数据。
- 执行结果:不返回分数,只更新内部状态或缓存。
- 插件示例:InterPodAffinity 插件在此阶段计算 Pod 亲和性所需的拓扑信息。
3.2.5 Score(评分)
作用:对 Filter 阶段通过的每个节点进行评分,分数越高表示节点越适合运行该 Pod。
- 典型用途:根据资源利用率、节点负载、亲和性规则等维度为节点打分。
- 评分范围:每个 Score 插件的评分范围通常是 0-100,多个插件的评分会加权求和。
- 插件示例:NodeResourcesBalancedAllocation 插件根据 CPU 和内存的均衡程度评分;ImageLocality 插件根据节点上是否已有 Pod 所需镜像评分。
3.2.6 NormalizeScore(归一化评分)
作用:对 Score 阶段产生的原始分数进行归一化处理,确保不同插件的分数在同一量级上可比。
- 典型用途:将插件的原始分数映射到统一的 0-100 范围;应用权重调整。
- 执行时机:在所有 Score 插件执行完毕后,对每个节点的总分进行归一化。
- 插件示例:大多数内置插件在 Score 阶段直接返回 0-100 分数,NormalizeScore 阶段主要用于自定义插件。
3.2.7 Reserve(预留)
作用:在选定节点后,预留该节点上的资源,防止其他调度周期使用同一资源。
- 典型用途:预留 Pod 所需的 CPU、内存、端口等资源;记录 Pod 与节点的绑定关系。
- 执行结果:如果预留失败(如资源冲突),调度周期回滚并重新选择节点。
- 插件示例:NodeResourcesFit 插件在此阶段更新节点的资源可用量。
3.3 绑定周期扩展点详解
绑定周期负责将 Pod 绑定到选定的节点。它包含 4 个扩展点,按顺序依次执行。
3.3.1 Permit(许可)
作用:在绑定之前进行最终审批,可以批准、拒绝或延迟绑定操作。
- 典型用途:实现多调度器协调、跨集群调度审批、基于外部系统的绑定决策。
- 执行结果:返回
Allow(允许绑定)、Deny(拒绝绑定)或Wait(等待外部条件满足)。 - 插件示例:自定义审批插件可以在此阶段调用外部 API 确认 Pod 是否可以绑定。
3.3.2 PreBind(预绑定)
作用:在正式绑定之前执行准备工作,如创建网络资源、挂载存储卷等。
- 典型用途:动态创建 PV、配置网络策略、设置 DNS 记录。
- 执行结果:如果 PreBind 失败,绑定周期终止,Reserve 阶段预留的资源会被释放。
- 插件示例:VolumeBinding 插件在此阶段处理动态卷绑定。
3.3.3 Bind(绑定)
作用:将 Pod 与选定节点正式绑定,更新 API Server 中的 Pod 对象。
- 典型用途:调用 Kubernetes API 创建 Pod 的
NodeName绑定关系。 - 执行方式:通常只有一个 Bind 插件会实际执行绑定操作。如果所有 Bind 插件都跳过,调度器使用默认绑定逻辑。
- 插件示例:DefaultBinder 插件是内置的默认绑定实现。
3.3.4 PostBind(后绑定)
作用:绑定完成后执行清理或通知操作。
- 典型用途:记录调度日志、更新监控指标、释放临时资源。
- 执行时机:无论 Bind 成功还是失败,PostBind 都会执行。
- 插件示例:自定义监控插件在此阶段记录调度延迟指标。
3.4 扩展点对比总结
| 扩展点 | 所属周期 | 核心职责 | 失败影响 | 典型插件 |
|---|---|---|---|---|
| PreFilter | 调度 | 预处理 Pod 调度约束 | 终止调度周期 | NodeResourcesFit |
| Filter | 调度 | 过滤不满足条件的节点 | 节点被排除 | TaintToleration |
| PostFilter | 调度 | 无可用节点时执行补救 | 触发抢占或报错 | DefaultPreemption |
| PreScore | 调度 | 为评分阶段准备数据 | 评分可能不准确 | InterPodAffinity |
| Score | 调度 | 对节点进行评分 | 影响节点选择 | NodeResourcesBalancedAllocation |
| NormalizeScore | 调度 | 归一化评分结果 | 评分权重异常 | 自定义插件 |
| Reserve | 调度 | 预留节点资源 | 回滚并重新选择 | NodeResourcesFit |
| Permit | 绑定 | 最终审批绑定操作 | 延迟或拒绝绑定 | 自定义审批插件 |
| PreBind | 绑定 | 绑定前准备工作 | 释放预留资源 | VolumeBinding |
| Bind | 绑定 | 执行 Pod 与节点绑定 | Pod 无法调度 | DefaultBinder |
| PostBind | 绑定 | 绑定后清理与通知 | 不影响调度结果 | 监控插件 |
3.5 插件注册与执行顺序
调度框架通过 调度配置(Scheduler Configuration) 管理插件的注册和执行顺序。每个扩展点可以注册多个插件,插件按配置顺序依次执行。
# 调度器配置示例(KubeSchedulerConfiguration)
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
filter:
enabled:
- name: NodeUnschedulable
- name: NodeResourcesFit
- name: NodeName
- name: TaintToleration
score:
enabled:
- name: NodeResourcesBalancedAllocation
weight: 1
- name: ImageLocality
weight: 1
reserve:
enabled:
- name: NodeResourcesFit
preBind:
enabled:
- name: VolumeBinding
bind:
enabled:
- name: DefaultBinder
- 插件可以同时注册到多个扩展点。例如
NodeResourcesFit同时注册了 Filter、Score、Reserve 三个扩展点。 - Score 插件的
weight参数控制该插件在总分中的权重,默认值为 1。 - 如果多个插件注册到同一个扩展点,执行顺序由配置中的顺序决定,而不是插件名称的字母顺序。
3.6 扩展点之间的数据传递
调度框架使用 CycleState 对象在扩展点之间传递数据。每个调度周期创建一个独立的 CycleState,插件可以从中读取和写入数据。
3.7 实践建议与常见误区
- 选择合适的扩展点:资源检查类逻辑放在 Filter,优化选择类逻辑放在 Score,预处理类逻辑放在 PreFilter 或 PreScore。
- 保持幂等性:插件在同一个扩展点可能被多次调用,确保逻辑是幂等的。
- 避免阻塞操作:调度周期是同步执行的,避免在 Filter 或 Score 中执行网络调用或磁盘 I/O。
- 利用 CycleState 缓存:如果多个扩展点需要相同数据,在 PreFilter 中计算一次并存入 CycleState。
- 误区:认为 PostFilter 一定会执行。
纠正:PostFilter 仅在 Filter 阶段后可用节点数为 0 时触发。 - 误区:在 Filter 中修改节点状态。
纠正:Filter 是只读操作,不应修改任何集群状态。 - 误区:认为 Bind 阶段可以回滚。
纠正:Bind 操作一旦提交到 API Server 就无法回滚,PostBind 只负责清理。 - 误区:所有插件都必须注册到所有扩展点。
纠正:插件只注册到它关心的扩展点,未注册的扩展点不会调用该插件。