惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

I
Intezer
人人都是产品经理
人人都是产品经理
博客园_首页
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
I
InfoQ
美团技术团队
罗磊的独立博客
F
Full Disclosure
Hugging Face - Blog
Hugging Face - Blog
T
The Blog of Author Tim Ferriss
Security Latest
Security Latest
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Last Week in AI
Last Week in AI
Vercel News
Vercel News
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
M
MIT News - Artificial intelligence
H
Hacker News: Front Page
IT之家
IT之家
S
Security Affairs
N
News and Events Feed by Topic
W
WeLiveSecurity
H
Help Net Security
C
Cyber Attacks, Cyber Crime and Cyber Security
雷峰网
雷峰网
B
Blog RSS Feed
Hacker News: Ask HN
Hacker News: Ask HN
The Cloudflare Blog
小众软件
小众软件
The GitHub Blog
The GitHub Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
H
Hackread – Cybersecurity News, Data Breaches, AI and More
A
Arctic Wolf
Google DeepMind News
Google DeepMind News
C
CERT Recently Published Vulnerability Notes
The Register - Security
The Register - Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Cloudbric
Cloudbric
Cyberwarzone
Cyberwarzone
爱范儿
爱范儿
阮一峰的网络日志
阮一峰的网络日志
Martin Fowler
Martin Fowler
月光博客
月光博客
MongoDB | Blog
MongoDB | Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Threat Research - Cisco Blogs
L
Lohrmann on Cybersecurity
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 热门话题

博客园 - 左扬

Kubernetes 编程 / client-go 专题【左扬精讲】—— 四种客户端:为什么、怎么选、怎么用 Kubernetes 编程 / Operator 专题【左扬精讲】—— controller-runtime、kubebuilder、operator-sdk 三大框架深度对比 Kubernetes 编程 / Operator 专题【左扬精讲】—— 深入理解 ManagedFields 字段冲突协调机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— k8s Finalizers 深度解析:对象的生命周期与删除控制 Kubernetes 编程 / Operator 专题【左扬精讲】—— OwnerReference 字段与级联删除机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— 深入学习 Server-Side Apply:managedFields 替代 last-applied-configuration 的演进方向 Kubernetes 编程 / Operator 专题【左扬精讲】—— k8s Annotations 与元数据体系(Operator 专题) Kubernetes 编程 / Operator 专题【左扬精讲】—— RESTMapper:把 Group / Version / Kind / Resource 四元组翻译成 REST 路径的"查字典"大师 Kubernetes 编程 / Operator 专题【左扬精讲】—— Converter 资源版本转换器 Kubernetes 编程 / Operator 专题【左扬精讲】—— Application 业务扩展:从单 Deployment 到多 Workload 的复合 Operator 演进 Kubernetes 编程 / Operator 专题【左扬精讲】—— OwnerReference / Finalizer / 准入控制:k8s 资源生命周期的三大支柱 Kubernetes 编程 / Operator 专题【左扬精讲】—— controller-runtime 框架内幕:从 Manager 到 Reconcile 的全栈拆解 Kubernetes 编程 / Operator 专题【左扬精讲】—— 生产级 Operator 最佳实践:并发安全、资源清理与高可用设计 Kubernetes 编程 / Operator 专题【左扬精讲】—— application-operator Reconcile 循环源码精讲:从 client-go Informer 到 workqueue 的全链路解剖 Kubernetes 编程 / Operator 专题【左扬精讲】—— 从零搭建一个 application-operator 新项目:脚手架、API 设计与基于原生 DeploymentStatus/ServiceStatus 的状态建模 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:生产级 Controller 实践:并发安全、资源清理与高可用设计 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析: Controller 调试与诊断工具:从日志分析到问题定位 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DynamicClient 操作 CRD:无需代码生成的动态操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:控制器与 APIServer 完整交互流程:从 Watch 到缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:错误处理与重试机制:WorkQueue 限速器详解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Leader 选举机制:高可用控制器的必备技能 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Controller 开发模式完整实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:SharedInformerFactory 与等待缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:从认证配置到 Deployment 操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:版本对应、架构组件与组件关系 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Informer 源码深度解析:从底层原理到实战应用 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Reflector 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:ListWatcher 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Indexer 与 ThreadSafeStore 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DeltaFIFO 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:workqueue 核心原理与实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— runtime.Codec 资源编解码:serializer 与 codec 差异、编解码数据结构、codec 核心调用链路 Kubernetes 编程 / Operator 专题【左扬精讲】—— Scheme 资源注册机制全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 自定义资源的内部版本与外部版本:从源码看版本定义机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 1.36.1 核心 API 数据结构全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 构建过程 【AIOPS】一文读懂LLM【左扬精讲】:从诞生到普及,解锁大语言模型的核心密码 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Hooks 源码精讲:Hooks 可观测性的无侵入式实现 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 配置解析源码精讲 ——SRE 自定义 Agent 核心技巧 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 并发模型解析 ——SRE 应对高并发采集的调优思路 【AIOPS】AI Agent 专题【左扬精讲】基础架构篇:MCP-VictoriaMetrics Golang 源码整体架构拆解 ——SRE 必懂的核心模块与数据流 OpenTelemetry 开发实战【左扬精讲】—— 云原生可观测体系构建与分布式追踪二次开发 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(下篇)—— 预测式弹性扩缩容 Operator 落地实现) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(上篇)—— 时序预测模型构建与离线训练) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 6 —— 基于运维专家知识库的智能故障诊断与排查 Operator 实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 5 —— 基于大语言模型(LLM)的实时日志流智能监测 Operator 实现 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 4 —— 基于 Operator 实现大模型私有化部署与管理 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 3(上篇)—— 面向 AI / 算力调度场景:GPU 竞价实例资源池统一调度管理 Operator 开发 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目 2 —— 面向零售 / 电商潮汐流量难题:多云多集群数据中心级全链路弹性伸缩 DataCenter Scaler Operator 从 0 到 1 全链路开发 Kubernetes编程 / Operator专题【左扬精讲】—— 深入理解Kubebuilder注解:为什么Operator开发离不开这些特殊注释 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目1 —— Applicaion Operator(通用应用生命周期管理 Operator 实战) Pod 镜像拉取失败?kubectl edit pods修改镜像地址的底层原理与实操 (该方法仅为临时应急方案,并非长期解决方案) Kubernetes编程/Operator专题精讲—— 理解控制器模式 —— 控制器模式的核心原理与实现逻辑(从原理到实践) 【AIOPS】AI Agent 专题【左扬精讲】模型微调实战:一站式平台 LLaMA-Factory 【AIOPS】AI Agent 专题【左扬精讲】基于 k8s+vLLM+Ray 分布式部署全指南:架构设计、资源调度与性能优化 【AIOPS】AI Agent专题【左扬精讲】非量化版DeepSeek分布式部署全指南:精度保障、显存规划与Ollama/vLLM选型 【AIOPS】AI Agent 专题【左扬精讲】零开发框架实现 ReAct Agent(Go SRE友好)
Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):初识调度模型、内部架构与事件驱动机制
左扬 · 2026-06-17 · via 博客园 - 左扬

Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):初识调度模型、内部架构与事件驱动机制

