深入源码: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                          │
│  └── 完全自定义:特殊场景                                       │
│                                                                 │
│  性能优化                                                       │
│  ├── 并行化调度                                                │
│  ├── 节点采样                                                  │
│  └── 缓存优化                                                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

思考题

  1. 为什么 Kubernetes 采用预选+优选的两阶段调度而不是其他算法?
  2. 如何设计一个支持多租户的调度策略,实现资源公平分配?
  3. 在大规模集群中,如何保证调度器的高性能和低延迟?

引用与参考

  1. Scheduler Architecture
  2. Scheduling Plugins
  3. kube-scheduler 源码

结语

恭喜你完成了「云原生与 Kubernetes」系列的学习!这个系列涵盖了从基础概念到高级实践的完整内容,希望能帮助你建立扎实的云原生知识体系。

后续学习建议

  1. 实践优先:在本地集群(minikube/kind)或云环境实际操作
  2. 持续跟踪:K8s 版本更新快,关注 release notes
  3. 深入源码:阅读 K8s 核心组件源码,理解设计理念
  4. 参与社区:贡献代码、提交 Issue、参与 SIG

推荐资源

祝你学习愉快!