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

推荐订阅源

月光博客
月光博客
Microsoft Security Blog
Microsoft Security Blog
T
Threatpost
D
DataBreaches.Net
B
Blog RSS Feed
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
N
Netflix TechBlog - Medium
O
OpenAI News
Webroot Blog
Webroot Blog
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
Google Online Security Blog
Google Online Security Blog
SecWiki News
SecWiki News
H
Help Net Security
Forbes - Security
Forbes - Security
N
News and Events Feed by Topic
aimingoo的专栏
aimingoo的专栏
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
腾讯CDC
B
Blog
H
Heimdal Security Blog
博客园 - 三生石上(FineUI控件)
C
CERT Recently Published Vulnerability Notes
L
Lohrmann on Cybersecurity
G
Google Developers Blog
Scott Helme
Scott Helme
C
CXSECURITY Database RSS Feed - CXSecurity.com
罗磊的独立博客
V
Visual Studio Blog
L
LINUX DO - 热门话题
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
F
Full Disclosure
C
Cisco Blogs
The Last Watchdog
The Last Watchdog
Security Archives - TechRepublic
Security Archives - TechRepublic
F
Fortinet All Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google DeepMind News
Google DeepMind News
S
Security Affairs
P
Privacy & Cybersecurity Law Blog
S
Secure Thoughts
V
V2EX
雷峰网
雷峰网
Security Latest
Security Latest
H
Hacker News: Front Page
TaoSecurity Blog
TaoSecurity Blog
M
MIT News - Artificial intelligence

博客园 - 左扬

VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— VictoriaLogs 协同:Metrics 到 Logs 的一体化监控 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 源码阅读路线图:如何高效阅读 VM 源码 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 开源生态:VM 在 CNCF 生态中的位置 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 与其他 TSDB 对比:Prometheus/InfluxDB/Thanos/VM VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 写入吞吐/查询延迟/内存占用的数学模型 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 模块依赖图——从 import 语句看组件关系 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Goroutine 池/atomic/零拷贝/sync.Pool VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 多租户架构——accountID/projectID 与 tenant 隔离 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 版本演进:1.146.0 LTS 重大更新解析 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 整体数据流:一条监控数据的完整生命周期 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 架构演进:从 TSDB 到 MergeSet 的设计取舍 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Single-Node vs Cluster 模式本质区别 VictoriaMetrics 1.146.0 源码【左扬精讲】—— 开篇总览 Rust 专题【左扬精讲】—— 从语法到灵魂:Ownership、Borrowing 与多语言对比 kubernetes 源码【左扬精讲】—— kube-scheduler 启动流程源码分析 Rust 专题【左扬精讲】—— 选择控制语句、运算符与格式化输出 Rust 专题【左扬精讲】—— 所有权详解 Rust 专题【左扬精讲】—— 作用域详解 Rust 专题【左扬精讲】—— 变量、常量与标量数据类型 kubernetes 源码 / Operator 专题【左扬精讲】—— Deployment Controller 源码分析:从对象创建到滚动更新 kubernetes 源码 / Operator 专题【左扬精讲】—— Operator 开发中的 Webhook:从准入控制到生产部署 Kubernetes源码 / Operator 专题【左扬精讲】—— 实现 Application Controller:从零构建生产级控制器 Kubernetes 编程 / Operator 专题【左扬精讲】—— 定义 Application 资源 + 添加自定义新 API 完整指南 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八):内部架构与核心组件 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八): —— 从入口到调度的全链路源码剖析(k8s v1.36.1) DeepSeek-R1 多模态 R1 / VLM-GRPO【左扬精讲】—— Qwen2-VL 微调与视觉推理强化学习实战 DeepSeek-R1 工业 RAG + 微调混合系统【左扬精讲】—— R1 系列收官之作:从 Prompt → RAG → 微调 选型决策树 DeepSeek-R1 推理时扩展【左扬精讲】—— o1 / R1 慢思考机制:Self-Consistency + ToT + PRM 详解 DeepSeek-R1 端侧 LLM 工程【左扬精讲】—— llama.cpp 调参与 Apple Silicon / 国产 NPU / Android 端侧落地全攻略 DeepSeek-R1 vLLM + k8s 生产部署【左扬精讲】—— 从单卡 7B 到 100 卡 671B MoE 集群的工业化部署实战 DeepSeek-R1 评估与系统(Evaluation & Systems)【左扬精讲】—— 从 GSM8K/MMLU 到 LLM-as-Judge 的工业级评估方法论 DeepSeek-R1 模型训练与算法【左扬精讲】—— GRPO 进阶算法:DAPO / PRIME / RLVR / PRM 四大 2025 前沿改进 DeepSeek-R1 模型训练与算法【左扬精讲】—— 数据蒸馏:用 DeepSeek-R1-671B 生成 800K 高质量 CoT 样本的完整流水线 DeepSeek-R1 优化与微调实战【左扬精讲】—— 从 R1 强化学习新范式到 GRPO 微调一站式入门 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 七):自定义插件开发实战 —— 手写一个 Score 插件并注册到集群 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 六):Scheduler Profile 与多调度器 —— 如何配置多个 profile 实现多租户、Coordinated LeaderElection Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 五):SchedulingQueue 与 QueueingHint —— 三段队列的细节、v1.36 新引入的 QueueingHint 工作机制 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 四):抢占(Preemption)算法剖析 —— DefaultPreemption 如何选 victim、PodDisruptionBudget 如何约束 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 二):内置插件逐个精读 — NodeResourcesFit / NodeAffinity / TaintToleration / PodTopologySpread / VolumeBinding / InterPodAffinity Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):调度器内置插件 逐个精读 Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):调度器内置插件精读 — NodeResourcesFit / NodeAffinity / TaintToleration / PodTopologySpread / VolumeBinding / InterPodAffinity Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):Scheduling Framework 扩展点逐个源码拆解 Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):初识调度模型、内部架构与事件驱动机制 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友好)
k8s 源码级精讲(二十六):调度器内置插件逐个精读
左扬 · 2026-06-19 · via 博客园 - 左扬

