在 Kubernetes 集群的日常运维中,你是否遇到过这样的场景:集群刚搭建好时,Pod 在各节点上分布得很均匀,但随着时间推移——节点扩容、Pod 驱逐、手动调度——某些节点开始变得拥挤,而新加入的节点却几乎空闲。这种调度偏斜不仅浪费资源,还可能拖垮热点节点上的所有工作负载。
Kubernetes 原生的 kube-scheduler 只在 Pod 创建时做一次调度决策,一旦 Pod 被绑定到节点上,它就再也不会被移动——哪怕后来那个节点已经严重过载。这就是 Descheduler 登场的理由:它像一个”调度纠偏器”,根据你定义的策略,周期性地扫描集群并驱逐那些导致不均衡的 Pod,让 kube-scheduler 重新将它们调度到更合适的位置。

为什么需要 Descheduler
要理解 Descheduler 的价值,我们先看几个典型的不均衡场景:
- 节点扩容后闲置:新节点加入集群后,已有的 Pod 不会自动迁移过去,新节点长时间处于低利用率状态。
- Pod 驱逐后的残留:节点维护或故障导致 Pod 被驱逐,恢复后这些 Pod 可能被调度到其他节点,原节点反而变得空闲。
- 手动调度干扰:通过
1nodeName
或节点亲和性强制调度的 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 数均低于
|
成为驱逐目标接收者 | ||
| 高利用率节点 | CPU/内存/Pod 数任一高于
|
成为驱逐源 | ||
| 适中节点 | 介于两者之间 | 不参与驱逐 |
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% 左右,避免因为一两个节点空闲就大规模迁移。

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

关键配置参数深度调优
驱逐安全阀:Descheduler 的保护机制
Descheduler 不会无脑驱逐,它内置了多层保护机制来防止过度迁移导致服务中断:
- PodDisruptionBudget 尊重:Descheduler 会遵守 PDB 的限制,不会驱逐超出 PDB
1maxUnavailable
的 Pod 数量。这是最关键的安全网。
- 优先级过滤:默认只驱逐
1priorityClassName
低于
1system-cluster-critical和
1system-node-critical的 Pod,核心系统组件不会被驱逐。
- 每节点驱逐上限:通过
1--max-evict-per-node
控制每次运行时每个节点最多驱逐的 Pod 数,默认为 0(无限制),生产环境建议设为 3-5。
- 命名空间过滤:通过
1evictableNamespaces
精确控制哪些命名空间的 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 会在下一次运行时:
- 识别 node-11/12/13 为低利用率节点(低于 20% 阈值)。
- 识别 node-01 到 node-10 为高利用率节点(CPU/内存高于 50%/60% 目标阈值)。
- 从高利用率节点上选择可驱逐的 Pod(排除有
1descheduler.alpha.kubernetes.io/evict: "false"
注解和 PDB 保护中的 Pod)。
- 按优先级从低到高排序,依次驱逐,直到高利用率节点降到目标阈值以下。
运行一轮后再次检查:
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%
可以看到,原本拥挤的老节点负载明显下降,新节点也承担了合理的工作量。整个再平衡过程完全自动完成,无需人工干预。

生产环境避坑指南
坑一:有状态应用被驱逐导致数据丢失
Descheduler 默认不会驱逐属于 StatefulSet 的 Pod(因为 StatefulSet 的 Pod 有严格的身份标识和存储绑定)。但如果你使用了自定义的控制器管理有状态应用,务必通过注解或命名空间过滤来保护它们。
坑二:驱逐风暴导致服务不可用
如果同时驱逐太多 Pod,即使有 PDB 保护,也可能因为逐个驱逐的延迟导致短暂的服务降级。建议:
- 设置
1--max-evict-per-node=3
,控制每轮每节点驱逐上限。
- 在非高峰期运行 Descheduler(通过 CronJob 调度到凌晨)。
- 对于多副本 Deployment,确保 PDB 的
1minAvailable
不低于 50%。
坑三:与 Cluster Autoscaler 冲突
Cluster Autoscaler(CA)会在节点资源不足时自动扩容,在节点空闲时自动缩容。如果 Descheduler 驱逐了 Pod 让某个节点变空闲,CA 可能立即缩容该节点,然后被驱逐的 Pod 又触发 CA 扩容新节点,形成循环。解决方法:
- 为 CA 的缩容预留足够的冷却时间(默认 10 分钟)。
- 使用
1cluster-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
核心指标包括:
| 指标名 | 含义 | ||
|---|---|---|---|
|
总驱逐次数(按策略和命名空间分类) | ||
|
每个策略驱逐的 Pod 数量 | ||
|
Descheduler 版本信息 |
建议在 Grafana 中建立 Descheduler 仪表盘,监控驱逐趋势。如果某个策略的驱逐次数持续飙升,可能意味着调度策略本身需要调整,而不是继续依赖 Descheduler 来纠偏。
总结与选型建议
Descheduler 是一个”亡羊补牢”式的工具——它解决的是调度器无法预见的不均衡问题。但它不应成为常规操作的一部分,频繁的驱逐意味着集群的调度策略本身可能需要优化。
以下是不同场景下的选型建议:
- 中小型集群 + 偶尔扩缩容:安装 Descheduler,使用 LowNodeUtilization + RemoveDuplicates,CronJob 每 15 分钟运行一次。
- 大型集群 + 频繁弹性伸缩:LowNodeUtilization + RemovePodsViolatingTopologySpreadConstraint,注意与 Cluster Autoscaler 的协调。
- 成本优化场景:HighNodeUtilization + 适当调高阈值,配合 Cluster Autoscaler 的缩容能力。
- 刚从节点故障中恢复:手动触发一次 Descheduler 运行,快速再平衡,之后关闭或降低频率。
记住,Descheduler 是一把手术刀而非大锤——精确配置策略、合理设置阈值、配合 PDB 和优先级使用,才能在不影响服务可用性的前提下,让集群始终保持健康的资源分布状态。
汤不热吧