引言

Kubernetes 调度器(kube-scheduler)是容器编排系统的核心决策引擎——它决定了每个 Pod 最终运行在集群的哪个节点上。在大规模生产环境中,调度的效率与公平性直接影响资源利用率、服务稳定性以及成本控制。本文从源码级的调度框架出发,完整拆解 Predicate 优选 → Score 打分 → PreScore/PostBind 的全链路流程,并结合 NodeAffinity、PodAffinity、Taint/Toleration、TopologySpreadConstraints 等核心调度原语,给出一套经过生产验证的调优策略。

一、调度框架总览

Kubernetes 调度器采用插件化的 Scheduling Framework(自 K8s 1.16 起 GA),将整个调度流程拆分为多个可扩展的阶段(Plugin Extension Points):

扩展点作用执行顺序
QueueSort对调度队列中的 Pod 排序(FIFO / 优先级)调度开始前
PreFilter预检查 Pod 合法性 + 计算缓存数据Predicate 之前
Filter (Predicate)过滤不满足硬性条件的节点第一轮筛选
PostFilterFilter 全失败后的挽救(如抢占)Filter 全部失败时
PreScoreScore 打分前准备(统一信息搜集)Score 之前
Score对候选节点评分(0-100 加权)第二轮打分排序
Reserve预留资源(防止后续 Pod 竞争)选中节点后
Permit最终同意/拒绝/等待(支持延迟绑定)绑定之前
PreBind / Bind / PostBind绑定操作:写 etcd 记录最终绑定

1.1 调度队列演进

K8s 1.26+ 引入了 Multi Scheduling Profiles 和增强的调度队列语义。调度队列内部维护三个子队列:

  • activeQ:待调度 Pod(堆实现,按优先级出队)
  • podBackoffQ:调度失败进入退避等待的 Pod(指数退避)
  • unschedulableQ:因资源不足暂时无法调度的 Pod(周期性重试或事件触发)

每个调度周期(scheduler.scheduleOne())优先从 activeQ 取 Pod,执行完整的调度流水线后,失败的 Pod 根据退避策略进入 podBackoffQ。

1.2 并发调度

自 K8s 1.22 起,调度器默认开启并行调度。通过 parallelism 字段控制同时运行的 goroutine 数(默认 16)。并行调度依赖 Scheduler Cache 的快照机制——每个 goroutine 持有一份节点信息的只读快照,避免全局锁竞争。

二、Predicate 优选:硬性条件过滤

Predicate 是过滤函数,判断节点是否满足 Pod 调度的硬性条件。默认调度器内置了约 12 种 Predicate 插件,执行时遍历所有节点,过滤结果为满足条件的候选节点列表。

2.1 核心 Predicate 插件分析

NodeName:精确匹配 spec.nodeName,仅用于静态 Pod 或手动绑定场景。

NodeSelector:基于节点标签的精确匹配(spec.nodeSelector)。实现极其简单——直接检查子集关系。

NodeAffinity:表达式引擎,支持 In、NotIn、Exists、DoesNotExist、Gt、Lt 操作符。支持两种模式:

  • requiredDuringSchedulingIgnoredDuringExecution:硬性过滤,不满足直接淘汰
  • preferredDuringSchedulingIgnoredDuringExecution:软性偏好,在 Score 阶段加权

PodAffinity / PodAntiAffinity:基于 Pod 间亲和性的过滤,依赖节点上已有 Pod 的标签实现拓扑感知。复杂度最高,在大规模集群中是性能瓶颈。

TaintToleration:检查 Pod 的 Toleration 是否能容忍节点的 Taint。

NodeResourcesFit:核心资源检查——比较节点可用资源与 Pod 的 resource.requests。K8s 1.27+ 引入了更丰富的 Score 策略。

2.2 默认 Predicate 执行流水线

完整执行顺序如下:

  1. NodeUnschedulable:过滤被 cordon 的节点
  2. NodeName:检查 nodeName 精确匹配
  3. NodePorts:端口冲突检查
  4. NodeAffinity:节点亲和 Label 匹配
  5. TaintToleration:污点容忍检查
  6. NodeLabel:自定义标签过滤
  7. NodeResourcesFit:CPU/Memory 资源充足性
  8. VolumeBinding:PVC/PV 拓扑约束
  9. NodeVolumeLimits:CSI 卷数量限制
  10. PodTopologySpread:拓扑分布约束
  11. InterPodAffinity:Pod 亲和与反亲和

