工作负载重新平衡(Descheduler)

Kubernetes 调度是一个基于当前时刻的决策。当创建 Pod 时,kube-scheduler 会根据该时刻的集群状态选择最合适的节点。随着集群变化,最初的决策可能会变得不再适用:节点可能变得过度利用或利用不足,taint 或 label 可能发生变化,affinity 规则可能被更新,故障节点可能恢复,新的节点也可能被添加。

Alauda Build of Descheduler 插件通过识别违反调度策略或被放置在不太合适节点上的运行中 Pod,驱逐这些 Pod,并允许默认调度器将替换 Pod 放置到更合适的节点上,从而帮助你重新平衡集群。

descheduler 本身不会调度替换 Pod。它只会调用 Kubernetes eviction API。默认调度器会为由 Deployment、ReplicaSet、StatefulSet 或 Job 等控制器管理的 Pod 重新调度。

使用 descheduler 的典型场景包括:

  • 节点利用不足或利用过高。
  • 在 Pod 调度后,node label、node taint、Pod affinity 或 Pod anti-affinity 需求发生了变化。
  • 节点故障导致 Pod 被迁移到较少的节点上,而恢复后的节点应重新承载工作负载。
  • 添加了新节点,需要重新分配现有工作负载。
  • Pod 重启次数过多,或者运行时间超过了预期生命周期。

Pod 驱逐规则和适用性

为了保持集群稳定性,descheduler 必须谨慎选择要驱逐的 Pod。除非你已确认该工作负载能够安全重建,并且驱逐在运维上是可接受的,否则请保留这些保护措施。

  • 受保护的 Pod
    • 位于系统或平台 namespace 中的 Pod,例如 kube-system 和 ACP 平台 namespace。如果你添加 namespace include 或 exclude 规则,请保持平台 namespace 处于排除状态。
    • priorityClassName 设置为 system-cluster-criticalsystem-node-critical 的关键 Pod。
    • 静态 Pod、镜像 Pod,或未由控制器管理的独立 Pod,因为这些 Pod 不会自动重建。
    • 与 DaemonSet 关联的 Pod。
    • 使用本地存储的 Pod,除非策略显式禁用 PodsWithLocalStorage 保护。
  • Pod Disruption Budget(PDB):descheduler 使用 eviction 子资源。如果驱逐 Pod 会违反其 PDB,则该 Pod 不会被驱逐。
  • 驱逐顺序:当有多个 Pod 符合条件时,会优先选择较低优先级的 Pod,而不是较高优先级的 Pod。对于相同优先级的 Pod,BestEffort Pod 会先于 Burstable 和 Guaranteed Pod 被驱逐。
  • 显式驱逐覆盖:带有 descheduler.alpha.kubernetes.io/evict 注解的 Pod 即使某些内部 descheduler 检查通常会跳过它,也仍然符合驱逐条件。此注解不会绕过 PDB 保护。仅在你了解 Pod 将如何被重建时使用它。
  • 驱逐偏好:带有 descheduler.alpha.kubernetes.io/prefer-no-eviction 注解的 Pod 会请求 descheduler 尽量不要驱逐它。该偏好是建议性还是强制性,取决于 DefaultEvictornoEvictionPolicy 设置。

理解 Descheduler 策略

descheduler policy 会启用策略插件。请使用与运维目标相匹配的策略;避免在同一个 policy profile 中启用方向相反的策略,例如使用 LowNodeUtilization 扩散工作负载,同时又使用 HighNodeUtilization 压缩工作负载。

