欢迎光临

Kubernetes Descheduler 实战:集群运行久了 Pod 分布不均怎么办

在 Kubernetes 集群的日常运维中,你是否遇到过这样的场景:集群刚搭建好时,Pod 在各节点上分布得很均匀,但随着时间推移——节点扩容、Pod 驱逐、手动调度——某些节点开始变得拥挤,而新加入的节点却几乎空闲。这种调度偏斜不仅浪费资源,还可能拖垮热点节点上的所有工作负载。

Kubernetes 原生的 kube-scheduler 只在 Pod 创建时做一次调度决策,一旦 Pod 被绑定到节点上,它就再也不会被移动——哪怕后来那个节点已经严重过载。这就是 Descheduler 登场的理由:它像一个”调度纠偏器”,根据你定义的策略,周期性地扫描集群并驱逐那些导致不均衡的 Pod,让 kube-scheduler 重新将它们调度到更合适的位置。

Kubernetes Cluster Architecture

为什么需要 Descheduler

要理解 Descheduler 的价值,我们先看几个典型的不均衡场景:

  • 节点扩容后闲置:新节点加入集群后,已有的 Pod 不会自动迁移过去,新节点长时间处于低利用率状态。
  • Pod 驱逐后的残留:节点维护或故障导致 Pod 被驱逐,恢复后这些 Pod 可能被调度到其他节点,原节点反而变得空闲。
  • 手动调度干扰:通过
    1
    nodeName

    或节点亲和性强制调度的 Pod 破坏了原本的均衡分布。

  • 资源碎片化:不同大小的 Pod 被调度后,某些节点剩余资源零碎,无法容纳大规格 Pod,而其他节点还有完整的大块资源。

这些问题在小型集群中可能不明显,但在拥有数十甚至上百个节点的生产环境中,调度偏斜会导致严重的后果:热点节点上的应用互相争抢 CPU 和内存,延迟飙升;冷门节点上的资源白白浪费,云账单居高不下。

Descheduler 不是 kube-scheduler 的替代品,而是它的补充。它不会直接把 Pod”搬”到另一个节点——它只负责驱逐,然后由 kube-scheduler 根据现有的调度规则重新放置。这个设计保证了调度逻辑的一致性。

Descheduler 的核心策略详解

Descheduler 提供了多种内置策略,每种策略解决一种特定类型的不均衡。你不必全部启用,而是根据集群的实际情况按需组合。以下逐一深入解析。

RemoveDuplicates:消除重复 Pod

这是最简单也最常用的策略。当一个节点上运行了同一个 ReplicaSet 或 Job 的多个副本时,RemoveDuplicates 会驱逐多余的副本,让它们分散到其他节点。这在节点故障恢复后特别有用——原本分散在其他节点的 Pod 可能已经重建,恢复的节点上又跑起了旧 Pod,导致同节点出现重复副本。

策略配置非常简单:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
profiles:
  - name: ProfileName
    pluginConfig:
      - name: RemoveDuplicates
        args:
          namespaces:
            include:
              - "production"
              - "staging"
    plugins:
      balance:
        enabled:
          - name: RemoveDuplicates

LowNodeUtilization:激活空闲节点

这是 Descheduler 最核心的策略。它根据节点的资源利用率(CPU、内存、Pod 数量)将节点分为三类:

类型 条件 动作
低利用率节点 CPU/内存/Pod 数均低于

1
lowThresholds
成为驱逐目标接收者
高利用率节点 CPU/内存/Pod 数任一高于

1
highThresholds
成为驱逐源
适中节点 介于两者之间 不参与驱逐

Descheduler 会从高利用率节点上选择 Pod 进行驱逐,直到这些节点的利用率降到

1
highThresholds

以下,或者低利用率节点达到

1
lowThresholds

以上。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
profiles:
  - name: ProfileName
    pluginConfig:
      - name: LowNodeUtilization
        args:
          thresholds:
            cpu: 20
            memory: 20
            pods: 10
          targetThresholds:
            cpu: 50
            memory: 60
            pods: 40
          numberOfNodes: 3
          evictableNamespaces:
            include:
              - "default"
              - "app-namespace"
    plugins:
      balance:
        enabled:
          - name: LowNodeUtilization

其中

1
numberOfNodes

