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

推荐订阅源

S
Schneier on Security
The Register - Security
The Register - Security
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
博客园 - 司徒正美
罗磊的独立博客
U
Unit 42
S
SegmentFault 最新的问题
Y
Y Combinator Blog
博客园_首页
Hugging Face - Blog
Hugging Face - Blog
J
Java Code Geeks
Schneier on Security
Schneier on Security
Know Your Adversary
Know Your Adversary
C
Check Point Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Simon Willison's Weblog
Simon Willison's Weblog
V
Vulnerabilities – Threatpost
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
阮一峰的网络日志
阮一峰的网络日志
The Hacker News
The Hacker News
博客园 - 叶小钗
C
Cybersecurity and Infrastructure Security Agency CISA
Spread Privacy
Spread Privacy
L
LINUX DO - 热门话题
T
The Exploit Database - CXSecurity.com
P
Palo Alto Networks Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
L
Lohrmann on Cybersecurity
A
About on SuperTechFans
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
S
Securelist
A
Arctic Wolf
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threatpost
Scott Helme
Scott Helme
博客园 - 聂微东
博客园 - 【当耐特】
T
Tenable Blog
I
Intezer
D
DataBreaches.Net
B
Blog RSS Feed
Security Latest
Security Latest
C
Cisco Blogs
T
Tor Project blog
N
Netflix TechBlog - Medium

博客园 - 左扬

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(调度专题 · 五):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(调度专题):调度器内置插件 逐个精读 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(调度专题 · 六):Scheduler Profile 与多调度器 —— 如何配置多个 profile 实现多租户、Coordinated LeaderElection
左扬 · 2026-06-20 · via 博客园 - 左扬

Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 六):Scheduler Profile 与多调度器 —— 如何配置多个 profile 实现多租户、Coordinated LeaderElection

上一篇调度专题【左扬精讲】拆解了 SchedulingQueue 与 QueueingHint,回答了"Pod 进调度器后如何排队"的问题。本篇换一个更高层的视角:一个 kube-scheduler 进程里能跑多种调度策略吗?多副本之间如何选主?

这正是 Scheduler ProfileCoordinated LeaderElection 要解决的两件事。前者是"同集群多策略",后者是"跨集群协调选主"。读完后,你应该能回答三个问题:多 Profile 的核心数据是哪个 struct?Coordinated LeaderElection 与普通选主本质差异是什么?以及一个 v1.36.1 集群最多能跑多少种调度策略?

Kubernetes Scheduler Profile LeaderElection 多租户 Go k8s v1.36.1

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

重点掌握(必须)

  • Profile 的核心数据结构profile.Mappkg/scheduler/profile/profile.go:46)和 KubeSchedulerProfilepkg/scheduler/apis/config/types.go
  • 同集群多 Profile 配置profiles[*].schedulerName + Pod 的 spec.schedulerNamepkg/scheduler/eventhandlers.go:458
  • Coordinated LeaderElection 入口cmd/kube-scheduler/app/server.go:230server.go:307CoordinatedLeaderElection 门控(pkg/features/kube_features.go:183
  • LeaseCandidate 角色leaderelection.NewCandidateserver.go:241)+ coordination.k8s.io/v1.LeaseCandidate CRD

次重点(了解即可)

  • Profile 内 PluginConfig 的传递链pluginConfigDecodeInto()pkg/scheduler/framework/runtime/registry.go:44
  • cfgValidator 的唯一性约束profile.go:105 不允许重名 schedulerName
  • Multi-scheduler 与多 Profile 的取舍:何时拆 Pod 何时拆 Profile

文章目录

一、Why:为什么需要 Scheduler Profile

