Kubernetes Pod调度深度实战:从Scheduler Framework到自定义调度器
本文深入剖析Kubernetes调度器内部机制,从Scheduling Cycle多阶段流水线到Scheduler Framework扩展点开发,带你掌握生产级Pod调度的核心原理与最佳实践。
一、调度器架构总览
Kubernetes调度器(kube-scheduler)是整个集群的"决策引擎",它持续监听API Server中spec.nodeName为空的Pod,为每个待调度Pod选择最优计算Node。整个过程看似简单,内部却涉及复杂的多阶段流水线、插件化扩展、并发安全和乐观并发控制。
1.1 调度器核心组件
kube-scheduler内部由以下核心模块协作完成调度决策:
- Informer/Cache:通过Watch机制实时同步Node、Pod、PV、PVC、Service等资源,构建本地缓存
scheduler.Cache,避免直接查询API Server - SchedulingQueue:三级队列架构(activeQ/unschedulableQ/backoffQ),支持优先级排序、事件驱动重试和 backoff 退避
- Scheduling Framework:可插拔的扩展框架,提供12个扩展点(Extension Points),支持自定义调度逻辑注入
- Scheduler Profile:支持多调度器实例共存,通过
schedulerName字段让Pod选择不同的调度策略
1.2 调度上下文与并发模型
scheduleOne()是调度器的主循环,每轮从activeQ取出一个Pod启动调度Goroutine。内部采用乐观并发:调度结果写入API Server时若发生冲突(ResourceVersion不一致),则重新进入队列等待下次调度。这种无锁设计使调度器能并行处理多个Pod,默认并发度为parallelism: 16。
二、Scheduling Cycle:多阶段调度流水线
一个完整的Scheduling Cycle包含两个子周期:Scheduling Cycle(调度周期)和Binding Cycle(绑定周期),它们串行执行,中间存在异步间隙。
2.1 Scheduling Cycle(调度周期)
调度周期的目标是选择一个最优Node,按以下顺序执行各扩展点:p>
QueueSort → PreFilter → Filter → PostFilter → PreScore → Score → NormalizeScore → Reserve → Permit
QueueSort:对activeQ中的Pod排序,默认实现为PrioritySort,按PriorityClass值降序排列,值相同则按创建时间FIFO。
PreFilter:前置检查,验证Pod是否具备被调度的基本条件。如检查Pod所需资源是否超过集群总容量、是否有已分配的PV等。同时计算并缓存NodeInfo以加速后续Filter插件。
Filter(Predicates节点过滤):过滤不满足硬约束的Node。核心内置过滤器包括:
NodeResourcesFit:检查Node可用CPU/内存/ephemeral-storage是否满足Pod请求量NodeAffinity:匹配Pod的nodeSelector/nodeAffinity硬约束NodePorts:检查hostPort冲突TaintToleration:检查Pod是否能容忍Node的NoSchedule/NoExecute污点VolumeBinding:检查PV拓扑约束与Node所在区域是否匹配NodeUnschedulable:过滤被标记为unschedulable的Node
Filter阶段采用并行执行策略,所有插件同时运行,但任一插件排除该Node即终止评估。若所有Node均被过滤,Pod进入unschedulableQ等待重试。
PostFilter:当Filter阶段无Node通过时触发DryRun预抢占(NodePreempt)。K8s 1.26+引入NodePreemption插件,通过抢占低优先级Pod来为高优先级Pod腾出空间。抢占流程:
1)DryRun模拟后续步骤,找到可驱逐的Node
2)选择 Victim Pod(按优先级+创建时间)
3)发送删除请求,Pod进入调度重试。
PreScore:评分前置阶段,通常用于准备全量Node评分所需的缓存数据。
Score(优先级评分):对通过Filter的所有Node进行加权评分。内置评分策略包括:
NodeResourcesFit:资源均衡度评分(LeastAllocated/BalancedAllocation/MostAllocated三种策略)ImageLocality:Node上已存在Pod所需镜像的层数越多分越高(减少镜像拉取延迟)InterPodAffinity:Pod亲和性/反亲和性评分NodeAffinity:Node亲和性偏好(preferredDuringScheduling)评分TopologySpreadConstraints:拓扑域分布均匀度评分TaintToleration:污点容忍评分
评分公式:FinalScore = Σ(score_i × weight_i) / Σ|weight_i|,权重可通过插件配置项自定义。
NormalizeScore:K8s 1.23新增阶段,将各插件的分数归一化到[0,100]区间,解决不同评分插件之间量纲差异问题。
Reserve:在Permit之前预留资源(写入schedulingCycle.Status)。如果调度失败(如Permit拒绝),则自动调用Unreserve释放资源。此机制确保调度器乐观并发期间本地Cache与API Server状态一致。
Permit:许可阶段。三种决策:
- Wait:Pod进入等待状态,超时(默认15s)后拒绝并重新排队
- Allow:直接进入Binding Cycle
- Deny:Pod调度失败,进入unschedulableQ并通知所有等待中的Pod重新评估。
Permit阶段的主要场景是:跨Pod协调调度(如Spark Driver+Executor同时调度)。
2.2 Binding Cycle(绑定周期)
Binding Cycle的目标是将已选Node绑定到Pod,执行以下扩展点:PreBind → Bind → PostBind。
PreBind:绑定前置操作。内置DefaultBinder在此阶段检查PV绑定状态、Volume调度结果有效性。
Bind:执行Bind操作——调用API Server的/api/v1/namespaces/{ns}/pods/{name}/binding子资源,写入spec.nodeName字段。Bind是一个独立的Goroutine,允许调度器立即进入下一轮调度循环,不等待Bind完成。
PostBind:绑定后的后置钩子。常用于发送监控指标、清理临时状态或通知外部系统。
值得注意的是,Binding Cycle是异步执行的,这意味着调度器和绑定器各自独立调度,若Bind因并发冲突失败(如Node已被其他Pod占用),会触发Unreserve释放资源,Pod重新进入调度队列。
三、Scheduler Framework核心扩展机制
Kubernetes 1.18正式GA的Scheduler Framework是调度器插件化的架构基础,允许开发者在不修改kube-scheduler源码的前提下注入自定义调度逻辑。
3.1 插件API模型
每个调度插件均需实现以下接口:
type Plugin interface {
Name() string
}
// 队列排序插件
type QueueSortPlugin interface {
Plugin
Less(*QueuedPodInfo, *QueuedPodInfo) bool
}
// 过滤插件
type FilterPlugin interface {
Plugin
Filter(ctx context.Context, state *CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status
}
// 评分插件
type ScorePlugin interface {
Plugin
Score(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status)
ScoreExtensions() ScoreExtensions
}
// 许可插件
type PermitPlugin interface {
Plugin
Permit(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) (*framework.Status, time.Duration)
}
// 绑定插件
type BindPlugin interface {
Plugin
Bind(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) *framework.Status
}
每个插件都可以通过共享的CycleState传递中间数据。CycleState是ipeline级别的KV存储,具备读锁和写锁分离机制,Filter插件只能读其他插件的缓存,Reserve/Score阶段的写操作会被严格控制。
3.2 多Profile与调度器配置
K8s调度器通过Profile支持多套配置并行运行。每个Profile定义:
- 开启/关闭的插件列表
- 各插件的执行顺序
- 插件权重(Score阶段)
- 插件特定配置参数
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
queueSort:
enabled:
- name: PrioritySort
filter:
enabled:
- name: NodeResourcesFit
- name: NodeAffinity
- name: MyCustomFilter
score:
enabled:
- name: NodeResourcesFit
weight: 2
- name: MyCustomScore
weight: 3
reserve:
enabled:
- name: DefaultPreemption
- schedulerName: binpack-scheduler
plugins:
score:
enabled:
- name: NodeResourcesFit
weight: 1
pluginConfig:
- name: NodeResourcesFit
args:
scoringStrategy:
type: MostAllocated # 适合密度调度场景
3.3 插件开发模板与注册流程
开发一个自定义调度插件,步骤如下:
第一步:定义插件结构体,实现目标扩展接口:
package gpascore
import (
"context"
"k8s.io/kubernetes/pkg/scheduler/framework"
)
const Name = "GpuAffinityScore"
type GpuAffinityScore struct {
handle framework.Handle
}
func (g *GpuAffinityScore) Name() string { return Name }
func (g *GpuAffinityScore) Score(ctx context.Context, state *framework CycleState,
pod *v1.Pod, nodeName string) (int64, *framework.Status) {
nodeInfo, err := g.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil {
return 0, framework.AsStatus(err)
}
// 自定义评分逻辑:优先选择GPU利用率最低且有匹配型号的节点
gpuAllocatable := nodeInfo.Allocatable.Resources["nvidia.com/gpu"]
gpuRequested := nodeInfo.Requested.Resources["nvidia.com/gpu"]
freeGpu := gpuAllocatable.MilliValue() - gpuRequested.MilliValue()
if freeGpu <= 0 {
return 0, nil // 无空闲GPU,较低分
}
// 检查Pod是否有特定GPU型号请求
requestedModel := pod.Annotations["gpu.model"]
if requestedModel != "" {
nodeModel := nodeInfo.Node().Labels["gpu.model"]
if nodeModel != requestedModel {
return 0, nil // 型号不匹配,零分
}
}
return freeGpu / 1000, nil // GPU数量越多分越高
}
func (g *GpuAffinityScore) ScoreExtensions() framework.ScoreExtensions {
return nil
}
第二步:在registry中注册插件工厂函数:
func New(_ runtime.Object, h framework.Handle) (framework.Plugin, error) {
return &GpuAffinityScore{handle: h}, nil
}
func NewCustomPluginsFactory() runtime.Object {
return &PluginFactory{GpuAffinityScore: New}
}
// 在 main 中注册
opts := app.Options{{Name: GpuAffinityScore, Factory: NewCustomPluginsFactory.GpuAffinityScore}}
// 或改用 in-tree 方式在 componentconfig 中注册
第三步:构建并部署自定义调度器镜像,通过SchedulerName让Pod选择:
apiVersion: v1
kind: Pod
metadata:
name: ml-training-job
spec:
schedulerName: binpack-scheduler # 使用自定义调度器
containers:
- name: training
image: ml-framework:latest
resources:
requests:
nvidia.com/gpu: 4
cpu: "16"
四、生产级调度策略实战
4.1 资源QoS三层模型
Kubernetes通过requests/limits实现不同保证等级的资源分配:
- Guaranteed(保证型):所有容器的requests=limits,获得最高优先保障,OOM时不会被OOMKilled
- Burstable(突发型):至少一个容器requests<limits,正常使用,但超出requests部分会受节点压力驱逐
- BestEffort(尽力型):无任何requests/limits,节点资源紧张时最先被驱逐
生产环境强烈建议所有Pod设置明确requests,不仅影响调度决策,还影响HPA垂直扩缩和资源核算。
4.2 高级调度约束组合
Topology Spread + Pod AntiAffinity + Taints 三件套是保障高可用服务的黄金组合:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: payment-api
matchLabelKeys:
- pod-template-hash
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: [payment-api]
topologyKey: kubernetes.io/hostname
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: [cache-redis]
topologyKey: kubernetes.io/hostname
该配置实现了:跨可用区均匀分布(maxSkew=1)+ 同服务不同副本不调度到同一Node + 优先靠近Redis缓存节点。三层约束叠加可在保障可用性的同时优化网络开销。
4.3 Descheduler:动态重平衡
即使调度器选择了最优Node,随着时间推移,Node资源碎片、Pod生命周期变化会导致调度结果次优化。Descheduler通过周期性重平衡策略解决这一问题:p>
RemoveDuplicates:移除同一RS/Deploy下多副本落在同一Node的冗余PodRemovePodsViolatingInterPodAntiAffinity:驱逐违反Pod反亲和性的PodRemovePodsViolatingTopologySpread:重新平衡违反拓扑分布的PodLowNodeUtilization:将低利用率Node上的Pod迁移到高利用率节点,空闲节点可缩容RemoveFailedPods:驱逐多次重启失败的Pod,释放其占用的Node slot
Descheduler作为CronJob或Deployment运行,执行驱逐时结合PodDisruptionBudget保障服务可用性,同时利用nodeSelector黑名单跳过关键节点。
4.4 大规模集群调度优化
当集群规模达到5000+ Node或30w+ Pod时,调度器可能成为瓶颈。常用优化手段:
- SchedulingProfiling:启用调度器性能分析端点(
/debug/api_priority_and_fairness/metrics),定位Filter/Score热点插件 - FrameworkHandle缓存:使用SharedInformer而非直接API调用,Node命中率可达99%+
- Scheduler并发配置:调整
parallelism参数(默认16),在调度延迟和吞吐量之间平衡 - 多调度器分片:按业务类型拆分调度器(如online-scheduler、batch-scheduler),各自负责不同Namespace的Pod,减少单个调度器的活跃队列
- Pod优先级与抢占:为关键负载配置高PriorityClass(如system-cluster-critical、production-high),在资源紧张时优先保障
- 调度结果缓存:K8s引入Scheduling Profiles的短时缓存机制,相同特征的Pod命中缓存可跳过Filter/Score全流程
五、云原生调度前沿趋势
5.1 基于AI/ML的智能调度
2025-2026年,越来越多云厂商将机器学习引入调度决策。典型方向包括:
- 强化学习调度器(如阿里巴巴I-CARL、百度DeepRM):通过PPO/A3C训练策略,优化多维度目标(吞吐、延迟、成本)
- 基于历史负载的模式预测:提前预判Pod资源用量波动,避免因burst导致的调度冲突
- 多目标Pareto最优:同时在节点成本、网络拓扑、GPU亲和性、碳排放等维度做多目标优化
5.2 边缘计算与多集群调度
KubeFed v2、Karmada等多集群调度框架正走向成熟,支持:
- 跨地域、跨云厂商的联邦调度策略
- 考虑网络延迟和数据本地化的拓扑感知调度
- 边缘节点离线自治和断网续传调度
5.3 WebAssembly与微容器调度
随着Wasm运行时(WasmEdge、Spin、runwasi)在Kubernetes中的生态成熟,调度器需要识别.wasm工作负载并匹配支持WASI的运行时节点。WasmPlugin扩展点和youki等轻量运行时正在推动这一领域的演进。
六、总结
Kubernetes调度器从最初的静态Predicates+Priority演进到如今可插拔的Scheduler Framework,已成为云原生基础设施中最复杂也最强大的调度系统之一。掌握Scheduling Cycle多阶段流水线、Scheduler Framework插件API以及生产级拓扑约束组合,是构建高可用、高性能、低成本集群的关键。
未来,随着AI智能调度的普及和多集群联邦架构的成熟,Kubernetes调度器将持续演进——从"资源分配引擎"升级为"意图驱动的自治编排平台"。

发表评论 取消回复