是一个重要参数:只有当低利用率节点数超过这个值时,策略才会生效。默认值为 0,表示只要有一个低利用率节点就触发驱逐。在大型集群中,建议设置为节点总数的 10% 左右,避免因为一两个节点空闲就大规模迁移。

Server Monitoring Dashboard

HighNodeUtilization:压缩到更少节点

与 LowNodeUtilization 相反,HighNodeUtilization 的目标是将 Pod 集中到尽可能少的节点上。这在需要节省云成本时特别有用——把 Pod 压缩到部分节点后,可以安全地缩容其余节点。

该策略会驱逐低利用率节点上的 Pod,前提是这些 Pod 可以被调度到其他利用率仍低于

1
thresholds

的节点上:


1
2
3
4
5
6
7
pluginConfig:
  - name: HighNodeUtilization
    args:
      thresholds:
        cpu: 60
        memory: 70
        pods: 50

RemovePodsViolatingInterPodAntiAffinity:修复反亲和违规

当你后来添加了 Pod 反亲和规则时,已经在运行的 Pod 可能并不满足这些新规则。此策略会驱逐违反 Pod 反亲和性的 Pod,让它们被重新调度到合规的位置。

RemovePodsViolatingNodeAffinity:修复节点亲和违规

类似地,当你修改了节点的标签或更新了 Pod 的节点亲和性规则后,现有 Pod 可能不再满足亲和性要求。此策略确保它们被迁移到符合新规则的节点上。

RemovePodsViolatingTopologySpreadConstraint:修复拓扑分布偏差

这是 Kubernetes 1.18+ 引入的 TopologySpreadConstraints 的配套策略。当实际分布与期望的拓扑分布存在偏差时,此策略会驱逐导致偏差的 Pod。在生产环境中结合拓扑分布约束使用时,这个策略几乎是必选的。

RemovePodsHavingTooManyRestarts:驱逐频繁重启的 Pod

如果一个 Pod 重启次数过多,可能是节点本身存在问题(如坏内存、磁盘故障)。此策略会驱逐这些 Pod,让它们在健康的节点上重新启动:


1
2
3
4
5
pluginConfig:
  - name: RemovePodsHavingTooManyRestarts
    args:
      podRestartThreshold: 100
      includingInitContainers: true

PodLifeTime:驱逐超龄 Pod

某些场景下,Pod 运行时间过长也需要被驱逐——比如 Batch Job 卡住了,或者需要强制滚动更新。此策略会驱逐超过指定生命周期的 Pod:


1
2
3
4
5
6
7
pluginConfig:
  - name: PodLifeTime
    args:
      maxPodLifeTimeSeconds: 86400  # 24小时
      states:
        - "Pending"
        - "Running"

完整安装与部署实战

Descheduler 支持以 Deployment 或 CronJob 方式运行。推荐使用 CronJob 模式,因为它只在需要时运行,不会常驻集群消耗资源。

Step 1:添加 Helm 仓库并安装


1
2
3
4
5
6
# 添加 Descheduler Helm 仓库
helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/
helm repo update

# 安装为 CronJob,每 15 分钟运行一次
helm install descheduler descheduler/descheduler   --namespace kube-system   --set kind=CronJob   --set cronJob.schedule="*/15 * * * *"   --set deschedulerPolicy.profileName=DefaultProfile

Step 2:自定义策略配置

生产环境中,你需要根据实际情况编写自定义策略。以下是一个覆盖常用场景的完整配置:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
apiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
profiles:
  - name: ProductionProfile
    pluginConfig:
      - name: RemoveDuplicates
        args:
          namespaces:
            include:
              - "production"
      - name: LowNodeUtilization
        args:
          thresholds:
            cpu: 20
            memory: 20
            pods: 10
          targetThresholds:
            cpu: 50
            memory: 60
            pods: 40
          numberOfNodes: 3
          evictableNamespaces:
            include:
              - "production"
              - "default"
      - name: RemovePodsViolatingTopologySpreadConstraint
        args:
          namespaces:
            include:
              - "production"
      - name: RemovePodsHavingTooManyRestarts
        args:
          podRestartThreshold: 100
          includingInitContainers: true
    plugins:
      balance:
        enabled:
          - name: RemoveDuplicates
          - name: LowNodeUtilization
          - name: RemovePodsViolatingTopologySpreadConstraint
      deschedule:
        enabled:
          - name: RemovePodsHavingTooManyRestarts