kube-scheduler 早期版本只支持"一种调度策略跑全集群"。当业务出现多种调度诉求时(在线 vs 离线、训练 vs 推理、不同部门配额),运维只能:

  • 方案 A:部署多套 kube-scheduler,每套独立进程、独立选主 —— 浪费资源、运维成本高
  • 方案 B:把所有规则塞进一个 scheduler,靠复杂的 if-else 区分 —— 代码腐化、调度慢
  • 方案 C(v1.18 引入):用 Scheduler Profile,同进程跑多种策略,Pod 通过 spec.schedulerName 按需路由

Profile 本质是 "调度策略包":每个 Profile 独立包含一份插件集合、插件配置、扩展点启用/禁用列表。一个 kube-scheduler 进程能跑 N 个 Profile(N 没有硬编码上限,但实际生产推荐 ≤ 10)。

设计精髓

Profile 的核心思想是 "策略外置":把"调度时怎么决策"从 Go 代码里抽出来,放到 KubeSchedulerConfiguration 这种声明式 YAML 里。这意味着:

  • 新增一个调度策略不需要重新编译 scheduler 二进制
  • 同一份 scheduler 镜像能服务多个不同业务方
  • 调度策略变更通过 ConfigMap reload--config 热加载

二、What:Profile 的核心数据结构

2.1 KubeSchedulerProfile —— 声明式配置

用户视角的 Profile 是 pkg/scheduler/apis/config/types.go 中的 KubeSchedulerProfile 结构体(v1 API 用 kubescheduler.config.k8s.io/v1):

// pkg/scheduler/apis/config/types.go (节选, k8s v1.36.1)
type KubeSchedulerConfiguration struct {
    Profiles []KubeSchedulerProfile  // 多 Profile 数组
    Extenders []Extender             // 全局 Extender,被所有 profile 共享
    DelayCacheUntilActive bool
    LeaderElection *LeaderElectionConfiguration
    ...
}

type KubeSchedulerProfile struct {
    SchedulerName string              // 唯一标识,对应 Pod.spec.schedulerName
    Plugins       *Plugins            // 该 profile 用哪些插件(enable/disable 列表)
    PluginConfig  []PluginConfig      // 该 profile 的插件配置(按插件名索引)
    PercentOfNodesToScore *int32      // 该 profile 自己的打分节点比例(覆盖全局)
}

type PluginConfig struct {
    Name string             // 插件名
    Args runtime.Object     // 插件配置(YAML/JSON 反序列化)
}

2.2 profile.Map —— 运行时实例

运行时,profile.NewMap()pkg/scheduler/profile/profile.go:49)把 []KubeSchedulerProfile 转成 profile.Map —— 就是一个 map[schedulerName]Framework

// pkg/scheduler/profile/profile.go (行 45-65, k8s v1.36.1)
// Map holds frameworks indexed by scheduler name.
type Map map[string]framework.Framework

func NewMap(ctx context.Context, cfgs []config.KubeSchedulerProfile,
    r frameworkruntime.Registry, recorderFact RecorderFactory,
    opts ...frameworkruntime.Option) (Map, error) {
    m := make(Map)
    v := cfgValidator{m: m}

    for _, cfg := range cfgs {
        p, err := newProfile(ctx, cfg, r, recorderFact, opts...)
        if err != nil {
            return nil, fmt.Errorf("creating profile for scheduler name %s: %v", cfg.SchedulerName, err)
        }
        if err := v.validate(cfg, p); err != nil {
            return nil, err
        }
        m[cfg.SchedulerName] = p
    }
    return m, nil
}

func (m Map) HandlesSchedulerName(name string) bool {
    _, ok := m[name]
    return ok
}

小贴士关于 cfgValidator

cfgValidatorprofile.go:92)做了3 项校验

  1. schedulerName 非空profile.go:99-101
  2. Plugins 非空profile.go:102-104
  3. schedulerName 唯一profile.go:105-107if v.m[f.ProfileName()] != nil

第 4 项校验更隐性:所有 profile 的 QueueSort 插件必须一致profile.go:117-125)—— 因为全局只有一个 SchedulingQueue,只能有一种排序方式