k8s 源码级精讲(二十六):调度器内置插件逐个精读

深入 kube-scheduler 源码,按调度流水线实际触发顺序,逐一拆解 NodeResourcesFit、NodeAffinity、TaintToleration、InterPodAffinity、PodTopologySpread、VolumeBinding 六大核心插件的职责边界、源码结构与生产踩坑点。所有源码行号基于 k8s v1.36.1 tag。

阅读收益:理解调度器两阶段决策模型 / 掌握六大插件核心逻辑 / 规避生产高频踩坑

● 核心前置清单(必掌握)

  • 两阶段决策:Filter(硬门槛,行/不行)vs Score(软打分,好/更好)
  • 插件名常量:全部集中在 pkg/scheduler/framework/plugins/names/names.go L19-43 行
  • 注册表:NewInTreeRegistry() 将插件名映射到工厂函数(registry.go L50-79)
  • VolumeBinding:唯一跨越 PreFilter / Filter / Reserve / PreBind 四个阶段
  • 短路机制:UnschedulableAndUnresolvable 立即中断,不调用后续插件

● 理解层级(了解即可)

  • 每个插件的 EventsToRegister 决定哪些事件触发 Pending Pod 重新入队
  • Unschedulable(资源不足,等扩容)vs UnschedulableAndUnresolvable(任何节点都不行)
  • SchedulingQueueHint(v1.28+)通过 QueueingHintFn 减少无效重试,降低调度延迟

文章导航

  1. 调度框架演进
  2. 插件总览与流水线
  3. ● 六大插件精读
  4. NodeResourcesFit
  5. NodeAffinity
  6. TaintToleration
  7. InterPodAffinity
  8. PodTopologySpread
  9. VolumeBinding
  10. 对比总结表
  11. 生产踩坑实录
  12. FAQ 高频答疑

k8s 源码级精讲(二十六):调度器内置插件逐个精读

一、调度框架的演进:为什么拆成多个插件

早期 k8s 调度器(v1.14 及之前)是一个 monolithic 大函数,内置几十个 predicate 和 priority 函数,每加一个策略都要改主循环代码。这带来三个根本痛点:

  • 改一个策略必须重新编译整个调度器二进制
  • 用户的特殊调度需求无法动态扩展
  • 所有 predicate 必须串行跑完才能进 priority 阶段,没有短路能力

从 v1.15 起,调度器重构成 Scheduler Framework:将"过滤 / 打分 / 绑定"切成固定扩展点,每个扩展点可挂载多个插件。内置插件是出厂预装,第三方可以 OutOfTree 方式注册自己的插件。

1.1 传统调度器 vs 框架方案对比

维度传统 monolithic(v1.14 及之前)Scheduler Framework + 内置插件(v1.15+)
扩展性 改源码 + 重新编译 OutOfTree 注册自定义插件
代码组织 所有 predicate/priority 堆在主文件 每个插件独占一个目录
替换单个策略 无法实现 SchedulerConfiguration 中 disablePlugin 禁用
短路能力 所有 predicate 必须跑完 UnschedulableAndUnresolvable 立刻中断
调试可见性 日志看不到哪个 predicate 失败 每个插件返回 Status 带 reason,describe pod 可看
扩展点数量 硬编码:预选 -> 优选 -> 绑定 14 个扩展点(PreEnqueue ~ Bind)

● 生产禁忌

不要直接修改 kube-scheduler 源码来定制内置插件的行为。正确做法是在 SchedulerConfiguration 中 disablePlugin: - NodeResourcesFit 后,实现自己的同名插件 OutOfTree 注册。直接改源码会让集群脱离上游,无法升级。

二、插件总览与调度流水线

2.1 两阶段决策模型

调度器对每个 Pod 的决策分为两个阶段:

┌──────────────────────────────────────────────────────────────┐ │ Filter(硬门槛,行/不行) │ │ │ │ NodeResourcesFit ──► NodeAffinity ──► TaintToleration │ │ │ │ │ │ │ 资源够吗? 节点 label 匹配? 容忍污点吗? │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ 任一插件返回 UnschedulableAndUnresolvable ──► 短路跳过 │ └──────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────┐ │ Score(软打分,好/更好) │ │ │ │ NodeResourcesFit ──► NodeAffinity ──► TaintToleration │ │ InterPodAffinity ──► PodTopologySpread ──► VolumeBinding │ │ │ │ │ │ │ 资源越闲越分高 亲和匹配越少越分高 卷容量越足越分高 │ │ │ │ ▼ NormalizeScore(归一化到 0-MaxNodeScore) │ │ 所有分数加权求和 ──► 选出分数最高节点 ──► Bind │ └──────────────────────────────────────────────────────────────┘

2.2 插件名称常量(names/names.go, L19-43)

所有内置插件的名字都定义在同一 const 块中,NewInTreeRegistry() 通过这个名字映射到对应的工厂函数:

pkg/scheduler/framework/plugins/names/names.go (L19-43, k8s v1.36.1)

