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

推荐订阅源

Engineering at Meta
Engineering at Meta
酷 壳 – CoolShell
酷 壳 – CoolShell
Forbes - Security
Forbes - Security
S
Schneier on Security
Cyberwarzone
Cyberwarzone
L
Lohrmann on Cybersecurity
NISL@THU
NISL@THU
T
Tenable Blog
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
Schneier on Security
Schneier on Security
K
Kaspersky official blog
AI
AI
Help Net Security
Help Net Security
Hacker News: Ask HN
Hacker News: Ask HN
Security Archives - TechRepublic
Security Archives - TechRepublic
T
Threatpost
Attack and Defense Labs
Attack and Defense Labs
I
Intezer
The Cloudflare Blog
博客园 - 叶小钗
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Blog — PlanetScale
Blog — PlanetScale
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
IT之家
IT之家
Recent Announcements
Recent Announcements
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
aimingoo的专栏
aimingoo的专栏
The GitHub Blog
The GitHub Blog
宝玉的分享
宝玉的分享
T
The Blog of Author Tim Ferriss
Y
Y Combinator Blog
O
OpenAI News
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
Microsoft Security Blog
Microsoft Security Blog
B
Blog
Jina AI
Jina AI
Hugging Face - Blog
Hugging Face - Blog
D
Docker
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
阮一峰的网络日志
阮一峰的网络日志
雷峰网
雷峰网
H
Hackread – Cybersecurity News, Data Breaches, AI and More
G
Google Developers Blog

博客园 - 左扬

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(调度专题 · 二):内置插件逐个精读 — NodeResourcesFit / NodeAffinity / TaintToleration / PodTopologySpread / VolumeBinding / InterPodAffinity Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):调度器内置插件 逐个精读 k8s 源码级精讲(二十六):调度器内置插件逐个精读 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友好)
Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 四):抢占(Preemption)算法剖析 —— DefaultPreemption 如何选 victim、PodDisruptionBudget 如何约束
左扬 · 2026-06-20 · via 博客园 - 左扬

Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 四):抢占(Preemption)算法剖析 —— DefaultPreemption 如何选 victim、PodDisruptionBudget 如何约束

上一篇调度专题【左扬精讲】拆解了 Scheduling Framework 的六大内置插件,并预告了本篇主线:当所有 Filter 阶段都失败、Score 也找不到合适节点时,kube-scheduler 怎么把已经在跑的低优先级 Pod "挪走",给待调度 Pod 腾位置?这个机制叫做抢占(Preemption)

本篇的核心问题是:当一个高优先级 Pod 调度不上时,调度器会进入 PostFilter 扩展点,调用 DefaultPreemption 插件(以下简称 DP)。DP 并不是简单地把节点上的"低优先级" Pod 全部杀光——它会先在候选节点找最少必要的 victim,且必须考虑 PodDisruptionBudget(以下简称 PDB)的保护性约束。本篇以 k8s v1.36.1 源码为蓝本,彻底讲透这两层逻辑。

读完本篇,你应该能回答三个问题:DefaultPreemption 怎么被触发?它怎么选节点?怎么在候选节点上选 victim,而 PodDisruptionBudget 又如何介入?

Kubernetes Scheduler Preemption DefaultPreemption PodDisruptionBudget Go k8s v1.36.1

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

重点掌握(必须)

  • Preemption 的 6 步主干流程Evaluator.Preemptpkg/scheduler/framework/preemption/preemption.go:103)的0 取最新 Pod → 1 资格检查 → 2 找候选节点 → 3 Extender 过滤 → 4 选最优候选 → 5 执行驱逐
  • DefaultPreemption 插件定义与触发点PostFilterPlugin / PreEnqueuePlugin 双接口(pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go:89-90
  • victim 选择算法SelectVictimsOnNodedefault_preemption.go:251)的"先全删后回填"贪心 + filterPodsWithPDBViolationdefault_preemption.go:414)的 PDB 分组
  • PDB 如何约束 victim 选择:PDB 不直接阻止抢占,而是把候选分成"违反 PDB 的"和"未违反 PDB 的"两组,调度器会优先保护后者