三、Score 打分:多维加权排序

Score 阶段对通过 Predicate 的候选节点进行量化评分(0-100 分 × 插件权重),最终总分最高的节点胜出。

3.1 核心 Score 插件分析

NodeResourcesFit(权重默认 1):LeastAllocated / MostAllocated / BalancedResourceAllocation 三种模式。

ImageLocality(权重 1):节点已存在 Pod 所需镜像则加分,避免冷启动拉镜像延迟。

InterPodAffinity(权重 1):Pod 亲和/反亲和偏好打分。

NodePreferAvoidPods(权重 10000):DaemonSet 强制调度,极高权重。

PodTopologySpread(权重 2):拓扑分布约束打分。

3.2 线性加权评分公式

最终总分计算方式:finalScore = Σ (pluginScore_i × weight_i)。被选中的节点满足 argmax 条件。这种设计允许运维人员通过调整权重来微调调度偏好。

四、NodeAffinity vs PodAffinity 深度对比

维度NodeAffinityPodAffinity
作用对象基于节点标签选择节点基于 Pod 间关系选择节点
典型场景SSD 机型、GPU 机型、区域约束协同部署 / 反亲和打散
性能开销O(n) 低O(n*m) 高

4.1 NodeAffinity 实践示例(GPU 专用节点)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-worker
spec:
  template:
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: nvidia.com/gpu.product
                operator: In
                values:
                - A100-SXM4-80GB
                - H100-80GB
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            preference:
              matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values: ["cn-shanghai-1a"]
      containers:
      - name: worker
        resources:
          requests:
            nvidia.com/gpu: 1
            memory: "32Gi"
            cpu: "8"

4.2 PodAffinity + TopologySpread 反亲和打散

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-frontend
spec:
  replicas: 6
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: web-frontend
            topologyKey: kubernetes.io/hostname
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: web-frontend

这种配置确保 6 个副本至少分散在 3 个可用区,并且同一节点不会运行同一应用的两个副本。

五、Taint 与 Toleration:污点隔离机制

Taint 是节点层面的"排斥标记",Toleration 是 Pod 的"容忍声明"。两者配合实现节点独占或特殊用途隔离。

5.1 三种 Taint 效果

  • NoSchedule:禁止不匹配的 Pod 调度到该节点(已有 Pod 不受影响)
  • PreferNoSchedule:尽量避免,但不强制
  • NoExecute:不仅禁止新调度,还会驱逐已在运行但不匹配的 Pod

5.2 经典场景

专用节点池:给 GPU 节点打上 nvidia.com/gpu=true:NoSchedule,只有带对应 Toleration 的 Pod 才能调度上去。

节点排空:维护时先 cordon 再 drain,触发 NoExecute 逐步驱逐。

故障隔离:Node Problem Detector 自动给不健康节点打 NoExecute 污点。

5.3 Toleration 匹配规则

tolerations:
- key: "nvidia.com/gpu"
  operator: "Equal"
  value: "true"
  effect: "# NoSchedule"
# 容忍所有不可调度节点(DaemonSet 常用)
- key: "node.kubernetes.io/unschedulable"
  operator: "Exists"
  effect: "NoSchedule"

六、TopologySpreadConstraints 拓扑分布约束

这是 K8s 1.19+ 引入的跨可用区/机架最佳实践,解决了 PodAntiAffinity 在大规模集群中"严格反亲和导致调度失败"的痛点。

6.1 核心参数

  • maxSkew:任意两个拓扑域中 Pod 数的最大差异(越小越均匀)
  • topologyKey:定义拓扑域的标签键
  • whenUnsatisfiable:DoNotSchedule 严格 / ScheduleAnyway 退化为软偏好
  • labelSelector:匹配要统计的 Pod 标签

6.2 多维度拓扑约束

可以堆叠多个 TopologySpreadConstraints 实现"先按可用区分 → 再按节点分"的两级均匀打散:

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels: {app: payment-service}
- maxSkew: 2
  topologyKey: kubernetes.io/hostname
  whenUnsatisfiable: ScheduleAnyway
  labelSelector:
    matchLabels: {app: payment-service}

七、Scheduler Extender 扩展机制

