背景

Kubernetes 默认调度器只在 Pod 创建时选一次节点。Pod 一旦跑起来,除非被删除、驱逐或所在节点不可用,否则不会因为目标节点变忙而自动迁走。

homelab 里曾出现 worker 节点(如 k8s-50)堆了三十多个 Pod,而另一节点几乎空闲的情况。2026-06-23 在 Grafana 上确认 k8s-50 CPU 接近 80% 而 Descheduler 不驱逐,经补 requests、加 K8s Node Workloads 看板、放宽 Nexus affinity 后已见效;全过程见 Homelab K8s Node Rebalance 案例。

当时节点内存压力指标仍为 MemoryPressure=False(kubelet 认为内存够用),但 CPU 争抢、有状态服务启动慢等问题已经很明显。手动 kubectl drain 后 Pod 能正常分散到其他节点,说明调度器本身没问题,缺的是主动重平衡机制。

Descheduler 是 Kubernetes SIG 维护的组件,周期性检查集群,按策略驱逐部分 Pod,让它们被默认调度器重新分配到更合适的节点。

调度器与 Descheduler 的分工

组件 时机 作用
kube-scheduler Pod 创建 / 被驱逐后重建 为新 Pod 选节点
Descheduler 周期性(如每 10 分钟) 发现「不均衡」或「违反策略」的 Pod,发起驱逐
kubelet eviction 节点资源真不足时 按优先级驱逐 Pod(OOM、磁盘满等)

三者互补:调度器负责「进门放哪」,Descheduler 负责「住久了要不要换房」,kubelet 负责「房子快塌了先赶人」。

什么时候需要 Descheduler

适合考虑部署的场景:

  • 多个 worker 长期负载不均,某些节点 Pod 特别多、另一些很空
  • 扩过节点内存或新加 worker 后,希望存量 Pod 逐步分散(调度器不会自动做这件事)
  • 配置了 podAntiAffinity / topologySpreadConstraints,但历史 Pod 是在规则加入之前创建的
  • 希望低利用率节点上的 Pod 被迁走,便于维护或省电

不能指望 Descheduler 单独解决的问题:

  • DaemonSet Pod(每个节点必须有一份,无法迁走)
  • 绑定了 nodeSelector / nodeAffinity 的 Pod(如只允许在特定 Longhorn 节点上跑的服务)
  • StatefulSet + RWO PVC:卷挂在哪个节点时 Pod 通常跟过去;Longhorn 多副本卷在驱逐后可能 attach 到其他节点,但仍受 nodeAffinity、单副本卷、PDB 等约束
  • control-plane 节点的 NoSchedule 污点:普通 Pod 本就不能上去

homelab 里常见的调度限制(与 Descheduler 无关)

即使装了 Descheduler,以下因素仍会限制 Pod 能去哪些节点:

  1. control-plane 污点:node-role.kubernetes.io/control-plane:NoSchedule,三个 control-plane 不接普通工作负载
  2. Longhorn 存储节点:只有部分 worker 参与 Longhorn,带 PVC 的 Pod 受卷副本与 volume.kubernetes.io/selected-node 等约束
  3. 显式 nodeAffinity:例如 Nexus 曾用 required 限定 k8s-50 / k8s-71(已改为 preferred,见 案例文)
  4. 资源 requests:Pod 未设 requests 时,调度器与 LowNodeUtilization 都几乎把它当 0;Grafana 上的实际 CPU 与 Descheduler 账本无关。见 Kubernetes Resource Requests。

Descheduler 只能驱逐允许被调度到其他节点的 Pod;上述硬约束不变。

安装方式(Helm)

官方 chart 在 kubernetes-sigs/descheduler。

helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/
helm repo update

helm upgrade --install descheduler descheduler/descheduler \
  --namespace kube-system \
  --create-namespace

默认以 CronJob 形式周期性运行(常见为每 10 分钟一次 Job),无需常驻 Deployment。

常用策略

通过 ConfigMap / Helm values.yaml 中的 deschedulerPolicy 配置。常见策略包括:

策略 作用
LowNodeUtilization 按节点 requests 占比重平衡;低利用率节点接收、高利用率节点迁出
RemovePodsViolatingInterPodAntiAffinity 清理违反 Pod 间反亲和的 Pod
RemovePodsViolatingTopologySpreadConstraint 清理违反拓扑分布约束的 Pod
RemoveDuplicates 同一节点上运行了多个同 ReplicaSet 副本时驱逐多余的

LowNodeUtilization 示例(需根据集群调整阈值):

apiVersion: descheduler/v1alpha2
kind: DeschedulerPolicy
profiles:
  - name: default
    pluginConfig:
      - name: LowNodeUtilization
        args:
          thresholds:
            cpu: 20
            memory: 20
            pods: 20
          targetThresholds:
            cpu: 50
            memory: 50
            pods: 50
    plugins:
      deschedule:
        enabled:
          - LowNodeUtilization

阈值含义简述:thresholds 以下(且三项都低)视为「低利用率」可接收 Pod;targetThresholds 以上(任一项高)视为「高利用率」可迁出 Pod。百分比基于 requests,不是 kubectl top 实际用量。

与 cordon / drain 的关系

操作 触发方 场景
kubectl cordon + drain 人工 节点维护、重启、换硬件前的一次性清空
Descheduler 自动、周期性 日常负载均衡,无需停机

维护窗口仍应使用 drain;Descheduler 不能替代有计划的下线流程。

DefaultEvictor 与带 PVC 的 Pod

Descheduler v0.36 的 DefaultEvictor 用 podProtections 控制哪些 Pod 不能被驱逐:

配置项 作用
defaultDisabled 关闭默认保护(如 PodsWithLocalStorage → 允许驱逐 emptyDir 等本地卷 Pod)
extraEnabled 开启额外保护;PodsWithPVC 在此列表,默认并不保护 PVC Pod

homelab 当前只 defaultDisabled: PodsWithLocalStorage,没有 extraEnabled: PodsWithPVC,因此带 PVC 的 Deployment Pod 可以被 DefaultEvictor 选中驱逐——前提是策略判定该节点 overutilized,且 Pod 没有 nodeAffinity 等硬约束。

若只想保护部分 StorageClass(例如单副本 longhorn-single),可显式启用并按 SC 过滤:

pluginConfig:
  - name: DefaultEvictor
    args:
      podProtections:
        extraEnabled:
          - PodsWithPVC
        config:
          PodsWithPVC:
            protectedStorageClasses:
              - name: longhorn-single

Nexus 使用 Longhorn 2 副本卷;曾用 required nodeAffinity 限定 k8s-50 / k8s-71,后改为 preferred,Descheduler 驱逐或 Deployment 滚动后 Pod 可落到 k8s-71(现网已验证 attach)。不会落到无卷副本的 k8s-21 / k8s-39,除非 Longhorn 后续扩 replica。

建议同时开启 nodeFit: true(homelab 已开):驱逐前做调度器 dry-run,避免迁到放不下该 Pod 的节点。

部署前注意

  1. 先设 Pod resources.requests,否则利用率策略缺乏可靠依据(详见 Resource Requests)
  2. 有状态服务驱逐会触发重建,确认 PVC 可在目标节点 attach(Longhorn 多副本卷通常可以,单副本需谨慎)
  3. PDB(PodDisruptionBudget) 会限制同时驱逐的数量,避免一次赶走过多副本
  4. 从保守策略开始,观察几轮 CronJob 日志再逐步加大力度
  5. homelab 若未安装 Descheduler,可先用 kubectl top nodes + kubectl get pods -A -o wide --field-selector spec.nodeName=<node> 人工巡检

相关阅读

维护记录

时间 修改内容 原因
2026-06-23 补充 DefaultEvictor / PVC 保护说明;链到 resource-requests 文 Descheduler 未触发排查与 v0.36 podProtections 行为澄清
2026-06-23 链到 homelab-k8s-node-rebalance 案例;更新 Nexus affinity 现网描述 问题已解决,记录完整过程