目标策略描述
移除重复副本RemoveDuplicates当同一节点上运行了多个副本时,会驱逐同一工作负载中的重复 Pod。这有助于在节点故障或恢复不均后重新分配 Pod。
从过载节点扩散LowNodeUtilization找出利用不足的节点,并从利用过高的节点驱逐 Pod,以便调度器更均匀地分布工作负载。默认情况下,利用率是根据 Pod 资源请求与节点可分配资源计算的,而不是根据 kubectl top 的实际使用量。
压缩工作负载HighNodeUtilization从利用不足的节点驱逐 Pod,使替换 Pod 能够被调度到更少的节点上。仅在调度器或 autoscaler 使用偏好更多已分配节点的 profile 时使用,例如 MostAllocated;否则 Pod 可能会再次被扩散。
回收旧 PodPodLifeTime驱逐超过配置生命周期或匹配配置生命周期状态的 Pod。这对于类似 VM 的长运行 Pod 或清理策略非常有用。
迁移反复失败的 PodRemovePodsHavingTooManyRestarts驱逐容器重启次数超过配置阈值的 Pod。可以包含 init container 的重启次数。
强制执行更新后的 taintRemovePodsViolatingNodeTaints驱逐当前节点上不再容忍 NoSchedule taint 的 Pod。
强制执行所需的 node affinityRemovePodsViolatingNodeAffinity驱逐当前节点已不再满足所需 node affinity 的 Pod,例如 requiredDuringSchedulingIgnoredDuringExecution
强制执行 Pod 间 anti-affinityRemovePodsViolatingInterPodAntiAffinity在集群状态或工作负载定义发生变化后,驱逐违反 Pod 间 anti-affinity 规则的 Pod。
重新平衡拓扑域RemovePodsViolatingTopologySpreadConstraint当违反 topology spread constraint 时,从较大的拓扑域中驱逐 Pod。默认情况下,使用 DoNotSchedule 这类硬约束;仅当你有意希望将 ScheduleAnyway 这类软拓扑约束纳入考虑时才包含它。

常见策略组:

适用场景策略
Affinity 和 taint你更改了 taint、label、node affinity 或 Pod 间 anti-affinity,并希望运行中的 Pod 遵循新规则。RemovePodsViolatingNodeTaintsRemovePodsViolatingNodeAffinityRemovePodsViolatingInterPodAntiAffinity
拓扑和重复副本你需要将相似 Pod 分布到不同节点或拓扑域中。RemoveDuplicatesRemovePodsViolatingTopologySpreadConstraint
生命周期和利用率你需要移除陈旧或不健康的 Pod,并将工作负载从过载节点上移开。RemovePodsHavingTooManyRestartsPodLifeTimeLowNodeUtilization
压缩和扩缩容你希望将工作负载打包到更少的节点上,以帮助缩减未使用的容量。HighNodeUtilization

在 ACP 中安装 Descheduler

Alauda Build of Descheduler 在 ACP 中作为 集群插件 打包和管理。

  1. 上架插件包

    • 从 Alauda customer portal 获取 Alauda Build of Descheduler 插件包。
    • 使用 violet 工具将其发布到平台。有关详细的 CLI 说明,请参阅 CLI Tools
    • 导航到 管理员 > Marketplace > 上架软件包,并确认该软件包显示在 集群插件 选项卡下。
  2. 安装插件

    • 导航到 管理员 > Marketplace > 集群插件
    • 选择目标集群,找到 Alauda Build of Descheduler 插件,然后单击 Install
    • 如有需要,在动态表单中调整安装时配置选项,然后确认安装。

配置调度策略

插件安装后,不要直接编辑 Helm values。已安装的插件会渲染 Kubernetes 资源,而运行时 descheduler policy 以 YAML 形式存储在 descheduler ConfigMap 中。

运行模式

  • CronJob(推荐):descheduler 作为 Job 定期运行。此模式避免在集群状态稳定时运行持久化 Agent。更新后的 policy YAML 会在下一次计划的 Job 中加载。
  • Deployment:descheduler 作为连续运行的 Pod 执行,并按照插件安装时配置的间隔来协调集群。更新 policy YAML 后,请重启 descheduler Pod,以便运行中的进程重新加载配置。

编辑 Policy YAML

