一个kubectl create deployment命令敲下去,几秒钟后Pod就运行起来了。这看似简单的背后,调度器完成了从资源评估、节点筛选、优先级排序到最终绑定的完整流程。当你在生产环境遇到Pod卡在Pending状态,或者发现集群节点负载严重不均衡时,理解调度器的工作原理就成为排查问题的关键。

调度的核心流程:从过滤到打分

Kubernetes调度器的工作可以概括为两个阶段:过滤(Filtering)和打分(Scoring)。在代码中,这两个阶段曾被称为Predicates和Priorities,v1.15之后引入调度框架(Scheduling Framework)重构了整个架构,但核心逻辑保持不变。

过滤阶段遍历集群中所有节点,排除那些不满足Pod运行条件的节点。如果Pod需要4核CPU,而某节点只有2核可用,这个节点就会被过滤掉。经过过滤后得到的节点集合称为可行节点(Feasible Nodes)。

打分阶段对每个可行节点进行评分,分数最高的节点赢得Pod的调度权。评分考虑多维因素:资源利用率、镜像本地性、拓扑分布约束等。默认调度器使用加权求和的方式综合所有评分维度。

flowchart TD
    A[Pod进入调度队列] --> B[从队列取出待调度Pod]
    B --> C[PreFilter阶段]
    C --> D[Filter阶段: 遍历所有节点]
    D --> E{是否有可行节点?}
    E -->|否| F[返回失败<br>Pod进入backoff队列]
    E -->|是| G[PreScore阶段]
    G --> H[Score阶段: 对可行节点打分]
    H --> I[归一化分数]
    I --> J[加权求和选出最优节点]
    J --> K[Reserve阶段]
    K --> L[Permit阶段]
    L --> M{是否允许调度?}
    M -->|否| N[Unreserve阶段]
    M -->|是| O[PreBind阶段]
    O --> P[Bind阶段: 更新API Server]
    P --> Q[PostBind阶段]
    Q --> R[调度完成]

整个调度周期与API Server的交互通过绑定(Binding)完成。调度器不会直接在节点上创建Pod,而是向API Server发送Binding对象,记录Pod应该运行在哪个节点上。kubelet监听到Binding后,才会在本地创建并启动容器。这种设计保证了控制平面的统一性和状态的一致性。

调度器采用非抢占式(non-preemptive)策略作为默认行为。当没有节点满足Pod需求时,调度器不会主动驱逐已有Pod来腾出空间,除非Pod配置了优先级并启用了抢占机制。这种保守策略避免了集群状态的剧烈波动。

调度框架:十二个扩展点重构调度逻辑

v1.15引入的调度框架(Scheduling Framework)将调度流程拆分为十二个扩展点,每个扩展点都可以通过插件实现自定义逻辑。这套架构设计借鉴了Kubernetes API Server的aggregator模式,实现了核心调度逻辑与扩展逻辑的解耦。

扩展点按照调度周期的顺序排列:

QueueSort控制Pod在调度队列中的顺序。默认插件根据优先级和等待时间排序,高优先级Pod优先调度。自定义QueueSort插件可以实现更复杂的排队策略,比如基于租户公平性或SLA等级。

PreFilter在过滤之前运行,用于预处理Pod信息或检查前置条件。例如,NodeResourcesFit插件的PreFilter会计算Pod的资源需求总和,为后续过滤阶段做准备。如果PreFilter返回错误,调度周期立即终止。

Filter是过滤阶段的扩展点,负责排除不满足条件的节点。一个节点必须通过所有Filter插件才算可行节点。任何Filter返回失败都会导致该节点被排除。

PostFilter在Filter阶段之后、没有可行节点时运行。它主要用于实现抢占逻辑——当调度失败时,尝试驱逐低优先级Pod为新Pod腾出空间。Preempt插件的PostFilter实现了这一逻辑。

PreScore在打分之前运行,用于生成打分阶段所需的共享状态。InterPodAffinity插件的PreScore会预先计算所有Pod的亲和性拓扑,避免Score阶段重复计算。

Score对每个可行节点打分,分数范围0-100。多个Score插件的分数通过加权平均合并。NodeResourcesFit插件的Score倾向于选择资源最充足的节点,而ImageLocality倾向于选择已有镜像的节点。