const (
    NodeResourcesFit               = "NodeResourcesFit"
    NodeAffinity                  = "NodeAffinity"
    TaintToleration              = "TaintToleration"
    InterPodAffinity             = "InterPodAffinity"
    PodTopologySpread            = "PodTopologySpread"
    VolumeBinding                = "VolumeBinding"
    NodeResourcesBalancedAllocation = "NodeResourcesBalancedAllocation"
    NodeUnschedulable            = "NodeUnschedulable"
    NodeVolumeLimits             = "NodeVolumeLimits"
    ImageLocality                = "ImageLocality"
    // ... 共 23 个内置插件,完整定义见 names.go L19-43
)

2.3 插件注册表(registry.go, L50-79)

pkg/scheduler/framework/plugins/registry.go (L50-79, k8s v1.36.1)

func NewInTreeRegistry() runtime.Registry {
    fts := plfeature.NewSchedulerFeaturesFromGates(feature.DefaultFeatureGate)
    registry := runtime.Registry{
        nodeaffinity.Name:        runtime.FactoryAdapter(fts, nodeaffinity.New),
        noderesources.Name:       runtime.FactoryAdapter(fts, noderesources.NewFit),
        tainttoleration.Name:     runtime.FactoryAdapter(fts, tainttoleration.New),
        podtopologyspread.Name:   runtime.FactoryAdapter(fts, podtopologyspread.New),
        volumebinding.Name:       runtime.FactoryAdapter(fts, volumebinding.New),
        interpodaffinity.Name:    runtime.FactoryAdapter(fts, interpodaffinity.New),
        // ... 共 25 个插件,完整注册见 registry.go L50-79
    }
    return registry
}

● FactoryAdapter 的作用

每个插件工厂函数被 runtime.FactoryAdapter 包裹,以支持 feature gate 动态控制插件启用状态。feature gate 关闭时,对应插件工厂返回 nil,插件不被实例化。

三、六大插件逐个精读

下面按调度流水线上的实际触发顺序,逐一拆解六个核心插件。每个插件统一六段式:核心职责 -> 设计目的 -> 源码精读 -> YAML 示例 -> 生产踩坑 -> FAQ。

3.1 NodeResourcesFit

核心职责:判断节点 CPU / 内存 / GPU / 临时存储是否满足 Pod 的 resource.requests。资源不够时 Filter 直接返回 Unschedulable。Score 阶段对节点打分,越闲的节点分越高(默认 LeastAllocated 策略)。

源码精读

pkg/scheduler/framework/plugins/noderesources/fit.go (k8s v1.36.1)

// Fit struct 定义(L92-105)
type Fit struct {
    ignoredResources    sets.Set[string]
    ignoredResourceGroups sets.Set[string]
    enableInPlacePodVerticalScaling bool
    enableSidecarContainers        bool
    enableSchedulingQueueHint       bool
    enablePodLevelResources         bool
    enableDRAExtendedResource       bool
    handle                         fwk.Handle
    *resourceAllocationScorer       // 复用打分计算器
    placementScorer  *resourceAllocationScorer
}
// Filter 方法(L612-648)
// 核心逻辑:累加 pod 所有容器的 requests,对比 nodeInfo.Allocatable
// 任意资源不足 → 返回 Unschedulable 或 UnschedulableAndUnresolvable
func (f *Fit) Filter(ctx context.Context, cycleState fwk.CycleState,
    pod *v1.Pod, nodeInfo fwk.NodeInfo) *fwk.Status {
    s, err := getPreFilterState(cycleState)
    insufficientResources := fitsRequest(s, nodeInfo, ...)
    if len(insufficientResources) != 0 {
        statusCode := fwk.Unschedulable
        for _, ir := range insufficientResources {
            if ir.Unresolvable {
                statusCode = fwk.UnschedulableAndUnresolvable
            }
        }
        return fwk.NewStatus(statusCode, failureReasons...)
    }
    return nil
}
// Score 方法(L767-786)- LeastAllocated 默认策略
// 公式: score = (capacity - requested) * MaxNodeScore / capacity
// 越闲 → 分子越大 → 分越高
func (f *Fit) Score(ctx context.Context, state fwk.CycleState,
    pod *v1.Pod, nodeInfo fwk.NodeInfo) (int64, *fwk.Status) {
    s, err := getPreScoreState(state)
    return f.score(ctx, pod, nodeInfo, s.podRequests, s.draPreScoreState)
}
// EventsToRegister(L368-398)
// 关键事件:Pod 删除/缩容、节点 Add/UpdateNodeAllocatable/UpdateNodeTaint/UpdateNodeLabel
// 配合 enableSchedulingQueueHint 时,只注册 Add | UpdateNodeAllocatable 减少无效重试

● Score 四种策略

LeastAllocated(默认):最闲优先,适合通用计算。MostAllocated:最忙优先,适合批量回收。BalancedAllocation:CPU/内存均衡。RequestedToCapacityRatio:自定义资源权重,适合 GPU/FPGA 异构集群。

YAML 配置示例

# 触发 NodeResourcesFit 的 Pod(k8s v1.36.1)
apiVersion: v1
kind: Pod
metadata:
  name: resource-demo
spec:
  containers:
  - name: nginx
    image: nginx:1.25
    resources:
      requests:
        cpu: "2000m"
        memory: "512Mi"
      limits:
        cpu: "4000m"
        memory: "1Gi"

3.2 NodeAffinity

核心职责:解析 pod.spec.affinity.nodeAffinitypod.spec.nodeSelector,根据节点 label 判断是否匹配。requiredDuringSchedulingIgnoredDuringExecution 是硬条件(Filter),preferredDuringSchedulingIgnoredDuringExecution 是软打分(Score)。

pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go (k8s v1.36.1)

