引言

Kubernetes 调度器(kube-scheduler)是整个容器编排平台的"大脑",它决定了 Pod 应该运行在集群的哪个节点上。看似简单的"放置"决策背后,融合了约束满足、资源优化、亲和性策略、优先级抢占等复杂机制。本文将从调度器架构出发,深入剖析调度框架(Scheduling Framework)、插件机制、优先级与抢占、以及自定义调度器的开发实践。

1. 调度器核心架构

1.1 调度的本质问题

从本质上说,K8s 调度是一个带约束的二维资源分配问题:给定一组 Pod(需求)和一组 Node(资源),找到满足所有硬约束(资源请求、节点选择器、亲和性)的最优放置方案,同时尽可能满足软约束(拓扑分布、负载均衡)。

问题的 NP-hard 特性在于:当 Pod 和 Node 规模增长时,穷举搜索的复杂度指数级上升。因此 K8s 采用了两阶段策略:

  • 过滤阶段(Predicates):快速排除不满足硬约束的节点,缩小候选集
  • 打分阶段(Priorities):对候选节点评分,选择最优节点

1.2 调度周期

每个调度周期(Scheduling Cycle)处理一个 Pod,包含以下步骤:

  1. 入队:Pod 通过 Informer Watch 机制进入调度队列
  2. 出队:调度器从活跃队列取出待调度 Pod
  3. 调度周期:执行 Filter → Score → Reserve → Permit → PreBind → Bind 全流程
  4. 绑定周期:异步执行 PreBind → Bind → PostBind,将调度结果持久化到 etcd

调度周期和绑定周期解耦,使得调度决策(同步)与持久化(异步)可以并行处理多个 Pod,提升吞吐。

2. 调度框架(Scheduling Framework)

自 K8s 1.18 起,调度器引入插件化的调度框架(Scheduling Framework),将原有硬编码的调度逻辑拆分为一系列可扩展的"扩展点"(Extension Points)。每个扩展点都有一组按顺序执行的插件。

2.1 扩展点全景

扩展点时相作用
QueueSort队列排序决定 Pod 在同一优先级下的出队顺序(FIFO/优先级)
PreFilter过滤前预处理预计算信息或验证前置条件(如检查 PVC 是否已绑定)
Filter过滤(硬约束)排除不满足条件的节点(资源不足、节点选择器不匹配)
PostFilter过滤后当所有节点被过滤后触发,用于抢占逻辑(找出可驱逐的低优先级 Pod)
PreScore打分前预处理预计算打分所需的共享数据
Score打分(软偏好)对候选节点打分(0-100),分数加权求和
NormalizeScore分数归一化将分数映射到统一范围后再加权聚合
Reserve预留在 Pod 绑定前预留资源(防止资源冲突)
Permit许可决定 Pod 是否可以/延迟/拒绝绑定(实现 Coscheduling)
PreBind绑定前执行绑定前的准备工作(如挂载网络卷)
Bind绑定将 Pod 与 Node 的绑定关系写入 etcd
PostBind绑定后绑定完成后的清理或日志记录
Unreserve释放预留绑定失败或 Pod 被删除时释放预留资源

2.2 内置插件作用对照

理解内置插件有助于理解默认调度行为:

  • NodeResourcesFit(Filter/Score):检查 CPU/内存请求是否满足,按资源使用率打分(LeastAllocated/MostAllocated)
  • NodeName(Filter):匹配 spec.nodeName(直接指定节点时)
  • NodePorts(Filter/PreBind):确保节点上所需端口未被占用
  • NodeAffinity(Filter/Score):实现 nodeSelector 和 requiredDuringScheduling 节点亲和
  • PodAffinity(Filter/Score/PreScore):实现 Pod 间亲和与反亲和
  • TaintToleration(Filter/Score):污点容忍匹配
  • TopologySpreadConstraints(Filter/Score):拓扑域均匀分布
  • VolumeBinding(Filter/PreBind/Reserve):CSI/持久卷绑定检查
  • PrioritySort(QueueSort):按 PriorityClass 优先级排序出队
  • DefaultBinder(Bind):默认绑定插件,写入 etcd

3. 优先级与抢占(Priority and Preemption)

当集群资源不足时,调度器需要在高优先级 Pod 无法调度时,通过驱逐低优先级 Pod 来"腾出空间"。

3.1 抢占流程

  1. PostFilter 扩展点触发抢占调度(schedulingCycle 中 Filter 全部失败后)
  2. 计算 Pod 的所有可抢占节点(Potential Nodes),即驱逐若干低优先级 Pod 后资源可用的节点
  3. 执行 Dry-Run 预绑定(Assume),验证驱逐后是否能调度成功
  4. 选择"伤害最小"的节点(Minimum Victims,即驱逐的 Pod 数量最少、优先级最低的组合)
  5. 发送驱逐请求给 API Server,等待低优先级 Pod 优雅终止(Graceful Termination)
  6. 低优先级 Pod 终止后释放资源,高优先级 Pod 重新入队并调度

3.2 PDB(Pod Disruption Budget)保护

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: nginx-pdb
spec:
  minAvailable: 2     # 或 maxUnavailable: 1
  selector:
    matchLabels:
      app: nginx

PDB 限制了同一时间可被驱逐的 Pod 数量。抢占调度必须尊重 PDB 约束——即使为了高优先级 Pod,也不能违反 PDB 的 minAvailable/maxUnavailable 限制。

4. 高级调度策略

4.1 拓扑分布约束(Topology Spread)

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: my-service
    matchLabelKeys:
      - pod-template-hash
    nodeAffinityPolicy: Honor
    nodeTaintsPolicy: Honor