Reserve在绑定之前预留节点资源。因为绑定是异步的,调度器需要先"预订"资源,防止其他Pod同时调度到同一节点造成资源超额分配。Reserve阶段失败的插件必须实现Unreserve方法进行回滚。

Permit是绑定前的最后一道关卡,可以实现延迟绑定或绑定拒绝。Volcano等批调度器利用Permit实现Gang Scheduling——等待所有同组Pod都准备好后同时绑定。

PreBind在绑定之前执行必要的准备工作。例如,volumeBinding插件的PreBind会等待PersistentVolume绑定完成。

Bind真正执行Pod到节点的绑定。默认调度器通过向API Server发送Binding对象完成绑定。自定义Bind插件可以实现不同的绑定策略。

PostBind在绑定成功后运行,用于清理或通知。它可以记录调度延迟指标或发送事件通知。

Unreserve在调度失败时执行清理。它在Reserve或Permit阶段失败后运行,释放已预留的资源。

flowchart LR
    subgraph QueueSort
        A[Pod排序]
    end
    
    subgraph SchedulingCycle[调度周期]
        direction LR
        B[PreFilter] --> C[Filter]
        C --> D[PostFilter]
        D --> E[PreScore]
        E --> F[Score]
        F --> G[Reserve]
        G --> H[Permit]
    end
    
    subgraph BindingCycle[绑定周期]
        direction LR
        I[PreBind] --> J[Bind]
        J --> K[PostBind]
    end
    
    L[Unreserve]
    
    A --> B
    H --> |成功| I
    H --> |失败| L
    L --> A

调度器配置通过KubeSchedulerConfiguration资源定义:

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: default-scheduler
    plugins:
      queueSort:
        enabled:
          - name: PrioritySort
      preFilter:
        enabled:
          - name: NodeResourcesFit
          - name: NodePorts
          - name: PodTopologySpread
          - name: InterPodAffinity
      filter:
        enabled:
          - name: NodeUnschedulable
          - name: NodeName
          - name: NodePort
          - name: NodeAffinity
          - name: TaintToleration
          - name: NodeResourcesFit
      score:
        enabled:
          - name: NodeResourcesBalancedAllocation
            weight: 1
          - name: NodeResourcesFit
            weight: 1
          - name: ImageLocality
            weight: 1
          - name: InterPodAffinity
            weight: 1
          - name: NodeAffinity
            weight: 1
    pluginConfig:
      - name: NodeResourcesFit
        args:
          scoringStrategy:
            type: LeastAllocated
            resources:
              - name: cpu
                weight: 1
              - name: memory
                weight: 1

默认插件覆盖了资源匹配、节点亲和性、污点容忍、拓扑分布等核心功能。理解默认插件的行为对于调试调度问题至关重要。

资源管理:Requests、Limits与QoS的三角关系

Kubernetes的资源模型建立在Requests和Limits两个概念之上。Requests定义了Pod所需的最小资源量,调度器以此为依据进行调度决策。Limits定义了Pod可使用的最大资源量,运行时系统以此为上限进行资源控制。

resources:
  requests:
    cpu: "500m"      # 调度时保证分配0.5核
    memory: "512Mi"  # 调度时保证分配512MB
  limits:
    cpu: "2"         # 运行时最多使用2核
    memory: "1Gi"    # 运行时最多使用1GB

调度器只关心Requests,因为它需要保证每个Pod都能获得声明的最小资源。Limits对调度器没有影响——一个节点上所有Pod的Limits之和可能远超节点实际资源,这在Burstable工作负载中很常见。

CPU是一种可压缩资源(compressible resource),当容器超过CPU limit时,内核通过throttling限制其使用。容器不会因为CPU超限而被杀死,只是变慢。内存是不可压缩资源(incompressible resource),超过memory limit的容器会被OOM Killed。

基于Requests和Limits的配置,Kubernetes自动为Pod分配QoS(Quality of Service)等级。QoS等级决定了节点资源不足时的驱逐顺序:

flowchart TD
    A[Pod资源配置] --> B{所有容器设置了<br>requests和limits?}
    B -->|否| C{部分容器设置了<br>requests或limits?}
    B -->|是| D{requests == limits?}
    C -->|是| E[Burstable]
    C -->|否| F[BestEffort]
    D -->|是| G[Guaranteed]
    D -->|否| E
    
    G --> H[最高优先级<br>最后被驱逐]
    E --> I[中等优先级<br>根据超用比例驱逐]
    F --> J[最低优先级<br>最先被驱逐]

Guaranteed:所有容器都设置了CPU和内存的requests和limits,且requests等于limits。这类Pod获得最高优先级,只有在系统内存耗尽、无法恢复时才会被驱逐。

Burstable:至少一个容器设置了requests或limits,但不满足Guaranteed条件。这类Pod在内存压力时会根据内存使用量相对于requests的比例排序,超出比例更多的先被驱逐。

BestEffort:所有容器都没有设置requests和limits。这类Pod优先级最低,最先被驱逐。

QoS等级的判定逻辑:

$$ \text{QoS} = \begin{cases} \text{Guaranteed} & \text{if } \forall c \in \text{containers}: \text{requests}_c = \text{limits}_c \\ \text{Burstable} & \text{if } \exists c: \text{requests}_c \neq \emptyset \lor \text{limits}_c \neq \emptyset \\ \text{BestEffort} & \text{otherwise} \end{cases} $$

生产环境建议为所有容器设置requests,让调度器做出正确决策。是否设置limits取决于工作负载特性——有明确资源边界的应用适合设置limits,资源使用波动大的应用可以只设置requests。

节点压力驱逐(Node Pressure Eviction)由kubelet执行,与调度器是独立的组件。kubelet监控节点资源使用情况,当memory.available或nodefs.available低于阈值时,根据QoS等级选择驱逐目标。驱逐顺序可以表示为:

$$ \text{eviction\_score} = \frac{\text{memory\_usage}}{\text{memory\_request}} $$

分数越高的Pod越先被驱逐。BestEffort Pod的request视为0,因此分数无穷大,总是优先驱逐。

高级调度策略:从亲和性到拓扑分布

节点选择从最初的nodeSelector字段发展到Node Affinity,提供了更灵活的匹配能力。Node Affinity支持required(硬性要求)和preferred(软性偏好)两种类型:

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/arch
              operator: In
              values:
                - arm64
            - key: node-pool
              operator: In
              values:
                - gpu-pool
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        preference:
          matchExpressions:
            - key: zone
              operator: In
              values:
                - zone-a

required节点亲和性在Filter阶段生效,不满足条件的节点被过滤掉。preferred节点亲和性在Score阶段生效,满足条件的节点获得额外分数。权重范围为1-100,用于多个preferred规则之间的加权计算。

Pod Affinity和Anti-Affinity实现了Pod之间的亲和性调度。典型的应用场景包括:将同一服务的多个副本分散到不同节点(反亲和性),或者将相互协作的服务部署在同一节点或同一可用区(亲和性)。

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

topologyKey定义了拓扑域的粒度。kubernetes.io/hostname表示节点级别,topology.kubernetes.io/zone表示可用区级别。通过组合不同的拓扑键,可以实现细粒度的分布控制。

flowchart TB
    subgraph ZoneA["可用区 A"]
        N1["Node 1<br>app=myapp Pod 1"]
        N2["Node 2<br>app=myapp Pod 2"]
    end
    
    subgraph ZoneB["可用区 B"]
        N3["Node 3<br>app=myapp Pod 3"]
        N4["Node 4<br>app=myapp Pod 4"]
    end
    
    P["新 Pod<br>app=myapp"] --> |"Pod Anti-Affinity<br>topologyKey=hostname"| N3
    P --> |"Pod Anti-Affinity<br>topologyKey=zone"| N4
    
    style N1 fill:#f9f,stroke:#333
    style N2 fill:#f9f,stroke:#333
    style N3 fill:#9f9,stroke:#333
    style N4 fill:#9f9,stroke:#333
    style P fill:#ff9,stroke:#333

污点和容忍度提供了节点主动排斥Pod的机制。节点可以设置污点,只有容忍该污点的Pod才能被调度到该节点。污点有三个效果:

NoSchedule:调度器不会将Pod调度到该节点,但已运行的Pod不受影响。

PreferNoSchedule:调度器尽量避免将Pod调度到该节点,但在资源紧张时仍可能调度。