// NodeAffinity struct(L38-44)
type NodeAffinity struct {
    handle                fwk.Handle
    addedNodeSelector    *nodeaffinity.NodeSelector   // 配置级追加的选择器
    addedPrefSchedTerms *nodeaffinity.PreferredSchedulingTerms
    enableSchedulingQueueHint bool
}
// Filter(L216-239)- 硬条件匹配
func (pl *NodeAffinity) Filter(ctx context.Context, state fwk.CycleState,
    pod *v1.Pod, nodeInfo fwk.NodeInfo) *fwk.Status {
    node := nodeInfo.Node()
    // 配置级追加的选择器优先判断
    if pl.addedNodeSelector != nil && !pl.addedNodeSelector.Match(node) {
        return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, errReasonEnforced)
    }
    // requiredDuringSchedulingIgnoredDuringExecution 硬匹配
    match, _ := s.requiredNodeSelectorAndAffinity.Match(node)
    if !match {
        return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, ErrReasonPod)
    }
    return nil
}
// Score(L269-297)- 软偏好打分
func (pl *NodeAffinity) Score(ctx context.Context, state fwk.CycleState,
    pod *v1.Pod, nodeInfo fwk.NodeInfo) (int64, *fwk.Status) {
    node := nodeInfo.Node()
    var count int64
    // 累加匹配的 preferred terms 权重
    if pl.addedPrefSchedTerms != nil {
        count += pl.addedPrefSchedTerms.Score(node)
    }
    if s.preferredNodeAffinity != nil {
        count += s.preferredNodeAffinity.Score(node)
    }
    return count, nil
}
# 触发 NodeAffinity 的 Pod(k8s v1.36.1)
# 必须调度到 node-type=general 或 compute 节点
apiVersion: v1
kind: Pod
metadata:
  name: affinity-demo
spec:
  nodeSelector:
    az: cn-east-1
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: node-type
            operator: In
            values: [general, compute]
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        preference:
          matchExpressions:
          - key: az
            operator: In
            values: [cn-east-1]

3.3 TaintToleration

核心职责:判断 Pod 的 tolerations 是否覆盖节点的污点。NoSchedule / NoExecute 污点无匹配时 Filter 返回 UnschedulableAndUnresolvablePreferNoSchedule 污点无匹配时 Score 扣分。NoExecute 的驱逐由 kubelet 处理,不参与调度决策。

pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go (k8s v1.36.1)

// TaintToleration struct(L34-39)
type TaintToleration struct {
    handle                                    fwk.Handle
    enableSchedulingQueueHint                bool
    enableTaintTolerationComparisonOperators bool  // 支持 Exists 操作符
}

// ErrReasonNotMatch(L47-54)
const (
    Name = names.TaintToleration
    ErrReasonNotMatch = "node(s) had taints that the pod didn't tolerate"
)
// Filter(L118-132)
// 核心:调用 FindMatchingUntoleratedTaint 找无法容忍的污点
// NoSchedule/NoExecute 污点无匹配 → UnschedulableAndUnresolvable
func (pl *TaintToleration) Filter(ctx context.Context, state fwk.CycleState,
    pod *v1.Pod, nodeInfo fwk.NodeInfo) *fwk.Status {
    logger := klog.FromContext(ctx)
    node := nodeInfo.Node()
    taint, isUntolerated := v1helper.FindMatchingUntoleratedTaint(
        logger, node.Spec.Taints, pod.Spec.Tolerations,
        helper.DoNotScheduleTaintsFilterFunc(),
        pl.enableTaintTolerationComparisonOperators)
    if !isUntolerated {
        return nil
    }
    return fwk.NewStatus(fwk.UnschedulableAndUnresolvable,
        "node(s) had untolerated taint(s)")
}
// Score(L194-207)
// 无法容忍的 PreferNoSchedule 污点越多 → 分越低(越不想去)
func (pl *TaintToleration) Score(ctx context.Context, state fwk.CycleState,
    pod *v1.Pod, nodeInfo fwk.NodeInfo) (int64, *fwk.Status) {
    logger := klog.FromContext(ctx)
    node := nodeInfo.Node()
    s, err := getPreScoreState(state)
    score := int64(pl.countIntolerableTaintsPreferNoSchedule(
        logger, node.Spec.Taints, s.tolerationsPreferNoSchedule))
    return score, nil
}
# 触发 TaintToleration 的 Pod(k8s v1.36.1)
apiVersion: v1
kind: Pod
metadata:
  name: taint-demo
spec:
  tolerations:
  - key: "dedicated"
    operator: "Equal"
    value: "true"
    effect: "NoSchedule"
  # 关键业务:对 not-ready/unreachable 延迟 5 分钟驱逐
  - key: "node.kubernetes.io/not-ready"
    operator: "Exists"
    effect: "NoExecute"
    tolerationSeconds: 300
  - key: "node.kubernetes.io/unreachable"
    operator: "Exists"
    effect: "NoExecute"
    tolerationSeconds: 300

3.4 InterPodAffinity

核心职责:根据 pod.spec.affinity.podAffinity(亲和)和 podAntiAffinity(反亲和)判断 Pod 之间的关系。硬亲和/反亲和在 Filter 阶段强制约束,软打分在 Score 阶段做偏好评估。

pkg/scheduler/framework/plugins/interpodaffinity/ (k8s v1.36.1)