当内置调度器无法满足复杂策略时,可通过 HTTP/HTTPS Scheduler Extender 扩展自定义调度逻辑。

7.1 配置方式

{
  "kind": "Policy",
  "apiVersion": "v1",
  "extenders": [
    {
      "urlPrefix": "http://custom-scheduler.kube-system:8080",
      "filterVerb": "filter",
      "prioritizeVerb": "prioritize",
      "bindVerb": "bind",
      "weight": 10,
      "nodeCacheCapable": true,
      "managedResources": [
        {"name": "example.com/sriov", "ignoredByScheduler": true}
      ]
    }
  ]
}

7.2 Scheduling Framework Plugin(推荐)

K8s 1.18+ 推荐使用 Go 插件直接注册到调度器,而非 HTTP Extender(延迟更低、类型安全):

// 自定义 Filter 插件示例
type SRIOVFilter struct{}

func (pl *SRIOVFilter) Name() string { return "SRIOVFilter" }

func (pl *SRIOVFilter) Filter(ctx context.Context, state *framework.CycleState,
    pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
    if pod.Annotations["sriov.network"] == "" {
        return framework.NewStatus(framework.Success)
    }
    if sriovAvailable := nodeInfo.Node().Labels["sriov.capable"]; sriovAvailable != "true" {
        return framework.NewStatus(framework.Unschedulable, "node lacks SR-IOV capability")
    }
    return framework.NewStatus(framework.Success)
}

八、生产级调优实践

8.1 避免 OOM:合理设置 Resource Requests

  • Requests = P50 日常使用量
  • Limits = P99 峰值使用量(超请求部分被限但不杀)
  • 使用 VPA Recommendation 模式辅助调参

8.2 大规模集群性能调优

  • PercentageOfNodesToScore:百分比采样评分,设为 10% 则只对 10% 的候选节点全量打分
  • 500+ 节点建议设为 5-20%
  • 启用 Scheduler Cache Snapshot(默认开启)减少快照复制开销

8.3 多可用区高可用部署模板

apiVersion: apps/v1
kind: Deployment
metadata:
  name: core-api
spec:
  replicas: 9
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    spec:
      terminationGracePeriodSeconds: 60
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchLabels: {app: core-api}
              topologyKey: topology.kubernetes.io/zone
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector: {matchLabels: {app: core-api}}
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: ScheduleAnyway
        labelSelector: {matchLabels: {app: core-api}}
      containers:
      - name: api
        resources:
          requests: {cpu: "2", memory: "4Gi"}
          limits: {cpu: "4", memory: "8Gi"}

8.4 抢占(Preemption)机制

当高优先级 Pod 因资源不足无法调度时,调度器会尝试抢占低优先级 Pod 的资源:模拟逐出 → 找最优驱逐集 → 发送驱逐请求。抢占受 PriorityClassName 和 API Server 的 PriorityClass 控制。

九、调度器性能基准

9.1 调度延迟指标

  • 500 节点 / 5000 Pod 集群:P99 < 200ms
  • 1000 节点 / 30000 Pod 集群:P99 < 1s
  • PercentageOfNodesToScore=5% 可降低 40-60% 调度延迟

9.2 调度队列深度告警

  • scheduler_pending_pods:pending 态 Pod 超过 50 触发告警
  • scheduler_schedule_attempts_total:failed/unschedulable 异常增长时检查节点资源

十、2026 调度器演进趋势

  • Dynamic Resource Allocation(Beta):CSI 设备类资源的延迟绑定与分配
  • Pod Scheduling Readiness(GA):不满足条件前不进入调度队列
  • Kueue 项目整合:批处理/ML 训练的队列式资源配额管理
  • TopologyAwareScheduling + NUMA:HPC 和 AI 推理的多 NUMA 节点亲和性优化

总结

Kubernetes 调度器从简单的标签匹配演进为高度可扩展的插件化框架,Predicate → Score → Bind 的全链路设计兼顾了灵活性与性能。在生产环境中,建议优先使用 NodeAffinity + TopologySpreadConstraints + PodAntiAffinity 三件套保证高可用分布;对于 GPU/FPGA 等稀缺资源搭配 Taint/Toleration 专用节点池;最后通过 PercentageOfNodesToScore 和调度器并行度参数应对大规模集群场景。深入理解调度源码框架后,还可以通过自定义 Scheduling Framework Plugin 实现业务专属调度策略。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部