NoExecute:调度器不会调度新Pod,且已运行的Pod如果没有容忍该污点会被驱逐。

# 节点打污点
kubectl taint nodes node1 gpu=true:NoSchedule

# Pod配置容忍度
tolerations:
  - key: "gpu"
    operator: "Equal"
    value: "true"
    effect: "NoSchedule"

污点的一个典型应用是专用节点池。通过污点标记GPU节点,只有配置了相应容忍度的机器学习任务才能使用这些节点。另一个应用场景是节点维护——在节点上添加NoExecute污点可以快速驱逐所有Pod。

Topology Spread Constraints是v1.19引入的特性,提供了比Pod Anti-Affinity更灵活的分布控制。Pod Anti-Affinity只能实现"每个拓扑域最多一个"的限制,而Topology Spread Constraints可以控制每个拓扑域的Pod数量差。

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

maxSkew定义了不同拓扑域之间Pod数量的最大差值。whenUnsatisfiable定义了无法满足约束时的行为:DoNotSchedule会阻止调度,ScheduleAnyway会继续调度但降低优先节点分数。

对比Pod Anti-Affinity,Topology Spread Constraints的优势在于:可以控制分布均匀度而不是强制每域一个Pod;支持软性约束,在资源不足时仍能调度;可以定义多个层级的约束,比如先在可用区间均匀分布,再在节点间均匀分布。

优先级与抢占:生死攸关的调度博弈

当集群资源不足时,调度器按照优先级决定Pod的调度顺序。PriorityClass资源定义了优先级数值和描述:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "高优先级工作负载"
preemptionPolicy: PreemptLowerPriority
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: critical-priority
value: 1000000000
globalDefault: false
description: "系统关键组件"
preemptionPolicy: Never

优先级数值范围是-2147483648到1000000000。系统保留高优先级范围(10亿以上)给关键组件。preemptionPolicy字段控制抢占行为:PreemptLowerPriority允许抢占低优先级Pod,Never禁止抢占。

Pod通过priorityClassName引用PriorityClass:

spec:
  priorityClassName: high-priority

抢占算法在PostFilter阶段执行。当高优先级Pod无法调度时,调度器尝试找一个节点,通过驱逐该节点上的低优先级Pod来腾出足够资源。算法步骤如下:

flowchart TD
    A[高优先级Pod调度失败] --> B[PostFilter阶段启动]
    B --> C[遍历所有候选节点]
    C --> D[计算每个节点需驱逐的Pod集合]
    D --> E[选择驱逐总优先级最低的节点]
    E --> F[发送抢占请求]
    F --> G[被驱逐Pod进入优雅终止]
    G --> H[等待N秒后重试调度]
    H --> I[新Pod成功调度]
    
    style A fill:#f99,stroke:#333
    style I fill:#9f9,stroke:#333
  1. 对于每个可行节点(通过Filter阶段但资源不足),计算需要驱逐哪些Pod。
  2. 优先驱逐优先级最低的Pod,直到释放的资源满足新Pod的需求。
  3. 在所有候选节点中,选择驱逐Pod优先级总和最小的节点。
  4. 发送抢占请求,被驱逐的Pod进入优雅终止流程。

抢占不是立即生效的。被抢占的Pod需要时间终止,调度器会等待一段时间后重试。这保证了即使抢占成功,新Pod也不会立即运行。

PodDisruptionBudget(PDB)保护应用免受自愿中断的影响。自愿中断包括节点排空、集群升级等操作,不包括节点故障或内核崩溃等非自愿中断。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: myapp-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: myapp

PDB通过minAvailable或maxUnavailable定义应用必须保持的最小可用副本数。当执行kubectl drain或集群升级时,控制器会检查PDB约束,确保不会同时终止太多Pod。

理解自愿中断和非自愿中断的区别很重要。PDB只保护自愿中断,节点故障或调度器抢占导致的Pod终止不受PDB约束。生产环境应该同时配置PDB和足够的副本数来应对各种故障场景。

性能调优:大规模集群的调度器优化

调度器的默认配置针对中小规模集群优化。当节点数超过100时,需要调整参数以获得更好的性能。

