4. Scheduler 调度策略与扩展:默认调度器工作流程、调度算法(节点筛选、评分)、自定义调度器与调度框架
Kubernetes Scheduler 是控制面的核心组件之一,负责将待调度的 Pod 绑定到最合适的 Node 上。调度决策直接影响集群的资源利用率、应用性能和稳定性。本章深入解析默认调度器的工作流程、核心调度算法,并探讨如何通过自定义调度器与调度框架(Scheduling Framework)实现扩展。
4.1 默认调度器工作流程
默认调度器(kube-scheduler)采用“两阶段”调度模型:筛选(Filtering) 和 评分(Scoring)。调度器为每个待调度的 Pod 执行以下步骤:
- 监听队列:从内部调度队列中取出一个待调度的 Pod。
- 预选(Predicates / Filtering):遍历所有 Node,应用一组预选策略,过滤掉不满足 Pod 调度约束的 Node。
- 优选(Priorities / Scoring):对通过预选的 Node 进行评分,计算每个 Node 的优先级分数。
- 绑定(Binding):选择分数最高的 Node,将 Pod 绑定到该 Node 上。
4.2 调度算法:节点筛选与评分
4.2.1 节点筛选(Predicates)
筛选阶段通过一系列预选策略判断 Node 是否满足 Pod 的基本运行条件。常见的预选策略包括:
| 预选策略 | 说明 | 检查维度 |
|---|---|---|
| PodFitsResources | 检查 Node 剩余 CPU、内存是否满足 Pod 请求 | 资源容量 |
| PodFitsHostPorts | 检查 Node 上请求的主机端口是否已被占用 | 端口冲突 |
| PodMatchNodeSelector | 检查 Pod 的 nodeSelector 或 nodeAffinity 是否匹配 Node 标签 | 节点亲和性 |
| PodToleratesNodeTaints | 检查 Pod 是否容忍 Node 上的 Taints | 污点容忍 |
| CheckNodeCondition | 检查 Node 是否处于 Ready、DiskPressure 等正常状态 | 节点健康 |
| CheckVolumeBinding | 检查 Pod 请求的 PVC 是否可被 Node 上的 PV 满足 | 存储绑定 |
4.2.2 节点评分(Priorities)
评分阶段对通过筛选的 Node 进行打分,分数越高表示 Node 越适合运行该 Pod。评分策略通常基于资源利用率、亲和性、拓扑分布等维度。常见的评分策略包括:
| 评分策略 | 权重 | 评分逻辑 |
|---|---|---|
| LeastRequestedPriority | 1 | 优先选择资源(CPU+内存)请求量最少的节点,促进资源均衡 |
| BalancedResourceAllocation | 1 | 优先选择 CPU 和内存使用率最均衡的节点 |
| NodeAffinityPriority | 2 | 根据 Pod 的 nodeAffinity 规则匹配程度加分 |
| InterPodAffinityPriority | 2 | 根据 Pod 间的亲和性/反亲和性规则加分 |
| ImageLocalityPriority | 1 | 优先选择已缓存 Pod 所需镜像的节点 |
| NodePreferAvoidPodsPriority | 10000 | 根据节点注解(scheduler.alpha.kubernetes.io/preferAvoidPods)进行高权重加分 |
最终得分 = Σ (每个评分策略的分数 × 该策略的权重)。调度器选择得分最高的 Node 进行绑定;若多个 Node 得分相同,则随机选择一个。
4.3 自定义调度器与调度框架
当默认调度器无法满足特定业务需求(如 GPU 拓扑感知、网络延迟优化、自定义资源约束)时,Kubernetes 提供了两种扩展方式:自定义调度器 和 调度框架(Scheduling Framework)。
4.3.1 自定义调度器
自定义调度器是一个独立的调度程序,可以完全替代默认调度器,也可以与默认调度器共存。Pod 通过 spec.schedulerName 字段指定使用哪个调度器。
apiVersion: v1
kind: Pod
metadata:
name: custom-scheduled-pod
spec:
schedulerName: my-custom-scheduler
containers:
- name: nginx
image: nginx:latest
resources:
requests:
cpu: "1"
memory: "512Mi"
自定义调度器的实现方式:
- 基于 kube-scheduler 源码修改:直接 Fork 官方调度器代码,修改调度逻辑。
- 基于调度框架扩展点:使用调度框架提供的插件机制,只实现特定扩展点。
- 完全独立开发:使用 client-go 监听 Pod 和 Node 事件,自行实现调度算法。
4.3.2 调度框架(Scheduling Framework)
调度框架是 Kubernetes 1.19+ 引入的插件化调度架构,将调度流程拆分为多个扩展点(Extension Points),允许用户通过插件注入自定义逻辑,而无需修改调度器核心代码。
调度框架的主要扩展点包括:
| 扩展点 | 阶段 | 插件作用 |
|---|---|---|
| QueueSort | 调度队列排序 | 自定义 Pod 在调度队列中的排序逻辑 |
| PreFilter | 筛选前 | 预处理 Pod 信息,或提前拒绝明显不可调度的 Pod |
| Filter | 筛选 | 实现自定义的节点过滤逻辑 |
| PostFilter | 筛选后 | 当所有节点都被过滤掉时,执行替代方案(如抢占) |
| PreScore | 评分前 | 预处理评分所需的数据 |
| Score | 评分 | 实现自定义的节点评分逻辑 |
| NormalizeScore | 评分归一化 | 对多个评分插件的分数进行归一化处理 |
| Reserve | 预留 | 在绑定前预留节点资源,防止并发调度冲突 |
| Permit | 许可 | 批准或延迟 Pod 的绑定操作 |
| PreBind | 绑定前 | 在绑定前执行预处理(如挂载卷) |
| Bind | 绑定 | 实现自定义的绑定逻辑 |
| PostBind | 绑定后 | 绑定完成后执行清理或通知 |
4.3.3 调度框架插件开发示例
以下是一个简单的调度框架插件示例,实现一个自定义的 Filter 插件,拒绝所有标签为 environment=production 的节点:
package myplugin
import (
"context"
"k8s.io/kubernetes/pkg/scheduler/framework"
)
type MyFilterPlugin struct{}
var _ framework.FilterPlugin = &MyFilterPlugin{}
func (pl *MyFilterPlugin) Name() string {
return "MyFilterPlugin"
}
func (pl *MyFilterPlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
node := nodeInfo.Node()
if node.Labels["environment"] == "production" {
return framework.NewStatus(framework.Unschedulable, "production nodes are not allowed")
}
return framework.NewStatus(framework.Success, "")
}
注册插件后,在调度器配置中启用:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: my-scheduler
plugins:
filter:
enabled:
- name: MyFilterPlugin
4.4 调度策略对比与选型
| 方案 | 适用场景 | 复杂度 | 维护成本 | 灵活性 |
|---|---|---|---|---|
| 默认调度器 | 通用工作负载,无需特殊调度逻辑 | 低 | 低 | 低 |
| 调度框架插件 | 需要特定调度策略(如 GPU、网络、存储感知) | 中 | 中 | 高 |
| 自定义调度器 | 完全替代调度逻辑,或需要与外部系统深度集成 | 高 | 高 | 极高 |
选择调度方案时,应优先评估默认调度器是否满足需求;若需扩展,优先使用调度框架插件;仅在调度框架无法满足的极端场景下,才考虑开发独立的自定义调度器。