kube-scheduler 超深度源码分析

基于 Kubernetes 源码 cmd/kube-scheduler/pkg/scheduler/staging/src/k8s.io/kube-scheduler/ 进行逐行级专业分析


一、模块定位

1.1 业务职责

kube-scheduler 是 Kubernetes 控制平面的核心组件之一,负责将未调度的 Pod 绑定到最合适的 Node。它是 Kubernetes 默认调度器的参考实现,其核心职责可概括为:

职责说明
Pod 调度监听未调度 Pod,为其选择最优 Node
资源匹配基于 CPU/Memory/存储等资源约束过滤节点
策略执行通过插件框架执行 Filter(过滤)、Score(打分)、Bind(绑定)等策略
抢占调度当高优先级 Pod 无法调度时,驱逐低优先级 Pod
扩展支持通过 Scheduling Framework 插件机制和 Extender HTTP API 支持自定义调度逻辑
多调度器通过 Profile 机制支持多个调度器实例共存

1.2 在系统中的位置

kube-scheduler 位于 Kubernetes 控制平面,与 kube-apiserver、kube-controller-manager 并列,是三大核心控制循环之一:

┌─────────────────────────────────────────────────────────┐
│                    Kubernetes 控制平面                     │
│  ┌──────────────┐  ┌───────────────────┐  ┌───────────┐ │
│  │ kube-apiserver│◄─►│  kube-scheduler   │◄─►│   etcd    │ │
│  └──────┬───────┘  └─────────▲─────────┘  └───────────┘ │
│         │                    │                           │
│  ┌──────▼───────┐   ┌───────┴────────┐                  │
│  │  controller   │   │  Informers     │                  │
│  │  manager      │   │  (Pod/Node/PV) │                  │
│  └──────────────┘   └────────────────┘                  │
└─────────────────────────────────────────────────────────┘

kube-scheduler 只读集群状态(通过 Informer),只写 Pod 的 .spec.nodeName 和 Binding 对象(通过 apiserver),不直接操作 Node 或其他资源。


二、模块整体结构

2.1 核心类结构

// ============ 顶层调度器对象 ============
type Scheduler struct {
    SchedulerCache  internalcache.Cache       // 调度器缓存,存储 Pod/Node 快照
    Algorithm       core.ScheduleAlgorithm     // 调度算法接口
    NextPod         func() *framework.QueuedPodInfo  // 从队列取下一个 Pod
    Error           func(*framework.QueuedPodInfo, error) // 错误处理
    StopEverything  <-chan struct{}            // 全局停止信号
    SchedulingQueue internalqueue.SchedulingQueue  // 调度队列
    Profiles        profile.Map               // 调度配置文件映射(按 schedulerName 索引)
    client          clientset.Interface       // kube-apiserver 客户端
}

// ============ 调度算法接口 ============
type ScheduleAlgorithm interface {
    Schedule(context.Context, framework.Framework, *framework.CycleState, *v1.Pod) (ScheduleResult, error)
    Extenders() []framework.Extender
}

// ============ 通用调度器实现 ============
type genericScheduler struct {
    cache                    internalcache.Cache
    extenders                []framework.Extender
    nodeInfoSnapshot         *internalcache.Snapshot
    percentageOfNodesToScore int32
    nextStartNodeIndex       int       // 轮转起点,保证公平性
}

// ============ Scheduling Framework 核心 ============
type frameworkImpl struct {
    registry              Registry                     // 插件注册表
    snapshotSharedLister  framework.SharedLister       // 快照共享列表
    waitingPods           *waitingPodsMap              // Permit 阶段等待中的 Pod
    pluginNameToWeightMap map[string]int               // Score 插件权重
    queueSortPlugins      []framework.QueueSortPlugin  // 各扩展点插件列表
    preFilterPlugins      []framework.PreFilterPlugin
    filterPlugins         []framework.FilterPlugin
    postFilterPlugins     []framework.PostFilterPlugin
    preScorePlugins       []framework.PreScorePlugin
    scorePlugins          []framework.ScorePlugin
    reservePlugins        []framework.ReservePlugin
    preBindPlugins        []framework.PreBindPlugin
    bindPlugins           []framework.BindPlugin
    postBindPlugins       []framework.PostBindPlugin
    permitPlugins         []framework.PermitPlugin
    // ... 其他字段
}

// ============ 调度器缓存 ============
type schedulerCache struct {
    mu           sync.RWMutex
    assumedPods  sets.String              // 假设已调度 Pod 集合
    podStates    map[string]*podState     // Pod 状态映射
    nodes        map[string]*nodeInfoListItem // Node 双向链表
    headNode     *nodeInfoListItem        // 最近更新的 Node(链表头)
    nodeTree     *nodeTree                // Node 扁平树(用于轮转)
    imageStates  map[string]*imageState   // 镜像状态映射
}

// ============ 优先级队列 ============
type PriorityQueue struct {
    framework.PodNominator
    activeQ        *heap.Heap                // 活跃队列(按优先级排序的堆)
    podBackoffQ    *heap.Heap                // 退避队列(按退避完成时间排序)
    unschedulableQ *UnschedulablePodsMap     // 不可调度队列
    schedulingCycle int64                    // 调度周期号(Pop 时递增)
    moveRequestCycle int64                   // 最近 MoveAll 请求的周期号
    clusterEventMap  map[framework.ClusterEvent]sets.String // 集群事件→插件映射
}

2.2 核心接口定义

// Framework 接口 —— 调度框架的统一入口
type Framework interface {
    Handle
    QueueSortFunc() LessFunc
    RunPreFilterPlugins(ctx, state, pod) *Status
    RunFilterPlugins(ctx, state, pod, nodeInfo) PluginToStatus
    RunPostFilterPlugins(ctx, state, pod, nodeToStatusMap) (*PostFilterResult, *Status)
    RunPreScorePlugins(ctx, state, pod, nodes) *Status
    RunScorePlugins(ctx, state, pod, nodes) (PluginToNodeScores, *Status)
    RunReservePluginsReserve(ctx, state, pod, nodeName) *Status
    RunReservePluginsUnreserve(ctx, state, pod, nodeName)
    RunPermitPlugins(ctx, state, pod, nodeName) *Status
    WaitOnPermit(ctx, pod) *Status
    RunPreBindPlugins(ctx, state, pod, nodeName) *Status
    RunBindPlugins(ctx, state, pod, nodeName) *Status
    RunPostBindPlugins(ctx, state, pod, nodeName)
    RunFilterPluginsWithNominatedPods(ctx, state, pod, info) *Status
    HasFilterPlugins() bool
    HasPostFilterPlugins() bool
    HasScorePlugins() bool
    ProfileName() string
}