当你 kubectl apply 一个 Pod 之后,Kubernetes 内部发生了什么事?Pod 是怎么"被分配"到某个节点上的?为什么同样一份 Deployment,有时会调度到这台机器,有时又跑到另一台?这背后默默工作的核心组件,就是 kube-scheduler

对很多初学者来说,kube-scheduler 是个"熟悉的陌生人":知道它是调度器、知道它选节点、但不知道它具体怎么选。本文作为调度专题的开篇,目标不是带你读完所有源码,而是建立一个总览心智模型。读完你应该能回答三个问题:kube-scheduler 的调度模型是什么?它内部架构由哪些模块组成?它如何靠事件驱动持续工作?至于 Scheduling Framework 各扩展点的细节、调度器插件开发、抢占算法、Scheduler Profile 等进阶内容,会在后续文章里展开。

本文源码分析基于 k8s v1.36.1,全部以 cmd/kube-scheduler/pkg/scheduler/ 目录下的实际代码为依据。

Kubernetes Scheduler Scheduling Framework Informer 事件驱动 Go k8s v1.36.1

🔓 学习重点提示  — 建议先通读全文,再重点回顾标注内容

★ 重点掌握(必须)
   • 两阶段调度模型:Filtering(过滤)→ Scoring(打分)→ Binding(绑定),三步走通杀一个 Pod
   • 核心 struct 与目录组织:Scheduler(pkg/scheduler/scheduler.go) / SchedulingQueue(backend/queue) / Cache(backend/cache) / Profiles(profile)
   • 事件驱动三件套:SharedInformer → ResourceEventHandler → SchedulingQueue,靠 Watch 增量同步 + Queue 异步处理

☆ 次重点(了解即可)
   • Scheduling Framework 的扩展点(PreFilter/Filter/Score/Reserve/Bind 等)有哪些,调用顺序如何
   • Assume(假定缓存)是什么、为什么需要它、Bind 是怎么把结果写回 apiserver 的
   • cmd/kube-scheduler/app/server.go 的 Run 流程(启动入口)


📋 文章目录

  1. 一、Why:为什么需要 kube-scheduler
  2. 二、What:kube-scheduler 的调度模型
  3. 三、How:kube-scheduler 的内部架构
  4. 四、Detail:事件驱动机制深度解析
  5. 五、Roadmap:后续专题预告与学习路径

一、Why:为什么需要 kube-scheduler

在 k8s 集群中,kube-scheduler 是控制平面的核心组件之一。它唯一的工作职责,就是为每一个新创建的 Pod 挑选一个最合适的 Node,并把这个决定以 Binding 对象的形式写回 apiserver。一旦 Binding 成功,对应节点上的 kubelet 就会"看到"这个 Pod 并开始创建容器。

它的工作流程可以用一句话概括:监听 → 入队 → 选节点 → 绑定。但这四个动作背后,隐藏着 k8s 集群里最复杂的并发模型之一——成百上千个 Pod 同时涌入、成百上千个 Node 不断变化、还要兼顾资源、亲和性、污点、抢占、本地卷、动态资源分配等几十种约束条件。所以 kube-scheduler 的设计目标就是:在高并发场景下,依然能可扩展地完成 Pod ↔ Node 的最优匹配。

理解 kube-scheduler 不仅是面试常考点,更是深入 k8s 的必经之路:它把 client-goapimachinery、informer、workqueue、controller pattern 几乎所有核心概念都串了起来——可以说,读懂 kube-scheduler,等于读懂半个 k8s 核心代码

🚀 小贴士  — 集群里可以同时跑多个 kube-scheduler 实例,通过 Leader Election 选举出一个 Leader 真正工作,其他作为备份(v1.36.1 还新增了 Coordinated LeaderElection,更进一步支持多 scheduler 协调)。
这意味着 kube-scheduler 本身也是一个"高可用"组件,但同一时刻只有一个实例在执行调度决策,避免并发冲突。


二、What:kube-scheduler 的调度模型

2.1 调度问题的本质

从算法角度看,kube-scheduler 解决的是一个典型的多约束配对问题:给定一个待调度的 Pod 和一组 Node,找出"最合适"的那个 Node。这里的"最合适"由一系列过滤(Filter)+打分(Score)规则共同决定。Filter 回答"能不能",Score 回答"好不好"。

社区里很多人把这套模型叫两阶段调度(Two-Stage Scheduling)

Text