percentageOfNodesToScore控制过滤阶段遍历的节点比例。默认情况下,调度器遍历所有节点。在大型集群中,遍历所有节点耗时过长,设置这个参数可以让调度器在找到一定数量的可行节点后就停止遍历。

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: default-scheduler
percentageOfNodesToScore: 50

这个参数的默认值与集群规模相关:100节点以下为100%,5000节点以上为10%。对于中间规模的集群,公式为:

$$ \text{percentage} = \max\left(5, 50 - \frac{\text{nodes}}{100}\right) $$

设置过低的值可能导致调度结果次优,因为可行节点样本太小。建议在测试环境验证后再应用到生产。

调度队列的退避机制处理调度失败的Pod。当Pod调度失败时,它不会立即重试,而是进入backoff队列等待。backoff时间从1秒开始指数增长,最大10分钟:

$$ \text{backoff}(n) = \min(2^n, 600) \text{ seconds} $$

退避机制避免了无效的重复调度尝试,减轻了API Server的压力。通过事件日志可以观察Pod的退避状态:

Warning  FailedScheduling  2m  default-scheduler  0/3 nodes are available: 3 Insufficient cpu.

调度器通过informer机制监听集群状态变化。Informer维护本地缓存,避免频繁访问API Server。理解informer的工作原理有助于排查状态不一致问题:

flowchart LR
    subgraph APIServer["API Server"]
        ETCD[(etcd)]
    end
    
    subgraph Scheduler["Scheduler"]
        Informer["Informer<br>本地缓存"]
        Queue["调度队列"]
        Cache["调度缓存"]
    end
    
    subgraph Nodes["集群节点"]
        N1["Node 1"]
        N2["Node 2"]
        N3["Node 3"]
    end
    
    ETCD --> |Watch| Informer
    Informer --> |Pod事件| Queue
    Informer --> |节点信息| Cache
    Queue --> |调度决策| ETCD
    N1 --> |状态上报| ETCD
    N2 --> |状态上报| ETCD
    N3 --> |状态上报| ETCD

调度器缓存假设节点信息是相对稳定的。如果节点状态频繁变化,缓存可能短暂不一致。对于这种场景,可以考虑增加缓存同步频率或使用更激进的调度策略。

生产环境的调度器配置示例:

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: /etc/kubernetes/scheduler.conf
profiles:
  - schedulerName: default-scheduler
    plugins:
      score:
        enabled:
          - name: NodeResourcesBalancedAllocation
            weight: 1
          - name: NodeResourcesFit
            weight: 1
    pluginConfig:
      - name: NodeResourcesFit
        args:
          scoringStrategy:
            type: MostAllocated  # 装箱策略,适合节点数量有限的场景
percentageOfNodesToScore: 30
bindTimeoutSeconds: 600

bindTimeoutSeconds控制绑定操作的超时时间。如果集群网络不稳定或API Server响应慢,可以适当增加这个值。

自定义调度器:扩展还是替换

Kubernetes允许多个调度器共存,每个Pod可以选择使用哪个调度器。通过spec.schedulerName字段指定:

spec:
  schedulerName: my-custom-scheduler

未指定schedulerName的Pod使用default-scheduler。自定义调度器可以是完全独立的服务,只需监听待调度Pod并更新Binding对象。

选择自定义调度器还是扩展默认调度器,取决于需求复杂度:

使用默认调度器配置适用于:

  • 需要调整打分权重或启用/禁用特定插件
  • 需要自定义Filter或Score逻辑
  • 性能调优和参数调整

Scheduler Extenders适用于:

  • 需要在过滤或打分阶段加入外部决策
  • 与外部系统集成(存储、网络)
  • 非侵入式扩展,无法修改调度器代码

Extenders通过HTTP webhook与调度器交互:

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
extenders:
  - urlPrefix: "http://scheduler-extender:8080"
    filterVerb: "filter"
    prioritizeVerb: "prioritize"
    weight: 5
    managedResources:
      - name: example.com/gpu
        ignoredByScheduler: true

ignoredByScheduler为true时,默认调度器不处理该资源,完全交给extender决策。

自定义调度器适用于:

  • 完全不同的调度算法(批调度、公平调度)
  • 特殊资源类型(GPU拓扑感知、FPGA)
  • 多集群或联邦调度