// 插件接口体系(每个扩展点对应一个接口)
type Plugin interface { Name() string }
type QueueSortPlugin interface { Plugin; Less(*QueuedPodInfo, *QueuedPodInfo) bool }
type PreFilterPlugin interface { Plugin; PreFilter(ctx, state, pod) *Status; PreFilterExtensions() PreFilterExtensions }
type FilterPlugin interface { Plugin; Filter(ctx, state, pod, nodeInfo) *Status }
type PostFilterPlugin interface { Plugin; PostFilter(ctx, state, pod, nodeToStatusMap) (*PostFilterResult, *Status) }
type PreScorePlugin interface { Plugin; PreScore(ctx, state, pod, nodes) *Status }
type ScorePlugin interface { Plugin; Score(ctx, state, pod, nodeName) (int64, *Status); ScoreExtensions() ScoreExtensions }
type ReservePlugin interface { Plugin; Reserve(ctx, state, pod, nodeName) *Status; Unreserve(ctx, state, pod, nodeName) }
type PreBindPlugin interface { Plugin; PreBind(ctx, state, pod, nodeName) *Status }
type BindPlugin interface { Plugin; Bind(ctx, state, pod, nodeName) *Status }
type PostBindPlugin interface { Plugin; PostBind(ctx, state, pod, nodeName) }
type PermitPlugin interface { Plugin; Permit(ctx, state, pod, nodeName) (*Status, time.Duration) }

// Extender 接口 —— HTTP 扩展器
type Extender interface {
    Name() string
    IsInterested(pod *v1.Pod) bool
    Filter(pod *v1.Pod, nodes []*v1.Node) (feasibleNodes []*v1.Node, failedMap, failedAndUnresolvableMap map[string]string, err error)
    Prioritize(pod *v1.Pod, nodes []*v1.Node) (hostPriorities *extenderv1.HostPriorityList, weight int64, err error)
    Bind(binding *v1.Binding) error
    IsBinder() bool
    IsIgnorable() bool
}

2.3 依赖注入关系

NewSchedulerCommand

Options

Config

CompletedConfig

scheduler.New

internalcache.New

frameworkplugins.NewInTreeRegistry

Configurator

profile.NewMap

frameworkruntime.NewFramework

frameworkImpl

internalqueue.NewSchedulingQueue

core.NewGenericScheduler

addAllEventHandlers

核心依赖注入路径:

  1. Options → Config → CompletedConfig:命令行参数 → 中间配置 → 最终配置
  2. scheduler.New 是调度器工厂函数,通过 Option 函数式选项模式注入:
    • WithProfiles — 调度配置文件
    • WithAlgorithmSource — 算法来源(Provider 或 Policy)
    • WithPercentageOfNodesToScore — 打分节点比例
    • WithFrameworkOutOfTreeRegistry — 树外插件注册表
    • WithPodMaxBackoffSeconds / WithPodInitialBackoffSeconds — 退避参数
    • WithExtenders — HTTP 扩展器配置
    • WithParallelism — 并行度
  3. Configurator 将上述配置组装为 Scheduler 实例
  4. profile.NewMap 为每个 Profile 创建 frameworkImpl,注入 ClientSet、InformerFactory、SnapshotSharedLister、PodNominator 等

2.4 核心方法清单

类/结构体方法作用
SchedulerRun(ctx)启动调度主循环
SchedulerscheduleOne(ctx)单个 Pod 调度全流程
Schedulerassume(pod, host)假设 Pod 已调度(乐观更新缓存)
Schedulerbind(ctx, fwk, pod, node, state)绑定 Pod 到 Node
SchedulerskipPodSchedule(fwk, pod)判断是否跳过调度
genericSchedulerSchedule(ctx, fwk, state, pod)执行调度算法(Filter+Score+Select)
genericSchedulerfindNodesThatFitPod(ctx, fwk, state, pod)执行 Filter 阶段
genericSchedulerprioritizeNodes(ctx, fwk, state, pod, nodes)执行 Score 阶段
genericSchedulerselectHost(nodeScoreList)从打分结果选择最终节点(蓄水池抽样)
frameworkImplRunPreFilterPlugins运行 PreFilter 扩展点
frameworkImplRunFilterPlugins运行 Filter 扩展点
frameworkImplRunScorePlugins运行 Score 扩展点(并行)
frameworkImplRunPermitPlugins运行 Permit 扩展点
frameworkImplRunBindPlugins运行 Bind 扩展点
frameworkImplRunFilterPluginsWithNominatedPods含提名 Pod 的 Filter(调度+抢占双场景)
PriorityQueueAdd(pod)新 Pod 入活跃队列
PriorityQueueAddUnschedulableIfNotPresent不可调度 Pod 入 unschedulableQ
PriorityQueuePop()取出最高优先级 Pod
PriorityQueueMoveAllToActiveOrBackoffQueue批量移动 Pod 到活跃/退避队列
schedulerCacheAssumePod(pod)假设 Pod 已调度
schedulerCacheForgetPod(pod)撤回假设
schedulerCacheUpdateSnapshot(snapshot)更新快照(增量更新)
DefaultPreemptionPostFilter(ctx, state, pod, m)抢占逻辑入口

2.5 数据流入流出

数据流入:

  • Informer 监听 kube-apiserver 的 Pod/Node/PV/PVC/Service/StorageClass/CSINode 事件
  • 未调度 Pod → SchedulingQueue.Add()
  • 已调度 Pod → SchedulerCache.AddPod()
  • Node 变更 → SchedulerCache.AddNode()/UpdateNode()

数据流出:

  • Pod 绑定 → 通过 client.CoreV1().Pods(ns).Bind() 写入 apiserver
  • Pod 状态更新 → updatePod() 更新 PodCondition 和 NominatedNodeName
  • 事件记录 → EventRecorder.Eventf()

三、核心业务逻辑深度解析

3.1 Scheduler 启动流程

Yes

No

main()

app.NewSchedulerCommand()

runCommand(cmd, opts)

Setup(ctx, opts)

opts.Validate()

opts.Config()

c.Complete() → CompletedConfig

scheduler.New(cc.Client, ...)

internalcache.New(30s, stop)

frameworkplugins.NewInTreeRegistry()

registry.Merge(outOfTreeRegistry)

Configurator.createFromProvider()

c.create() → Scheduler

addAllEventHandlers(sched, informerFactory)

Run(ctx, cc, sched)

EventBroadcaster.StartRecordingToSink()

InformerFactory.Start()

InformerFactory.WaitForCacheSync()

LeaderElection?

leaderelection.NewLeaderElector()

leaderElector.Run(ctx)

OnStartedLeading: sched.Run(ctx)

sched.Run(ctx)

逐行解析 scheduler.gomain() 函数:

func main() {
    rand.Seed(time.Now().UnixNano())                    // 1. 初始化随机种子(用于 selectHost 蓄水池抽样)
    pflag.CommandLine.SetNormalizeFunc(cliflag.WordSepNormalizeFunc) // 2. 标准化 flag 名(如 --algorithm-provider → algorithm_provider)
    command := app.NewSchedulerCommand()                 // 3. 创建 cobra 命令
    logs.InitLogs()                                     // 4. 初始化日志
    defer logs.FlushLogs()                              // 5. 确保退出时刷日志
    if err := command.Execute(); err != nil {            // 6. 执行命令
        os.Exit(1)
    }
}

逐行解析 server.goRun() 函数:

func Run(ctx context.Context, cc *CompletedConfig, sched *scheduler.Scheduler) error {
    klog.V(1).Infof("Starting Kubernetes Scheduler version %+v", version.Get())  // 打印版本

    // 注册 configz
    if cz, err := configz.New("componentconfig"); err == nil {
        cz.Set(cc.ComponentConfig)
    }

    // 启动事件广播器
    cc.EventBroadcaster.StartRecordingToSink(ctx.Done())

    // 设置健康检查
    var checks []healthz.HealthChecker
    if cc.ComponentConfig.LeaderElection.LeaderElect {
        checks = append(checks, cc.LeaderElection.WatchDog)
    }

    // waitingForLeader 通道用于判断是否是 leader
    waitingForLeader := make(chan struct{})
    isLeader := func() bool {
        select {
        case _, ok := <-waitingForLeader:  // 通道关闭 = 成为 leader
            return !ok
        default:
            return false
        }
    }

    // 启动 HTTP 服务器(insecure/secure/metrics)
    if cc.InsecureServing != nil {
        handler := buildHandlerChain(newHealthzHandler(...), nil, nil)
        cc.InsecureServing.Serve(handler, 0, ctx.Done())
    }
    if cc.SecureServing != nil {
        handler := buildHandlerChain(newHealthzHandler(...), cc.Authentication.Authenticator, cc.Authorization.Authorizer)
        cc.SecureServing.Serve(handler, 0, ctx.Done())
    }

    // ★ 关键:启动所有 Informer 并等待缓存同步
    cc.InformerFactory.Start(ctx.Done())
    cc.InformerFactory.WaitForCacheSync(ctx.Done())

    // ★ Leader 选举
    if cc.LeaderElection != nil {
        cc.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{
            OnStartedLeading: func(ctx context.Context) {
                close(waitingForLeader)  // 关闭通道,表示成为 leader
                sched.Run(ctx)           // ★ 启动调度主循环
            },
            OnStoppedLeading: func() {
                klog.Fatalf("leaderelection lost")
            },
        }
        leaderElector, _ := leaderelection.NewLeaderElector(*cc.LeaderElection)
        leaderElector.Run(ctx)    // 阻塞,参与选举
        return fmt.Errorf("lost lease")
    }

    // 不启用选举时直接运行
    close(waitingForLeader)
    sched.Run(ctx)
    return fmt.Errorf("finished without leader elect")
}

3.2 Scheduling Framework 架构

Binding Cycle(异步 goroutine)

Scheduling Cycle(同步)

仅在 Filter 失败时

Filter 成功

Score 结果

Reserve 失败

Permit 拒绝

PreBind 失败

Bind 失败

QueueSort

PreFilter

Filter

PostFilter
(抢占)

PreScore

Score

NormalizeScore

Reserve

Permit
(可能 Wait)

WaitOnPermit

PreBind

Bind

PostBind

selectHost
(蓄水池抽样)

Unreserve

Scheduling Framework 的核心设计理念:

  1. 扩展点(Extension Points):将调度流程拆分为 11 个扩展点,每个扩展点可注册多个插件
  2. 插件化:所有调度逻辑通过插件实现,In-Tree 插件通过 NewInTreeRegistry() 注册,Out-Of-Tree 插件通过 WithPlugin() 注入
  3. Profile:每个调度器名对应一个 Profile,Profile 定义启用的插件和配置
  4. CycleState:每次调度周期创建一个 CycleState,插件间通过它共享数据

3.3 调度器扩展点详解

扩展点与插件映射

QueueSort
━━━━━━━━━━
PrioritySort

PreFilter
━━━━━━━━━━
NodeResourcesFit
NodePorts
PodTopologySpread
InterPodAffinity
VolumeBinding
NodeAffinity

Filter
━━━━━━━━━━
NodeUnschedulable
NodeName
TaintToleration
NodeAffinity
NodePorts
NodeResourcesFit
VolumeRestrictions
NodeVolumeLimits
VolumeBinding
VolumeZone
PodTopologySpread
InterPodAffinity

PostFilter
━━━━━━━━━━
DefaultPreemption

PreScore
━━━━━━━━━━
InterPodAffinity
PodTopologySpread
TaintToleration
NodeAffinity

Score
━━━━━━━━━━
NodeResourcesBalancedAlloc(1)
ImageLocality(1)
InterPodAffinity(1)
NodeResourcesLeastAlloc(1)
NodeAffinity(1)
NodePreferAvoidPods(10000)
PodTopologySpread(2)
TaintToleration(1)

Reserve
━━━━━━━━━━
VolumeBinding

Permit
━━━━━━━━━━
(默认无)

PreBind
━━━━━━━━━━
VolumeBinding

Bind
━━━━━━━━━━
DefaultBinder

PostBind
━━━━━━━━━━
(默认无)

3.4 Pod 调度全流程

是(已调度)

否(未调度)

删除中/已假设

仅1个

多个

Reject

Wait

Success

Reject

Allow

Pod 创建/更新事件

assignedPod?

SchedulerCache.AddPod()

responsibleForPod?

忽略

SchedulingQueue.Add(pod)

NextPod() = Pop()

skipPodSchedule?

跳过

frameworkForPod(pod)

g.snapshot() — 更新缓存快照

节点数==0?

ErrNoNodesAvailable

fwk.RunPreFilterPlugins()

PreFilter 成功?

FitError

有 NominatedNode?

evaluateNominatedNode()

通过?

feasibleNodes

findNodesThatPassFilters()

feasibleNodes > 0?

fwk.RunPostFilterPlugins() — 抢占

直接选该节点

fwk.RunPreScorePlugins()

fwk.RunScorePlugins() — 并行打分

Extender.Prioritize() — 扩展器打分

selectHost() — 蓄水池抽样

sched.assume() — 乐观假设

fwk.RunReservePluginsReserve()

Reserve 成功?

Unreserve + ForgetPod + 失败

fwk.RunPermitPlugins()

Permit 结果?

Unreserve + ForgetPod + 失败

go func() { WaitOnPermit }

WaitOnPermit()

等待结果?

Unreserve + ForgetPod + 失败

fwk.RunPreBindPlugins()

PreBind 成功?

Unreserve + ForgetPod + 失败

sched.bind()

绑定成功?

Unreserve + ForgetPod + 失败

fwk.RunPostBindPlugins()

记录调度指标

逐行解析 scheduleOne() —— 最核心的方法:

func (sched *Scheduler) scheduleOne(ctx context.Context) {
    // 1. 从队列获取下一个 Pod(阻塞式)
    podInfo := sched.NextPod()       // 底层调用 PriorityQueue.Pop()
    if podInfo == nil || podInfo.Pod == nil {
        return                        // 队列已关闭
    }
    pod := podInfo.Pod
    
    // 2. 根据 pod.Spec.SchedulerName 找到对应的 Framework Profile
    fwk, err := sched.frameworkForPod(pod)
    if err != nil {
        return  // 不应发生,入队时已校验
    }
    
    // 3. 跳过正在删除或已假设的 Pod
    if sched.skipPodSchedule(fwk, pod) {
        return
    }

    // 4. 创建调度周期状态(跨插件共享数据)
    state := framework.NewCycleState()
    state.SetRecordPluginMetrics(rand.Intn(100) < pluginMetricsSamplePercent)  // 10% 采样
    schedulingCycleCtx, cancel := context.WithCancel(ctx)
    defer cancel()

    // 5. ★ 执行调度算法:Filter → Score → SelectHost
    scheduleResult, err := sched.Algorithm.Schedule(schedulingCycleCtx, fwk, state, pod)
    if err != nil {
        // 6. 调度失败处理
        nominatedNode := ""
        if fitError, ok := err.(*framework.FitError); ok {
            // 6a. 尝试抢占(PostFilter)
            if fwk.HasPostFilterPlugins() {
                result, status := fwk.RunPostFilterPlugins(ctx, state, pod, fitError.Diagnosis.NodeToStatusMap)
                if status.IsSuccess() && result != nil {
                    nominatedNode = result.NominatedNodeName
                }
            }
            metrics.PodUnschedulable(fwk.ProfileName(), ...)
        } else if err == core.ErrNoNodesAvailable {
            metrics.PodUnschedulable(...)
        } else {
            metrics.PodScheduleError(...)
        }
        sched.recordSchedulingFailure(fwk, podInfo, err, v1.PodReasonUnschedulable, nominatedNode)
        return
    }

    // 7. ★ 假设 Pod 已调度(乐观更新缓存,避免等待 Binding 完成)
    assumedPodInfo := podInfo.DeepCopy()
    assumedPod := assumedPodInfo.Pod
    err = sched.assume(assumedPod, scheduleResult.SuggestedHost)
    if err != nil {
        sched.recordSchedulingFailure(...)
        return
    }

    // 8. ★ Reserve 阶段(如 VolumeBinding 预留卷)
    if sts := fwk.RunReservePluginsReserve(schedulingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost); !sts.IsSuccess() {
        fwk.RunReservePluginsUnreserve(...)
        sched.SchedulerCache.ForgetPod(assumedPod)
        sched.recordSchedulingFailure(...)
        return
    }

    // 9. ★ Permit 阶段(可能延迟绑定)
    runPermitStatus := fwk.RunPermitPlugins(schedulingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost)
    if runPermitStatus.Code() != framework.Wait && !runPermitStatus.IsSuccess() {
        // Permit 拒绝 → Unreserve → Forget → 记录失败
        fwk.RunReservePluginsUnreserve(...)
        sched.SchedulerCache.ForgetPod(assumedPod)
        sched.recordSchedulingFailure(...)
        return
    }

    // 10. ★ 异步绑定周期(goroutine)
    go func() {
        bindingCycleCtx, cancel := context.WithCancel(ctx)
        defer cancel()

        // 10a. 等待 Permit(阻塞直到所有 Permit 插件 Allow 或超时/拒绝)
        waitOnPermitStatus := fwk.WaitOnPermit(bindingCycleCtx, assumedPod)
        if !waitOnPermitStatus.IsSuccess() {
            fwk.RunReservePluginsUnreserve(...)
            sched.SchedulerCache.ForgetPod(assumedPod)
            sched.recordSchedulingFailure(...)
            return
        }

        // 10b. PreBind 阶段(如 VolumeBinding 绑定 PVC-PV)
        preBindStatus := fwk.RunPreBindPlugins(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost)
        if !preBindStatus.IsSuccess() {
            fwk.RunReservePluginsUnreserve(...)
            sched.SchedulerCache.ForgetPod(assumedPod)
            sched.recordSchedulingFailure(...)
            return
        }

        // 10c. ★ 执行绑定
        err := sched.bind(bindingCycleCtx, fwk, assumedPod, scheduleResult.SuggestedHost, state)
        if err != nil {
            // 绑定失败 → Unreserve → Forget → 记录失败
            fwk.RunReservePluginsUnreserve(...)
            sched.SchedulerCache.ForgetPod(assumedPod)
            sched.recordSchedulingFailure(...)
        } else {
            // 绑定成功
            metrics.PodScheduled(...)
            // 10d. PostBind 阶段(通知、清理)
            fwk.RunPostBindPlugins(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost)
        }
    }()
}

关键设计点:

  1. 乐观假设(Optimistic Assumption)assume() 在绑定前将 Pod 写入缓存,使得后续调度可以感知该 Pod 的资源占用,避免资源超额分配。绑定成功则保留,失败则 ForgetPod() 回滚。

  2. Scheduling Cycle vs Binding Cycle

    • Scheduling Cycle(同步):从 NextPod()Permit,在主 goroutine 中串行执行
    • Binding Cycle(异步):WaitOnPermitPreBindBindPostBind,在单独的 goroutine 中执行,不阻塞下一个 Pod 的调度
  3. 错误恢复路径:任何阶段失败都执行 UnreserveForgetPodrecordSchedulingFailure,保证缓存和插件状态的一致性。

3.5 Filter 过滤阶段深度解析

findNodesThatPassFilters — 并行过滤

Unschedulable

Error

Success

checkNode(i) — 检查单个节点

Success

Fail

nodeInfo = nodes[(nextStart+i) % len]

RunFilterPluginsWithNominatedPods()

有 >= 优先级的提名 Pod?

addNominatedPods() → 克隆 state + nodeInfo

RunFilterPlugins() — 含提名 Pod

RunFilterPlugins() — 原始 nodeInfo

两次都通过?

加入 feasibleNodes

加入 NodeToStatusMap

findNodesThatFitPod()

RunPreFilterPlugins(ctx, state, pod)

PreFilter 成功?

所有节点标记为失败

中止调度

pod.NominatedNodeName 非空
且 PreferNominatedNode 启用?

evaluateNominatedNode()

提名节点通过?

返回提名节点

findNodesThatPassFilters()

numFeasibleNodesToFind() — 计算需要检查的节点数

Parallelizer.Until(len(nodes), checkNode)

feasibleNodes >= numNodesToFind?

cancel() — 提前终止

继续检查

findNodesThatPassExtenders()

返回 feasibleNodes + diagnosis

findNodesThatPassFilters 关键实现解析:

func (g *genericScheduler) findNodesThatPassFilters(...) ([]*v1.Node, error) {
    // 计算需要找到多少个可行节点
    numNodesToFind := g.numFeasibleNodesToFind(int32(len(nodes)))
    
    feasibleNodes := make([]*v1.Node, numNodesToFind)  // 预分配
    errCh := parallelize.NewErrorChannel()
    var feasibleNodesLen int32
    
    ctx, cancel := context.WithCancel(ctx)
    
    // ★ 并行检查每个节点
    checkNode := func(i int) {
        // 从 nextStartNodeIndex 开始轮转,保证公平性
        nodeInfo := nodes[(g.nextStartNodeIndex+i)%len(nodes)]
        
        // ★ 运行 Filter 插件(含提名 Pod 的两轮过滤)
        status := fwk.RunFilterPluginsWithNominatedPods(ctx, state, pod, nodeInfo)
        
        if status.IsSuccess() {
            length := atomic.AddInt32(&feasibleNodesLen, 1)
            if length > numNodesToFind {
                cancel()  // ★ 找够了,提前终止
                atomic.AddInt32(&feasibleNodesLen, -1)
            } else {
                feasibleNodes[length-1] = nodeInfo.Node()
            }
        } else {
            statusesLock.Lock()
            diagnosis.NodeToStatusMap[nodeInfo.Node().Name] = status
            diagnosis.UnschedulablePlugins.Insert(status.FailedPlugin())
            statusesLock.Unlock()
        }
    }
    
    // ★ 使用 Parallelizer 并行执行
    fwk.Parallelizer().Until(ctx, len(nodes), checkNode)
    
    // 更新轮转起点
    g.nextStartNodeIndex = (g.nextStartNodeIndex + processedNodes) % len(nodes)
    return feasibleNodes[:feasibleNodesLen], nil
}