次重点(了解即可)

  • 异步抢占 EnableAsyncPreemption:v1.36.1 起,通过 PreEnqueue 阻止 Pod 在抢占未完成前重入
  • 工作负载感知抢占 EnableWorkloadAwarePreemptionPodGroupPostFilterdefault_preemption.go:468)接管 PodGroup 整体抢占
  • MinCandidateNodesPercentage / MinCandidateNodesAbsolute:FindCandidates 的"抽样"策略(default_preemption.go:218-227

文章目录

一、Why:为什么需要 Preemption

k8s 是一个多租户共享集群的系统。当一个高优先级 Pod 调度不上时,调度器有三种选择:

  • 拒绝并 PendingFailedScheduling → Pod 卡在unschedulable 状态 → 业务炸裂
  • "先来后到"排队:等现有 Pod 死亡再调度 → 等待时间不可控,业务不答应
  • 抢占(Preemption):杀低优先级 Pod 腾位置 → 高优先级 Pod 立刻有地方跑

Preemption 是 k8s 在QoS资源利用率之间取平衡的核心机制:日常让低优先级 Pod 跑满资源(binpack 提利用率),关键时刻"以小换大"(高优先级驱逐低优先级)。

设计精髓

抢占不是"无脑驱逐",而是一套带约束的贪心:① 抢占者(preemptor)必须 priority 高于被驱逐者(victim) ② victim 集合最小化(少杀多得) ③ 必须尊重 PDB(不能为了驱逐而破坏业务 SLO)。这三条约束保证了抢占"有序、可控、可解释"。

二、What:Preemption 在调度框架里的位置

调度框架里有两个扩展点与抢占直接相关:PostFilterPreEnqueue。DefaultPreemption 同时实现这两个接口(default_preemption.go:89-90):

// pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go (行 89-95, k8s v1.36.1)
// DefaultPreemption 是 PostFilter 扩展点实现:当所有 Filter 失败后被调用
// 同时是 PreEnqueue 扩展点:阻止一个 Pod 在抢占未完成前被重新入队
var _ fwk.PostFilterPlugin = &DefaultPreemption{}
var _ fwk.PreEnqueuePlugin = &DefaultPreemption{}
扩展点方法何时被调用
PostFilter PostFilter(ctx, state, pod, m) 调度周期内,所有 Filter 插件都失败时,调度器调用 PostFilter 扩展点上的所有插件。DefaultPreemption 是唯一内置实现
PreEnqueue PreEnqueue(ctx, pod) Pod 进入 activeQ 之前,用来门控。当 EnableAsyncPreemption 开启时,正在异步抢占的 Pod 会被 PreEnqueue 拒绝

抢占的总入口在 pkg/scheduler/framework/preemption/preemption.go:103Evaluator.Preempt。DefaultPreemption 插件的 PostFilterdefault_preemption.go:135)只是薄薄一层,把活儿转交给 pl.Evaluator.Preempt

注意

PostFilter 是可选扩展点:只有 UnschedulableUnschedulableAndUnresolvable 状态触发。DefaultPreemption 内部把 PostFilter 写得相当克制:5 行 if 处理 PodGroup 特例(default_preemption.go:140-155),其余全部委托给 Evaluator。

三、How:Preemption 主干流程 6 步

Evaluator.Preemptpreemption.go:103-170)是抢占的"大脑"。它严格按 6 步执行:

┌──────────────────────────────────────────────────────────────────────┐
│  Evaluator.Preempt(ctx, state, pod, m)  preemption.go:103             │
│                                                                      │
│  0) 取最新 Pod  ──── PodLister 拿 preemptor 的最新对象(行 110-115)  │
│      │                                                                │
│      ▼                                                                │
│  1) 资格检查   ──── PodEligibleToPreemptOthers()(行 117-122)         │
│      │            ├─ preemptionPolicy=Never  → 拒绝                    │
│      │            └─ nominatedNode 上有 terminating victim → 拒绝     │
│      ▼                                                                │
│  2) 找候选节点 ──── findCandidates()(行 124-148)                     │
│      │            ├─ 随机 offset + N 个候选                            │
│      │            ├─ 调 SelectVictimsOnNode 模拟抢占                    │
│      │            └─ 过滤出"能装下 preemptor"的节点                     │
│      ▼                                                                │
│  3) Extender  ──── callExtenders()(行 150-154)                       │
│      │           把候选节点给外部 Extender 再过滤一次                  │
│      ▼                                                                │
│  4) 选最优候选  ──── SelectCandidate()(行 156-162)                    │
│      │           按"违反 PDB 最少"的策略挑节点                          │
│      ▼                                                                │
│  5) 执行驱逐   ──── executor.actuatePodPreemption()(行 164-167)      │
│                  调 apiserver Evict 接口删除 victim Pod                 │
└──────────────────────────────────────────────────────────────────────┘

每一步都有可观测的返回值和日志:

步骤关键函数源码行号失败/空结果语义
0 取最新 Pod PodLister.Get preemption.go:110-115 apiserver 错误,AsStatus 透传
1 资格检查 PodEligibleToPreemptOthers default_preemption.go:363 返回 Unschedulable,Pod 进 unschedulableQ 等待
2 找候选节点 findCandidates preemption.go:174 返回 Unschedulable + Preemption is not helpful
3 Extender callExtenders preemption.go:151 透传 extender 返回的错误
4 选最优候选 SelectCandidate preemption.go:157 返回 no candidate node for preemption
5 执行驱逐 actuatePodPreemption executor.go 驱逐失败,Unschedulable

小贴士关于"非必要不抢"

findCandidates 返回空集合时,调度器不会杀任何 Pod,而是直接给 preemptor 打上 Unschedulable 状态(preemption.go:147)。这种"preemption is not helpful"的诊断对应一个经典的运维难题:明明 priority 已经最高,为什么还在 Pending?答案往往是没有"可被驱逐"的目标——所有 victim 的 priority 都不低于 preemptor。

四、Detail:DefaultPreemption 选 victim 详解

SelectVictimsOnNodedefault_preemption.go:251-353)是 victim 选择的核心算法。它有 4 个关键步骤:

4.1 收集 potentialVictims —— "全删假设"

第一阶段对节点上所有 Pod 做资格检查:

// pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go (行 277-293, k8s v1.36.1)
// As the first step, remove all pods eligible for preemption from the node and
// check if the given pod can be scheduled without them present.
for _, pi := range nodeInfo.GetPods() {
    if pl.isPreemptionAllowed(nodeInfo, pi, pod) {
        potentialVictims = append(potentialVictims, pi)
    }
}
for _, pi := range potentialVictims {
    if err := removePod(pi); err != nil {  // 模拟删除
        return nil, 0, fwk.AsStatus(err)
    }
}

关键函数 isPreemptionAlloweddefault_preemption.go:395-398)做了两件事:

// pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go (行 395-398, k8s v1.36.1)
func (pl *DefaultPreemption) isPreemptionAllowed(nodeInfo fwk.NodeInfo, victim fwk.PodInfo, preemptor *v1.Pod) bool {
    // The victim must have lower priority than the preemptor
    return corev1helpers.PodPriority(victim.GetPod()) < corev1helpers.PodPriority(preemptor) &&
        pl.IsEligiblePod(nodeInfo, victim, preemptor)
}
  • 优先级比较:victim.priority 必须小于 preemptor.priority,否则该 Pod 完全不可能成为 victim
  • IsEligiblePod 钩子:可由用户/Out-of-Tree 插件覆盖,默认永远返回 true

4.2 "全删后能否装下"—— 第一道门槛

第二阶段:把所有 potentialVictims 全部模拟删除后,跑一次 Filter 看看 preemptor 能否装下:

// pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go (行 295-303, k8s v1.36.1)
if status := pl.fh.RunFilterPluginsWithNominatedPods(ctx, state, pod, nodeInfo); !status.IsSuccess() {
    return nil, 0, status
}

如果删除全部 victim 后还装不下,直接放弃这个节点(selectVictimsOnNode 返回 0 候选)。这是性能优化:避免无意义的 PDB 分析。

注意

这一步在 v1.36.1 中有个已知边界default_preemption.go:297-300 的注释明确说"如果 preemptor 失败是因为对某个 victim 有 inter-pod affinity,不支持这种场景"。换句话说,不要给高优先级 Pod 配 requiredDuringSchedulingIgnoredDuringExecution 指向一个低优先级 Pod —— 这会让抢占在大多数情况下"找不到候选节点"。

4.3 PDB 分组 —— 保护性约束介入

第三阶段:把 potentialVictims 按PDB 是否会被违反分成两组:

// pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go (行 311-314, k8s v1.36.1)
// Try to reprieve as many pods as possible. We first try to reprieve the PDB
// violating victims and then other non-violating ones. In both cases, we start
// from the highest importance victims.
violatingVictims, nonViolatingVictims := filterPodsWithPDBViolation(potentialVictims, pdbs)

filterPodsWithPDBViolationdefault_preemption.go:414-465)的逻辑:

// pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go (行 414-465, k8s v1.36.1)
func filterPodsWithPDBViolation(podInfos []fwk.PodInfo, pdbs []*policy.PodDisruptionBudget) (violatingPodInfos, nonViolatingPodInfos []fwk.PodInfo) {
    pdbsAllowed := make([]int32, len(pdbs))
    for i, pdb := range pdbs {
        pdbsAllowed[i] = pdb.Status.DisruptionsAllowed
    }

    for _, podInfo := range podInfos {
        pod := podInfo.GetPod()
        pdbForPodIsViolated := false
        if len(pod.Labels) != 0 {
            for i, pdb := range pdbs {
                if pdb.Namespace != pod.Namespace { continue }
                selector, err := metav1.LabelSelectorAsSelector(pdb.Spec.Selector)
                if err != nil || selector.Empty() || !selector.Matches(labels.Set(pod.Labels)) { continue }
                // 关键:已经被记录在 DisruptedPods 里的 Pod 不算"违反"
                if _, exist := pdb.Status.DisruptedPods[pod.Name]; exist { continue }
                pdbsAllowed[i]--
                if pdbsAllowed[i] < 0 { pdbForPodIsViolated = true }
            }
        }
        if pdbForPodIsViolated { violatingPodInfos = append(violatingPodInfos, podInfo) } else { nonViolatingPodInfos = append(nonViolatingPodInfos, podInfo) }
    }
    return violatingPodInfos, nonViolatingPodInfos
}

这是 PDB 介入的关键算法:逐 Pod 模拟驱逐,每模拟驱逐一个属于 PDB 保护集的 Pod,pdbsAllowed[i] 就减 1。当 pdbsAllowed[i] < 0 时,意味着如果驱逐这个 Pod,PDB 就会被违反。

小贴士关于 DisruptedPods 字段

DisruptedPodsdefault_preemption.go:446)是 PDB status 的一个 map,记录当前正在被驱逐的 Pod。当驱逐流程进行时,apiserver 端 eviction controller 会把目标 Pod 写入这个 map。已经DisruptedPods 里的 Pod 不会被重复计数,避免对同一 Pod 重复扣减 DisruptionsAllowed

4.4 "回填"——贪心回收

第四阶段:把 victim 按重要度降序排,依次尝试回填(reprieve)到节点,看 preemptor 还能不能装下:

// pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go (行 315-329, k8s v1.36.1)
reprievePod := func(pi fwk.PodInfo) (bool, error) {
    if err := addPod(pi); err != nil { return false, err }
    status := pl.fh.RunFilterPluginsWithNominatedPods(ctx, state, pod, nodeInfo)
    fits := status.IsSuccess()
    if !fits {
        if err := removePod(pi); err != nil { return false, err }
        victims = append(victims, pi)
        logger.V(5).Info("Pod is a potential preemption victim on node", "pod", klog.KObj(pi.GetPod()), "node", klog.KObj(nodeInfo.Node()))
    }
    return fits, nil
}
// 先尝试回填"违反 PDB"的那批
for _, p := range violatingVictims { ... }
// 再尝试回填"未违反 PDB"的那批
for _, p := range nonViolatingVictims { ... }

关键设计

  • 先处理 violatingVictims(PDB 会违反的):这些 Pod 已经被杀也会破坏 PDB,那就让它们牺牲吧
  • 再处理 nonViolatingVictims(PDB 不会违反的):这批 Pod 在 PDB 保护下"安全驱逐",调度器会尽可能保留它们
  • 不能回填的 Pod 留在 victims 里返回 —— 这就是最终的 victim 集合

回填顺序按 MoreImportantPoddefault_preemption.go:86降序(先处理 priority 高、runtime 长的 Pod),保证"重要 Pod 优先存活"。

设计精髓

这个"全删 → 按序回填"的贪心算法,本质上是"最少驱逐集合"的近似解:每次回填一个不破坏可调度性的 Pod,直到"再回填一个就不行了"。由于每一步都跑 RunFilterPluginsWithNominatedPods,算法复杂度是 O(n·F)(n=potentialVictims 数量,F=Filter 插件耗时),但相比"穷举所有子集"的指数复杂度,这已经是工业界可接受的近似最优。

五、Core:PodDisruptionBudget 如何约束 victim

PDB 是 k8s 给运维/业务方的一把"保护伞":保证"任意时刻同时中断的 Pod 数量"不超过阈值。它只关心自愿中断(eviction),被抢占的 Pod 也算自愿中断。

5.1 PDB 的三要素

字段含义示例
spec.selector 匹配哪些 Pod 受保护 app=nginx
spec.minAvailable 最少保持可用数量(或百分比) 2(至少有 2 个 Pod 跑着)
status.disruptionsAllowed 当前可中断配额(由 controller 实时算) 1(还能再中断 1 个)

5.2 PDB 介入 victim 选择的全过程