┌──────────────────────────────────────────────────────────────┐
│  Stage 1: Filtering(过滤 / 预选)                              │
│    输入:Pod 描述 + 全部 Node 列表                              │
│    操作:依次执行若干 Filter 插件,每条规则淘汰不满足的 Node     │
│    输出:Feasible Nodes(可行节点集合)                          │
├──────────────────────────────────────────────────────────────┤
│  Stage 2: Scoring(打分 / 优选)                                │
│    输入:Feasible Nodes                                         │
│    操作:每个 Score 插件为每个 Node 打一个分数(0~100)          │
│    输出:每个插件的得分加权求和,得到最终排名                    │
├──────────────────────────────────────────────────────────────┤
│  Stage 3: Binding(绑定)                                       │
│    输入:排名第一的 Node                                        │
│    操作:调用 Bind 插件把 Pod.Spec.NodeName 写入并提交 apiserver  │
│    输出:apiserver 创建 Binding 对象                            │
└──────────────────────────────────────────────────────────────┘

这套模型自 kube-scheduler v1.0 起就没有变过——变的只是每阶段的插件实现、扩展点和性能优化。

2.2 一个 Pod 的"一生"

理解了"两阶段"还不够,kube-scheduler 真正的复杂度在于它不止处理新 Pod。下面这些场景全部要触发调度:

  • 新建 Pod:spec.nodeName 为空,Pod 处于 Pending 状态,需要被调度到某个 Node
  • 已经调度的 Pod:Node 上有变化(资源变化、label 改变、污点改变),需要重新评估是否仍然"合适"
  • 被抢占的 Pod:集群资源紧张时,某个高优先级 Pod 抢占低优先级 Pod,被抢占的 Pod 需要重新调度
  • unschedulable Pod:之前调度失败(如资源不足)的 Pod,当相关资源释放后需要重新尝试

这就需要 kube-scheduler 维护一个待调度 Pod 队列,并对外监听多种资源变化。这正是后面要讲的"事件驱动"机制的核心价值。

2.3 Scheduling Framework:插件化的两阶段

自 k8s 1.19 起,kube-scheduler 全面迁移到了 Scheduling Framework 架构(v1.36.1 中位于 staging/src/k8s.io/kube-scheduler/framework/)。它的核心思想:把"调度一个 Pod"拆成若干扩展点(Extension Point),每个扩展点是一个 Go interface,开发者可以注册任意多个 plugin 来实现。Framework 负责把这些 plugin 按固定顺序串成一个调度流水线。

下表列出 v1.36.1 中的所有扩展点(按调度流水线顺序排列)。注意:初学者只需要了解前 5 个就够用了,其余会在后续 Scheduling Framework 专题里详解。