RunFilterPluginsWithNominatedPods — 含提名 Pod 的两轮过滤机制:

这是调度器的一个精妙设计。当节点上有 >= 当前 Pod 优先级的提名 Pod 时,需要做两轮过滤:

  1. 第一轮:将提名 Pod 加入 nodeInfoPreFilter state,模拟它们已调度的情况运行 Filter
  2. 第二轮:仅当第一轮通过时,移除提名 Pod 后再运行 Filter

原因:

  • 资源类 Filter(如 NodeResourcesFit)在提名 Pod 存在时更可能失败(资源更多被占用)
  • 亲和性 Filter(如 InterPodAffinity)在提名 Pod 不存在时更可能失败(缺少亲和目标)
  • 两次都通过才算真正可行,这是保守决策
func (f *frameworkImpl) RunFilterPluginsWithNominatedPods(ctx, state, pod, info) *Status {
    podsAdded := false
    for i := 0; i < 2; i++ {
        stateToUse := state
        nodeInfoToUse := info
        if i == 0 {
            // 第一轮:加入 >= 优先级的提名 Pod
            podsAdded, stateToUse, nodeInfoToUse, _ = addNominatedPods(ctx, f, pod, state, info)
        } else if !podsAdded || !status.IsSuccess() {
            break  // 无提名 Pod 或第一轮失败 → 跳过第二轮
        }
        statusMap := f.RunFilterPlugins(ctx, stateToUse, pod, nodeInfoToUse)
        status = statusMap.Merge()
    }
    return status
}

numFeasibleNodesToFind — 自适应节点数量策略:

func (g *genericScheduler) numFeasibleNodesToFind(numAllNodes int32) int32 {
    if numAllNodes < 100 || g.percentageOfNodesToScore >= 100 {
        return numAllNodes  // 小集群或显式100%,检查所有节点
    }
    adaptivePercentage := g.percentageOfNodesToScore
    if adaptivePercentage <= 0 {
        // 默认:50% - nodes/125,最低 5%,确保至少 100 个
        basePercentageOfNodesToScore := int32(50)
        adaptivePercentage = basePercentageOfNodesToScore - numAllNodes/125
        if adaptivePercentage < 5 {
            adaptivePercentage = 5
        }
    }
    numNodes = numAllNodes * adaptivePercentage / 100
    if numNodes < 100 {
        return 100  // 至少检查 100 个节点
    }
    return numNodes
}

3.6 Score 打分阶段深度解析

RunScorePlugins — 三阶段并行

Phase 1: 并行对每个节点调用每个 Score 插件

Phase 2: 并行对每个插件调用 NormalizeScore

Phase 3: 并行应用权重(score * weight)

prioritizeNodes()

有 Score 插件或 Extender?

所有节点得分=1

RunPreScorePlugins()

PreScore 成功?

返回错误

RunScorePlugins()

汇总所有插件得分

有 Extender?

并行调用 Extender.Prioritize()

Extender 得分 * (MaxNodeScore/MaxExtenderPriority)

合并 Extender 得分

直接使用框架得分

返回 NodeScoreList

RunScorePlugins 三阶段并行:

func (f *frameworkImpl) RunScorePlugins(ctx, state, pod, nodes) (PluginToNodeScores, *Status) {
    pluginToNodeScores := make(PluginToNodeScores, len(f.scorePlugins))
    for _, pl := range f.scorePlugins {
        pluginToNodeScores[pl.Name()] = make(NodeScoreList, len(nodes))
    }
    
    // ★ Phase 1: 并行对每个节点调用每个插件的 Score 方法
    f.Parallelizer().Until(ctx, len(nodes), func(index int) {
        for _, pl := range f.scorePlugins {
            s, status := f.runScorePlugin(ctx, pl, state, pod, nodes[index].Name)
            pluginToNodeScores[pl.Name()][index] = NodeScore{Name: nodes[index].Name, Score: s}
        }
    })
    
    // ★ Phase 2: 并行对每个插件调用 NormalizeScore
    f.Parallelizer().Until(ctx, len(f.scorePlugins), func(index int) {
        pl := f.scorePlugins[index]
        if pl.ScoreExtensions() != nil {
            pl.ScoreExtensions().NormalizeScore(ctx, state, pod, pluginToNodeScores[pl.Name()])
        }
    })
    
    // ★ Phase 3: 应用权重并验证分数范围
    f.Parallelizer().Until(ctx, len(f.scorePlugins), func(index int) {
        pl := f.scorePlugins[index]
        weight := f.pluginNameToWeightMap[pl.Name()]
        for i, nodeScore := range pluginToNodeScores[pl.Name()] {
            // 验证分数在 [0, 100] 范围内
            if nodeScore.Score > MaxNodeScore || nodeScore.Score < MinNodeScore {
                errCh.SendErrorWithCancel(...)
                return
            }
            // ★ 应用权重:最终分数 = 原始分数 * 权重
            pluginToNodeScores[pl.Name()][i].Score = nodeScore.Score * int64(weight)
        }
    })
    
    return pluginToNodeScores, nil
}

分数汇总与 selectHost 蓄水池抽样:

// 汇总所有插件的加权得分
for i := range nodes {
    result[i] = NodeScore{Name: nodes[i].Name, Score: 0}
    for j := range scoresMap {
        result[i].Score += scoresMap[j][i].Score  // 各插件加权分数求和
    }
}

// ★ selectHost:从同分节点中随机选择(蓄水池抽样算法)
func (g *genericScheduler) selectHost(nodeScoreList NodeScoreList) (string, error) {
    maxScore := nodeScoreList[0].Score
    selected := nodeScoreList[0].Name
    cntOfMaxScore := 1
    for _, ns := range nodeScoreList[1:] {
        if ns.Score > maxScore {
            maxScore = ns.Score
            selected = ns.Name
            cntOfMaxScore = 1
        } else if ns.Score == maxScore {
            cntOfMaxScore++
            if rand.Intn(cntOfMaxScore) == 0 {
                selected = ns.Name  // 以 1/cntOfMaxScore 的概率替换
            }
        }
    }
    return selected, nil
}

默认插件权重一览(DefaultProvider):

插件权重说明
NodeResourcesBalancedAllocation1CPU/内存均衡分配
ImageLocality1镜像本地性
InterPodAffinity1Pod 间亲和性
NodeResourcesLeastAllocated1最少已分配资源(尽量分散)
NodeAffinity1Node 亲和性偏好
NodePreferAvoidPods10000避免调度到标记了 preferAvoidPods 的 Node(权重极高)
PodTopologySpread2拓扑分布约束(权重加倍)
TaintToleration1污点容忍度偏好

3.7 Bind 绑定阶段深度解析

RunBindPlugins

有且感兴趣

Skip

Success

Error

Skip

Success

Error

sched.bind(ctx, fwk, pod, node, state)

extendersBinding(pod, node)

有 Extender 实现 Bind?

Extender.Bind()

fwk.RunBindPlugins()

BindPlugin1.Bind()

返回码?

BindPlugin2.Bind()

绑定成功

绑定失败

返回码?

所有 Bind 插件都 Skip → 返回 Skip

finishBinding()

SchedulerCache.FinishBinding()

EventRecorder: 'Scheduled'