PDB 在 victim 选择中是软约束,不是硬约束。它的介入体现在三处:

                ┌────────────────────────────────────────────┐
                │  PDB 介入 victim 选择的 3 个时间点           │
                ├────────────────────────────────────────────┤
                │                                            │
   ① 收集阶段 ──┼─► isPreemptionAllowed 仍然只看 priority    │
                │    (PDB 不参与:先收集所有可能被抢的 Pod)  │
                │                                            │
   ② 分组阶段 ──┼─► filterPodsWithPDBViolation 按"模拟驱逐  │
                │    是否破坏 PDB"把 Pod 切成两组              │
                │                                            │
   ③ 回填阶段 ──┼─► 先回填 violatingVictims(已破坏,救不回)│
                │    再回填 nonViolatingVictims(努力救)      │
                │    重要度降序,先救高 priority 再说          │
                └────────────────────────────────────────────┘

5.3 "违反最少"作为候选节点的排序键

在步骤 4 "选最优候选"中,调度器会优先选"违反 PDB 数最少"的节点(preemption.go:157SelectCandidate)。这就让 PDB 不只是"在节点内约束 victim",还成了"跨节点比较候选"的排序键

注意

PDB 的"保护"不是"禁止"。当一个高优先级 Pod 必须抢占且所有候选节点都会违反 PDB 时,调度器仍然会选那个违反最少的节点、仍然会驱逐它认为必要的 Pod —— 只是优先保护那些"在 PDB 保护下能存活"的 Pod。不要把 PDB 当作"绝对不能动"的硬墙 —— 它只是"尽量少动"。

5.4 源码对照:victim 选择与 PDB 交互的核心 30 行

// pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go (行 304-353, k8s v1.36.1)
var victims []fwk.PodInfo
numViolatingVictim := 0
// Sort potentialVictims by descending importance, which ensures reprieve of
// higher importance pods first.
sort.Slice(potentialVictims, func(i, j int) bool {
    return pl.MoreImportantPod(potentialVictims[i].GetPod(), potentialVictims[j].GetPod())
})
// Try to reprieve as many pods as possible. We first try to reprieve the PDB
// violating victims and then other non-violating ones. In both cases, we start
// from the highest importance victims.
violatingVictims, nonViolatingVictims := filterPodsWithPDBViolation(potentialVictims, pdbs)
reprievePod := func(pi fwk.PodInfo) (bool, error) {
    if err := addPod(pi); err != nil { return false, err }
    status := pl.fh.RunFilterPluginsWithNominatedPods(ctx, state, pod, nodeInfo)
    fits := status.IsSuccess()
    if !fits {
        if err := removePod(pi); err != nil { return false, err }
        victims = append(victims, pi)
        logger.V(5).Info("Pod is a potential preemption victim on node", "pod", klog.KObj(pi.GetPod()), "node", klog.KObj(nodeInfo.Node()))
    }
    return fits, nil
}
for _, p := range violatingVictims {
    if fits, err := reprievePod(p); err != nil { return nil, 0, fwk.AsStatus(err) } else if !fits { numViolatingVictim++ }
}
for _, p := range nonViolatingVictims {
    if _, err := reprievePod(p); err != nil { return nil, 0, fwk.AsStatus(err) }
}

代码结构很清晰:

  • 第 308-310 行:按 MoreImportantPod 降序排序(priority 高的、runtime 长的排前面)
  • 第 314 行:filterPodsWithPDBViolation 把 potentialVictims 二分
  • 第 330-336 行:先回填 violatingVictims —— 反正"已经要违反",能救就救
  • 第 338-342 行:再回填 nonViolatingVictims —— 这批"在 PDB 保护下",要尽量保护
  • 第 345-347 行:最终 victim 列表再按重要度降序排一次,保证驱逐顺序合理

设计精髓

注意第 345 行的二次排序:v1.36.1 在合并 violating 与 nonViolating 两组后,会再排一次降序,避免"violating 排前、nonViolating 排后"导致实际驱逐顺序不符合"重要度优先"。这种"先分组、再合并、最后重排"是经典的归并排序思想,工程实现值得借鉴。

六、Compare:6 步流程与 victim 选择

把 6 步流程和 victim 选择摆到一张表里,看清每一步调用什么:

Preempt 步骤关键函数行号victim 视角
0 取最新 Pod PodLister.Get preemption.go:110 -
1 资格检查 PodEligibleToPreemptOthers default_preemption.go:363 检查 nominatedNode 上有 terminating victim
2 找候选节点 findCandidates preemption.go:174 对每个候选节点调 SelectVictimsOnNode
3 Extender 过滤 callExtenders preemption.go:151 外部 hook,可拒绝候选节点
4 选最优候选 SelectCandidate preemption.go:157 按"违反 PDB 数"升序
5 执行驱逐 actuatePodPreemption executor.go victims 顺序调 apiserver Evict