2.3 Scheduler 持有 Profile Map

最终的 Scheduler 结构体持有 Profiles profile.Mappkg/scheduler/scheduler.go:102)。整个 Scheduler 的关键字段:

字段类型作用
Profiles profile.Map 所有 profile 的 Framework,键为 schedulerName
SchedulingQueue internalqueue.SchedulingQueue 全局唯一 三段队列
Cache internalcache.Cache 节点 / Pod 快照,被所有 profile 共享
Extenders []*SchedulerExtender 所有 profile 共享 的外部扩展

注意

虽然有 N 个 Profile,但 Scheduler 只持有 1 个 SchedulingQueue、1 个 Cache、1 个 informer 集。Pod 进队列前会按 schedulerName 路由到对应 Profile 的 plugin 集合,但共享同一份节点视图。这是 Profile 比"多 scheduler 进程"高效的核心原因。

三、How:多 Profile 的配置与运行机制

3.1 典型配置:在线 + 离线 双 Profile

下面是一份"在线 vs 离线"双 Profile 配置:

# /etc/kubernetes/scheduler-config.yaml (k8s v1.36.1)
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: online-scheduler
  plugins:
    enable:
    - name: CapacityScheduling       # 在线业务需要容量保证
    - name: NodeResourcesFit
    - name: NodeAffinity
    - name: InterPodAffinity
    - name: PodTopologySpread
    pluginConfig:
    - name: CapacityScheduling
      args:
        apiVersion: kubescheduler.config.k8s.io/v1
        kind: CapacitySchedulingArgs
        defaultPreemption: false       # 在线业务禁止被抢占
        weight: 100
- schedulerName: batch-scheduler
  plugins:
    enable:
    - name: NodeResourcesFit
    - name: NodeAffinity
    - name: TaintToleration
    - name: Coscheduling               # 离线用 gang
    disable:
    - name: CapacityScheduling
    pluginConfig:
    - name: Coscheduling
      args:
        apiVersion: kubescheduler.config.k8s.io/v1
        kind: CoschedulingArgs
        permitWaitingTimeSeconds: 60
leaderElection:
  leaderElect: true
  resourceNamespace: kube-system
  resourceName: kube-scheduler
  leaseDuration: 15s
  renewDeadline: 10s
  retryPeriod: 2s

3.2 Pod 如何路由到指定 Profile?

Pod 通过 spec.schedulerName 字段选择 Profile,默认是 "default-scheduler"

# 在线业务 Pod
apiVersion: v1
kind: Pod
metadata:
  name: web-server
spec:
  schedulerName: online-scheduler   # 路由到 online-scheduler Profile
  containers:
  - name: nginx
    image: nginx:1.27

---
# 离线业务 Pod
apiVersion: v1
kind: Pod
metadata:
  name: spark-driver
spec:
  schedulerName: batch-scheduler    # 路由到 batch-scheduler Profile
  containers:
  - name: spark
    image: spark:3.5

当 Pod 被 informer 监听到时,调度器在 pkg/scheduler/eventhandlers.go:458 调用 profiles.HandlesSchedulerName() 判定该 Pod 能否被本进程处理:

// pkg/scheduler/eventhandlers.go (行 450-460, k8s v1.36.1)
return profiles.HandlesSchedulerName(pod.Spec.SchedulerName)

如果集群里没有匹配 schedulerName 的 Profile,Pod 永远不会被调度(也不会被拒绝)—— 这是常见踩坑点:Pod 卡 Pending 但没有任何报错事件。

设计精髓

v1.36.1 起,每个 Profile 独立有一份 QueueingHint、PreEnqueue、SignPod 配置(在 scheduler.go:387-411 的 profile 循环里按 profileName 分组)。这意味着:

  • batch-scheduler 注册 Node Add 事件时,online-scheduler 的 Pod 不会被错误地重排
  • 两个 Profile 可以完全独立调优退避参数、节点比例