// InterPodAffinity struct(plugin.go L46-53)
type InterPodAffinity struct {
    parallelizer              fwk.Parallelizer
    args                     config.InterPodAffinityArgs
    sharedLister             fwk.SharedLister    // 访问所有 Pod / Node
    nsLister                 listersv1.NamespaceLister
    enableSchedulingQueueHint bool
}
// Filter(filtering.go L410-432)
// 三层判断:Pod 亲和 -> Pod 反亲和 -> 已有 Pod 反亲和
// 任一层不满足 → 返回 Unschedulable 或 UnschedulableAndUnresolvable
func (pl *InterPodAffinity) Filter(ctx context.Context, cycleState fwk.CycleState,
    pod *v1.Pod, nodeInfo fwk.NodeInfo) *fwk.Status {
    state, err := getPreFilterState(cycleState)
    if !satisfyPodAffinity(state, nodeInfo) {
        return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, ErrReasonAffinityRulesNotMatch)
    }
    if !satisfyPodAntiAffinity(state, nodeInfo) {
        return fwk.NewStatus(fwk.Unschedulable, ErrReasonAntiAffinityRulesNotMatch)
    }
    if !satisfyExistingPodsAntiAffinity(state, nodeInfo) {
        return fwk.NewStatus(fwk.Unschedulable, ErrReasonExistingAntiAffinityRulesNotMatch)
    }
    return nil
}
// Score(scoring.go L236-255)
// 统计节点拓扑域内匹配的 Pod 数,越少分越高(反亲和)或越高(亲和)
func (pl *InterPodAffinity) Score(ctx context.Context, cycleState fwk.CycleState,
    pod *v1.Pod, nodeInfo fwk.NodeInfo) (int64, *fwk.Status) {
    node := nodeInfo.Node()
    s, err := getPreScoreState(cycleState)
    var score int64
    for tpKey, tpValues := range s.topologyScore {
        if v, exist := node.Labels[tpKey]; exist {
            score += tpValues[v]
        }
    }
    return score, nil
}
# 触发 InterPodAffinity 的 Pod(k8s v1.36.1)
# web 和 redis 必须分布在不同 zone(反亲和)
apiVersion: v1
kind: Pod
metadata:
  name: web-pod
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app: redis-cache
        topologyKey: topology.kubernetes.io/zone
    podAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchLabels:
              app: web-frontend
          topologyKey: topology.kubernetes.io/zone

3.5 PodTopologySpread

核心职责:解析 pod.spec.topologySpreadConstraints,让同一组 Pod 按 maxSkew 均匀分布在 zone / node 等拓扑域里。v1.18 引入,替代手动写反亲和规则。对没有声明约束的 Pod,会自动注入 system-defaulted 约束(v1.27+)。

pkg/scheduler/framework/plugins/podtopologyspread/plugin.go (k8s v1.36.1)

// PodTopologySpread struct(L59-73)
type PodTopologySpread struct {
    systemDefaulted         bool   // 是否为 system 自动注入的约束
    defaultConstraints    []v1.TopologySpreadConstraint
    sharedLister          fwk.SharedLister
    services              corelisters.ServiceLister
    replicaSets           appslisters.ReplicaSetLister
    statefulSets          appslisters.StatefulSetLister
    enableMatchLabelKeysInPodTopologySpread bool
    enableSchedulingQueueHint                 bool
    enableTaintTolerationComparisonOperators  bool
}

// Filter:skew = count(匹配Pod) - min(所有拓扑域匹配数)
// 超过 maxSkew → UnschedulableAndUnresolvable

// Score:
//   scoreForCount(cnt, maxSkew, tpWeight) = cnt * tpWeight + (maxSkew - 1)
//   匹配 Pod 越少 → skew 越小 → 分越高

# PodTopologySpread skew 计算示例 假设 3 个 zone,各有 Pod 数: zone-a: 5 个 Pod ← skew = 5 - 2 = 3 ← 超过 maxSkew zone-b: 2 个 Pod ← skew = 2 - 2 = 0 ← OK zone-c: 2 个 Pod ← skew = 2 - 2 = 0 ← OK 若 maxSkew = 2: → zone-a 超过 maxSkew,新 Pod 无法调度到 zone-a → 只能调度到 zone-b 或 zone-c 若 whenUnsatisfiable = ScheduleAnyway: → 允许调度到 zone-a,但记录拓扑违规

# 触发 PodTopologySpread 的 Pod(k8s v1.36.1)
# 让 demo-app 副本均匀分布在 zone 级别,maxSkew=1
apiVersion: v1
kind: Pod
metadata:
  name: topology-spread-demo
spec:
  topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule  # 硬约束
    labelSelector:
      matchLabels:
        app: demo-app
  - maxSkew: 2
    topologyKey: topology.kubernetes.io/hostname
    whenUnsatisfiable: ScheduleAnyway  # 软约束
    labelSelector:
      matchLabels:
        app: demo-app

3.6 VolumeBinding(跨四阶段)

核心职责:唯一横跨四个调度阶段的内置插件。解决"并发调度时 PV 被两个 Pod 同时抢占"的并发安全问题。核心机制是 AssumeCache:PreFilter 阶段"先假设能绑",Reserve 占位,PreBind 提交真实绑定,冲突时 RevertAssumedPodVolumes 回退。

pkg/scheduler/framework/plugins/volumebinding/ (k8s v1.36.1)