Victim 选择只发生在第 2 步,但它的结果(numViolatingVictim)会被第 4 步用作排序键。也就是说:

  • 节点内filterPodsWithPDBViolation 控制 victim 集合
  • 节点间numViolatingVictim 控制选哪个节点

小贴士关于异步抢占

v1.36.1 起,EnableAsyncPreemption 特性门控(默认开启)开启时,actuatePodPreemption 不再同步等 apiserver Evict 返回,而是异步触发驱逐。Pod 进入 preempting 集合(executor.go:82-86),PreEnqueue 会拒绝该 Pod 重新入队(default_preemption.go:182-185),直到 victim 真正死亡、Pod 调度成功。

七、Pitfall:踩坑实录

下面 3 个坑都是生产真实碰到过的,按发生概率从高到低排列。

P1. 高优先级 Pod 卡 Pending:所有 victim priority 都 >= preemptor

症状:业务给核心 Pod 配了 priorityClassName: system-cluster-critical,但仍卡 Pending 状态,describe 显示 Preemption is not helpful for scheduling

原因:所有目标节点上的 Pod 都已经用了 system-cluster-critical 或更高 priority,isPreemptionAlloweddefault_preemption.go:395-398)全部返回 false,potentialVictims 为空,findCandidates 找不到任何候选节点。

修复

  • 检查 kubectl get pods -A -o custom-columns=NAME:.metadata.name,PRIORITY:.spec.priorityClassName | sort
  • 把核心 Pod 升到 system-node-critical(v1.36.1 中最高)
  • 或给低优先级 Pod 显式降级(如 priorityClassName: low-priority,value=0)

P2. PDB 设得太死导致抢占"找不到节点"

症状:某业务 Deployment 配了 minAvailable: 100%,所有节点上的同标签 Pod 都受 PDB 保护。当高优先级 Pod 调度时,filterPodsWithPDBViolation 几乎全部归类为 violatingVictimsSelectCandidate 选不到"违反最少"的节点。

原因minAvailable: 100% 意味着 DisruptionsAllowed = 0,任何被 PDB 匹配的 Pod 都不能被驱逐,调度器只能"放弃"。

修复

  • 改用 minAvailable: N-1,至少留 1 个驱逐配额
  • 或者用 maxUnavailable: 1,明确允许中断 1 个
  • 再或者把 PDB 拆细,按 zone / node 维度做更精细的预算

P3. 抢占循环:Pod A 抢 B、B 抢 A、B 抢 A……

症状:集群里同时有"高优先级"和"中优先级"两个业务,"高优先级"驱逐"中优先级",但"中优先级"被驱逐后找不到位置,又去驱逐"高优先级"的某个 Pod → 集群抖动。

原因PodEligibleToPreemptOthersdefault_preemption.go:363-387)只在"自己的 nominatedNode 上有 terminating victim"时才拒绝抢占,检查"自己是不是别人 nominatedNode 上的 terminating victim"。

修复

  • priorityClassName 严格分层,避免同 priority 互抢
  • 给 critical Pod 加 preemptionPolicy: Never(v1.36.1 起,default_preemption.go:364
  • 或者用 PodDisruptionBudget 给"中优先级"业务配 maxUnavailable: N%主动接受被驱逐

设计精髓

抢占循环的本质是"没有静态优先级":调度器只看"当前快照"做决策,不考虑"驱逐 A 会引发 A 抢 B"的连锁反应。这种贪心 + 本地视角在大多数情况下 OK,但当业务"互相抢"时就会暴露。生产经验:把 priority 当成静态等级,每个等级预留足够资源,宁可留 buffer 也不靠动态抢占。

八、FAQ:常见疑问(20 问)

Q1. 抢占是同步还是异步?

从 v1.32 引入 EnableAsyncPreemption(v1.36.1 默认开启)后,驱逐是异步的。调度器在 pkg/scheduler/framework/preemption/executor.goactuatePodPreemption不等待 apiserver Evict 返回,而是把 Pod UID 放入 preempting 集合(executor.go:82-86),由 PreEnqueue 阻止该 Pod 重入(default_preemption.go:182-185)。但"找候选节点"和"选 victim"仍然是同步的。

Q2. 抢占者被驱逐会怎么样?

不会被isPreemptionAlloweddefault_preemption.go:395)要求 victim.priority 严格小于 preemptor.priority。在生产中,不要给两个业务设相同 priority 后让它们互相抢——优先用 priorityClassName 区分(PriorityClass 是集群级资源)。