这种"共享 cache + 独立 plugin 链"是 Profile 的灵魂。

3.3 Profile 与 Multi-Scheduler 的取舍

新人经常困惑:什么时候用 Profile,什么时候拆多个 kube-scheduler 进程?下面是推荐取舍

场景建议方案原因
在线 vs 离线(共享节点池) Profile 共享 cache,开销低,资源视图一致
不同集群(异构节点池) 多 scheduler 节点池互不重叠,独立选主更稳
A/B 测试(新旧调度策略对比) Profile + label selector 新策略只调度带特定 label 的 Pod
需要独立选主的子集群 多 scheduler + Coordinator 每个 scheduler 副本独立 Lease

四、Detail:Coordinated LeaderElection 的工作机制

v1.31 引入(Alpha,pkg/features/kube_features.go:2232)、v1.33 Beta 的 CoordinatedLeaderElection 是为"多 scheduler 跨集群共享视图"设计的选主机制。

4.1 为什么需要"协调"选主?

传统的 kube-scheduler 选主(基于 coordination.k8s.io/v1.Lease)存在两个问题

  • 版本不一致:老 Leader 还在跑 v1.35,新 Leader 已经升到 v1.36。两个不同版本同时持有 Lease 时,集群行为不可预测
  • 二进制不对齐:滚动升级过程中,新旧二进制混跑,调度决策可能不一致

Coordinated LeaderElection 引入 LeaseCandidate 机制:每个候选者声明自己的 二进制版本仿真版本只有相同版本组 内能互选,避免跨版本选主。

4.2 LeaseCandidate 与 Lease 关系

┌──────────────────────────────────────────────────────────────────────┐
│  Coordinated LeaderElection  ·  v1.36.1                                │
│                                                                      │
│   ┌──────────────────────────┐                                       │
│   │  kube-scheduler (v1.36)  │                                       │
│   │   - binaryVersion=v1.36   │                                       │
│   │   - emulationVersion=v1.36│                                       │
│   └─────────────┬────────────┘                                       │
│                 │                                                     │
│                 ▼                                                     │
│   ┌──────────────────────────┐     ┌──────────────────────────┐    │
│   │ coordination.k8s.io/v1   │     │ coordination.k8s.io/v1   │    │
│   │ LeaseCandidate           │     │ Lease                     │    │
│   │ - name: kube-scheduler-0 │     │ - name: kube-scheduler    │    │
│   │ - binaryVersion: v1.36   │     │ - holderIdentity: ...    │    │
│   │ - emulationVersion: v1.36│     │ - renewTime: ...         │    │
│   └─────────────┬────────────┘     └──────────────────────────┘    │
│                 │                            ▲                        │
│                 └──── 每 1s 心跳 ────────────┘                        │
│                                                                      │
│   其他 scheduler 候选者 (v1.35, v1.34, ...) 不会选 v1.36 为主        │
└──────────────────────────────────────────────────────────────────────┘

4.3 入口源码

cmd/kube-scheduler/app/server.gorunCommand 里有两个关键判断:

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

// 1) 启动 LeaseCandidate controller(让本节点作为候选者可见)
if cc.LeaderElection != nil && utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection) {
    binaryVersion, err := semver.ParseTolerant(...)
    emulationVersion, err := semver.ParseTolerant(...)
    leaseCandidate, waitForSync, err := leaderelection.NewCandidate(
        cc.Client,
        metav1.NamespaceSystem,
        cc.LeaderElection.Lock.Identity(),
        kubeScheduler,                          // component 名
        binaryVersion.FinalizeVersion(),
        emulationVersion.FinalizeVersion(),
        coordinationv1.OldestEmulationVersion, // 最低兼容版本
    )
    if err != nil { return err }
    readyzChecks = append(readyzChecks, healthz.NewInformerSyncHealthz(waitForSync))
    go leaseCandidate.Run(ctx)
}

