深入源码:Kube-Scheduler 调度框架
深入理解 Kubernetes Scheduler 的调度框架、插件机制和核心算法,学习如何编写自定义调度器。
概述
Kubernetes Scheduler 负责为新创建的 Pod 选择最合适的节点。本文将从源码层面深入理解调度器的工作原理:
学习目标:
- 理解调度框架(Scheduling Framework)架构
- 掌握核心调度算法(预选、优选、绑定)
- 学习常用调度插件的实现
- 学会编写自定义调度器
Scheduler 架构概述
调度流程
┌─────────────────────────────────────────────────────────────────┐
│ Pod 调度流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户创建 Pod │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ API Server │ │
│ │ │ │
│ │ Pod 进入调度队列(Scheduling Queue) │ │
│ │ └── PriorityQueue(优先级队列) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Scheduling Cycle │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Queue │→ │ Filter │→ │ Score │ │ │
│ │ │ Sort │ │ (预选) │ │ (优选) │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │ │ │ │
│ │ └────────────────┴────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────┐ │ │
│ │ │ Select Node │ │ │
│ │ │ (选择节点) │ │ │
│ │ └────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Binding Cycle │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Reserve │→ │ Permit │→ │ PreBind │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │ │ │ │
│ │ └────────────────┴────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────┐ │ │
│ │ │ Bind │ │ │
│ │ │ (绑定节点) │ │ │
│ │ └────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────┐ │ │
│ │ │ PostBind │ │ │
│ │ │ (后置处理) │ │ │
│ │ └────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ kubelet 创建 Pod │
│ │
└─────────────────────────────────────────────────────────────────┘
调度框架插件
┌─────────────────────────────────────────────────────────────────┐
│ Scheduling Framework │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 插件扩展点: │
│ │
│ QueueSort ─────▶ PriorityQueue 排序 │
│ │ │
│ ▼ │
│ PreFilter ─────▶ 预处理,生成共享状态 │
│ │ │
│ ▼ │
│ Filter ────────▶ 预选:节点是否满足条件 │
│ │ │
│ ▼ │
│ PostFilter ───▶ 后置过滤(当 Filter 全部失败时) │
│ │ │
│ ▼ │
│ PreScore ─────▶ 预评分:生成共享状态 │
│ │ │
│ ▼ │
│ Score ────────▶ 优选:为每个节点打分 │
│ │ │
│ ▼ │
│ NormalizeScore ▶ 归一化打分结果 │
│ │ │
│ ▼ │
│ Reserve ──────▶ 预留资源(此时 Pod 已"占位") │
│ │ │
│ ▼ │
│ Permit ───────▶ 许可(可以暂停/拒绝/批准) │
│ │ │
│ ▼ │
│ PreBind ──────▶ 绑定前处理(允许外部操作) │
│ │ │
│ ▼ │
│ Bind ─────────▶ 绑定 Pod 到节点 │
│ │ │
│ ▼ │
│ PostBind ─────▶ 绑定后处理(清理/通知) │
│ │
└─────────────────────────────────────────────────────────────────┘
调度算法详解
预选(Filtering)
// 源码位置:pkg/scheduler/framework/plugins/helper.go
// FindFiltersThatCanSchedule 这个函数遍历所有 Filter 插件
// 每个插件返回 NodePluginToStatusScores
// 最终得到可调度的节点列表
// 预选阶段的核心逻辑
func (g *genericScheduler) findNodesThatFitPod(ctx context.Context, f framework.Framework, pod *v1.Pod, nodes []*v1.Node) ([]*v1.Node, framework.NodeToStatusMap) {
// 1. 获取所有 Filter 插件
filteringPlugins := f.FilterPlugins()
// 2. 并行检查每个节点
// 使用 workqueue 并行处理
allNodes := make([]*v1.Node, 0, len(nodes))
failedNodes := make(framework.NodeToStatusMap)
// 3. Filter 插件检查节点
// 常见的 Filter 插件:
// - NodeResourcesFit: 资源是否满足
// - NodeLabel: 标签匹配
// - NodeAffinity: 节点亲和性
// - PodAffinity: Pod 亲和性
// - TaintToleration: 污点容忍
// - DiskPressure: 磁盘压力
// - MemoryPressure: 内存压力
// - PIDPressure: PID 压力
return allNodes, failedNodes
}
// 预选失败的原因
// - NoNodeSelectorTerms: 节点选择器不匹配
// - InsufficientResources: 资源不足
// - NodeNetworkUnavailable: 网络不可用
// - NodeUnschedulable: 节点不可调度
// - TaintToleration: 污点不容忍
// - VolumeZoneConflict: 存储卷区域冲突
优选(Scoring)
// 源码位置:pkg/scheduler/framework/plugins/
// Score 插件为每个节点打分
// 最终分数是所有插件分数的加权平均
// 常见 Score 插件:
// 1. NodeResourcesBalancedAllocation
// - 均衡分配 CPU 和内存
// - 分数 = 1 - |cpu_ratio - memory_ratio| / 2
// 2. ImageLocality
// - 镜像已经在节点上得高分
// - 基于已缓存镜像大小
// 3. InterPodAffinity
// - Pod 亲和性/反亲和性
// - 计算拓扑域中匹配的 Pod 加权求和
// 4. NodeAffinity
// - 节点亲和性匹配
// - 计算 PreferredDuringScheduling 的加权得分
// 5. NodeLabel
// - 标签存在性
// - 简单的正则匹配
// 6. RequestedToCapacityRatio
// - 请求/容量比率
// - 自定义资源分配
// 示例:NodeResourcesFit Score
func (s *NodeResourcesFit) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
nodeInfo, err := s.sharedLister.NodeInfo(nodeName)
if err != nil {
return 0, framework.AsStatus(err)
}
score := calculateScore(pod, nodeInfo, s.resourceAllocationType)
return score, nil
}
选择节点算法
// 源码位置:pkg/scheduler/core/generic_scheduler.go
// selectTopScoreNode 选择分数最高的节点
// 当多个节点分数相同时,使用以下策略:
func (g *genericScheduler) selectTopScoreNode(
nodes []*v1.Node,
nodeScores framework.NodeScoreList,
) (string, error) {
// 1. 排序(按分数降序,分数相同则随机)
sort.Sort(nodeScores)
// 2. 选择第一个节点
// 如果启用 NodeResourcesMostRequested 策略
// 可能会选择资源最多的节点
return nodeScores[0].Name, nil
}
// 默认的分数归一化
// 所有分数归一化到 [0, 100] 范围
// 默认权重为 1
调度插件实现
注册插件
# kube-scheduler 配置文件
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
# 启用/禁用/重排序插件
enablePlugins:
- NodeResourcesFit
- NodeLabel
- TaintToleration
- InterPodAffinity
- VolumeBinding
- VolumeRestrictions
disabledPlugins:
- ResourceLimits
pluginConfig:
- name: NodeResourcesFit
args:
scoringStrategy:
strategy: LeastRequested # 或 MostRequested
- name: InterPodAffinity
args:
hardPodAffinityWeight: 1
自定义调度器配置
// 自定义调度器配置文件
type CustomSchedulerConfiguration struct {
metav1.TypeMeta
KubeSchedulerConfiguration
// 自定义配置
CustomPlugins CustomPluginsConfig
}
type CustomPluginsConfig struct {
// 自定义插件参数
EnableCustomPlugin bool
CustomThreshold int32
}
// 调度器插件配置示例
pluginConfig:
- name: CustomPlugin
args:
param1: value1
param2: value2
调度扩展点扩展
// pkg/scheduler/framework/plugins/myplugin/myplugin.go
package myplugin
import (
"context"
"fmt"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/scheduler/framework"
)
// Filter 实现预选逻辑
func (pl *MyPlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
// 检查节点是否满足条件
if !pl.shouldScheduleOnNode(pod, nodeInfo) {
return framework.NewStatus(framework.Unschedulable, "node does not meet criteria")
}
return nil
}
// Score 实现优选逻辑
func (pl *MyPlugin) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
// 计算节点得分
score := pl.calculateScore(pod, nodeName)
return score, nil
}
// 注册插件
var _ framework.FilterPlugin = &MyPlugin{}
var _ framework.ScorePlugin = &MyPlugin{}
func New(_ runtime.Object, handle framework.Handle) (framework.Plugin, error) {
return &MyPlugin{}, nil
}
自定义调度器开发
框架选择
┌─────────────────────────────────────────────────────────────────┐
│ 自定义调度器方案 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 方案1:修改默认调度器 │
│ ├─ 优点:简单,不需要额外组件 │
│ ├─ 缺点:难以维护,每次升级都要修改 │
│ └─ 适用:简单场景 │
│ │
│ 方案2:调度器框架插件(Scheduling Framework) │
│ ├─ 优点:符合 K8s 设计,升级友好 │
│ ├─ 缺点:只能扩展不能完全替换 │
│ └─ 适用:大多数场景 │
│ │
│ 方案3:完全自定义调度器 │
│ ├─ 优点:完全控制调度逻辑 │
│ ├─ 缺点:复杂度高,需要自建调度队列 │
│ └─ 适用:特殊场景(如多租户调度、AI 任务调度) │
│ │
│ 方案4:多调度器(Multiple Schedulers) │
│ ├─ 优点:与默认调度器共存,互不影响 │
│ ├─ 缺点:Pod 需要指定调度器名称 │
│ └─ 适用:不同团队使用不同调度策略 │
│ │
└─────────────────────────────────────────────────────────────────┘
多调度器配置
# custom-scheduler-config.yaml
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
leaderElection:
leaderElect: true
resourceName: my-custom-scheduler
resourceNamespace: kube-system
profiles:
- schedulerName: my-custom-scheduler
pluginConfig:
- name: NodeResourcesFit
args:
scoringStrategy:
strategy: MostRequested
---
# Pod 使用自定义调度器
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
schedulerName: my-custom-scheduler # 指定调度器
containers:
- name: app
image: myapp:1.0
---
# Deployment 使用自定义调度器
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
template:
spec:
schedulerName: my-custom-scheduler # 指定调度器
开发自定义调度器
// cmd/scheduler/main.go
package main
import (
"os"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/component-base/command"
"k8s.io/kubernetes/pkg/scheduler"
"k8s.io/kubernetes/pkg/scheduler/apis/config"
"k8s.io/kubernetes/pkg/scheduler/options"
)
func main() {
// 创建调度器选项
options := options.NewOptions()
// 注册自定义插件
// 可以在此处添加自定义插件
// 运行调度器
command := app.NewSchedulerCommand(
options,
app.WithPlugin("custom-plugin", NewCustomPlugin),
)
os.Exit(command.Execute())
}
调度器插件示例
// 自定义 Filter 插件
type CustomFilterPlugin struct {
handle framework.Handle
config *Config
}
func (pl *CustomFilterPlugin) Name() string {
return "CustomFilter"
}
func (pl *CustomFilterPlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
node := nodeInfo.Node()
// 检查节点标签
if node.Labels["custom-type"] == "gpu" && !needsGPU(pod) {
return framework.NewStatus(framework.Unschedulable, "node is GPU node but pod doesn't need GPU")
}
return nil
}
func NewCustomFilterPlugin(_ runtime.Object, f framework.Handle) (framework.Plugin, error) {
return &CustomFilterPlugin{handle: f}, nil
}
// 注册
var _ framework.FilterPlugin = &CustomFilterPlugin{}
调度优化与调参
调度器调参
# kube-scheduler 配置
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
# 调度器名称
schedulerName: default-scheduler
# Leader 选举
leaderElection:
leaderElect: true
resourceLock: leases
resourceName: kube-scheduler
resourceNamespace: kube-system
# 并行化配置
parallelism:
maxParallelism: 50 # 最大并行调度数
# 调度队列
percentageOfNodesToScore: 50 # 采样百分比,默认 50%
调度器性能优化
// 调度器性能配置
// 1. NodeCache 用于缓存节点信息
type SchedulerOptions struct {
// 节点缓存 TTL
NodeCacheTTL time.Duration
// 并行化
MaxParallelism int
}
// 2. 优化 Filter 性能
// - 使用 NodeCache 减少 API 调用
// - 减少不必要的检查
// 3. Score 优化
// - 使用分数缓存
// - 减少不必要的分数计算
// 4. 批量处理
// - 批量调度多个 Pod
// - 减少调度开销
常见调度问题
┌─────────────────────────────────────────────────────────────────┐
│ 调度问题排查 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 问题1:Pod 长时间 Pending │
│ ├─ 原因:调度器性能不足 │
│ ├─ 解决方案:增加 parallelism │
│ └─ 配置:scheduler.alpha.kubernetes.io/corecheck │
│ │
│ 问题2:节点分数相同导致调度不均 │
│ ├─ 原因:缺少随机化 │
│ └─ 解决方案:Randomize 方法 │
│ │
│ 问题3:调度延迟高 │
│ ├─ 原因:Filter 插件太多 │
│ ├─ 解决方案:禁用不必要的插件 │
│ └─ 配置:enabledPlugins │
│ │
│ 问题4:抢占导致频繁重新调度 │
│ ├─ 原因:优先级设置不当 │
│ └─ 解决方案:合理设置 PriorityClass │
│ │
└─────────────────────────────────────────────────────────────────┘
调度器监控
调度指标
# 查看调度器指标
curl -s localhost:10251/metrics | grep scheduler
# 关键指标:
# - scheduler_schedule_attempts_total
# - scheduler_scheduling_duration_seconds
# - scheduler_pod_scheduling_duration_seconds
# - scheduler_framework_extension_point_duration_seconds
# - scheduler_pod_preemption_victims
# Prometheus 查询
# 调度成功率
sum(rate(scheduler_schedule_attempts_total{result="scheduled"}[5m])) /
sum(rate(scheduler_schedule_attempts_total[5m]))
# 调度延迟 P99
histogram_quantile(0.99,
sum(rate(scheduler_pod_scheduling_duration_seconds_bucket[5m])) by (le)
)
# 插件执行时间
histogram_quantile(0.99,
sum(rate(scheduler_framework_extension_point_duration_seconds_bucket[5m])) by (le, plugin)
)
调度器调试
# 1. 查看调度器日志
kubectl logs -n kube-system kube-scheduler-<node>
# 2. 启用调度器调试日志
# 在 kube-scheduler 配置中添加
# -v=4
# 3. 使用调度器事件
kubectl describe pod <pod-name> | grep -A 10 "Events:"
# 4. 调度器 profiling
curl localhost:10251/debug/pprof/
# 5. 查看调度队列状态
curl localhost:10251/debug/scheduler_cache
高级调度模式
亲和性调度
# Pod 亲和性
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 3
template:
spec:
affinity:
# 与缓存服务器亲和
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: cache
topologyKey: kubernetes.io/hostname
# 与数据库反亲和
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: database
topologyKey: kubernetes.io/hostname
拓扑分布约束
# 拓扑分布约束(Pod Topology Spread)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
replicas: 6
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web-server
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: web-server
资源预留调度
# 为系统预留资源
# kube-apiserver 启动参数
--kube-reserved cpu=500m,memory=1Gi,ephemeral-storage=1Gi
--kube-reserved-cgroup /system.slice/kube-apiserver.service
# 为系统预留 PID
--kube-reserved-pids=1000
# 为 eviction 预留
--eviction-hard memory.available<100Mi,nodefs.available<5%
总结
┌─────────────────────────────────────────────────────────────────┐
│ 核心要点回顾 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 调度框架 │
│ ├── Scheduling Cycle:预选→优选→选择节点 │
│ ├── Binding Cycle:Reserve→Permit→Bind→PostBind │
│ └── 插件扩展点:QueueSort, Filter, Score, Bind... │
│ │
│ 预选算法 │
│ ├── NodeResourcesFit:资源匹配 │
│ ├── TaintToleration:污点容忍 │
│ └── PodAffinity:亲和性 │
│ │
│ 优选算法 │
│ ├── NodeResourcesBalancedAllocation:均衡分配 │
│ ├── ImageLocality:镜像本地性 │
│ └── InterPodAffinity:Pod 亲和性 │
│ │
│ 自定义调度 │
│ ├── 调度器框架插件:扩展 Filter/Score │
│ ├── 多调度器:不同调度器管理不同 Pod │
│ └── 完全自定义:特殊场景 │
│ │
│ 性能优化 │
│ ├── 并行化调度 │
│ ├── 节点采样 │
│ └── 缓存优化 │
│ │
└─────────────────────────────────────────────────────────────────┘
思考题
- 为什么 Kubernetes 采用预选+优选的两阶段调度而不是其他算法?
- 如何设计一个支持多租户的调度策略,实现资源公平分配?
- 在大规模集群中,如何保证调度器的高性能和低延迟?
引用与参考
结语
恭喜你完成了「云原生与 Kubernetes」系列的学习!这个系列涵盖了从基础概念到高级实践的完整内容,希望能帮助你建立扎实的云原生知识体系。
后续学习建议:
- 实践优先:在本地集群(minikube/kind)或云环境实际操作
- 持续跟踪:K8s 版本更新快,关注 release notes
- 深入源码:阅读 K8s 核心组件源码,理解设计理念
- 参与社区:贡献代码、提交 Issue、参与 SIG
推荐资源:
祝你学习愉快!