扩展点对应的 Go Interface所属阶段作用
PreEnqueue PreEnqueuePlugin 入队前 Pod 进队列前做快速检查(如 schedulingGates
PreFilter PreFilterPlugin 过滤前预处理 预处理(如把资源请求算好、提前算好 inter-pod affinity),写 CycleState
Filter FilterPlugin 过滤(PreSelection) 逐个 Node 检查是否满足硬约束(资源、亲和、污点、端口等)
PostFilter PostFilterPlugin 过滤失败后 尝试抢占(默认实现是 defaultpreemption)
PreScore PreScorePlugin 打分前预处理 为 Score 阶段做轻量预处理(如批量算亲和性)
Score ScorePlugin 打分(Scoring) 给每个 Feasible Node 打分(资源均衡、亲和权重、镜像本地性等)
NormalizeScore ScoreExtensions 分数归一化 把不同 plugin 的分数归一化到 [0, 100]
Reserve ReservePlugin 假定(Assume) 在 cache 中"假定"这个 Pod 已经分配到该 Node(关键设计,下文详述)
Permit PermitPlugin 同步等待 可阻塞等待外部批准(默认 none,所有 Pod 立即放行)
PreBind PreBindPlugin 绑定前 绑定前执行(如 VolumeBinding 预留 PV/PVC)
Bind BindPlugin 绑定 把 Pod 的 nodeName 写回 apiserver(默认实现是 DefaultBinder)
PostBind PostBindPlugin 绑定后 绑定后的清理(默认 none)

一个 Pod 走完这个流水线,就是一次完整的 scheduling cycle。如果失败(Filter 全部淘汰),会进入 PostFilter / 抢占流程

2.4 Assume(假定缓存):两阶段解耦的关键设计

初学者最应该理解的设计是 Assume(假定缓存)。它解决了一个核心问题:

在 k8s v1.0 时代,调度器选完节点后,必须等 Binding 写回 apiserver 完成(可能耗时几十毫秒~几秒),才能认为这个 Node 的资源被占用了。这就导致:

  • 如果两个 Pod 几乎同时调度,第二个 Pod 可能把第一个 Pod"还没写入"的资源也算进来 → 过载(overbooking)
  • 高并发场景下,Binding 网络往返成为瓶颈

Assume 机制巧妙地解决了两者:调度器一旦决定把 Pod 放到 Node X,立刻在本地 cache 里把 Pod 标记为"假定已分配"。之后调度别的 Pod 时,cache 会把这个假定的 Pod 算进 Node X 的资源占用里。

后续如果 Binding 成功,cache 里的假定状态会被"确认"为正式状态;如果 Binding 失败(如 apiserver 拒绝),cache 会调用 ForgetPod 撤销假定,把资源"还回去"。这就是为什么 pkg/scheduler/backend/cache/cache.goAssumePodForgetPod 这对方法在 v1.36.1 中依然存在(位置:pkg/scheduler/backend/cache/cache.go:397)。

💡 注意
在 v1.36.1 中,Assume 机制对应的源码方法是 Scheduler.Cache.AssumePod(),由 pkg/scheduler/schedule_one.goschedulingCycle 成功结束后立即调用。后续 bind 阶段无论成功失败,对应的 cache 状态都已经准备好了,不会出现"先来后到"问题。


三、How:kube-scheduler 的内部架构

我们把视线从"模型"切到"代码",看 kube-scheduler 的内部模块。k8s 1.36.1 的 kube-scheduler 实现主要在两个目录:

  • cmd/kube-scheduler/:二进制入口(main.go / app/server.go
  • pkg/scheduler/:核心实现(含 scheduler.goschedule_one.goeventhandlers.gobackend/queue/backend/cache/framework/
  • staging/src/k8s.io/kube-scheduler/:对外发布的 Scheduling Framework 接口库(独立 module)

3.1 入口:cmd/kube-scheduler/app/server.go

二进制的启动入口遵循 k8s 组件的"标准模板":cobra 命令 → flags 解析 → runCommandSetupRun。我们重点看 Run 函数:

// cmd/kube-scheduler/app/server.go (行 173-310, k8s v1.36.1)

Go

// Run executes the scheduler based on the given configuration. It only returns on error or when context is done.
func Run(ctx context.Context, cc *schedulerserverconfig.CompletedConfig, sched *scheduler.Scheduler) error {
    logger := klog.FromContext(ctx)
    logger.Info("Starting Kubernetes Scheduler", "version", utilversion.Get())

    // Configz registration.
    if cz, err := configz.New("componentconfig"); err != nil { ... }

    // Start events processing pipeline.
    cc.EventBroadcaster.StartRecordingToSink(ctx.Done())
    defer cc.EventBroadcaster.Shutdown()

    // Setup healthz checks.
    readyzChecks = append(readyzChecks, healthz.NewShutdownHealthz(ctx.Done()))

    // 启动健康检查 endpoint
    if cc.SecureServing != nil { ... }

    // 核心:启动 Informer(Pod / Node / PV / PVC / CSINode ...)并等待同步
    startInformersAndWaitForSync := func(ctx context.Context) {
        cc.InformerFactory.Start(ctx.Done())
        if cc.DynInformerFactory != nil {
            cc.DynInformerFactory.Start(ctx.Done())
        }
        cc.InformerFactory.WaitForCacheSync(ctx.Done())
        // 关键:等待所有事件 handler 完成首次同步
        if err := sched.WaitForHandlersSync(ctx); err != nil {
            logger.Error(err, "handlers are not fully synchronized")
        }
        close(handlerSyncReadyCh)
    }

    // 启动 Leader Election(如启用)
    if cc.LeaderElection != nil { ... }

    // 启动调度器主循环
    sched.Run(ctx)

    return nil
}

可以看到,Run 函数的逻辑非常克制:它只负责搭建"骨架"——健康检查、配置注册、Informer 启动、Leader 选举——然后调用 sched.Run(ctx) 把自己交给核心循环阻塞等待。所有真正的调度逻辑都在 pkg/scheduler 里。

3.2 核心 struct:Scheduler

整个 kube-scheduler 的"灵魂"是 pkg/scheduler/scheduler.go 中的 Scheduler struct(v1.36.1 定义于行 68)。我们可以把它拆成 5 个核心字段:

// pkg/scheduler/scheduler.go (行 68-125, k8s v1.36.1)

Go

type Scheduler struct {
    // 1. 本地缓存:NodeInfo、PVC、PV、ResourceClaim 等
    //    Cache 决定了 Filter / Score 阶段能"看到"什么数据
    Cache internalcache.Cache

    // 2. 调度队列:待调度的 Pod 在这里排队
    SchedulingQueue internalqueue.SchedulingQueue

    // 3. Scheduling Framework 句柄:含全部 Profile 和 Plugin
    Profiles profile.Map

    // 4. 客户端:与 apiserver 交互
    client clientset.Interface

    // 5. 闭包函数:可被外部替换为测试用的 fake
    NextPod         func(logger klog.Logger) (*framework.QueuedPodInfo, error)
    SchedulePod     func(ctx context.Context, fwk framework.Framework, ...) (ScheduleResult, error)
    FailureHandler  FailureHandlerFn
    registeredHandlers []cache.ResourceEventHandlerRegistration  // 已注册的事件 handler
}

一个很巧妙的设计是 NextPod / SchedulePod / FailureHandler 都是函数字段(闭包),默认实现由 applyDefaultHandlers() 注入(行 127),但测试时可以用 fake 实现替换。这是典型的"依赖注入"模式,让 Scheduler 易于单测。

3.3 主循环:Scheduler.Run

看核心的 Run 函数(pkg/scheduler/scheduler.go:546):

// pkg/scheduler/scheduler.go (行 545-573, k8s v1.36.1)

Go

// Run begins watching and scheduling. It starts scheduling and blocked until the context is done.
func (sched *Scheduler) Run(ctx context.Context) {
    logger := klog.FromContext(ctx)
    sched.SchedulingQueue.Run(logger)              // 1. 启动队列(维护 activeQ/unschedulableQ/backoffQ)
    if sched.APIDispatcher != nil {
        sched.APIDispatcher.Run(logger)            // 2. 启动异步 API 调用派发器
    }
    // 3. 关键:启动 scheduleOne 循环,0 表示不间隔
    go wait.UntilWithContext(ctx, sched.ScheduleOne, 0)

    <-ctx.Done()                                   // 4. 阻塞到 ctx 取消
    if sched.APIDispatcher != nil {
        sched.APIDispatcher.Close()
    }
    sched.SchedulingQueue.Close()                  // 5. 关闭队列
    err := sched.Profiles.Close()                  // 6. 关闭 Profiles(释放 plugin 资源)
}

整个 Scheduler.Run 极其简洁:3 行核心逻辑,1 行阻塞等待。真正的"调度"行为发生在 scheduleOne 这个循环函数里(下一个 goroutine 启动)。

🌟 设计精髓
为什么用 go wait.UntilWithContext(ctx, sched.ScheduleOne, 0) 启动新 goroutine?注释说得很清楚:scheduleOne 会阻塞在 NextPod 上等下一个 Pod。如果在主 goroutine 里跑,关闭队列时就会死锁(没人调 Pop,主 goroutine 永远不退出)。所以单独开一个 goroutine 跑 scheduleOne,主 goroutine 专门负责监听 ctx 取消和清理。

3.4 核心模块全景图

把上面的信息拼起来,kube-scheduler 内部可以拆成 6 大模块,它们各司其职、相互协作:

Text

┌────────────────────────────────────────────────────────────────────┐
│              kube-scheduler (k8s v1.36.1) 内部模块全景                 │
└────────────────────────────────────────────────────────────────────┘
                                                                       │
  ┌──────────────────────┐    ┌──────────────────────┐                │
  │  1. cmd/kube-scheduler│   │  2. Scheduler struct  │               │
  │  ──────────────────  │    │  ──────────────────  │                │
  │  main.go             │    │  pkg/scheduler/       │                │
  │  app/server.go       │    │  scheduler.go         │                │
  │  ─ Run/Setup/flags   │    │  ─ Run/ScheduleOne    │                │
  │  ─ healthz/metrics   │    │  ─ Cache/Queue/Profile│                │
  │  ─ Leader Election   │    │  ─ NextPod/SchedulePod│                │
  └──────────┬───────────┘    └──────────┬───────────┘                │
             │                           │                            │
             │  启动入口                  │  核心调度循环               │
             └─────────┬─────────────────┘                            │
                       ▼                                              │
  ┌─────────────────────────────────────────────────┐                │
  │  3. eventhandlers.go                              │                │
  │  ─────────────────────────────────────────────   │                │
  │  addAllEventHandlers:                            │                │
  │    注册 Pod/Node/PV/PVC/CSINode/Service 等        │                │
  │    informer 的 ResourceEventHandler              │                │
  │  ─ 把集群事件"翻译"成 SchedulingQueue 的操作      │                │
  │    (addPod/updatePod/deletePod/...)            │                │
  └──────────┬──────────────────────────────────────┘                │
             │                                                        │
             ▼                                                        │
  ┌──────────────────────────────────────────────────┐               │
  │  4. backend/queue/scheduling_queue.go              │               │
  │  ─ PriorityQueue 实现                              │               │
  │  ─ 三段队列: activeQ / unschedulableQ / backoffQ  │               │
  │  ─ Pop() / Add() / AddIfNotPresent() / MoveAll...  │               │
  │  ─ 维护 inFlightPods(QHint 特性)                │               │
  └──────────┬───────────────────────────────────────┘               │
             │ Pop() 返回 QueuedPodInfo                               │
             ▼                                                        │
  ┌──────────────────────────────────────────────────┐               │
  │  5. schedule_one.go (ScheduleOne / schedulePod)   │               │
  │  ──────────────────────────────────────────────  │               │
  │  ① 选 Framework(按 pod.Spec.SchedulerName)      │               │
  │  ② schedulingCycle:                              │               │
  │     PreFilter → Filter → PostFilter               │               │
  │     → PreScore → Score → Reserve (AssumePod)      │               │
  │  ③ bindingCycle:                                  │               │
  │     Permit → PreBind → Bind (写 apiserver)        │               │
  │  ④ 失败 → handleSchedulingFailure                 │               │
  └──────────┬───────────────────────────────────────┘               │
             │ 调用 Filter / Score                                    │
             ▼                                                        │
  ┌──────────────────────────────────────────────────┐               │
  │  6. framework/ (Scheduling Framework 核心)         │               │
  │  ──────────────────────────────────────────────  │               │
  │  framework.go        ─ Framework interface 实现  │               │
  │  plugins/            ─ 内置插件(NodeResourcesFit,│               │
  │                        NodeAffinity, TaintToleration,│             │
  │                        VolumeBinding, PodTopologySpread, ...)│   │
  │  cycle_state.go      ─ CycleState(plugin 间传数据)│              │
  │  registry.go         ─ Plugin Registry            │               │
  └──────────┬───────────────────────────────────────┘               │
             │                                                        │
             ▼                                                        │
  ┌──────────────────────────────────────────────────┐               │
  │  7. backend/cache/cache.go (Cache)                 │               │
  │  ──────────────────────────────────────────────  │               │
  │  AddPod/UpdatePod/RemovePod/AddNode/...          │               │
  │  AssumePod/ForgetPod(假定缓存,调度即占用资源)   │               │
  │  UpdateSnapshot(生成 snapshot 给 Score 阶段用)   │               │
  └──────────────────────────────────────────────────┘               │

记住这个全景图:事件驱动模块(3)把集群变化翻译为队列操作 → 队列模块(4)管理待调度 Pod → 调度循环(5)从队列取 Pod 跑调度流水线 → Framework(6)执行具体的 Filter / Score 逻辑 → Cache(7)为整个过程提供"实时"集群状态视图。

3.5 scheduleOne 一次循环做了什么

代码位于 pkg/scheduler/schedule_one.go(v1.36.1 行 67)。它的核心骨架如下:

// pkg/scheduler/schedule_one.go(k8s v1.36.1 关键流程简化)

Go

func (sched *Scheduler) ScheduleOne(ctx context.Context) {
    // 1. 从 SchedulingQueue 阻塞获取下一个待调度 Pod
    pInfo, err := sched.NextPod(logger)
    if pInfo == nil { return }

    // 2. 根据 pod.Spec.SchedulerName 选择对应的 Framework(profile)
    fwk, err := sched.frameworkForPod(pInfo.Pod)

    // 3. 判断是否跳过(如 pod 已被 nominated 或被替换)
    if skip, status := sched.skipPodSchedule(ctx, fwk, pInfo.Pod); skip { return }

    // 4. scheduling cycle:跑 Filter + Score
    state := framework.NewCycleState()
    scheduleResult, status := sched.schedulingCycle(ctx, fwk, state, pInfo)

    // 5. 调度成功 → binding cycle
    if status.IsSuccess() {
        status = sched.bindingCycle(ctx, fwk, state, scheduleResult, pInfo, start, podsToActivate)
    }

    // 6. 失败 → handleSchedulingFailure
    if !status.IsSuccess() {
        sched.FailureHandler(ctx, fwk, pInfo, status, clearNominatedNode, start)
    }
}

注意 schedulingCycle 和 bindingCycle 是分开的。schedulingCycle 只在内存里"算"出最合适的 Node 并假定(Assume),bindingCycle 才真正把 Binding 对象写到 apiserver。中间用 CycleStatepkg/scheduler/framework/cycle_state.go)传数据——这是一个基于 sync.Map 的"写一次读多次"容器,让 PreFilter 算出的中间结果在 Filter 阶段被复用。

3.6 SchedulingQueue:三段优先级队列

实现位于 pkg/scheduler/backend/queue/scheduling_queue.go,核心是 PriorityQueue。它内部维护三个堆/队列:

队列存放什么何时出队
activeQ 新 Pod / 重新激活的 Pod 按 priority + 时间排序,由 scheduleOne Pop() 取出
unschedulableQ 调度失败的 Pod(含失败原因) 等 backoff timer 到期 或 相关事件触发 MoveAllToActiveOrBackoffQueue
backoffQ 被 backoff 限流的 Pod 指数退避计时器到期后移入 unschedulableQ
inFlightPods(v1.36.1) 正在被 scheduleOne 处理的 Pod 处理完成后调 Done(uid) 移除;用于 QueueingHint 特性

为什么要分这么多队列?防止"抖动"和"惊群"。比如一个 Pod 调度失败,盲目立刻重试没有意义(资源还没释放),所以先放 unschedulableQ 等事件触发;又比如大量 Pod 同时涌入,先在 backoffQ 限流避免 apiserver 过载。

🚀 v1.36.1 新增
v1.36.1 引入了 QueueingHint 特性:插件可以告诉队列"某种事件会让某些 Pod 重新可调度",队列据此精准地把 Pod 从 unschedulableQ 移到 activeQ,避免全量重排。代码位置:pkg/scheduler/framework/plugins/schedulinggates/scheduling_gates.go:69(SchedulingGates 插件已率先支持)。


四、Detail:事件驱动机制深度解析

到这里你可能有个疑问:调度器怎么知道有新的 Pod?Node 资源变化时它怎么知道要重排队列?答案就是本节要讲的事件驱动机制。它的核心是 k8s 所有组件都在用的标准模式:List + Watch + Local Cache + EventHandler

4.1 整体流程图

Text

              ┌────────────────────┐
              │    kube-apiserver   │
              │   (etcd 真实存储)   │
              └─────────┬──────────┘
                        │   ▲
              List 拉全量│   │Watch 推增量(HTTP long-poll)
                        ▼   │
              ┌──────────────────────────────┐
              │      SharedInformerFactory     │
              │      (client-go 提供)          │
              │  ─ PodInformer / NodeInformer  │
              │  ─ PVInformer / PVCInformer    │
              │  ─ CSINodeInformer / ...       │
              └─────────────┬────────────────┘
                            │ Reflector 把事件分发给
                            ▼
              ┌──────────────────────────────┐
              │   ResourceEventHandlerFuncs   │
              │   (注册在 informer 上)        │
              │  ─ AddFunc / UpdateFunc       │
              │  ─ DeleteFunc                 │
              └─────────────┬────────────────┘
                            │ 由 Scheduler 在 addAllEventHandlers
                            │ 中注册,桥接到 SchedulingQueue
                            ▼
              ┌──────────────────────────────┐
              │   SchedulingQueue             │
              │   (PriorityQueue 三段队列)    │
              │  ─ Add / AddIfNotPresent      │
              │  ─ MoveAllToActiveOrBackoffQ  │
              └─────────────┬────────────────┘
                            │ Pop
                            ▼
              ┌──────────────────────────────┐
              │   ScheduleOne 循环            │
              │   走 Filter / Score / Bind    │
              └──────────────────────────────┘

这是 k8s 所有控制面组件的通用范式:从 apiserver 拉数据到本地缓存 → 通过事件回调通知业务 → 业务把事件转化为工作队列。kube-scheduler 也不例外。

4.2 起点:SharedInformerFactory + 多资源 Informer

kube-scheduler 启动时,通过 staging/src/k8s.io/client-go/informers 包下的 SharedInformerFactory 同时启动多个 Informer:

// cmd/kube-scheduler/app/server.go(v1.36.1)

Go

// 用 client-go 提供的 factory 启动所有内置 Informer
cc.InformerFactory.Start(ctx.Done())
if cc.DynInformerFactory != nil {
    cc.DynInformerFactory.Start(ctx.Done())
}
// 阻塞等待所有缓存完成首次 List 同步
cc.InformerFactory.WaitForCacheSync(ctx.Done())
// 关键:还要等所有注册的 EventHandler 处理完首次 List 的事件
if err := sched.WaitForHandlersSync(ctx); err != nil {
    logger.Error(err, "handlers are not fully synchronized")
}

在 v1.36.1 中,kube-scheduler 至少需要监听 以下资源类型

资源来源作用
Pod informerFactory.Core().V1().Pods() 新 Pod 入队、已调度的 Pod 重排
Node informerFactory.Core().V1().Nodes() 资源/标签/污点变化,触发 unschedulable Pod 重排
PersistentVolume informerFactory.Core().V1().PersistentVolumes() 新增 PV 后,等 PV 的 Pod 可重新调度
PersistentVolumeClaim informerFactory.Core().V1().PersistentVolumeClaims() PVC Bound 后让依赖 Pod 可调度
CSINode / CSIDriver informerFactory.Storage().V1().CSINodes() VolumeBinding 插件需要感知存储拓扑
StorageClass informerFactory.Storage().V1().StorageClasses() 新建 StorageClass 后影响未绑定 PVC 的 Pod
Service informerFactory.Core().V1().Services() 用于某些 plugin(如 NodeAffinity)做 Service 拓扑感知
PodGroup (v1.36.1) informerFactory.Scheduling().V1alpha2().PodGroups() Gang Scheduling(需开启 GenericWorkload feature gate)

4.3 核心枢纽:pkg/scheduler/eventhandlers.go

所有"事件 → 队列"的翻译工作,都集中在 pkg/scheduler/eventhandlers.go 中。其中 addAllEventHandlers 函数是入口(v1.36.1 行 481):

// pkg/scheduler/eventhandlers.go (行 481-507, k8s v1.36.1)

Go

func addAllEventHandlers(
    sched *Scheduler,
    informerFactory informers.SharedInformerFactory,
    dynInformerFactory dynamicinformer.DynamicSharedInformerFactory,
    ...
) error {
    // 1. Pod Informer:调度器最关心的事件源
    informerFactory.Core().V1().Pods().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
        AddFunc:    sched.addPod,    // 新 Pod → 入队
        UpdateFunc: sched.updatePod, // Pod 更新 → 重新入队或忽略
        DeleteFunc: sched.deletePod, // Pod 删除 → ForgetPod
    })

    // 2. Node Informer:节点变化触发 unschedulable Pod 重排
    informerFactory.Core().V1().Nodes().Informer().AddEventHandler(
        cache.ResourceEventHandlerFuncs{
            AddFunc:    sched.addNodeToCache,
            UpdateFunc: sched.updateNodeInCache,
            DeleteFunc: sched.deleteNodeFromCache,
        },
    )

    // 3. 其他资源:CSINode / PV / PVC / StorageClass / Service / PodGroup ...
    //    通过 buildEvtResHandler 工厂方法动态注册
    for gvk, at := range gvkMap {
        switch gvk {
        case fwk.CSINode:           /* ... */
        case fwk.PersistentVolume:  /* ... */
        case fwk.PersistentVolumeClaim: /* ... */
        case fwk.PodGroup:          /* ... */
        default:                    /* 用 dynInformerFactory 处理 CRD */
        }
    }
}