// 2) 创建 LeaderElector,标记 Coordinated = true
if cc.LeaderElection != nil {
    if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection) {
        cc.LeaderElection.Coordinated = true    // 关键:启用协调选主
    }
    cc.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{...}
    leaderElector, err := leaderelection.NewLeaderElector(*cc.LeaderElection)
    ...
    leaderElector.Run(ctx)
}

小贴士关于 OldestEmulationVersion

coordinationv1.OldestEmulationVersion 是 v1.31 引入的"可被仿真运行的最低版本"。比如集群从 v1.35 升到 v1.36 时,v1.36 节点可以声明自己仿真 v1.35,从而接管 v1.35 的 Lease,避免升级期出现"无主"空窗。

4.4 OnStartedLeading 的妙用

cmd/kube-scheduler/app/server.go:309-318OnStartedLeading 回调做了3 件事

OnStartedLeading: func(ctx context.Context) {
    close(waitingForLeader)
    if cc.ComponentConfig.DelayCacheUntilActive {
        logger.Info("Starting informers and waiting for sync...")
        startInformersAndWaitForSync(ctx)
        logger.Info("Sync completed")
    }
    sched.Run(ctx)
},
  • close(waitingForLeader):通知 healthz/readyz 探针:"我已经是 Leader"
  • DelayCacheUntilActive:若开启,只有成为 Leader 后才启动 informer,避免非 Leader 节点浪费内存同步 cache
  • sched.Run(ctx):启动 scheduler 主循环

注意

DelayCacheUntilActive 在多 scheduler 场景非常重要:默认 false 时所有副本都同步 cache,内存放大 N 倍;设为 true 时只 Leader 同步 cache,非 Leader 副本内存占用极低。生产大集群建议必开

五、Compare:3 种选主模式对比

模式数据载体选主逻辑适用场景
无选主 每副本都调度(会冲突) 仅开发测试
Lease 选主
(默认)
coordination.k8s.io/v1.Lease Lease 持有者即 Leader 同版本集群
Coordinated 选主
(v1.31+)
Lease + LeaseCandidate 同版本组内选主 滚动升级、多版本共存

六、Pitfall:踩坑实录

P1. Pod 永远 Pending,没有 FailedScheduling 事件

症状:业务方反映 Pod 卡 Pending,kubectl describe pod 显示 Events: <none>,没有 FailedScheduling 也没有任何报错。

原因:Pod 的 spec.schedulerName 设成了不存在的 Profile 名(比如 "my-ai-scheduler" 写错了)。调度器在 eventhandlers.go:458 直接丢弃该 Pod,不会入队,不会调度,不会报错。

修复

# 1. 查 Pod 实际要的 schedulerName
kubectl get pod <name> -o jsonpath='{.spec.schedulerName}'

# 2. 查 scheduler 实际支持的 profile
kubectl -n kube-system get cm kube-scheduler-config -o yaml | grep schedulerName
# 或
kubectl logs -n kube-system kube-scheduler-xxx | grep "scheduler name"

P2. Coordinated 选主启用后所有副本都是 Leader

症状:开了 CoordinatedLeaderElection=true 后,kubectl get lease -n kube-system 显示 Lease 的 holderIdentity 不断随机变化,每个副本都当了一会 Leader。

原因:v1.31 Alpha 期间 LeaseCandidate 与 Lease 的协调有 bug,多副本同时声明自己是 Leader。升级到 v1.33+ Beta 后修复。

修复:升级到 v1.33+,或暂时关掉 CoordinatedLeaderElection 回到普通 Lease 选主。

P3. cfgValidator 报"plugins required"

症状:scheduler 启动失败,日志:plugins required for profile with scheduler name "xxx"

原因:Profile 配置里 plugins: null。v1.36.1 的 cfgValidator.validate()profile.go:102强制要求每个 profile 都显式写 plugins 段,不能省略。

