引言
Kubernetes 调度器(kube-scheduler)是容器编排系统的核心决策引擎——它决定了每个 Pod 最终运行在集群的哪个节点上。在大规模生产环境中,调度的效率与公平性直接影响资源利用率、服务稳定性以及成本控制。本文从源码级的调度框架出发,完整拆解 Predicate 优选 → Score 打分 → PreScore/PostBind 的全链路流程,并结合 NodeAffinity、PodAffinity、Taint/Toleration、TopologySpreadConstraints 等核心调度原语,给出一套经过生产验证的调优策略。
一、调度框架总览
Kubernetes 调度器采用插件化的 Scheduling Framework(自 K8s 1.16 起 GA),将整个调度流程拆分为多个可扩展的阶段(Plugin Extension Points):
| 扩展点 | 作用 | 执行顺序 |
|---|---|---|
QueueSort | 对调度队列中的 Pod 排序(FIFO / 优先级) | 调度开始前 |
PreFilter | 预检查 Pod 合法性 + 计算缓存数据 | Predicate 之前 |
Filter (Predicate) | 过滤不满足硬性条件的节点 | 第一轮筛选 |
PostFilter | Filter 全失败后的挽救(如抢占) | Filter 全部失败时 |
PreScore | Score 打分前准备(统一信息搜集) | Score 之前 |
Score | 对候选节点评分(0-100 加权) | 第二轮打分排序 |
Reserve | 预留资源(防止后续 Pod 竞争) | 选中节点后 |
Permit | 最终同意/拒绝/等待(支持延迟绑定) | 绑定之前 |
PreBind / Bind / PostBind | 绑定操作:写 etcd 记录 | 最终绑定 |
1.1 调度队列演进
K8s 1.26+ 引入了 Multi Scheduling Profiles 和增强的调度队列语义。调度队列内部维护三个子队列:
- activeQ:待调度 Pod(堆实现,按优先级出队)
- podBackoffQ:调度失败进入退避等待的 Pod(指数退避)
- unschedulableQ:因资源不足暂时无法调度的 Pod(周期性重试或事件触发)
每个调度周期(scheduler.scheduleOne())优先从 activeQ 取 Pod,执行完整的调度流水线后,失败的 Pod 根据退避策略进入 podBackoffQ。
1.2 并发调度
自 K8s 1.22 起,调度器默认开启并行调度。通过 parallelism 字段控制同时运行的 goroutine 数(默认 16)。并行调度依赖 Scheduler Cache 的快照机制——每个 goroutine 持有一份节点信息的只读快照,避免全局锁竞争。
二、Predicate 优选:硬性条件过滤
Predicate 是过滤函数,判断节点是否满足 Pod 调度的硬性条件。默认调度器内置了约 12 种 Predicate 插件,执行时遍历所有节点,过滤结果为满足条件的候选节点列表。
2.1 核心 Predicate 插件分析
NodeName:精确匹配 spec.nodeName,仅用于静态 Pod 或手动绑定场景。
NodeSelector:基于节点标签的精确匹配(spec.nodeSelector)。实现极其简单——直接检查子集关系。
NodeAffinity:表达式引擎,支持 In、NotIn、Exists、DoesNotExist、Gt、Lt 操作符。支持两种模式:
requiredDuringSchedulingIgnoredDuringExecution:硬性过滤,不满足直接淘汰preferredDuringSchedulingIgnoredDuringExecution:软性偏好,在 Score 阶段加权
PodAffinity / PodAntiAffinity:基于 Pod 间亲和性的过滤,依赖节点上已有 Pod 的标签实现拓扑感知。复杂度最高,在大规模集群中是性能瓶颈。
TaintToleration:检查 Pod 的 Toleration 是否能容忍节点的 Taint。
NodeResourcesFit:核心资源检查——比较节点可用资源与 Pod 的 resource.requests。K8s 1.27+ 引入了更丰富的 Score 策略。
2.2 默认 Predicate 执行流水线
完整执行顺序如下:
- NodeUnschedulable:过滤被 cordon 的节点
- NodeName:检查 nodeName 精确匹配
- NodePorts:端口冲突检查
- NodeAffinity:节点亲和 Label 匹配
- TaintToleration:污点容忍检查
- NodeLabel:自定义标签过滤
- NodeResourcesFit:CPU/Memory 资源充足性
- VolumeBinding:PVC/PV 拓扑约束
- NodeVolumeLimits:CSI 卷数量限制
- PodTopologySpread:拓扑分布约束
- InterPodAffinity:Pod 亲和与反亲和
三、Score 打分:多维加权排序
Score 阶段对通过 Predicate 的候选节点进行量化评分(0-100 分 × 插件权重),最终总分最高的节点胜出。
3.1 核心 Score 插件分析
NodeResourcesFit(权重默认 1):LeastAllocated / MostAllocated / BalancedResourceAllocation 三种模式。
ImageLocality(权重 1):节点已存在 Pod 所需镜像则加分,避免冷启动拉镜像延迟。
InterPodAffinity(权重 1):Pod 亲和/反亲和偏好打分。
NodePreferAvoidPods(权重 10000):DaemonSet 强制调度,极高权重。
PodTopologySpread(权重 2):拓扑分布约束打分。
3.2 线性加权评分公式
最终总分计算方式:finalScore = Σ (pluginScore_i × weight_i)。被选中的节点满足 argmax 条件。这种设计允许运维人员通过调整权重来微调调度偏好。
四、NodeAffinity vs PodAffinity 深度对比
| 维度 | NodeAffinity | PodAffinity |
|---|---|---|
| 作用对象 | 基于节点标签选择节点 | 基于 Pod 间关系选择节点 |
| 典型场景 | SSD 机型、GPU 机型、区域约束 | 协同部署 / 反亲和打散 |
| 性能开销 | O(n) 低 | O(n*m) 高 |
4.1 NodeAffinity 实践示例(GPU 专用节点)
apiVersion: apps/v1
kind: Deployment
metadata:
name: gpu-worker
spec:
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: nvidia.com/gpu.product
operator: In
values:
- A100-SXM4-80GB
- H100-80GB
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["cn-shanghai-1a"]
containers:
- name: worker
resources:
requests:
nvidia.com/gpu: 1
memory: "32Gi"
cpu: "8"
4.2 PodAffinity + TopologySpread 反亲和打散
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-frontend
spec:
replicas: 6
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: web-frontend
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web-frontend
这种配置确保 6 个副本至少分散在 3 个可用区,并且同一节点不会运行同一应用的两个副本。
五、Taint 与 Toleration:污点隔离机制
Taint 是节点层面的"排斥标记",Toleration 是 Pod 的"容忍声明"。两者配合实现节点独占或特殊用途隔离。
5.1 三种 Taint 效果
- NoSchedule:禁止不匹配的 Pod 调度到该节点(已有 Pod 不受影响)
- PreferNoSchedule:尽量避免,但不强制
- NoExecute:不仅禁止新调度,还会驱逐已在运行但不匹配的 Pod
5.2 经典场景
专用节点池:给 GPU 节点打上 nvidia.com/gpu=true:NoSchedule,只有带对应 Toleration 的 Pod 才能调度上去。
节点排空:维护时先 cordon 再 drain,触发 NoExecute 逐步驱逐。
故障隔离:Node Problem Detector 自动给不健康节点打 NoExecute 污点。
5.3 Toleration 匹配规则
tolerations:
- key: "nvidia.com/gpu"
operator: "Equal"
value: "true"
effect: "# NoSchedule"
# 容忍所有不可调度节点(DaemonSet 常用)
- key: "node.kubernetes.io/unschedulable"
operator: "Exists"
effect: "NoSchedule"
六、TopologySpreadConstraints 拓扑分布约束
这是 K8s 1.19+ 引入的跨可用区/机架最佳实践,解决了 PodAntiAffinity 在大规模集群中"严格反亲和导致调度失败"的痛点。
6.1 核心参数
- maxSkew:任意两个拓扑域中 Pod 数的最大差异(越小越均匀)
- topologyKey:定义拓扑域的标签键
- whenUnsatisfiable:DoNotSchedule 严格 / ScheduleAnyway 退化为软偏好
- labelSelector:匹配要统计的 Pod 标签
6.2 多维度拓扑约束
可以堆叠多个 TopologySpreadConstraints 实现"先按可用区分 → 再按节点分"的两级均匀打散:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels: {app: payment-service}
- maxSkew: 2
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels: {app: payment-service}
七、Scheduler Extender 扩展机制
当内置调度器无法满足复杂策略时,可通过 HTTP/HTTPS Scheduler Extender 扩展自定义调度逻辑。
7.1 配置方式
{
"kind": "Policy",
"apiVersion": "v1",
"extenders": [
{
"urlPrefix": "http://custom-scheduler.kube-system:8080",
"filterVerb": "filter",
"prioritizeVerb": "prioritize",
"bindVerb": "bind",
"weight": 10,
"nodeCacheCapable": true,
"managedResources": [
{"name": "example.com/sriov", "ignoredByScheduler": true}
]
}
]
}
7.2 Scheduling Framework Plugin(推荐)
K8s 1.18+ 推荐使用 Go 插件直接注册到调度器,而非 HTTP Extender(延迟更低、类型安全):
// 自定义 Filter 插件示例
type SRIOVFilter struct{}
func (pl *SRIOVFilter) Name() string { return "SRIOVFilter" }
func (pl *SRIOVFilter) Filter(ctx context.Context, state *framework.CycleState,
pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
if pod.Annotations["sriov.network"] == "" {
return framework.NewStatus(framework.Success)
}
if sriovAvailable := nodeInfo.Node().Labels["sriov.capable"]; sriovAvailable != "true" {
return framework.NewStatus(framework.Unschedulable, "node lacks SR-IOV capability")
}
return framework.NewStatus(framework.Success)
}
八、生产级调优实践
8.1 避免 OOM:合理设置 Resource Requests
- Requests = P50 日常使用量
- Limits = P99 峰值使用量(超请求部分被限但不杀)
- 使用 VPA Recommendation 模式辅助调参
8.2 大规模集群性能调优
- PercentageOfNodesToScore:百分比采样评分,设为 10% 则只对 10% 的候选节点全量打分
- 500+ 节点建议设为 5-20%
- 启用 Scheduler Cache Snapshot(默认开启)减少快照复制开销
8.3 多可用区高可用部署模板
apiVersion: apps/v1
kind: Deployment
metadata:
name: core-api
spec:
replicas: 9
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
spec:
terminationGracePeriodSeconds: 60
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels: {app: core-api}
topologyKey: topology.kubernetes.io/zone
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector: {matchLabels: {app: core-api}}
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector: {matchLabels: {app: core-api}}
containers:
- name: api
resources:
requests: {cpu: "2", memory: "4Gi"}
limits: {cpu: "4", memory: "8Gi"}
8.4 抢占(Preemption)机制
当高优先级 Pod 因资源不足无法调度时,调度器会尝试抢占低优先级 Pod 的资源:模拟逐出 → 找最优驱逐集 → 发送驱逐请求。抢占受 PriorityClassName 和 API Server 的 PriorityClass 控制。
九、调度器性能基准
9.1 调度延迟指标
- 500 节点 / 5000 Pod 集群:P99 < 200ms
- 1000 节点 / 30000 Pod 集群:P99 < 1s
- PercentageOfNodesToScore=5% 可降低 40-60% 调度延迟
9.2 调度队列深度告警
- scheduler_pending_pods:pending 态 Pod 超过 50 触发告警
- scheduler_schedule_attempts_total:failed/unschedulable 异常增长时检查节点资源
十、2026 调度器演进趋势
- Dynamic Resource Allocation(Beta):CSI 设备类资源的延迟绑定与分配
- Pod Scheduling Readiness(GA):不满足条件前不进入调度队列
- Kueue 项目整合:批处理/ML 训练的队列式资源配额管理
- TopologyAwareScheduling + NUMA:HPC 和 AI 推理的多 NUMA 节点亲和性优化
总结
Kubernetes 调度器从简单的标签匹配演进为高度可扩展的插件化框架,Predicate → Score → Bind 的全链路设计兼顾了灵活性与性能。在生产环境中,建议优先使用 NodeAffinity + TopologySpreadConstraints + PodAntiAffinity 三件套保证高可用分布;对于 GPU/FPGA 等稀缺资源搭配 Taint/Toleration 专用节点池;最后通过 PercentageOfNodesToScore 和调度器并行度参数应对大规模集群场景。深入理解调度源码框架后,还可以通过自定义 Scheduling Framework Plugin 实现业务专属调度策略。

发表评论 取消回复