通过 Helm 的

1
--set-file

参数传入自定义策略:


1
helm upgrade descheduler descheduler/descheduler   --namespace kube-system   --set kind=CronJob   --set cronJob.schedule="*/15 * * * *"   --set-file deschedulerPolicy.customPolicyFilePath=./descheduler-policy.yaml

Data Analytics

关键配置参数深度调优

驱逐安全阀:Descheduler 的保护机制

Descheduler 不会无脑驱逐,它内置了多层保护机制来防止过度迁移导致服务中断:

  • PodDisruptionBudget 尊重:Descheduler 会遵守 PDB 的限制,不会驱逐超出 PDB
    1
    maxUnavailable

    的 Pod 数量。这是最关键的安全网。

  • 优先级过滤:默认只驱逐
    1
    priorityClassName

    低于

    1
    system-cluster-critical

    1
    system-node-critical

    的 Pod,核心系统组件不会被驱逐。

  • 每节点驱逐上限:通过
    1
    --max-evict-per-node

    控制每次运行时每个节点最多驱逐的 Pod 数,默认为 0(无限制),生产环境建议设为 3-5。

  • 命名空间过滤:通过
    1
    evictableNamespaces

    精确控制哪些命名空间的 Pod 可以被驱逐。

运行频率的选择

CronJob 的运行频率直接影响集群的稳定性。以下是一些经验值:

集群规模 推荐频率 理由
小型(<20 节点) 每 30 分钟 变化不频繁,不需要太高的检查频率
中型(20-100 节点) 每 15 分钟 平衡检查频率和集群负载
大型(>100 节点) 每 5-10 分钟 快速响应不均衡,避免资源浪费累积
缩容场景 每 5 分钟 尽快将 Pod 集中到目标节点以便缩容

与 PDB 配合的最佳实践

Descheduler 和 PDB 是天生的搭档。在启用 Descheduler 之前,务必确保关键应用都有合理的 PDB:


1
2
3
4
5
6
7
8
9
10
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-server-pdb
  namespace: production
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: api-server

如果你希望某个应用完全不受 Descheduler 影响,也可以直接在 Pod 模板中添加注解:


1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: apps/v1
kind: Deployment
metadata:
  name: critical-app
spec:
  template:
    metadata:
      annotations:
        descheduler.alpha.kubernetes.io/evict: "false"
    spec:
      containers:
        - name: app
          image: myapp:latest

实战案例:节点扩容后的自动再平衡

假设你管理一个 10 节点的生产集群,流量增长后你扩容了 3 个新节点。没有 Descheduler 的情况下,新节点只会接收新创建的 Pod,已有的 Pod 不会迁移过去。让我们看看 Descheduler 如何自动修复这种不均衡。

首先,检查当前节点资源分布:


1
2
3
4
5
6
7
8
9
10
11
kubectl top nodes

# 输出示例(扩容后新节点 node-11/12/13 利用率极低)
# NAME      CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
# node-01   1800m        45%    12000Mi         60%
# node-02   1900m        47%    11000Mi         55%
# ...
# node-10   1700m        42%    13000Mi         65%
# node-11   200m         5%     2000Mi          10%
# node-12   150m         3%     1800Mi           9%
# node-13   180m         4%     2100Mi          10%

启用 LowNodeUtilization 策略后,Descheduler 会在下一次运行时:

  1. 识别 node-11/12/13 为低利用率节点(低于 20% 阈值)。
  2. 识别 node-01 到 node-10 为高利用率节点(CPU/内存高于 50%/60% 目标阈值)。
  3. 从高利用率节点上选择可驱逐的 Pod(排除有
    1
    descheduler.alpha.kubernetes.io/evict: "false"

    注解和 PDB 保护中的 Pod)。

  4. 按优先级从低到高排序,依次驱逐,直到高利用率节点降到目标阈值以下。

运行一轮后再次检查:


1
2
3
4
5
6
7
8
9
10
11
kubectl top nodes

# 输出示例(再平衡后分布更均匀)
# NAME      CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
# node-01   1200m        30%    8000Mi          40%
# node-02   1300m        32%    7800Mi          39%
# ...
# node-10   1100m        27%    8200Mi          41%
# node-11   900m         22%    6000Mi          30%
# node-12   850m         21%    5800Mi          29%
# node-13   880m         22%    6100Mi          30%