修复

profiles:
- schedulerName: my-scheduler
  plugins:                  # 必填,即使为空也要写
    enable: []              # 空数组 = 用默认插件
    disable: []

P4. QueueSort 不一致导致调度乱序

症状:多 Profile 集群里,Pod 不按 Priority 出队,低优先级 Pod 先调度。

原因:两个 Profile 配置了不同的 QueueSort 插件。但全局只有一个 activeQ(scheduling_queue.go:409),只能用一种 Less 函数profile.go:117 的 cfgValidator 第一个 profile 的 QueueSort 决定全局顺序,第二个 profile 写啥都不生效。

修复:所有 Profile 必须用同一种 QueueSort(通常是 PrioritySort)。生产中通过 Helm / Kustomize 模板强制统一

七、FAQ:常见疑问

Q1. 一个集群能跑多少个 Profile?

没有硬编码上限,但每多一个 Profile,scheduler 启动时多 O(N) 个 Framework 实例、调度时多 O(N) 个路由判断。生产经验 ≤ 10,超过建议拆多进程

Q2. 多 Profile 共享 informer cache 吗?

共享scheduler.New()pkg/scheduler/scheduler.go:314)只创建一份 informerFactory,所有 profile 共用同一份 Node / Pod 快照。

Q3. Profile 之间的 Pod 会互相影响调度吗?

不会。Pod 进 activeQ 时按 pod.Spec.SchedulerName 入对应 profile 的 plugin 集合,但同一份 activeQ。Pop 时根据 Pod 的 schedulerName 选 framework。竞争点是同一节点:两个 profile 的 Pod 可能都想调度到同一节点,由节点的容量裁决。

Q4. PluginConfig 的 args 是怎么传给插件的?

pkg/scheduler/framework/runtime/registry.go:44-66DecodeInto() 把 YAML/JSON 字节流转成 runtime.Object,由插件构造函数断言成具体类型(如 *CapacitySchedulingArgs)。

Q5. 怎么给已有集群加一个 Profile?

/etc/kubernetes/scheduler-config.yaml,加一个 profiles 条目,重启 scheduler 即可(k8s 不支持 scheduler 配置热加载)。生产建议先用 --config 走 ConfigMap 挂载,缩短 reload 时间。

Q6. CoordinatedLeaderElection 启用后还需要配 LeaderElection 吗?

需要CoordinatedLeaderElection 不是替代 LeaderElection,而是叠加

  • LeaderElection 提供基础选主(Lease)
  • CoordinatedLeaderElection 在 Lease 之上加版本协调(LeaseCandidate)

所以两份配置得写。

Q7. 怎么确认选主状态?

# 1. Lease 当前持有者
kubectl -n kube-system get lease kube-scheduler -o yaml

# 2. 候选者列表
kubectl -n kube-system get leasecandidate

# 3. 每个 scheduler 副本的角色
kubectl -n kube-system logs kube-scheduler-xxx | grep -E "leader|leading"

Q8. 启用 CoordinatedLeaderElection 对延迟有影响吗?

几乎无影响。LeaseCandidate 每 1 秒一次心跳(server.go:254 go leaseCandidate.Run(ctx)),apiserver 的 LeaseCandidate 写入是异步的,对调度主循环无阻塞

Q9. 滚动升级 v1.35 → v1.36 时 Coordinated 起什么作用?

v1.36 节点声明 binaryVersion=v1.36, emulationVersion=v1.35(最后一个兼容版本),让 v1.35 的 Lease 被 v1.36 节点顺利接管,避免"无主空窗"。升级完成后所有节点统一 v1.36,再统一 emulationVersion。

Q10. 同一个 Pod 能被两个 Profile 处理吗?

不能。Pod 的 spec.schedulerName唯一路由键。如果业务想要"先 X 调度,失败后 Y 兜底",需要两个 Deployment改 schedulerName(通过 controller 改写)。

