前言
Kubernetes(K8s)作为容器编排的事实标准,其调度系统是整个集群的大脑。Pod 作为 K8s 中最小的调度单元,如何被高效、合理地分配到集群节点上,直接影响着资源利用率和应用性能。本文将深入剖析 K8s 调度器的工作原理,并结合生产环境中的常见场景,分享调度优化的实战经验。
1. Kubernetes 调度器架构总览
Kubernetes Scheduler(kube-scheduler)是独立的二进制进程,核心职责是为新创建的 Pod 选择最优的节点。其工作流程可以概括为以下四个阶段:
待调度Pod → 调度队列(Queue) → 过滤(Filtering) → 打分(Scoring) → 绑定(Binding) → 目标节点
调度器采用插件化架构(Scheduling Framework),每个阶段都提供了扩展点:
- QueueSort:定义 Pod 在调度队列中的排序策略
- PreFilter:执行预检查,验证 Pod 的基本信息
- Filter:过滤不满足条件的节点(如资源不足、标签不匹配)
- PostFilter:过滤后触发,可用于记录日志或触发抢占地逻辑
- PreScore:打分前的准备工作
- Score:对过滤后的节点评分,分数范围 0-100
- Reserve/Nominate:预留节点,支持抢占场景
- Permit:最终审批,可设置等待、拒绝或允许
- PreBind/PostBind:绑定前后的钩子
2. 调度周期深入解析
2.1 调度队列与优先级
调度器内部维护三级队列结构:
activeQ(活跃队列): 待调度的 Pod,按优先级排序
unschedulableQ(不可调度队列): 暂时无法调度的 Pod,等待重试
backoffQ(退避队列): 绑定失败的 Pod,按退避时间等待
关键源码位置:pkg/scheduler/internal/heap/heap.go。队列排序使用 PriorityCompare 函数,优先比较 Pod 的优先级数值,其次比较入队时间戳。
2.2 过滤阶段 —— 淘汰不合格节点
Filter 插件按顺序执行,任何一个返回 Error 即淘汰该节点。核心过滤器包括:
| 过滤器 | 功能 |
|---|---|
| NodeResourcesFit | 检查 CPU/内存资源是否满足,支持超售 |
| NodeName | 匹配 metadata.name 精确指定节点 |
| NodeAffinity | 基于节点标签的亲和性选择 |
| TaintToleration | 检查 Pod 是否容忍节点的 Taint 污点 |
| NodeUnschedulable | 过滤被标记为不可调度的节点 |
| VolumeBinding | 检查存储卷是否可绑定到节点 |
2.3 打分阶段 —— 量化节点优劣
打分插件返回 0-100 的分数,最终加权求和。常用打分器:
- RequestedToCapacityRatio:基于资源请求率与容量的比值打分,支持偏斜分布
- NodeResourcesLeastAllocated:优选资源剩余最多的节点(默认策略)
- NodeResourcesMostAllocated:优选资源使用最多的节点(紧凑装箱)
- ImageLocality
- InterPodAffinity:基于 Pod 间亲和性聚集或分散 Pod
- TopologySpreadConstraints:跨拓扑域均匀分布 Pod
3. 亲和性与反亲和性实战
3.1 Node Affinity(节点亲和性)
节点亲和性让 Pod 吸引到特定节点。两种模式:
# requiredDuringSchedulingIgnoredDuringExecution —— 硬性要求
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role
operator: In
values: ["gpu-worker"]
# preferredDuringSchedulingIgnoredDuringExecution —— 软性偏好
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: disktype
operator: In
values: ["ssd"]
3.2 Pod Affinity/AntiAffinity(Pod 间亲和与反亲和)
Pod 间亲和性使 Pod 倾向于调度到已运行特定 Pod 的节点,反亲和性则倾向于避开。典型场景:
# 反亲和性 —— 将同一应用的多副本分散在不同节点
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: ["web-frontend"]
topologyKey: kubernetes.io/hostname
注意:Pod 亲和性/反亲和性会带来调度性能开销,大规模集群中建议谨慎使用 required 模式。
4. 生产调度优化策略
4.1 拓扑分布约束 TopologySpreadConstraints
K8s 1.19+ 引入的 TopologySpreadConstraints 是实现高可用部署的最佳实践:
topologySpreadConstraints:
- maxSkew: 2
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: payment-service
该配置确保 payment-service 在各可用区之间的副本数差异不超过 2,即使无法满足也不会阻止调度(ScheduleAnyway),避免因单区故障导致无法部署。
4.2 优先级与抢占
设置 PriorityClass 控制 Pod 被调度的顺序,当集群资源不足时高优先级 Pod 可抢占低优先级 Pod 的资源:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: production-critical
value: 1000000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
description: "生产核心服务最高优先级"
4.3 资源请求与限制的最佳实践
- 设置合理的 requests:调度器依据 requests 做决策,通常设为 P50 资源使用量
- limits 保持适度:limits 过高会导致资源浪费,过低可能触发 OOM Kill
- Burstable 策略:非关键服务的 limits 可设为 requests 的 1.5-2 倍,允许弹性使用资源
- Guaranteed QoS:关键服务设置 requests=limits,获得最高服务质量等级
4.4 调度器性能调优
大规模集群(5000+ 节点)中调度器可能成为瓶颈,关键配置:
# kube-scheduler 参数
--percent-of-nodes-to-score=30 # 打分阶段只评估部分节点(默认50%)
--bind-timeout=600ms # 绑定超时时间
# 高级调度配置
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
pluginConfig:
- name: NodeResourcesFit
args:
scoringStrategy:
type: RequestedToCapacityRatio
requestedToCapacityRatio:
shape:
- utilization: 0
score: 0
- utilization: 50
score: 6
- utilization: 100
score: 10
5. 自定义调度器开发
当默认调度器无法满足特殊需求时(如 GPU 亲和调度、数据本地性定制),可以基于 Scheduling Framework 开发自定义扩展点插件。
最小化自定义调度器示例(Go):
package main
import (
"context"
v1 "k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/scheduler/framework"
)
type CustomScheduler struct{}
func (cs *CustomScheduler) Name() string { return "custom-scheduler" }
func (cs *CustomScheduler) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
// 自定义过滤逻辑:仅允许带有 custom-allow=true 标签的节点
if nodeInfo.Node().Labels["scheduler-hint"] != "enabled" {
return framework.NewStatus(framework.Unschedulable, "node does not have required label")
}
return nil
}
func (cs *CustomScheduler) Score(ctx context.Context, state *framework.CycleState, pod *p1.Pod, nodeName string) (int64, *framework.Status) {
// 基于自定义指标打分
return calculateCustomScore(pod, nodeName), nil
}
6. 总结与展望
Kubernetes 调度器经过多年演进,已从简单的 nodeName/nodeSelector 发展为高度可扩展的调度框架。理解调度机制的核心原理,是构建稳定高效容器平台的基础。
随着云原生生态的发展,调度器也在持续进化:Kueue 项目带来了更高级的批处理能力,Dynamic Resource Allocation 为 GPU 等扩展资源提供了灵活分配方案,Cluster Autoscaler 与调度器的协同让集群具备了弹性伸缩能力。掌握这些机制,将帮助你在生产环境中构建更健壮、高效的云原生基础设施。

发表评论 取消回复