{* YBB Header v4.0 — Alpine.js + 自定义 CSS *}

前言

Kubernetes(K8s)作为容器编排的事实标准,其调度系统是整个集群的大脑。Pod 作为 K8s 中最小的调度单元,如何被高效、合理地分配到集群节点上,直接影响着资源利用率和应用性能。本文将深入剖析 K8s 调度器的工作原理,并结合生产环境中的常见场景,分享调度优化的实战经验。

1. Kubernetes 调度器架构总览

Kubernetes Scheduler(kube-scheduler)是独立的二进制进程,核心职责是为新创建的 Pod 选择最优的节点。其工作流程可以概括为以下四个阶段:

待调度Pod → 调度队列(Queue) → 过滤(Filtering) → 打分(Scoring) → 绑定(Binding) → 目标节点

调度器采用插件化架构(Scheduling Framework),每个阶段都提供了扩展点:

  • QueueSort:定义 Pod 在调度队列中的排序策略
  • PreFilter:执行预检查,验证 Pod 的基本信息
  • Filter:过滤不满足条件的节点(如资源不足、标签不匹配)
  • PostFilter:过滤后触发,可用于记录日志或触发抢占地逻辑
  • PreScore:打分前的准备工作
  • Score:对过滤后的节点评分,分数范围 0-100
  • Reserve/Nominate:预留节点,支持抢占场景
  • Permit:最终审批,可设置等待、拒绝或允许
  • PreBind/PostBind:绑定前后的钩子

2. 调度周期深入解析

2.1 调度队列与优先级

调度器内部维护三级队列结构:

activeQ(活跃队列): 待调度的 Pod,按优先级排序
unschedulableQ(不可调度队列): 暂时无法调度的 Pod,等待重试
backoffQ(退避队列): 绑定失败的 Pod,按退避时间等待

关键源码位置:pkg/scheduler/internal/heap/heap.go。队列排序使用 PriorityCompare 函数,优先比较 Pod 的优先级数值,其次比较入队时间戳。

2.2 过滤阶段 —— 淘汰不合格节点

Filter 插件按顺序执行,任何一个返回 Error 即淘汰该节点。核心过滤器包括:

过滤器功能
NodeResourcesFit检查 CPU/内存资源是否满足,支持超售
NodeName匹配 metadata.name 精确指定节点
NodeAffinity基于节点标签的亲和性选择
TaintToleration检查 Pod 是否容忍节点的 Taint 污点
NodeUnschedulable过滤被标记为不可调度的节点
VolumeBinding检查存储卷是否可绑定到节点

2.3 打分阶段 —— 量化节点优劣

打分插件返回 0-100 的分数,最终加权求和。常用打分器:

  • RequestedToCapacityRatio:基于资源请求率与容量的比值打分,支持偏斜分布
  • NodeResourcesLeastAllocated:优选资源剩余最多的节点(默认策略)
  • NodeResourcesMostAllocated:优选资源使用最多的节点(紧凑装箱)
  • ImageLocality
  • InterPodAffinity:基于 Pod 间亲和性聚集或分散 Pod
  • TopologySpreadConstraints:跨拓扑域均匀分布 Pod

3. 亲和性与反亲和性实战

3.1 Node Affinity(节点亲和性)

节点亲和性让 Pod 吸引到特定节点。两种模式:

# requiredDuringSchedulingIgnoredDuringExecution —— 硬性要求
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: node-role
          operator: In
          values: ["gpu-worker"]

# preferredDuringSchedulingIgnoredDuringExecution —— 软性偏好
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 80
      preference:
        matchExpressions:
        - key: disktype
          operator: In
          values: ["ssd"]

3.2 Pod Affinity/AntiAffinity(Pod 间亲和与反亲和)

Pod 间亲和性使 Pod 倾向于调度到已运行特定 Pod 的节点,反亲和性则倾向于避开。典型场景:

# 反亲和性 —— 将同一应用的多副本分散在不同节点
affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchExpressions:
        - key: app
          operator: In
          values: ["web-frontend"]
      topologyKey: kubernetes.io/hostname

注意:Pod 亲和性/反亲和性会带来调度性能开销,大规模集群中建议谨慎使用 required 模式。

4. 生产调度优化策略

4.1 拓扑分布约束 TopologySpreadConstraints

K8s 1.19+ 引入的 TopologySpreadConstraints 是实现高可用部署的最佳实践:

topologySpreadConstraints:
- maxSkew: 2
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: ScheduleAnyway
  labelSelector:
    matchLabels:
      app: payment-service

该配置确保 payment-service 在各可用区之间的副本数差异不超过 2,即使无法满足也不会阻止调度(ScheduleAnyway),避免因单区故障导致无法部署。

4.2 优先级与抢占

设置 PriorityClass 控制 Pod 被调度的顺序,当集群资源不足时高优先级 Pod 可抢占低优先级 Pod 的资源:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: production-critical
value: 1000000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
description: "生产核心服务最高优先级"

4.3 资源请求与限制的最佳实践

  • 设置合理的 requests:调度器依据 requests 做决策,通常设为 P50 资源使用量
  • limits 保持适度:limits 过高会导致资源浪费,过低可能触发 OOM Kill
  • Burstable 策略:非关键服务的 limits 可设为 requests 的 1.5-2 倍,允许弹性使用资源
  • Guaranteed QoS:关键服务设置 requests=limits,获得最高服务质量等级

4.4 调度器性能调优

大规模集群(5000+ 节点)中调度器可能成为瓶颈,关键配置:

# kube-scheduler 参数
--percent-of-nodes-to-score=30   # 打分阶段只评估部分节点(默认50%)
--bind-timeout=600ms              # 绑定超时时间

# 高级调度配置
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
  pluginConfig:
  - name: NodeResourcesFit
    args:
      scoringStrategy:
        type: RequestedToCapacityRatio
        requestedToCapacityRatio:
          shape:
          - utilization: 0
            score: 0
          - utilization: 50
            score: 6
          - utilization: 100
            score: 10

5. 自定义调度器开发

当默认调度器无法满足特殊需求时(如 GPU 亲和调度、数据本地性定制),可以基于 Scheduling Framework 开发自定义扩展点插件。

最小化自定义调度器示例(Go):

package main

import (
    "context"
    v1 "k8s.io/api/core/v1"
    "k8s.io/kubernetes/pkg/scheduler/framework"
)

type CustomScheduler struct{}

func (cs *CustomScheduler) Name() string { return "custom-scheduler" }

func (cs *CustomScheduler) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
    // 自定义过滤逻辑:仅允许带有 custom-allow=true 标签的节点
    if nodeInfo.Node().Labels["scheduler-hint"] != "enabled" {
        return framework.NewStatus(framework.Unschedulable, "node does not have required label")
    }
    return nil
}

func (cs *CustomScheduler) Score(ctx context.Context, state *framework.CycleState, pod *p1.Pod, nodeName string) (int64, *framework.Status) {
    // 基于自定义指标打分
    return calculateCustomScore(pod, nodeName), nil
}

6. 总结与展望

Kubernetes 调度器经过多年演进,已从简单的 nodeName/nodeSelector 发展为高度可扩展的调度框架。理解调度机制的核心原理,是构建稳定高效容器平台的基础。

随着云原生生态的发展,调度器也在持续进化:Kueue 项目带来了更高级的批处理能力,Dynamic Resource Allocation 为 GPU 等扩展资源提供了灵活分配方案,Cluster Autoscaler 与调度器的协同让集群具备了弹性伸缩能力。掌握这些机制,将帮助你在生产环境中构建更健壮、高效的云原生基础设施。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部