// VolumeBinding struct(volume_binding.go L70-79)
type VolumeBinding struct {
    Binder      SchedulerVolumeBinder  // 核心绑定器接口
    PVCLister   corelisters.PersistentVolumeClaimLister
    classLister storagelisters.StorageClassLister
    scorer      volumeCapacityScorer   // 容量打分器(alpha)
    fts         feature.Features
}
// SchedulerVolumeBinder 接口(binder.go L153-196)
type SchedulerVolumeBinder interface {
    // 获取 Pod 所有 PVC(已绑定 / 延迟绑定 / 立即绑定)
    GetPodVolumeClaims(pod *v1.Pod) (*PodVolumeClaims, error)
    // 查找匹配的 PV,检查节点亲和/容量/拓扑
    FindPodVolumes(pod, node) (*PodVolumes, ConflictReasons, error)
    // PreFilter 阶段:假设能绑,提前写入 AssumeCache
    AssumePodVolumes(assumedPod, nodeName, volumes) (allFullyBound bool, err error)
    // 绑定失败时回退
    RevertAssumedPodVolumes(volumes)
    // PreBind 阶段:提交真实绑定到 etcd
    BindPodVolumes(ctx, assumedPod, volumes) error
}
// Filter(volume_binding.go L409-453)
// 调用 FindPodVolumes 检查 PV 节点亲和/容量/拓扑匹配
// 匹配失败 → UnschedulableAndUnresolvable(无任何节点可满足)
func (pl *VolumeBinding) Filter(ctx context.Context, cs fwk.CycleState,
    pod *v1.Pod, nodeInfo fwk.NodeInfo) *fwk.Status {
    logger := klog.FromContext(ctx)
    node := nodeInfo.Node()
    podVolumes, reasons, err := pl.Binder.FindPodVolumes(
        logger, pod, state.podVolumeClaims, node)
    if len(reasons) > 0 {
        status := fwk.NewStatus(fwk.UnschedulableAndUnresolvable)
        for _, reason := range reasons {
            status.AppendReason(string(reason))
        }
        return status
    }
    state.Lock()
    state.podVolumesByNode[node.Name] = podVolumes
    state.Unlock()
    return nil
}
// Score(volume_binding.go L470-523)
// 节点指定 StorageClass 剩余容量越多 → 分越高
// 需要 --feature-gates=StorageCapacityScoring=true(alpha)
func (pl *VolumeBinding) Score(...) (int64, *fwk.Status) {
    if pl.scorer == nil { return 0, nil }
    // 按 StorageClass 分组,计算剩余容量
    // broken linear function:剩余越多分越高
    return pl.scorer(classResources), nil
}

● AssumeCache 并发安全机制

PreFilter 阶段调用 AssumePodVolumes 把 PVC 信息临时写入 AssumeCache。Reserve 阶段占住节点,PreBind 阶段提交真实绑定。如果另一个 Pod 在此期间已经真正绑定了这个 PV,PreBind 阶段会发现冲突并调用 RevertAssumedPodVolumes 回退。这就是 VolumeBinding 要跨越四个阶段的原因——解决"并发调度时 PV 抢用"的 race condition。

# 触发 VolumeBinding 的 Pod(k8s v1.36.1)
apiVersion: v1
kind: Pod
metadata:
  name: volume-demo
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: data
      mountPath: /usr/share/nginx/html
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: my-pvc

---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: pd.csi.storage.gke.io
parameters:
  type: pd-ssd
volumeBindingMode: WaitForFirstConsumer  # 关键:延迟绑定
allowVolumeExpansion: true

四、插件对比总结表

插件Filter 职责Score 职责跨越阶段核心数据结构
NodeResourcesFit CPU/内存/GPU/临时存储是否足够 LeastAllocated 最闲优先(默认) Filter / Score resourceAllocationScorer
NodeAffinity nodeSelector + nodeAffinity required 硬匹配 preferred 软偏好打分 Filter / Score nodeaffinity.NodeSelector
TaintToleration NoSchedule/NoExecute 污点容忍判断 PreferNoSchedule 污点越多分越低 Filter / Score Toleration[] vs NodeSpec.Taints[]
InterPodAffinity 硬亲和/反亲和拓扑域匹配 软亲和/反亲和加权打分 Filter / Score WeightedPodAffinityTerm[]
PodTopologySpread 拓扑域 skew 是否超过 maxSkew 匹配 Pod 数越少分越高 Filter / Score TopologySpreadConstraint[]
VolumeBinding PV 节点亲和/容量/拓扑匹配 StorageClass 剩余容量打分(alpha) PreFilter / Filter / Reserve / PreBind SchedulerVolumeBinder

五、生产踩坑实录

坑一:NodeResourcesFit 漏算了 Pod 级 resources(生产高危)

● 问题现象

Pod 申请了 GPU 资源(nvidia.com/gpu: 1),节点确认有 GPU,但调度反复报 Insufficient nvidia.com/gpu,Pod 始终 Pending。

根因:v1.26 之前 NodeResourcesFit 读取的是 container[].resources.limits 计算资源;v1.26 起改为读取 container[].resources.requests。如果 Pod 只写了 limits 没写 requests,v1.26+ 的行为与之前不一致。

排查命令:

# 查看节点真实 Allocatable
kubectl describe node <node-name> | grep -A5 "Allocated resources"

# 查看 Pod 实际 requests/limits
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].resources}'

# 开启调度器详细日志
kubectl get pod <pod-name> -n kube-system -o jsonpath='{.spec.nodeName}' 2>/dev/null || echo "未调度"

修复:

containers:
- name: gpu-worker
  resources:
    requests:
      nvidia.com/gpu: "1"   # v1.26+ 必须显式声明
    limits:
      nvidia.com/gpu: "1"

坑二:NodeAffinity required 条件与节点 label 同步延迟

● 问题现象

Pod 的 nodeSelector 写了 node-type: general,节点确认打上了 label,但调度一直失败。

根因:调度器通过 node informer 同步节点 label。如果 Pod 提交后、调度前 label 刚更新但 informer 还未同步完成,调度器看到的是旧数据。Filter 阶段匹配失败。

修复:使用 preferredDuringSchedulingIgnoredDuringExecution 替代硬匹配,或确保 Pod 提交前 label 已存在。

坑三:InterPodAffinity 硬亲和导致死锁

● 问题现象

两个 Deployment 互为 required 硬亲和,结果双方各有部分 Pod 调度成功,但始终有部分 Pod 一直 Pending。