我们以 addPod / addNodeToCache 为例,看具体怎么"翻译"。先看 addNodeToCache 的完整实现(pkg/scheduler/eventhandlers.go:53):

// pkg/scheduler/eventhandlers.go (行 53-66, k8s v1.36.1)

Go

func (sched *Scheduler) addNodeToCache(obj interface{}) {
    evt := fwk.ClusterEvent{Resource: fwk.Node, ActionType: fwk.Add}
    defer metrics.EventHandlingLatency.ObserveSince(time.Now(), evt.Label())()
    logger := sched.logger
    node, ok := obj.(*v1.Node)
    if !ok {
        utilruntime.HandleErrorWithLogger(logger, nil, "Cannot convert to *v1.Node", "obj", obj)
        return
    }
    logger.V(3).Info("Add event for node", "node", klog.KObj(node))

    // 步骤 1:把 Node 加入本地 Cache
    nodeInfo := sched.Cache.AddNode(logger, node)
    // 步骤 2:把所有 unschedulable Pod 重新移到 activeQ 重新评估
    sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(logger, evt, nil, node, preCheckForNode(logger, nodeInfo))
}

一个看似简单的"新增节点"事件,触发了两个动作:

  • 更新 Cachesched.Cache.AddNode() 把 Node 加入本地缓存,下一次 Score 就能看到这个 Node
  • 重排队列MoveAllToActiveOrBackoffQueue() 把所有当前被"判 unschedulable"的 Pod 重新激活,加入下一轮调度