绑定优先级:Extender 的 Bind > Framework 的 Bind 插件。这是因为 Extender 可能管理特定的资源(如外部存储),需要在框架 Bind 之前处理。

DefaultBinder 实现(最简单的绑定逻辑):

func (b *DefaultBinder) Bind(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) *framework.Status {
    binding := &v1.Binding{
        ObjectMeta: metav1.ObjectMeta{Name: pod.Name, UID: pod.UID},
        Target:     v1.ObjectReference{Kind: "Node", Name: nodeName},
    }
    if err := b.handle.ClientSet().CoreV1().Pods(pod.Namespace).Bind(ctx, binding, metav1.CreateOptions{}); err != nil {
        return framework.AsStatus(err)
    }
    return nil
}

3.8 Scheduler Cache 数据结构

Snapshot

NodeInfo

nodeInfoListItem
(双向链表)

podState

schedulerCache

Clone()

list()

mu sync.RWMutex

assumedPods sets.String
'namespace/name'

podStates map[string]*podState

nodes map[string]*nodeInfoListItem

headNode *nodeInfoListItem
(双向链表头)

nodeTree *nodeTree
(扁平数组,轮转用)

imageStates map[string]*imageState

pod *v1.Pod

deadline *time.Time
(assumed pod 过期时间)

bindingFinished bool

info *NodeInfo

next *nodeInfoListItem

prev *nodeInfoListItem

node *v1.Node

Pods []*PodInfo

PodsWithAffinity []*PodInfo

PodsWithRequiredAntiAffinity []*PodInfo

UsedPorts HostPortInfo

Requested *Resource
MilliCPU/Memory/EphemeralStorage/ScalarResources

NonZeroRequested *Resource

Allocatable *Resource

ImageStates map[string]*ImageStateSummary

Generation int64

nodeInfoMap map[string]*NodeInfo

nodeInfoList []*NodeInfo
(按 nodeTree 顺序)

havePodsWithAffinityNodeInfoList

havePodsWithRequiredAntiAffinityNodeInfoList

generation int64

Cache 关键操作解析:

  1. AssumePod:将 Pod 标记为"已假设调度",加入 assumedPodspodStates,更新 NodeInfo 的资源请求。不设置 deadline,bindingFinished = false。

  2. FinishBinding:绑定完成后调用,设置 bindingFinished = truedeadline = now + ttl(默认 30s)。assumed Pod 在 TTL 过期后由后台 goroutine 清理。

  3. ForgetPod:撤回假设,从 assumedPodspodStates 删除,从 NodeInfo 移除 Pod。

  4. UpdateSnapshot(增量更新,性能关键路径):

    • 从双向链表头开始遍历,只更新 Generation > snapshotGeneration 的 NodeInfo
    • 通过 Generation 机制避免每次全量 Clone
    • 新增/删除节点时重建列表
func (cache *schedulerCache) UpdateSnapshot(nodeSnapshot *Snapshot) error {
    cache.mu.Lock()
    defer cache.mu.Unlock()
    
    snapshotGeneration := nodeSnapshot.generation
    updateAllLists := false
    
    // 从链表头遍历(最近更新的在前)
    for node := cache.headNode; node != nil; node = node.next {
        if node.info.Generation <= snapshotGeneration {
            break  // 后续都不会更新,提前退出
        }
        if np := node.info.Node(); np != nil {
            existing, ok := nodeSnapshot.nodeInfoMap[np.Name]
            if !ok {
                updateAllLists = true   // 新增节点
                existing = &framework.NodeInfo{}
                nodeSnapshot.nodeInfoMap[np.Name] = existing
            }
            clone := node.info.Clone()
            *existing = *clone  // 浅拷贝赋值
        }
    }
    // 处理已删除节点、重建列表...
}

3.9 PriorityQueue 调度队列

触发 MoveAllToActiveOrBackoffQueue 的事件

Pod 流转路径

PriorityQueue

Add()

Pop()

调度成功

调度失败

moveRequestCycle >= podSchedulingCycle

否则

flushUnschedulableQLeftover()
停留>60s

flushBackoffQCompleted()
退避到期

backoff中

backoff结束

activeQ *Heap
(按优先级排序)

podBackoffQ *Heap
(按退避完成时间排序)

unschedulableQ *UnschedulablePodsMap
(map存储)

schedulingCycle int64
(Pop时递增)

moveRequestCycle int64
(MoveAll时更新)

nominatedPodMap
(提名Pod映射)

新Pod

出队调度

绑定

AddUnschedulableIfNotPresent()

movePodsToActiveOrBackoffQueue()

NodeAdd

NodeSpecUnschedulableChange

NodeAllocatableChange

NodeLabelChange

NodeTaintChange

AssignedPodDelete

PVAdd/PVUpdate

PVCAdd/PVCUpdate

StorageClassAdd

ServiceAdd/Update/Delete

CSINodeAdd/Update

退避算法:

func (p *PriorityQueue) calculateBackoffDuration(podInfo *QueuedPodInfo) time.Duration {
    duration := p.podInitialBackoffDuration  // 默认 1s
    for i := 1; i < podInfo.Attempts; i++ {
        duration = duration * 2               // 指数退避
        if duration > p.podMaxBackoffDuration {
            return p.podMaxBackoffDuration     // 上限 10s
        }
    }
    return duration
}

智能队列移动(SchedulingQueue → ActiveQ 的优化):

MoveAllToActiveOrBackoffQueue 不再盲目移动所有 Pod,而是通过 clusterEventMapUnschedulablePlugins 做精准匹配:

func (p *PriorityQueue) movePodsToActiveOrBackoffQueue(podInfoList, event) {
    for _, pInfo := range podInfoList {
        // ★ 如果 Pod 的 UnschedulablePlugins 与事件不相关,跳过
        if len(pInfo.UnschedulablePlugins) != 0 && !p.podMatchesEvent(pInfo, event) {
            continue
        }
        // 否则移动到 activeQ 或 backoffQ
    }
}

3.10 DefaultPreemption 抢占流程

PrepareCandidate

FindCandidates

dryRunPreemption — 对每个候选节点

通过

不通过

SelectVictimsOnNode()

移除低优先级 Pod

重新运行 Filter 验证

记录 Candidate

移除更多 Pod 重试

PostFilter: DefaultPreemption.PostFilter()

获取最新 Pod 版本

PodEligibleToPreemptOthers()?

返回 Unschedulable

FindCandidates()

getOffsetAndNumCandidates() — 随机偏移+候选数

并行 dry-run 抢占

返回 []Candidate

有候选?

返回 Unschedulable

CallExtenders() — Extender 过滤候选

SelectCandidate() — 选择最佳候选

PrepareCandidate() — 执行驱逐

删除受害者 Pod

设置 pod.Status.NominatedNodeName

返回 NominatedNodeName

抢占的五个步骤(preempt 方法):

  1. 获取最新 Pod:从 Informer 缓存重新获取 Pod(避免过期数据)
  2. 检查抢占资格PodEligibleToPreemptOthers 检查 Pod 是否有权抢占(如已提名节点是否还有空间)
  3. 查找候选节点FindCandidates 并行对部分节点进行 dry-run 抢占
  4. Extender 过滤:调用 Extender 的 Preempt 方法进一步筛选
  5. 选择并执行SelectCandidate 选最佳候选,PrepareCandidate 驱逐受害者并设置 NominatedNodeName