maxSkew 控制不同拓扑域中 Pod 数量的最大差异。topologyKey 定义拓扑域(可用 zone、region、rack 等)。matchLabelKeys 用于排除滚动更新时新旧 Pod 属于同一副本集的 skew 计算。

4.2 亲和性调度进阶

K8s 提供了两套亲和机制:

  • Node Affinity(K8s 1.4+):将 Pod 调度到匹配节点标签的节点,支持 required/Preferred 两级
  • Inter-Pod Affinity/AntiAffinity(K8s 1.6+):基于 Pod 标签的关系约束,实现 Pod 间的"抱团"或"避让"

Pod AntiAffinity 的典型用法是高可用部署:

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app: database
        topologyKey: kubernetes.io/hostname
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchLabels:
              app: database
          topologyKey: topology.kubernetes.io/zone

4.3 多调度器

K8s 支持同时在集群中运行多个调度器,每个调度器有独立名称。通过 spec.schedulerName 指定使用哪个调度器:

apiVersion: v1
kind: Pod
metadata:
  name: custom-scheduled-pod
spec:
  schedulerName: my-custom-scheduler
  containers:
    - name: app
      image: nginx

5. 自定义调度器开发

5.1 开发模式

自定义调度器有两种开发模式:

  • 调度器扩展(Scheduler Extender):通过 Webhook 将 Filter/Score 远程调用到外部服务,适合渐进式集成
  • 独立调度器(Out-of-tree Scheduler):基于 K8s 调度框架库(k8s.io/kube-scheduler)构建独立的二进制,拥有完全控制力

5.2 自定义调度器骨架

使用 k8s.io/kube-scheduler 框架构建自定义调度器:

package main

import (
    "os"
    "k8s.io/component-base/cli"
    "k8s.io/kube-scheduler/config/v1beta3"
    "k8s.io/kube-scheduler/scheduler/framework/runtime"
    "k8s.io/kube-scheduler/scheduler/plugins/defaultbinder"
    "k8s.io/kube-scheduler/scheduler/plugins/defaultpreemption"
)

func main() {
    // 默认配置
    cfg := v1beta3.KubeSchedulerConfiguration{}
    // 注册自定义插件
    rts, err := runtime.NewRuntime(componentConfig, registryWithCustomPlugins...)
    // ...
    cmd := app.NewSchedulerCommand(
        app.WithPlugin("CustomScheduler", NewCustomScheduler),
    )
    if err := cmd.Execute(); err != nil {
        os.Exit(1)
    }
}

5.3 自定义 Scheduler Profile 配置

无需编写代码也可以通过 Scheduler Profile 微调内置插件行为:

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: default-scheduler
    plugins:
      score:
        disabled:
          - name: NodeResourcesFit        # 禁用默认资源评分
        enabled:
          - name: MyCustomScore           # 启用自定义评分
            weight: 2
      preScore:
        enabled:
          - name: MyCustomPreScore
    pluginConfig:
      - name: NodeResourcesFit
        args:
          scoringStrategy:
            type: MostAllocated           # 反向评分:优先填满单节点
            resources:
              - name: cpu
                weight: 1
              - name: memory
                weight: 3                   # 内存权重更高

6. 性能调优与排障

6.1 调度器性能指标

调度器暴露 Prometheus 监控:

  • scheduler_scheduling_attempts_total:调度尝试成功/失败次数
  • scheduler_scheduling_duration_seconds:调度延迟(p99 通常应 < 100ms)
  • scheduler_pending_pods:各类队列中等待调度的 Pod 数
  • scheduler_preemption_attempts_total:抢占次数及成功率

6.2 常见排障场景

Q: Pending Pod 为什么无法调度?

排查顺序:

  1. 检查 kubectl describe pod 的 Events,查看 Filter 失败的节点和原因
  2. 检查节点资源容量 vs 请求量:kubectl top nodes
  3. 检查亲和性/污点配置是否过于严格
  4. 检查 PVC 是否已成功绑定(WaitForFirstConsumer 模式下 PVC 延迟绑定)

Q: 如何排查调度延迟过高?

  • 调度器本身瓶颈:查看 scheduling_duration_seconds 直方图,定位是 Filter 还是 Score 阶段慢
  • API Server 压力大:调度频率过高(大量 Pod 同时创建/重启),考虑启用 PercentageOfNodesToScore 限制打分节点数
  • etcd 延迟:绑定周期写 etcd 慢,影响调度吞吐

6.3 PercentageOfNodesToScore 调优

大规模集群(>5000 节点)中,默认对所有节点打分成本过高。调度器提供 PercentageOfNodesToScore 配置,限制打分的节点比例(按最快原则选择该百分比的随机节点打分):

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
percentageOfNodesToScore: 10   # 仅对 10% 节点打分(最低 5%,默认 50%)

7. 总结与选型建议

场景推荐策略
高可用部署Pod AntiAffinity(required) + TopologySpread(maxSkew=1)
资源利用率最优NodeResourcesFit(LeastAllowed) + 紧凑打分
混合负载/批处理低优先级 + 抢占 + PriorityClass
GPU 共享调度自定义 Score 插件(Binpack/Spread 策略)
边缘节点调度NodeAffinity + Taint/Toleration + 自定义拓扑约束
大规模集群(>5000 节点)PercentageOfNodesToScore 调低 + 自定义调度器分区
延迟敏感型应用NodeResourcesFit(MostAllocated 留余量) + Preemption 禁用

K8s 调度器的核心价值在于声明式约束 + 自动化优化:开发者只需声明"需要什么资源"和"偏好什么位置",调度器自动找到最优放置。理解其内部机制,能帮助我们在生产环境中做出更合理的设计决策。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部