Q3. 抢占和 PodDisruptionBudget 谁先生效?

PDB 不参与"是否抢占"的判定,只参与"抢哪些 Pod"的判定。判定流程是:① 抢占者 priority 够不够(isPreemptionAllowed)→ ② 哪些 Pod 可以被抢(PDB 分组)→ ③ 实际驱逐谁(贪心回填)。

Q4. 抢占时 victim 的 terminationGracePeriodSeconds 怎么算?

被抢占的 Pod 走 apiserver Evict API,apiserver 会给 victim 加 PodReasonPreemptionByScheduler 条件(default_preemption.go:407-410),并强制使用 30s 的优雅期(即便 victim 自己的 terminationGracePeriodSeconds 更长)。这是 k8s 的硬约束,不可配置

Q5. 怎么观察一次抢占?

kubectl describe events 配合 --all-namespaces

# 1) 抓所有 Preemption 事件
kubectl get events --all-namespaces --field-selector reason=Preempted

# 2) 看 victim 上的 DisruptionTarget condition
kubectl get pod <victim-pod> -o jsonpath='{.status.conditions[?(@.type=="DisruptionTarget")]}'

# 3) kube-scheduler 日志 (--v=4 看到候选节点筛选, --v=6 看到 victim 选择)
kubectl logs -n kube-system kube-scheduler-<node> | grep -i preempt

Q6. 多个高优先级 Pod 同时抢占同一节点会怎样?

调度器是单线程处理一个 Pod 的抢占流程,但多个 Pod 的调度周期并行。可能出现"A 抢 B、C 抢 D,但 B 和 D 在同一节点"的情况,此时 PreEnqueue 的 IsPodRunningPreemption 检查(default_preemption.go:182-185)会阻止一个 Pod 在 victim 未死亡前再次入队。极端情况下会出现"饿死",但生产中很少见。

Q7. 抢占的 victim 集合一定是"最少必要"吗?

不一定SelectVictimsOnNodedefault_preemption.go:251)的"全删 → 贪心回填"算法是近似最优,不是数学最优。例如:理论上只杀 1 个大 Pod 就够,但贪心会先按重要度排序,重要度高的会被保留,实际可能多杀几个小的。这是性能与最优性的折衷。

Q8. PreemptionPolicy 怎么影响抢占?

Pod spec.preemptionPolicy 字段(v1.36.1 中可选值:PreemptLowerPriority 默认、Never):设为 Never 后,PodEligibleToPreemptOthersdefault_preemption.go:364-366)直接返回 false,Pod 不参与任何抢占。这个字段主要给"critical workload"(如集群插件)使用——保证它们不主动抢别人

Q9. 抢占会删除 PV 吗?

不会。Preemption 只调 apiserver Evict API,删除 PV / PVC / Service 等关联资源。如果业务 Pod 的 PVC 是 Retain 策略,PV 会在 Pod 删除后保留,需要手动清理。这是个常见的"灰度测试"陷阱:抢占了 Pod 之后,PV 残留导致新 Pod FailedMount

Q10. 抢占后 preemptor 立刻就能调度吗?

不是。抢占流程在 preemption.go:165-169 设置 nominatedNodeName,但 preemptor 仍要等 victim 真正死亡(Pod 消失、kubelet 释放资源)后,再次进入调度周期才能 Bind。Pod 状态会显示 nominatedNodeName 字段。

Q11. PDB 的 MinAvailable 用百分比和绝对值哪个更好?

绝对值更可控。生产中强烈建议用绝对值(如 minAvailable: 3),原因有三:① 百分比在扩容瞬间会"陡变"(如 Deployment 从 2 副本扩到 3 副本,minAvailable: 50% 从 1 变成 2) ② 百分比下"可驱逐配额"会随副本数变化 ③ 监控和告警配置更简单。

Q12. 抢占是否会受 NodeSelector 限制?

不会单独限制。抢占候选节点的全集SnapshotSharedLister.NodeInfos().List()preemption.go:125),即所有节点。但如果 preemptor 自身有 NodeSelectorNodeAffinity,调度器会在 Filter 阶段就把不匹配的节点淘汰,到 PostFilter 阶段候选集里也只剩下匹配的节点。

Q13. 怎么禁用某个 Pod 的抢占?