根因:requiredDuringSchedulingIgnoredDuringExecution 的硬亲和在 Filter 阶段强制要求拓扑域内已有匹配 Pod。初始部署时双方 Pod 互相依赖,出现"鸡生蛋蛋生鸡"的死锁。

修复:将 required 改为 preferred,降低初始部署阶段的耦合度。

坑四:PodTopologySpread 隐式注入 defaultConstraints

● 问题现象

Pod 没有写任何 topologySpreadConstraints,但调度器仍然给它应用了 zone 打散约束,导致 Pod 被调度到不符合业务预期的 zone。

根因:v1.27+ 中,当 Pod 不声明约束且 SchedulerConfiguration 的 defaultingType = SystemDefaulting 时,PodTopologySpread 自动注入 hostname skew=3 和 zone skew=5 两个 system-defaulted 约束。

修复:显式声明自己的约束覆盖默认行为,或在 SchedulerConfiguration 中把 defaultingType 设为 CustomDefaulting 并清空 defaultConstraints。

坑五:VolumeBinding AssumeCache 导致的调度死循环

● 问题现象

使用 WaitForFirstConsumer PVC 的 Pod 反复调度、始终不绑定,卡在 Pending。

根因:VolumeBinding 在 PreFilter 阶段假设 PVC 能绑定。如果 StorageClass 是 WaitForFirstConsumer,真正绑定要等 Pod 调度到节点后才触发。假设期间节点信息变化(污点/label 更新),AssumeCache 里的假设失效,导致调度循环。

修复:确保 Pod 的 nodeSelector 或亲和性先选定候选节点,再让 PVC 在该节点附近绑定。

六、FAQ 高频答疑(分类版)

6.1 基础概念(5 题)

Q1: Filter 和 Score 的本质区别是什么?

Answer:Filter 是"行 / 不行"的二元判断,返回 Unschedulable 或 nil。任何 Filter 返回 UnschedulableAndUnresolvable 时调度立即短路,不再调用后续 Filter 和所有 Score。Score 是"好 / 更好"的打分,返回 0-MaxNodeScore 的整数值,所有 Score 分数加权求和后决定节点最终排名。Filter 是硬约束,Score 是软偏好。


Q2: Unschedulable 和 UnschedulableAndUnresolvable 的差别?

Answer:Unschedulable 表示当前节点不行,但其他节点可能行(如资源不够,扩容节点就能解决),调度器会等事件触发重试。UnschedulableAndUnresolvable 表示不仅当前节点不行,任何节点都不行(如 PVC 不存在),调度器直接让 Pod 进入 Unschedulable 队列,不做无意义的重试。


Q3: EventsToRegister 和调度队列重试是什么关系?

Answer:每个插件在 EventsToRegister 中声明自己关心哪些 Kubernetes 事件。当这些事件发生时(如节点 label 更新、Pod 删除、PV 绑定完成),调度器的 SchedulingQueue 把等待中的 Pod 重新入队触发重试。如果没有注册对应事件,即使资源释放了 Pod 也不会被重新调度。


Q4: 为什么 VolumeBinding 是唯一跨越四个阶段的内置插件?

Answer:因为它需要解决并发调度时的 PV 抢用问题。PreFilter 假设能绑(AssumePodVolumes),Reserve 占住节点,PreBind 提交真实绑定。如果另一个 Pod 已经绑定了这个 PV,PreBind 阶段会发现冲突并 RevertAssumedPodVolumes 回退。这个两阶段设计(假设 + 确认)是并发安全的核心。


Q5: NodeResourcesFit 的四个 Score 策略分别适合什么场景?

Answer:LeastAllocated(默认):最闲优先,适合通用计算,让资源均匀分布。MostAllocated:最忙优先,适合批量回收,把碎片 Pod 往已满节点集中。BalancedAllocation:CPU/内存均衡,适合计算密集型。RequestedToCapacityRatio:自定义每种资源权重,适合 GPU/FPGA 异构集群。

6.2 源码原理(8 题)

Q6: PodTopologySpread 的 skew 公式是什么?

Answer:skew = count(当前拓扑域匹配 Pod) - min(所有拓扑域匹配 Pod)。例如 zone-a 有 5 个 Pod,zone-b 有 2 个,zone-c 有 2 个,则 zone-a 的 skew=5-2=3。如果 maxSkew=2,zone-a 超过约束,新 Pod 无法调度到 zone-a。


Q7: TaintToleration 的 tolerationSeconds 只对哪种 effect 有效?

Answer:只对 NoExecute effect 有效。如果节点被打上 NoExecute 污点,有 tolerationSeconds: 300 的 Pod 会等 300 秒后再驱逐(给节点恢复或 Pod 迁移留出缓冲)。NoSchedulePreferNoSchedule 不支持此字段。


Q8: NodeAffinity 的 addedNodeSelector 和 addedPrefSchedTerms 是什么?

Answer:这两个字段用于 SchedulerConfiguration 级别的 NodeAffinity 扩展。可以在配置中为所有 Pod 统一追加节点选择器和优先调度条件,让所有 Pod 自动继承额外的亲和性规则,而不需要每个 Pod 单独配置。


Q9: InterPodAffinity 的 Filter 阶段具体计算什么?

Answer:对硬亲和(requiredDuringSchedulingIgnoredDuringExecution)的每个 topologyTerm,统计当前节点所在拓扑域内已运行 Pod 的加权匹配数。Filter 阶段分三层判断:satisfyPodAffinity、satisfyPodAntiAffinity、satisfyExistingPodsAntiAffinity,任一层不满足就过滤。软打分(preferred)不参与 Filter。