Volcano是Kubernetes上流行的批调度器,支持Gang Scheduling、Fair Scheduling、Queue管理等高级特性。对于机器学习训练、大数据分析等批处理场景,Volcano比默认调度器更适合。

apiVersion: scheduling.volcano.sh/v1beta1
kind: PodGroup
metadata:
  name: training-job
spec:
  minMember: 4  # 所有4个Pod同时就绪后才开始调度
  queue: default

生产环境诊断手册

Pod卡在Pending状态是最常见的调度问题。排查步骤:

检查事件日志

kubectl describe pod <pod-name>

事件日志会显示调度失败的具体原因:资源不足、节点亲和性不匹配、污点不容忍等。常见错误信息:

  • 0/3 nodes are available: 3 Insufficient cpu:所有节点CPU资源不足
  • 0/3 nodes are available: 3 node(s) didn't match node selector:节点标签不匹配
  • 0/3 nodes are available: 3 node(s) had taints that the pod didn't tolerate:污点不容忍

检查节点状态

kubectl get nodes -o wide
kubectl describe node <node-name>

关注Allocatable和Allocated resources的差值。注意,Allocated resources是requests之和,不是实际使用量。节点可能显示资源充足,但实际已被Limits占满。

检查资源配额

kubectl get resourcequota
kubectl describe resourcequota <quota-name>

ResourceQuota限制了命名空间的总资源量,可能导致"明明节点有资源但无法调度"的情况。

节点碎片化问题

集群整体资源充足,但每个节点剩余资源都不足以运行新Pod。这种情况称为碎片化(fragmentation)。

flowchart TB
    subgraph Before["碎片化状态"]
        N1A["Node 1<br>CPU: 3.9/4 核<br>剩余 0.1 核"]
        N2A["Node 2<br>CPU: 3.8/4 核<br>剩余 0.2 核"]
        N3A["Node 3<br>CPU: 3.7/4 核<br>剩余 0.3 核"]
    end
    
    subgraph After["碎片整理后"]
        N1B["Node 1<br>CPU: 3.0/4 核<br>剩余 1.0 核"]
        N2B["Node 2<br>CPU: 2.0/4 核<br>剩余 2.0 核"]
        N3B["Node 3<br>CPU: 1.0/4 核<br>剩余 3.0 核"]
    end
    
    P["新Pod需要 2 核CPU"]
    
    Before --> |"descheduler<br>驱逐重调度"| After
    P --> |"无法调度"| Before
    P --> |"成功调度"| N2B
    
    style N1A fill:#f99,stroke:#333
    style N2A fill:#f99,stroke:#333
    style N3A fill:#f99,stroke:#333
    style N1B fill:#9f9,stroke:#333
    style N2B fill:#9f9,stroke:#333
    style N3B fill:#9f9,stroke:#333

descheduler项目提供了碎片整理功能。它根据策略驱逐Pod,让调度器重新分配以优化资源利用:

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "RemoveDuplicates":
    enabled: true
  "LowNodeUtilization":
    enabled: true
    params:
      nodeResourceUtilizationThresholds:
        thresholds:
          cpu: 20
          memory: 20
        targetThresholds:
          cpu: 50
          memory: 50

LowNodeUtilization策略会驱逐利用率高于targetThresholds的节点上的Pod,让它们迁移到利用率低于thresholds的节点上。

装箱与分散策略的选择

MostAllocated打分策略倾向于将Pod调度到资源使用率最高的节点,实现装箱效果。这减少了节点数量需求,但可能导致碎片化。

LeastAllocated打分策略倾向于将Pod调度到资源使用率最低的节点,实现分散效果。这提高了容错性,但可能浪费节点资源。

pluginConfig:
  - name: NodeResourcesFit
    args:
      scoringStrategy:
        type: MostAllocated  # 或 LeastAllocated

选择哪种策略取决于业务需求:资源成本敏感的场景选择装箱,可用性敏感的场景选择分散。

关键配置决策点

生产环境调度器配置需要平衡多个因素。以下决策树帮助选择合适的策略:

资源策略选择

  • 所有容器必须设置requests
  • 关键应用设置limits且requests=limits(Guaranteed)
  • 普通应用可只设置requests(Burstable)
  • 避免BestEffort