这就是"事件驱动"的核心价值:不需要轮询,集群一有变化就被推过来,业务侧只需把事件翻译成工作队列操作

4.4 Pod 事件:addPod / updatePod / deletePod

Pod 是最复杂的事件源,调度器需要做精细判断(不是所有 Pod Update 都要重排)。典型的 addPod 处理逻辑(v1.36.1)会做:

  1. 判断是否需要调度:spec.nodeName 为空、spec.schedulerName 匹配、Pod 未被删除(deletionTimestamp 为空)
  2. 调用 runPreEnqueuePlugins:每个 profile 可以注册 PreEnqueue plugin 做"快速门禁"(如 SchedulingGates 检查)
  3. 加入 SchedulingQueue.Add(已存在则用 AddIfNotPresent 跳过)

updatePod 更复杂:

  • 如果 Pod 已经被 nominated(被预选为某个 Node)、或新加入了 SchedulingGates、或关键 label 改变——需要重新调度
  • 如果只是 status 字段更新(如容器启动状态变化),调度器不关心,直接 return

这种"事件过滤"非常重要,否则 apiserver 的每一个事件都会冲击调度队列,导致大量无效工作。

4.5 关键:WaitForHandlersSync 是什么?

很多新手会把 WaitForCacheSyncWaitForHandlersSync 混为一谈,其实它们是两件事:

方法在哪等等什么
WaitForCacheSync Informer 内部 Reflector 拉完第一次 List,本地 Indexer 填好
WaitForHandlersSync Scheduler 层 所有已注册 EventHandler 把 List 中的每个对象都处理完(都调用了一次 AddFunc/UpdateFunc)

为什么要等第二层?因为 WaitForCacheSync 完成后,本地缓存虽然填好了,但 List 阶段产生的事件还没被 EventHandler 处理(这些事件是异步分发的)。如果此时 scheduleOne 开始取 Pod,可能错过"先创建后又被删"的 Pod,或漏算某些已被假定但未进 cache 的 Pod。

v1.36.1 的 server.go 专门为它加了 healthz check:

// cmd/kube-scheduler/app/server.go (行 220-227, k8s v1.36.1)

Go

handlerSyncReadyCh := make(chan struct{})
handlerSyncCheck := healthz.NamedCheck("sched-handler-sync", func(_ *http.Request) error {
    select {
    case <-handlerSyncReadyCh:
        return nil
    default:
    }
    return fmt.Errorf("handlers are not fully synchronized")
})
readyzChecks = append(readyzChecks, handlerSyncCheck)

也就是说,/readyz 返回 200 之前,kube-scheduler 不会真正开始调度。这是生产环境排障的常用入口:如果你看到 kube-scheduler 一直没 Ready,第一反应就是看是不是 handler 还没同步完。

4.6 整体时序图

Text