两种方式:

  • spec.preemptionPolicy: Never(v1.36.1 起,default_preemption.go:364
  • 不给它分配 priorityClassName,默认 priority=0,只能被抢不会抢别人

Q14. 抢占会让 victim 容器收到 SIGTERM 吗?

。抢占流程走标准的 Pod 删除路径:apiserver 把 Pod deletionTimestamp 设上 → kubelet 收到删除事件 → 跑 PreStop 钩子 → 给容器发 SIGTERM。整个过程最长 30s 优雅期(见 Q4),超时后 SIGKILL

Q15. DefaultPreemption 之外的抢占插件?

v1.36.1 支持 3 个抢占相关特性:

特性特性门控入口适用场景
异步抢占 EnableAsyncPreemption default_preemption.go:166 大集群吞吐
PodGroup 抢占 EnableWorkloadAwarePreemption default_preemption.go:468 Volcano / Kueue
TAS 抢占 EnableTopologyAwareWorkloadScheduling default_preemption.go:140 拓扑感知调度

Q16. 抢占候选节点数怎么定?

MinCandidateNodesPercentageMinCandidateNodesAbsolute 决定(default_preemption.go:218-227)。默认:10%100 个,取大者。100 节点以下会全量评估;1000 节点集群只评估 100 个。这是个性能与精度的折衷。

Q17. 抢占会让 Pod 进 unschedulableQ 吗?

。当抢占后仍然调度不上(所有候选都被拒绝),Preemptor 会清空 nominatedNodeNamepreemption.go:147),回到 unschedulableQ 等待下次重试。重试间隔由 scheduler.conf 里的 backoff 机制控制。

Q18. 抢占的资源回收是即时的吗?

不是。抢占流程只调 apiserver Evict,资源真正释放要等:① kubelet 收到删除事件 ② 容器进程退出(最多 30s 优雅期) ③ kubelet 释放 cgroup / 网络 / 端口等。整个流程通常 5~30s。在这段时间内,新 Pod 不能立刻拿到资源

Q19. PDB 的 DisruptionsAllowed 是怎么算的?

policy/v1 PDB controller 实时计算:

DisruptionsAllowed = max(0, min(
    minAvailable - (currentHealthy - DisruptedPods.size),
    totalReplicas - currentHealthy
))

简单说:当前健康 Pod 数减去 minAvailable 还能被驱逐几个。详细算法见 pkg/controller/disruption/disruption.go。注意 DisruptedPods 已经包含了"正在被驱逐"的 Pod,调度器在 filterPodsWithPDBViolationdefault_preemption.go:446)会跳过它们。

Q20. 怎么排查"抢占没生效"?

按下面 5 步法排查:

  1. 看 prioritykubectl get pod -o jsonpath='{.spec.priorityClassName}'。preemptor 必须高于 victim
  2. 看 preemptionPolicy:必须不是 Never
  3. 看 Pod 状态kubectl describe pod → 找 nominatedNodeName 和 Events
  4. 看 scheduler 日志-v=6"Pod is not eligible for preemption""No preemption candidate is found"
  5. 看 PDBkubectl get pdb -A -o yaml,检查 minAvailable / disruptionsAllowed

小贴士80% 的"抢占不生效"是 priority 没配对
在 k8s 里,priority 是唯一决定"谁能抢谁"的字段。PDB、NodeSelector、ResourceQuota 都不会"禁止"抢占,只会影响"抢哪些"和"在哪儿抢"。

九、Roadmap:调度专题后续预告

本篇把 DefaultPreemption 怎么选 victimPDB 怎么约束讲透了,但还有几条主线尚未展开:

  • CycleState 与扩展点源码精讲pkg/scheduler/framework/runtime/framework.go 里的 9 个 RunXxxPlugins 方法,CycleState 如何在扩展点之间传值
  • SchedulingQueue 与 PriorityQueue:Pod 在调度前经历的 3 个队列(activeQ / unschedulableQ / backoffQ),backoff 退避机制
  • Out-of-Tree 插件实战:用 scheduler.RegisterPluginBuilder 注册一个自定义抢占插件,覆盖 IsEligiblePod / MoreImportantPod
  • WorkloadAware 抢占EnableWorkloadAwarePreemption 开启后,PodGroup 整体抢占的 pkg/scheduler/framework/preemption/podgrouppreemption.go 实现

调度专题的目标读者是资深运维开发:能读 Go 源码、有集群运维经验、对 k8s 整体架构已有认知。下一篇我会从 CycleState 切入,把框架运行时的"骨架"补全。


本文参考与源码链接:
  • default_preemption.go · DefaultPreemption 插件
  • preemption.go · Evaluator 6 步流程
  • executor.go · 抢占执行器(含异步抢占)
  • types.go · Candidate 与 Victims 结构
  • schedule_one.go · PostFilter 调用入口
  • disruption.go · PDB DisruptionsAllowed 算法