分布策略选择

  • 单副本服务:无需分布策略
  • 多副本无状态服务:Pod Anti-Affinity + topologyKey=hostname
  • 跨可用区高可用:Topology Spread Constraints + topologyKey=zone
  • 批处理任务:考虑Volcano等批调度器

优先级策略选择

  • 系统组件:system-cluster-critical或system-node-critical
  • 关键业务:自定义PriorityClass,value=100000-999999
  • 普通业务:使用默认优先级或更低

性能参数选择

  • 节点数<100:使用默认配置
  • 节点数100-1000:percentageOfNodesToScore=30-50
  • 节点数>1000:percentageOfNodesToScore=10-20,考虑分片调度器

调度器配置没有银弹。理解调度原理、监控调度指标、持续优化参数,才能构建稳定高效的Kubernetes集群。kubectl describe pod和kubectl get events是最实用的调试工具,它们会告诉你调度器为什么做出了特定的决策。

参考资料

  1. Kubernetes官方文档 - Scheduler: https://kubernetes.io/docs/concepts/scheduling-eviction/kube-scheduler/
  2. Kubernetes官方文档 - Scheduling Framework: https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/
  3. Kubernetes Enhancement Proposal #624 - Scheduling Framework: https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/624-scheduling-framework/
  4. Kubernetes官方文档 - Scheduler Performance Tuning: https://kubernetes.io/docs/concepts/scheduling-eviction/scheduler-perf-tuning/
  5. Kubernetes官方文档 - Taints and Tolerations: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/
  6. Kubernetes官方文档 - Pod Topology Spread Constraints: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/
  7. Kubernetes官方文档 - Pod Priority and Preemption: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/
  8. Kubernetes官方文档 - Pod Quality of Service Classes: https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/
  9. Kubernetes官方文档 - PodDisruptionBudget: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/
  10. Kubernetes官方文档 - kube-scheduler Configuration: https://kubernetes.io/docs/reference/config-api/kube-scheduler-config.v1/
  11. Kubernetes Blog - Introducing PodTopologySpread: https://kubernetes.io/blog/2020/05/introducing-podtopologyspread/
  12. Kubernetes Blog - v1.35 Workload Aware Scheduling: https://kubernetes.io/blog/2025/12/29/kubernetes-v1-35-introducing-workload-aware-scheduling/
  13. Julia Evans - How does the Kubernetes scheduler work?: https://jvns.ca/blog/2017/07/27/how-does-the-kubernetes-scheduler-work/
  14. The New Stack - A Deep Dive into Kubernetes Scheduling: https://thenewstack.io/a-deep-dive-into-kubernetes-scheduling/
  15. Alibaba Cloud - A Brief Analysis on the Implementation of the Kubernetes Scheduler: https://www.alibabacloud.com/blog/a-brief-analysis-on-the-implementation-of-the-kubernetes-scheduler_595083
  16. IBM Developer - Creating a Custom Kube-Scheduler: https://developer.ibm.com/articles/creating-a-custom-kube-scheduler/
  17. Scheduler Plugins SIG - Installation and Documentation: https://scheduler-plugins.sigs.k8s.io/docs/user-guide/installation/
  18. Volcano Documentation - Gang Scheduling: https://volcano.sh/en/docs/gang_scheduling/
  19. Heba Elayoty - Deep Dive into the Kubernetes Scheduler Framework: https://www.helayoty.org/p/deep-dive-into-the-kubernetes-scheduler
  20. Ahmet Alp Balkan - Every pod eviction in Kubernetes, explained: https://ahmet.im/blog/kubernetes-evictions/
  21. Akila Welihinda - A Deeper Dive of kube-scheduler: https://www.awelm.com/posts/kube-scheduler
  22. GitHub Issue #108606 - Scheduler throughput in large clusters: https://github.com/kubernetes/kubernetes/issues/108606
  23. Salesforce Engineering - How Data 360 Optimized Kubernetes Scheduling Architecture: https://engineering.salesforce.com/how-data-360-optimized-kubernetes-scheduling-architecture-that-delivered-13-cost-savings/
  24. CNCF Blog - Top Kubernetes Troubleshooting Techniques: https://www.cncf.io/blog/2025/09/12/top-kubernetes-k8s-troubleshooting-techniques-part-1/