用户         kubectl         kube-apiserver      SharedInformer       ResourceEventHandler     SchedulingQueue       ScheduleOne
 │             │                  │                    │                    │                      │                    │
 │ apply Pod ──┼─────────────────►│                    │                    │                      │                    │
 │             │                  │ 写入 etcd           │                    │                      │                    │
 │             │                  │ 触发 Watch 事件 ───►│ 收到 ADD 事件       │                      │                    │
 │             │                  │                    │ 调用 addPod handler│                      │                    │
 │             │                  │                    │ ──────────────────►│                      │                    │
 │             │                  │                    │                    │ Cache.AddPod         │                    │
 │             │                  │                    │                    │ Queue.Add(pod) ─────►│                    │
 │             │                  │                    │                    │                      │ Pop() 阻塞          │
 │             │                  │                    │                    │                      │ ───────────────────►│
 │             │                  │                    │                    │                      │                    │ 取到 Pod
 │             │                  │                    │                    │                      │                    │ schedulingCycle
 │             │                  │                    │                    │                      │                    │ Filter/Score
 │             │                  │                    │                    │                      │                    │ AssumePod
 │             │                  │                    │                    │                      │                    │ bindingCycle
 │             │                  │◄───────────────────┼────────────────────┼──────────────────────┼────────────────────│ POST /bindings
 │             │                  │ 写入 Pod.Spec.NodeName                                  │                    │
 │             │                  │ ────► Node 端 kubelet 监听到                          │                    │
 │             │                  │      开始创建容器                                     │                    │
 │             │                  │                    │                    │                      │                    │ Done(uid)
 │             │                  │                    │                    │                      │                    │ 循环取下一个
 │             │                  │                    │                    │                      │                    │

4.7 一个小例子:Node 增加资源后会发生什么?

把上述知识串成一个具体场景:

  1. 运维给 Node worker-1 扩容 32Gi 内存(kubectl edit node 或 ccm 自动调整)
  2. apiserver 把 Node 对象更新到 etcd
  3. kube-scheduler 的 NodeInformer 通过 Watch 收到 Update 事件
  4. updateNodeInCache 被调用:sched.Cache.UpdateNode() 更新本地 cache
  5. 计算 NodeSchedulingPropertiesChange,识别出"capacity 变化"等关键属性
  6. 调用 MoveAllToActiveOrBackoffQueue(),把所有之前因"内存不足"被标 unschedulable 的 Pod 移到 activeQ
  7. scheduleOne 从 activeQ 取出这些 Pod 重新跑调度,可能命中新 Node

整个过程无需重启 scheduler、无需轮询,纯粹由事件驱动。这就是 k8s 控制面的优雅之处。

💡 注意
有些事件会触发"全量重排"(MoveAllToActiveOrBackoffQueue),这在超大集群(>5000 节点)上可能成为性能瓶颈。v1.36.1 引入的 QueueingHint 特性正是为了解决这个问题:插件能告诉队列"只有 X 类 Pod 受影响",避免无差别重排。


五、Roadmap:后续专题预告与学习路径

本文是调度专题的开篇,只建立总览心智模型。后续会按下面顺序逐篇展开,感兴趣的读者可以先标记:

  1. Scheduling Framework 深度解析:逐个讲解 PreFilter/Filter/Score/Reserve/Bind 等扩展点的源码、调用顺序、数据如何在 CycleState 中传递
  2. 内置插件逐个精读:NodeResourcesFit、NodeAffinity、TaintToleration、PodTopologySpread、VolumeBinding、InterPodAffinity 等
  3. 抢占(Preemption)算法剖析:defaultpreemption 插件如何选择 victim、PodDisruptionBudget 如何约束
  4. SchedulingQueue 与 QueueingHint:三段队列的细节、v1.36 新引入的 QueueingHint 工作机制
  5. Scheduler Profile 与多调度器:如何配置多个 profile 实现多租户、Coordinated LeaderElection
  6. 自定义插件开发实战:手写一个 Score 插件并注册到集群

5.1 给初学者的源码阅读建议

如果你想直接啃源码,推荐按下面的顺序读,每一步都建立在上一部的概念上:

  1. cmd/kube-scheduler/app/server.go — 看 Run 函数,理解启动流程(约 100 行)
  2. pkg/scheduler/scheduler.go — 看 Scheduler struct 定义和 Run 方法(约 100 行)
  3. pkg/scheduler/eventhandlers.go — 看 addAllEventHandlers(约 200 行)
  4. pkg/scheduler/schedule_one.go — 看 ScheduleOne / schedulingCycle / bindingCycle(约 400 行)
  5. pkg/scheduler/framework/framework.go — 看 RunPreFilterPlugins / RunFilterPlugins / RunScorePlugins 等 framework 核心方法
  6. pkg/scheduler/framework/plugins/noderesources/fit.go — 看一个具体 plugin 的完整实现,作为范例

读完这些,你就能把本文的"心智模型"对到具体代码上。

5.2 一句话总结

kube-scheduler 是个"事件驱动的两阶段调度器":通过 Informer 监听 Pod/Node/各种资源变化,把事件翻译成 SchedulingQueue 的入队操作;调度循环从队列里取 Pod,先用一系列 Filter 插件过滤,再用 Score 插件打分排名,最后 Bind 插件把结果写回 apiserver。Assume 缓存机制让调度决策和资源占用"瞬时一致",避免并发过载;Scheduling Framework 把所有这些规则抽象成可插拔的扩展点,让调度行为可以灵活定制。

记住这三个关键词:两阶段(Filter → Score)插件化(Framework)事件驱动(Informer → Queue)。下次有人问你 kube-scheduler 怎么工作,就可以用这三点展开。


本文参考与源码链接:
   • cmd/kube-scheduler/ 入口
   • pkg/scheduler/ 核心实现
   • staging/src/k8s.io/kube-scheduler/framework 扩展点定义
   • Kubernetes 官方文档:调度与驱逐
   • Scheduling Framework 官方文档

Kubernetes 调度专题【左扬精讲】—— 初识 kube-scheduler:调度模型、内部架构与事件驱动机制 · 来源:k8s 源码 v1.36.1 深度分析

D:\worker-go\kubernetes-1.36.1\blog-output\kubernetes-1.36.1-kube-scheduler-intro-cnblogs.html