定位 descheduler policy ConfigMap:

kubectl get configmap -n kube-system -l app.kubernetes.io/name=descheduler

在编辑前备份当前 policy:

kubectl get configmap -n kube-system <descheduler-configmap-name> -o yaml > descheduler-configmap.backup.yaml

编辑上一条命令返回的 ConfigMap:

kubectl edit configmap -n kube-system <descheduler-configmap-name>

data.policy.yaml 中,请保留现有 policy,仅合并你需要更改的字段。不要用示例替换整个 policy,因为这样可能会移除现有的保护措施、已启用的策略、namespace 过滤器或驱逐限制。

示例:重新平衡利用率过高的节点

此示例启用 LowNodeUtilization,并设置与扩散型 descheduling 常用模式相同的阈值:CPU、内存和 Pod 容量低于 20% 的节点属于利用不足;上述任一资源高于 50% 的节点属于利用过高。

相关 policy 片段:

apiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
maxNoOfPodsToEvictTotal: 20
profiles:
  - name: default
    pluginConfig:
      - name: DefaultEvictor
        args:
          nodeFit: true
      - name: LowNodeUtilization
        args:
          thresholds:
            cpu: 20
            memory: 20
            pods: 20
          targetThresholds:
            cpu: 50
            memory: 50
            pods: 50
          evictionLimits:
            node: 5
    plugins:
      balance:
        enabled:
          - LowNodeUtilization

注意:

  • thresholdstargetThresholds 必须定义相同的资源键。
  • 有效百分比范围为 0100
  • 对同一资源,thresholds 不能大于 targetThresholds
  • 仅当至少存在一个利用不足节点和一个利用过高节点时,该策略才会运行。
  • 默认情况下,节点利用率是根据 Pod 资源请求和节点可分配资源计算的。如果你需要基于实际使用量的 descheduling,请在 policy 中配置受支持的 metrics provider 和 metricsUtilization

常见 Policy 自定义

需求Policy 位置说明
限制每次运行的总中断量顶层 maxNoOfPodsToEvictTotalmaxNoOfPodsToEvictPerNodemaxNoOfPodsToEvictPerNamespace先从较小的限制开始,仅在观察驱逐行为后再增加。
在驱逐前检查替换 Pod 是否能够放得下DefaultEvictor.args.nodeFit: true可防止驱逐那些按照当前 selector、toleration、affinity、资源请求和可调度性无法在其他节点上运行的 Pod。
排除或包含 namespace策略 args.namespacesLowNodeUtilization / HighNodeUtilizationargs.evictableNamespaces只能使用 includeexclude 其中一种,不要同时使用。请保持 ACP 和 Kubernetes 系统 namespace 处于排除状态。
按 Pod 优先级限制DefaultEvictor.args.priorityThreshold.nameDefaultEvictor.args.priorityThreshold.value不要同时设置 namevalue。该 priority class 必须已经存在。
驱逐旧 PodPodLifeTime.args.maxPodLifeTimeSeconds该值以秒表示,例如 24 小时对应 86400
驱逐重启次数过多的 PodRemovePodsHavingTooManyRestarts.args.podRestartThreshold如果需要将 init container 的重启次数也计入,请使用 includingInitContainers: true
考虑软 topology spread constraintRemovePodsViolatingTopologySpreadConstraint.args.constraints仅当软约束也应触发驱逐时,才添加 ScheduleAnyway
允许驱逐使用本地存储的 PodDefaultEvictor.args.podProtections.defaultDisabled 搭配 PodsWithLocalStorage这会禁用默认保护。仅对能够安全容忍驱逐的工作负载使用。
保护基于 PVC 的 PodDefaultEvictor.args.podProtections.extraEnabled 搭配 PodsWithPVC这会为使用 PVC 的 Pod 添加额外保护。

示例:生命周期和健康清理

此片段会驱逐运行超过 24 小时的 Pod,以及其容器重启次数超过 100 次的 Pod:

profiles:
  - name: default
    pluginConfig:
      - name: PodLifeTime
        args:
          maxPodLifeTimeSeconds: 86400
      - name: RemovePodsHavingTooManyRestarts
        args:
          podRestartThreshold: 100
          includingInitContainers: true
    plugins:
      deschedule:
        enabled:
          - PodLifeTime
          - RemovePodsHavingTooManyRestarts

示例:Affinity、Taint 和 Topology 漂移

此片段会驱逐不再匹配已更新 node taint、所需 node affinity、Pod 间 anti-affinity 或硬 topology spread constraint 的 Pod:

profiles:
  - name: default
    pluginConfig:
      - name: RemovePodsViolatingNodeTaints
      - name: RemovePodsViolatingNodeAffinity
        args:
          nodeAffinityType:
            - requiredDuringSchedulingIgnoredDuringExecution
      - name: RemovePodsViolatingInterPodAntiAffinity
      - name: RemovePodsViolatingTopologySpreadConstraint
        args:
          constraints:
            - DoNotSchedule
    plugins:
      deschedule:
        enabled:
          - RemovePodsViolatingNodeTaints
          - RemovePodsViolatingNodeAffinity
          - RemovePodsViolatingInterPodAntiAffinity
      balance:
        enabled:
          - RemovePodsViolatingTopologySpreadConstraint

验证安装和驱逐

1. 检查插件安装状态

确认 ModuleInfo 已转换为 Running 状态:

kubectl get moduleinfo -l cpaas.io/module-name=descheduler

2. 验证 Policy ConfigMap

检查 policy YAML 是否包含预期的策略和保护措施:

kubectl get configmap -n kube-system <descheduler-configmap-name> -o yaml

3. 查看 Descheduler 运行情况和日志

如果以 CronJob 方式运行,请列出已完成或正在运行的 Job:

kubectl get jobs -n kube-system -l app.kubernetes.io/name=descheduler

如果以 Deployment 方式运行,请确认正在运行的 Pod,并在 policy 变更后重启它:

kubectl get pods -n kube-system -l app.kubernetes.io/name=descheduler
kubectl rollout restart deployment -n kube-system -l app.kubernetes.io/name=descheduler

获取 descheduler 日志,以检查节点评估、被跳过的 Pod 和驱逐操作:

kubectl logs -n kube-system -l app.kubernetes.io/name=descheduler --tail=100

示例驱逐日志:

I0601 15:30:15.123456       1 evictions.go:160] "Evicting pod" pod="default/my-app-67d7f8d68c-xxxxx" reason="RemoveDuplicates"

4. 检查 Pod 事件

当 Pod 被 descheduler 驱逐时,请检查 Pod 事件和 Pod 描述。事件原因会因策略而异,因此不要仅依赖单一的 reason=Descheduled 过滤条件。

kubectl get events -n <namespace> --field-selector involvedObject.kind=Pod --sort-by=.lastTimestamp
kubectl describe pod -n <namespace> <pod-name>

运维指导

  • 从较小范围开始:限制 namespace,设置保守的驱逐限制,并在扩大 policy 之前先验证日志。
  • 在启用可能驱逐大量 Pod 的策略之前,请确保工作负载具有控制器并且有足够的副本。
  • 为关键应用保持最新的 PDB。descheduler 会尊重 PDB,但缺少 PDB 就没有中断预算。
  • 仅当调度器或 autoscaler 已配置为将 Pod 压缩时,才使用 HighNodeUtilization。否则,被驱逐的 Pod 可能会再次被扩散。
  • 除非工作负载有明确且经过测试的恢复路径,否则不要禁用本地存储、DaemonSet、系统关键或独立 Pod 保护。
  • 对于基于实际使用量的决策,请确认已配置 metrics provider,并且所选策略会使用这些 metrics;否则,利用率策略将基于请求值。