Q11. 多 Profile 集群的 ResourceQuota 怎么算?

Quota 与 scheduler 无关,是 namespace 级 admission control。建议按业务部门拆 namespace,不要靠 Profile 强隔离。Profile 只决定"调度策略",不决定"资源配额"。

Q12. Helm 部署时 Profile 配置怎么模板化?

# values.yaml
scheduler:
  profiles:
  - name: online-scheduler
    plugins: [...]
  - name: batch-scheduler
    plugins: [...]

# templates/configmap.yaml
{{- range .Values.scheduler.profiles }}
- schedulerName: {{ .name }}
  plugins:
{{ toYaml .plugins | indent 4 }}
{{- end }}

Q13. cfgValidator 还校验什么?

profile.go:98-127 的 3 项校验:

  1. schedulerName 非空
  2. Plugins 非空
  3. schedulerName 唯一
  4. 所有 profile 的 QueueSort 相同 + 第一个 profile 的 QueueSort 决定了全局 Less 函数

Q14. 升级到 v1.36 后 Coordinated 怎么关闭?

v1.36.1 起 可以关:--feature-gates=CoordinatedLeaderElection=false(Beta 阶段仍可关)。GA 后不可关。生产建议先在 staging 跑 2 周再上生产。

Q15. Pod 的 spec.schedulerName 改了会怎样?

调度器监听 Pod Update 事件:PriorityQueue.Update()scheduling_queue.go:126)会处理。如果新 schedulerName 在本进程 Profile Map 里,正常调度;否则 Pod 卡 Pending没有事件

Q16. Profile 内能装 Out-of-Tree 插件吗?

可以。在 scheduler 启动时通过 WithFrameworkOutOfTreeRegistry()scheduler.go:209-215)传入 Out-of-Tree 插件,然后 Profile 配置里 plugins.enable 写插件名即可。

Q17. kube-scheduler 是不是必须开启 LeaderElection?

不是。但生产强烈建议开,否则多副本会重复调度,Pod 出现"已被调度但又被改写"的诡异行为。单副本开发环境可以关。

Q18. LeaseCandidate CRD 是谁创建的?

kube-scheduler 自己创建leaderelection.NewCandidate()server.go:241)内部会通过 EnsureLeaseCandidateRegistration 检查并创建 CRD。

Q19. 两个 scheduler 实例冲突了怎么办?

检查两件事:

  1. resourceName 是否不同:多 scheduler 用不同的 Lease 名避免冲突
  2. schedulerName Profile 是否冲突:同一集群里 spec.schedulerName 不能被两个 scheduler 同时认领(否则会双发 Bind)

Q20. Coordinated LeaderElection 与 Priority-based 选主能组合吗?

v1.33 引入的 Priority-based 选主是另一种新机制(LeaderElection.Priority),目前 不能与 Coordinated 共用。要么用 Coordinated,要么用 Priority。

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

本篇覆盖了 Scheduler Profile 与 Coordinated LeaderElection。调度专题剩余主线

  • Out-of-Tree 插件实战:从零写一个 Score 插件并发布到 Helm chart(下一篇)
  • 抢占(DefaultPreemption)机制:调度失败时如何挪走低优先级 Pod
  • DRA 与 Scheduling:v1.36 起新资源维度的调度

调度专题的目标读者是资深运维开发:能读 Go 源码、有集群运维经验、对 k8s 整体架构已有认知。下一篇会从 手写 Score 插件 切入,把"自研插件落地"这件事讲透。


本文参考与源码链接:
  • profile.go · profile.Map 实现
  • scheduler.go · Scheduler + Profiles
  • config/types.go · KubeSchedulerProfile
  • eventhandlers.go · schedulerName 路由
  • server.go · Coordinated LeaderElection 入口
  • kube_features.go · CoordinatedLeaderElection 门控
  • registry.go · DecodeInto PluginConfig