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的冗余Pod
  • RemovePodsViolatingInterPodAntiAffinity:驱逐违反Pod反亲和性的Pod
  • RemovePodsViolatingTopologySpread:重新平衡违反拓扑分布的Pod
  • LowNodeUtilization:将低利用率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调度器将持续演进——从"资源分配引擎"升级为"意图驱动的自治编排平台"。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论