可以看到,原本拥挤的老节点负载明显下降,新节点也承担了合理的工作量。整个再平衡过程完全自动完成,无需人工干预。

Network Infrastructure

生产环境避坑指南

坑一:有状态应用被驱逐导致数据丢失

Descheduler 默认不会驱逐属于 StatefulSet 的 Pod(因为 StatefulSet 的 Pod 有严格的身份标识和存储绑定)。但如果你使用了自定义的控制器管理有状态应用,务必通过注解或命名空间过滤来保护它们。

坑二:驱逐风暴导致服务不可用

如果同时驱逐太多 Pod,即使有 PDB 保护,也可能因为逐个驱逐的延迟导致短暂的服务降级。建议:

  • 设置
    1
    --max-evict-per-node=3

    ,控制每轮每节点驱逐上限。

  • 在非高峰期运行 Descheduler(通过 CronJob 调度到凌晨)。
  • 对于多副本 Deployment,确保 PDB 的
    1
    minAvailable

    不低于 50%。

坑三:与 Cluster Autoscaler 冲突

Cluster Autoscaler(CA)会在节点资源不足时自动扩容,在节点空闲时自动缩容。如果 Descheduler 驱逐了 Pod 让某个节点变空闲,CA 可能立即缩容该节点,然后被驱逐的 Pod 又触发 CA 扩容新节点,形成循环。解决方法:

  • 为 CA 的缩容预留足够的冷却时间(默认 10 分钟)。
  • 使用
    1
    cluster-autoscaler.kubernetes.io/safe-to-evict: "false"

    注解保护不应被缩容的节点上的 Pod。

  • 在 CA 的
    1
    --scale-down-delay-after-add

    参数中设置足够长的延迟。

坑四:TopologySpreadConstraints 与 Descheduler 的重复

Kubernetes 1.19+ 的 kube-scheduler 已经内置了对 TopologySpreadConstraints 的调度支持。如果启用了

1
RemovePodsViolatingTopologySpreadConstraint

策略,确保 Descheduler 的运行频率不会太高,否则可能与调度器的均衡动作形成”抖动”——刚被调度器放好的 Pod 立刻又被 Descheduler 驱逐。

监控与可观测性

Descheduler 以 Prometheus 格式暴露了关键指标,你可以通过以下端点获取:


1
2
kubectl port-forward -n kube-system deployment/descheduler 10258:10258
curl http://localhost:10258/metrics

核心指标包括:

指标名 含义
1
descheduler_evictions_total
总驱逐次数(按策略和命名空间分类)
1
descheduler_pods_evicted
每个策略驱逐的 Pod 数量
1
descheduler_build_info
Descheduler 版本信息

建议在 Grafana 中建立 Descheduler 仪表盘,监控驱逐趋势。如果某个策略的驱逐次数持续飙升,可能意味着调度策略本身需要调整,而不是继续依赖 Descheduler 来纠偏。

总结与选型建议

Descheduler 是一个”亡羊补牢”式的工具——它解决的是调度器无法预见的不均衡问题。但它不应成为常规操作的一部分,频繁的驱逐意味着集群的调度策略本身可能需要优化。

以下是不同场景下的选型建议:

  • 中小型集群 + 偶尔扩缩容:安装 Descheduler,使用 LowNodeUtilization + RemoveDuplicates,CronJob 每 15 分钟运行一次。
  • 大型集群 + 频繁弹性伸缩:LowNodeUtilization + RemovePodsViolatingTopologySpreadConstraint,注意与 Cluster Autoscaler 的协调。
  • 成本优化场景:HighNodeUtilization + 适当调高阈值,配合 Cluster Autoscaler 的缩容能力。
  • 刚从节点故障中恢复:手动触发一次 Descheduler 运行,快速再平衡,之后关闭或降低频率。

记住,Descheduler 是一把手术刀而非大锤——精确配置策略、合理设置阈值、配合 PDB 和优先级使用,才能在不影响服务可用性的前提下,让集群始终保持健康的资源分布状态。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » Kubernetes Descheduler 实战:集群运行久了 Pod 分布不均怎么办
分享到: 更多 (0)