工作负载重新均衡(Descheduler)

Kubernetes 调度是一个基于某一时刻状态做出的决策。创建 Pod 时,kube-scheduler 会根据集群在该时刻的状态选择最合适的节点。随着集群发生变化,最初的决策可能会过时:节点可能变得过度利用或利用不足,污点或标签可能发生变化,亲和性规则可能被更新,故障节点可能恢复,并且可能添加新节点。

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

descheduler 不会自行调度替代 Pod,而只会调用 Kubernetes 驱逐 API。默认调度器会重新调度由 Deployment、ReplicaSet、StatefulSet 或 Job 等控制器管理的 Pod。

NOTE

从 ACP 4.5 开始,Alauda Build of Descheduler 更名为 Alauda Container Platform Descheduler。从 Customer Portal 下载插件时,请搜索 Alauda Container Platform Descheduler。在更名之前发布的插件软件包上传后,平台中仍会显示 Alauda Build of Descheduler。

使用 descheduler 的典型场景包括:

  • 节点利用不足或利用过度。
  • Pod 调度完成后,节点标签、节点污点、Pod 亲和性或 Pod 反亲和性要求发生了变化。
  • 节点故障导致 Pod 被迁移到较少的节点上,恢复的节点应再次接收工作负载。
  • 添加了新节点,现有工作负载应重新分布。
  • Pod 重启次数过多,或运行时间超过预期生命周期。

Pod 驱逐规则和资格

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

  • 受保护的 Pod:
    • 系统或平台命名空间中的 Pod,例如 kube-system 和 ACP 平台命名空间。如果添加命名空间包含或排除规则,请继续排除平台命名空间。
    • 设置了 priorityClassName 且值为 system-cluster-critical 或 system-node-critical 的关键 Pod。
    • 静态 Pod、镜像 Pod 或不受控制器管理的独立 Pod,因为这些 Pod 不会自动重建。
    • 与 DaemonSet 关联的 Pod。
    • 使用本地存储的 Pod,除非策略明确禁用 PodsWithLocalStorage 保护。
  • Pod 中断预算(PDB):descheduler 使用驱逐子资源。如果驱逐某个 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 避免驱逐它。此设置是建议性的还是强制性的,取决于 DefaultEvictor noEvictionPolicy 设置。

了解 Descheduler 策略

descheduler 策略用于启用策略插件。请选择与运维目标匹配的策略;避免在同一个策略配置中启用会将集群导向相反方向的策略,例如使用 LowNodeUtilization 分散工作负载,同时使用 HighNodeUtilization 压缩工作负载。

目标策略描述
移除重复副本RemoveDuplicates当同一工作负载的多个副本运行在同一节点上时,驱逐重复的 Pod。这有助于在节点故障或恢复不均衡后重新分布 Pod。
将工作负载从过载节点分散LowNodeUtilization查找利用不足的节点,并从利用过度的节点驱逐 Pod,以便调度器更均衡地分散工作负载。默认情况下,利用率根据 Pod 资源请求与节点可分配资源计算,而不是根据 kubectl top 的实际使用量计算。
压缩工作负载HighNodeUtilization从利用不足的节点驱逐 Pod,以便将替换 Pod 调度到更少的节点上。仅当调度器或 autoscaler 配置倾向于使用更多已分配节点时才使用此策略,例如 MostAllocated;否则 Pod 可能会再次分散。
回收旧 PodPodLifeTime驱逐运行时间超过配置生命周期或匹配所配置生命周期状态的 Pod。此策略适用于类似 VM 的长时间运行 Pod 或清理策略。
移动反复失败的 PodRemovePodsHavingTooManyRestarts驱逐其容器重启次数超过配置阈值的 Pod。可以包含 Init 容器的重启次数。
强制应用更新后的污点RemovePodsViolatingNodeTaints驱逐在当前节点上不再容忍 NoSchedule 污点的 Pod。
强制应用所需的节点亲和性RemovePodsViolatingNodeAffinity驱逐当前节点不再满足所需节点亲和性的 Pod,例如 requiredDuringSchedulingIgnoredDuringExecution。
强制应用 Pod 间反亲和性RemovePodsViolatingInterPodAntiAffinity在集群状态或工作负载定义发生变化后,驱逐违反 Pod 间反亲和性规则的 Pod。
重新平衡拓扑域RemovePodsViolatingTopologySpreadConstraint当拓扑分布约束被违反时,从较大的拓扑域中驱逐 Pod。默认情况下,使用 DoNotSchedule 等硬约束;仅当你明确希望考虑软拓扑约束时,才包含 ScheduleAnyway。

常见策略组:

组使用场景策略
亲和性和污点你更改了污点、标签、节点亲和性或 Pod 间反亲和性,并希望运行中的 Pod 遵循新规则。RemovePodsViolatingNodeTaints、RemovePodsViolatingNodeAffinity、RemovePodsViolatingInterPodAntiAffinity
拓扑和重复副本你需要将相似的 Pod 分散到不同节点或拓扑域。RemoveDuplicates、RemovePodsViolatingTopologySpreadConstraint
生命周期和利用率你需要移除过时或不健康的 Pod,并将工作负载从过载节点分散出去。RemovePodsHavingTooManyRestarts、PodLifeTime、LowNodeUtilization
压缩和扩缩容你希望将工作负载集中到更少的节点上,以帮助缩减未使用的容量。HighNodeUtilization

在 ACP 中安装 Descheduler

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

  1. 上架插件包:

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

    • 导航到管理员 > Marketplace > 集群插件。
    • 选择目标集群,找到 Alauda Container Platform Descheduler 插件(对于重命名之前发布的软件包,该插件显示为 Alauda Build of Descheduler),然后单击安装。
    • 如有需要,在动态表单中调整安装时配置选项,然后确认安装。

配置调度策略

安装插件后,请勿直接编辑 Helm values。已安装的插件会渲染 Kubernetes 资源,运行时 Descheduler 策略以 YAML 形式存储在 Descheduler ConfigMap 中。

运行模式

  • CronJob(推荐):Descheduler 作为 Job 定期运行。当集群状态稳定时,此模式无需运行持久化智能体。更新后的策略 YAML 将由下一个计划运行的 Job 加载。
  • Deployment:Descheduler 作为持续运行的 Pod 运行,并按照插件安装期间配置的间隔调节集群。更新策略 YAML 后,重启 Descheduler Pod,使正在运行的进程重新加载配置。

编辑策略 YAML

找到 Descheduler 策略 ConfigMap:

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

编辑前备份当前策略:

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 中保留现有策略,仅合并需要更改的字段。请勿使用示例替换整个策略,因为这可能会移除现有的保护措施、已启用的策略、命名空间过滤器或驱逐限制。

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

此示例启用 LowNodeUtilization,并设置了通常用于分散式调度的相同阈值模式:CPU、内存和 Pod 容量低于 20% 的节点属于利用不足;其中任一资源高于 50% 的节点属于利用过度。

相关策略片段:

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

注意:

  • thresholds 和 targetThresholds 必须定义相同的资源键。
  • 有效百分比范围为 0 到 100。
  • 对于同一资源,thresholds 不得大于 targetThresholds。
  • 仅当至少存在一个利用不足的节点和一个利用过度的节点时,该策略才会运行。
  • 默认情况下,节点利用率根据 Pod 资源请求和节点可分配资源计算。如果需要基于实际使用量进行调度,请在策略中配置受支持的指标提供程序和 metricsUtilization。

常见策略自定义

要求策略位置说明
限制每次运行的总中断量顶层 maxNoOfPodsToEvictTotal、maxNoOfPodsToEvictPerNode 或 maxNoOfPodsToEvictPerNamespace从较小的限制开始,仅在观察驱逐行为后再逐步增加。
在驱逐前检查替换 Pod 是否可以放置DefaultEvictor.args.nodeFit: true根据当前选择器、容忍度、亲和性、资源请求和可调度性,防止驱逐无法放置到其他节点的 Pod。
排除或包含命名空间策略 args.namespaces 或 LowNodeUtilization / HighNodeUtilization args.evictableNamespaces使用 include 或 exclude 之一,不要同时使用两者。保留 ACP 和 Kubernetes 系统命名空间的排除状态。
按 Pod 优先级限制DefaultEvictor.args.priorityThreshold.name 或 DefaultEvictor.args.priorityThreshold.value不要同时设置 name 和 value。优先级类必须已存在。
驱逐旧 PodPodLifeTime.args.maxPodLifeTimeSeconds值以秒为单位表示,例如 86400 表示 24 小时。
驱逐重启次数过多的 PodRemovePodsHavingTooManyRestarts.args.podRestartThreshold如果应统计 Init 容器的重启次数,请使用 includingInitContainers: true。
考虑软拓扑分布约束RemovePodsViolatingTopologySpreadConstraint.args.constraints仅当软约束应触发驱逐时,才添加 ScheduleAnyway。
允许驱逐使用本地存储的 Pod使用 PodsWithLocalStorage 的 DefaultEvictor.args.podProtections.defaultDisabled这会禁用默认保护措施。仅用于能够安全承受驱逐的工作负载。
保护由 PVC 支持的 Pod使用 PodsWithPVC 的 DefaultEvictor.args.podProtections.extraEnabled这会为使用 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

示例:亲和性、污点和拓扑漂移

此片段会驱逐不再匹配更新后的节点污点、所需节点亲和性、Pod 间反亲和性或硬拓扑分布约束的 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. 验证策略 ConfigMap

检查策略 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,并在策略变更后将其重启:

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>

运维指导

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