PodEligibleToPreemptOthers 检查条件:

  • 如果 Pod 有 NominatedNode,检查该节点上是否已有更高优先级 Pod 占用了空间
  • 如果 NominatedNode 的 Filter 通过了(即该节点现在可以直接调度),则不需要抢占

3.11 VolumeBinding 卷绑定调度流程

不匹配

匹配

不能

VolumeBinding 插件

PreFilter: GetPodVolumes()

Pod 有 PVC?

skip=true

有未绑定的 immediate PVC?

返回 UnschedulableAndUnresolvable
'pod has unbound immediate PVCs'

保存 boundClaims + claimsToBind 到 CycleState

Filter: FindPodVolumesByNode()

已绑定 PV 的 NodeAffinity
匹配当前节点?

节点不兼容

未绑定 PVC 能找到
匹配的可用 PV?

无可用 PV

保存 podVolumesByNode

Reserve: reservePodVolumes()

预留成功?

返回错误

PreBind: bindPodVolumes()

绑定 PV 成功?

返回错误(将 Unreserve)

卷绑定完成

VolumeBinding 的三阶段设计是必要的:

  1. PreFilter:识别 Pod 的 PVC 需求,区分 immediate binding 和 delayed binding
  2. Filter:对每个节点检查 PV 可用性和 NodeAffinity 兼容性
  3. Reserve:在选中的节点上预留 PV(防止其他 Pod 抢占同一 PV)
  4. PreBind:在绑定 Pod 之前,先完成 PVC-PV 的 API 绑定

这种设计确保了卷绑定和 Pod 绑定的原子性——如果卷绑定失败,Pod 绑定也不会执行(Unreserve 回滚)。

3.12 事件处理器(eventhandlers.go)

事件处理器是调度器与集群状态同步的桥梁:

func addAllEventHandlers(sched *Scheduler, informerFactory informers.SharedInformerFactory) {
    // 已调度 Pod(过滤:assignedPod = true)
    informerFactory.Core().V1().Pods().Informer().AddEventHandler(
        cache.FilteringResourceEventHandler{
            FilterFunc: assignedPod,  // 只关注已绑定 Node 的 Pod
            Handler: cache.ResourceEventHandlerFuncs{
                AddFunc:    sched.addPodToCache,        // 更新缓存 + 触发亲和性匹配的 Pod 重新调度
                UpdateFunc: sched.updatePodInCache,
                DeleteFunc: sched.deletePodFromCache,   // 移除缓存 + MoveAll
            },
        },
    )
    
    // 未调度 Pod(过滤:!assignedPod && responsibleForPod)
    informerFactory.Core().V1().Pods().Informer().AddEventHandler(
        cache.FilteringResourceEventHandler{
            FilterFunc: func(obj) bool {
                return !assignedPod(obj) && responsibleForPod(obj, sched.Profiles)
            },
            Handler: cache.ResourceEventHandlerFuncs{
                AddFunc:    sched.addPodToSchedulingQueue,
                UpdateFunc: sched.updatePodInSchedulingQueue,
                DeleteFunc: sched.deletePodFromSchedulingQueue,
            },
        },
    )
    
    // Node 事件
    informerFactory.Core().V1().Nodes().Informer().AddEventHandler(...)
    // PV/PVC/StorageClass/CSINode/Service 事件...
}

Node 变更的精准触发机制:

func nodeSchedulingPropertiesChange(newNode, oldNode *v1.Node) string {
    if nodeSpecUnschedulableChanged(newNode, oldNode) { return NodeSpecUnschedulableChange }
    if nodeAllocatableChanged(newNode, oldNode) { return NodeAllocatableChange }
    if nodeLabelsChanged(newNode, oldNode) { return NodeLabelChange }
    if nodeTaintsChanged(newNode, oldNode) { return NodeTaintChange }
    if nodeConditionsChanged(newNode, oldNode) { return NodeConditionChange }
    return ""  // 无调度相关变更
}

只有影响调度的属性变更才会触发 MoveAllToActiveOrBackoffQueue,避免不必要的队列抖动。

3.13 Configurator 与 Profile 创建流程

func (c *Configurator) create() (*Scheduler, error) {
    // 1. 创建 Extender
    var extenders []framework.Extender
    for _, extenderConfig := range c.extenders {
        extender, _ := core.NewHTTPExtender(&extenderConfig)
        if !extender.IsIgnorable() {
            extenders = append(extenders, extender)
        }
    }
    
    // 2. 创建 PodNominator
    nominator := internalqueue.NewPodNominator()
    
    // 3. ★ 创建 Profile Map(每个 SchedulerName 对应一个 Framework)
    clusterEventMap := make(map[framework.ClusterEvent]sets.String)
    profiles, _ := profile.NewMap(c.profiles, c.registry, c.recorderFactory,
        frameworkruntime.WithClientSet(c.client),
        frameworkruntime.WithInformerFactory(c.informerFactory),
        frameworkruntime.WithSnapshotSharedLister(c.nodeInfoSnapshot),
        frameworkruntime.WithPodNominator(nominator),
        frameworkruntime.WithClusterEventMap(clusterEventMap),
        frameworkruntime.WithExtenders(extenders),
        frameworkruntime.WithParallelism(int(c.parallellism)),
    )
    
    // 4. 所有 Profile 必须使用相同的 QueueSort 插件
    lessFn := profiles[c.profiles[0].SchedulerName].QueueSortFunc()
    
    // 5. 创建调度队列
    podQueue := internalqueue.NewSchedulingQueue(lessFn, c.informerFactory, ...)
    
    // 6. 创建调度算法
    algo := core.NewGenericScheduler(c.schedulerCache, c.nodeInfoSnapshot, extenders, c.percentageOfNodesToScore)
    
    // 7. 组装 Scheduler
    return &Scheduler{
        SchedulerCache:  c.schedulerCache,
        Algorithm:       algo,
        Profiles:        profiles,
        NextPod:         internalqueue.MakeNextPodFunc(podQueue),
        Error:           MakeDefaultErrorFunc(...),
        SchedulingQueue: podQueue,
    }, nil
}

3.14 In-Tree 插件注册表

NewInTreeRegistry() 注册了所有内置插件:

func NewInTreeRegistry() runtime.Registry {
    return runtime.Registry{
        selectorspread.Name:                        selectorspread.New,
        imagelocality.Name:                         imagelocality.New,
        tainttoleration.Name:                       tainttoleration.New,
        nodename.Name:                              nodename.New,
        nodeports.Name:                             nodeports.New,
        nodepreferavoidpods.Name:                   nodepreferavoidpods.New,
        nodeaffinity.Name:                          nodeaffinity.New,
        podtopologyspread.Name:                     podtopologyspread.New,
        nodeunschedulable.Name:                     nodeunschedulable.New,
        noderesources.FitName:                      noderesources.NewFit,
        noderesources.BalancedAllocationName:       noderesources.NewBalancedAllocation,
        noderesources.MostAllocatedName:            noderesources.NewMostAllocated,
        noderesources.LeastAllocatedName:           noderesources.NewLeastAllocated,
        noderesources.RequestedToCapacityRatioName: noderesources.NewRequestedToCapacityRatio,
        volumebinding.Name:                         volumebinding.New,
        volumerestrictions.Name:                    volumerestrictions.New,
        volumezone.Name:                            volumezone.New,
        nodevolumelimits.CSIName:                   nodevolumelimits.NewCSI,
        nodevolumelimits.EBSName:                   nodevolumelimits.NewEBS,
        nodevolumelimits.GCEPDName:                 nodevolumelimits.NewGCEPD,
        nodevolumelimits.AzureDiskName:             nodevolumelimits.NewAzureDisk,
        nodevolumelimits.CinderName:                nodevolumelimits.NewCinder,
        interpodaffinity.Name:                      func(...) { return interpodaffinity.New(plArgs, fh, fts) },
        nodelabel.Name:                             nodelabel.New,
        serviceaffinity.Name:                       serviceaffinity.New,
        queuesort.Name:                             queuesort.New,
        defaultbinder.Name:                         defaultbinder.New,
        defaultpreemption.Name:                     defaultpreemption.New,
    }
}