Q10: VolumeBinding 的 SchedulerVolumeBinder 接口有哪些核心方法?

Answer:五个核心方法:GetPodVolumeClaims 获取 Pod 所需 PVC;FindPodVolumes 查找匹配的 PV 并返回冲突原因;AssumePodVolumes 预占 PV(AssumeCache);RevertAssumedPodVolumes 绑定失败时回退;BindPodVolumes 在 PreBind 阶段提交真实绑定到 etcd。


Q11: enableSchedulingQueueHint(调度队列提示)功能是什么?

Answer:v1.28+ beta 特性。SchedulingQueueHint 利用插件注册的 QueueingHintFn 智能判断"某个事件发生后,Pod 是否真的需要重新调度"。避免每次节点 label 变化都盲目重试所有 Pending Pod。这能显著减少无效调度循环,降低调度延迟和 API server 压力。


Q12: PodTopologySpread 的 systemDefaulted 字段何时为 true?

Answer:当 Pod 没有声明任何 topologySpreadConstraints 且 SchedulerConfiguration 的 defaultingType = SystemDefaulting(v1.27+ 默认)时,PodTopologySpread 自动注入 hostname skew=3 和 zone skew=5 两个 system-defaulted 约束。不想自动注入可设为 CustomDefaulting 并清空 defaultConstraints。


Q13: VolumeBinding 的 Score 策略 volumeCapacityScoring 是什么?

Answer:VolumeBinding 的 Score 目标不是节点资源,而是指定 StorageClass 的剩余卷容量。采用 broken linear function 公式,节点剩余容量越多得分越高。需要 --feature-gates=StorageCapacityScoring=true 开启(v1.36.1 中为 alpha)。对多 StorageClass 集群很有用。

6.3 生产实践(9 题)

Q14: 如何确认某个 Pod 最终由哪个插件决定了调度失败?

Answer:kubectl describe pod <pod-name> 的 Events 会按时间顺序列出调度失败原因(如 0/3 nodes are available: 1 node(s) had taints..., 2 node(s) resource fit failed)。开启调度器详细日志 --v=5,每个 Filter 插件的通过/失败都有记录。


Q15: 如何禁用某个内置插件而不影响其他插件?

Answer:在 SchedulerConfiguration 的 profiles[].plugins.multiPoint.disabled 中禁用指定插件名:disabledPlugin: - TaintToleration。禁用后该插件在所有 Profile 所有扩展点都不会被调用。如果只想禁用 Score 而保留 Filter,需要在 Score 扩展点单独配置。


Q16: InterPodAffinity 和 PodTopologySpread 都能做 Pod 打散,区别是什么?

Answer:InterPodAffinity 通过 pod.spec.affinity.podAntiAffinity 做两两 Pod 之间的精确控制,适合"Pod A 必须和 Pod B 在一起/分开"的场景。PodTopologySpread 是 v1.18 引入的声明式拓扑打散,通过 topologySpreadConstraints 自动计算全局最优分布,适合"让 N 个副本均匀散开"的场景。两者可以叠加使用。


Q17: 如何解决 InterPodAffinity 硬亲和导致的新建 Deployment 死锁?

Answer:将 requiredDuringSchedulingIgnoredDuringExecution 改为 preferredDuringSchedulingIgnoredDuringExecution。初始部署阶段使用软约束降低耦合度,Pod 调度成功后自然形成拓扑分布。如果必须用硬亲和,先单独部署一方到目标节点,再部署依赖方。


Q18: VolumeBinding 的 WaitForFirstConsumer 和 Immediate 模式分别适合什么场景?

Answer:Immediate(默认):PVC 创建时立即绑定 PV,适合已知 Pod 会调度到哪个节点的场景。WaitForFirstConsumer:等第一个 Pod 调度到节点后才绑定 PV,让 PV 优先选择 Pod 所在节点或拓扑域,适合云盘等有区域限制的存储类型,可减少跨区流量。


Q19: 六个插件的执行顺序是怎么确定的?

Answer:Filter 阶段按 SchedulerConfiguration/profiles[].plugins.filter.enabled 数组顺序执行(默认按注册顺序)。Score 阶段先并行执行所有 Score 插件,再用 NormalizeScore 归一化到 [0, MaxNodeScore] 范围,最后加权求和。插件执行顺序不影响最终结果,只影响中间短路时机。


Q20: NodeResourcesFit 中 enablePodLevelResources 和 enableInPlacePodVerticalScaling 的区别?

Answer:enablePodLevelResources(v1.27+)允许在 Pod 级而非容器级声明资源请求,多容器 Pod 的所有容器 requests 会被累加计算。enableInPlacePodVerticalScaling(v1.27+)支持 Pod 在运行中动态调整容器 resource.requests 而不需要重建容器,结合 InPlacePodVerticalScaling feature gate 使用。


Q21: TaintToleration 的 enableTaintTolerationComparisonOperators 参数有什么用?

Answer:默认情况下 toleration 的 key 和 taint 的 key 必须完全匹配。启用此参数后支持 Exists 操作符:operator: Exists 且不指定 value 表示"容忍所有带此 key 的污点"。例如 tolerations: - operator: Exists 可以容忍所有 NoExecute 污点。


Q22: Pod 调度失败时,SchedulingQueue 的 Unschedulable 队列和 BackoffQ 队列有什么区别?

Answer:Unschedulable 队列存放调度失败的 Pod,等待触发事件重新入队(如节点 label 变化、Pod 删除)。BackoffQ 是退避队列,对反复调度失败的 Pod 做指数退避等待,减少无效重试对 API server 的压力。当有 BackoffPods 或 UnschedulablePods 时,可用 kubectl drain 或调整 Pod 优先级让它优先重试。