DefaultProvider 的默认插件配置algorithmprovider/registry.go):

func getDefaultConfig() *schedulerapi.Plugins {
    return &schedulerapi.Plugins{
        QueueSort: {Enabled: []Plugin{{Name: "PrioritySort"}}},
        PreFilter: {Enabled: []Plugin{
            {Name: "NodeResourcesFit"}, {Name: "NodePorts"},
            {Name: "PodTopologySpread"}, {Name: "InterPodAffinity"},
            {Name: "VolumeBinding"}, {Name: "NodeAffinity"},
        }},
        Filter: {Enabled: []Plugin{
            {Name: "NodeUnschedulable"}, {Name: "NodeName"},
            {Name: "TaintToleration"}, {Name: "NodeAffinity"},
            {Name: "NodePorts"}, {Name: "NodeResourcesFit"},
            {Name: "VolumeRestrictions"}, {Name: "NodeVolumeLimits-EBSCSI..."},
            {Name: "VolumeBinding"}, {Name: "VolumeZone"},
            {Name: "PodTopologySpread"}, {Name: "InterPodAffinity"},
        }},
        PostFilter: {Enabled: []Plugin{{Name: "DefaultPreemption"}}},
        PreScore: {Enabled: []Plugin{
            {Name: "InterPodAffinity"}, {Name: "PodTopologySpread"},
            {Name: "TaintToleration"}, {Name: "NodeAffinity"},
        }},
        Score: {Enabled: []Plugin{
            {Name: "NodeResourcesBalancedAllocation", Weight: 1},
            {Name: "ImageLocality", Weight: 1},
            {Name: "InterPodAffinity", Weight: 1},
            {Name: "NodeResourcesLeastAllocated", Weight: 1},
            {Name: "NodeAffinity", Weight: 1},
            {Name: "NodePreferAvoidPods", Weight: 10000},
            {Name: "PodTopologySpread", Weight: 2},
            {Name: "TaintToleration", Weight: 1},
        }},
        Reserve:  {Enabled: []Plugin{{Name: "VolumeBinding"}}},
        PreBind:  {Enabled: []Plugin{{Name: "VolumeBinding"}}},
        Bind:     {Enabled: []Plugin{{Name: "DefaultBinder"}}},
    }
}

3.15 CycleState 跨插件状态传递

CycleState 是调度周期内的全局状态存储,插件间通过它共享数据:

type CycleState struct {
    mx      sync.RWMutex
    storage map[StateKey]StateData
    recordPluginMetrics bool  // 10% 采样率
}

// 使用示例(VolumeBinding 插件):
// PreFilter 阶段写入
state.Write(stateKey, &stateData{boundClaims: boundClaims, claimsToBind: claimsToBind, ...})

// Filter 阶段读取
s, _ := cs.Read(stateKey)
stateData := s.(*stateData)

// Clone 机制:在 RunFilterPluginsWithNominatedPods 中克隆 CycleState
// 因为 addNominatedPods 会修改 PreFilter 状态
stateOut := state.Clone()

3.16 MakeDefaultErrorFunc 错误处理

当调度失败后,MakeDefaultErrorFunc 决定 Pod 的后续处理:

func MakeDefaultErrorFunc(...) func(*framework.QueuedPodInfo, error) {
    return func(podInfo *framework.QueuedPodInfo, err error) {
        // 1. 注入 UnschedulablePlugins(用于智能队列移动)
        if fitError, ok := err.(*framework.FitError); ok {
            podInfo.UnschedulablePlugins = fitError.Diagnosis.UnschedulablePlugins
        }
        
        // 2. 处理 NodeNotFound 错误:从缓存移除不存在的 Node
        if apierrors.IsNotFound(err) {
            if errStatus.Status().Details.Kind == "node" {
                nodeName := errStatus.Status().Details.Name
                _, err := client.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{})
                if apierrors.IsNotFound(err) {
                    schedulerCache.RemoveNode(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}})
                }
            }
        }
        
        // 3. 检查 Pod 是否已被绑定(防止 Extender 超时导致的重复入队)
        cachedPod, _ := podLister.Pods(pod.Namespace).Get(pod.Name)
        if len(cachedPod.Spec.NodeName) != 0 {
            return  // 已绑定,不再入队
        }
        
        // 4. ★ 加入不可调度队列
        podInfo.PodInfo = framework.NewPodInfo(cachedPod.DeepCopy())
        podQueue.AddUnschedulableIfNotPresent(podInfo, podQueue.SchedulingCycle())
    }
}

四、关键设计总结

4.1 性能优化设计

优化点实现
增量快照UpdateSnapshot 基于 Generation 增量更新,只 Clone 变更的 NodeInfo
并行过滤Parallelizer.Until() 并行执行 Filter,找到足够可行节点后提前终止
轮转起点nextStartNodeIndex 保证所有节点公平被检查
自适应节点比例numFeasibleNodesToFind 大集群只检查部分节点
乐观假设AssumePod 在绑定前更新缓存,避免串行等待
异步绑定Binding Cycle 在 goroutine 中执行,不阻塞下一个 Pod 调度
智能队列移动基于集群事件与失败插件的精准匹配,避免无谓重调度

4.2 一致性保障设计

设计说明
Assume/Forget 机制绑定前 Assume,失败时 Forget + Unreserve,保证缓存与实际一致
Unreserve 反向调用Reserve 失败/Permit 拒绝/PreBind 失败/Bind 失败都触发 Unreserve,按反向顺序调用
NominatedNode 机制抢占成功后设置 NominatedNodeName,下次调度优先检查该节点
两轮 FilterRunFilterPluginsWithNominatedPods 做保守的两次过滤
Leader Election多副本调度器通过选举保证同一时间只有一个工作

4.3 扩展性设计

设计说明
Scheduling Framework11 个扩展点,插件式架构
Profile支持 Multiple Scheduler,每个有独立插件配置
ExtenderHTTP API 扩展,支持 Filter/Prioritize/Bind/Preempt
Out-of-Tree 插件WithPlugin() 注入自定义插件
CycleState插件间通过 StateKey 共享数据
EnqueueExtensions插件声明关注的事件,精准触发重调度

本文档基于 Kubernetes 源码严格分析,覆盖了 kube-scheduler 从启动到 Pod 调度完成的完整链路,包含所有关键数据结构、算法细节和设计决策。

更多推荐