Use hotspot-aware descheduling to balance node loads

更新时间:
复制 MD 格式

The ack-koordinator Descheduler detects overloaded nodes by actual resource utilization and evicts Pods to rebalance the cluster.

This topic describes how to install the descheduler, enable hotspot-aware descheduling, and configure its advanced parameters.

Limitations

  • Only ACK managed Pro clusters are supported.

  • Required component versions:

    Component Version
    ACK Scheduler v1.22.15-ack-4.0 or later, v1.24.6-ack-4.0 or later
    ack-koordinator v1.1.1-ack.1 or later
    Helm v3.0 or later
Important

The Koordinator Descheduler only evicts Pods; the ACK Scheduler handles rescheduling. Use descheduling with load-aware scheduling to prevent evicted Pods from landing back on hot spot nodes.

Important

During descheduling, old Pods are evicted before new Pods are created. Ensure your application has enough redundant replicas to maintain availability.

Important

Descheduling uses the standard Kubernetes eviction API. Ensure your Pods are re-entrant so restarts after eviction do not disrupt your service.

Billing

ack-koordinator is free to install and use. Additional charges may apply in these scenarios:

  • Worker node resources: ack-koordinator is a self-managed component that consumes worker node resources. Configure resource requests for each module during installation.

  • Prometheus custom metrics: If you enable Enable Prometheus Monitoring for ACK-Koordinator and use Managed Service for Prometheus, the exposed metrics are counted as custom metrics and incur fees. Fees depend on cluster size and number of applications. Review billing of Prometheus instances before enabling this feature. Query usage data to monitor your resource consumption.

How it works

Execution cycle

The Koordinator Descheduler runs periodically. Each execution cycle has three stages:

Koordinator Descheduler execution procedure
  1. Data collection: Fetches node and workload information and resource utilization data.

  2. Plugin execution (LowNodeLoad example):

    1. Identifies hot spot nodes based on highThresholds and lowThresholds.

    2. Traverses all hot spot nodes, scores the eligible Pods, and sorts them for eviction. See Pod scoring policy.

    3. Checks each candidate Pod against migration constraints—cluster capacity, resource utilization, and replica ratio limits. See Load-aware hot spot descheduling policies.

    4. Marks passing Pods as migration candidates and skips the rest.

  3. Pod eviction and migration: Evicts migration candidates via the eviction API.

Node classification

The LowNodeLoad plugin classifies nodes into three categories based on two thresholds: lowThresholds (idle threshold) and highThresholds (hot spot threshold).

Example with lowThresholds = 45% and highThresholds = 70%:

Node classification diagram
  1. Idle node: Resource utilization is below lowThresholds (< 45%).

  2. Normal node: Resource utilization is between lowThresholds and highThresholds (45%–70%). This is the target range.

  3. Hot spot node: Resource utilization exceeds highThresholds (> 70%). Pods are evicted until the node load drops to 70% or below.

Resource utilization data is updated every minute and reflects the 5-minute average.

If all nodes exceed lowThresholds, the cluster load is considered high and descheduling is suspended, even if some nodes exceed highThresholds.

Load-aware hot spot descheduling policies

Policy Description
Hot spot check retry A node is classified as a hot spot only after exceeding highThresholds for consecutive cycles (default: 5), preventing false positives from momentary spikes.
Node sorting Descheduling starts with the highest-utilization hot spot node. CPU and memory are compared in order.
Pod scoring For each hot spot node, Pods are scored and sorted before eviction: (1) lower Priority class first (default is 0, the lowest); (2) lower QoS class; (3) for equal priority and QoS, Pods are ranked by resource utilization and startup time. Configure Priority or QoS classes to control eviction order.
Filter Scopes descheduling to specific namespaces, Pods, or nodes using label selectors. See evictableNamespaces, podSelectors, and nodeSelector in LowNodeLoad plugin configuration.
Pre-check Before eviction, the descheduler verifies: (1) nodes exist matching Node Affinity, Node Selector, Tolerations, and resource requirements; (2) the target node has sufficient capacity without exceeding highThresholds. Available capacity = (highThresholds − current load) × total capacity. Example: 20% load, 70% highThresholds, 96 vCores → (70% − 20%) × 96 = 48 available vCores.
Migration throttling Limits concurrent Pod migrations per node, namespace, and workload. A time window prevents Pods in the same workload from migrating too frequently. Compatible with Kubernetes Pod Disruption Budget (PDB) for fine-grained availability control.
Observability Migration events are emitted for each Pod, showing the reason and status. Run kubectl get event | grep <pod-name> to view migration details.

Prerequisites

Before you begin, make sure you have:

  • An ACK managed Pro cluster

  • ACK Scheduler v1.22.15-ack-4.0 or later, ack-koordinator v1.1.1-ack.1 or later, and Helm v3.0 or later

Step 1: Enable descheduling in ack-koordinator

  • New installation: Install ack-koordinator and select Enable Descheduling For Ack-koordinator on the configuration page.

  • Existing installation: On the configuration page, select Enable Descheduling For Ack-koordinator. See Modify ack-koordinator.

Step 2: Enable the LowNodeLoad plugin

  1. Create a koord-descheduler-config.yaml ConfigMap to enable the LowNodeLoad plugin.

    # koord-descheduler-config.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: koord-descheduler-config
      namespace: kube-system
    data:
      koord-descheduler-config: |
        # System configuration for koord-descheduler. Do not modify this section.
        apiVersion: descheduler/v1alpha2
        kind: DeschedulerConfiguration
        leaderElection:
          resourceLock: leases
          resourceName: koord-descheduler
          resourceNamespace: kube-system
        deschedulingInterval: 120s  # Execution interval. The descheduler runs every 120s.
                                    # Must not exceed detectorCacheTimeout (default: 5m).
        dryRun: false               # Set to true to run in read-only mode (no evictions).
        # End of system configuration.
    
        profiles:
        - name: koord-descheduler
          plugins:
            balance:
              enabled:
                - name: LowNodeLoad      # Enable the LowNodeLoad plugin for hot spot descheduling.
            evict:
              enabled:
                - name: MigrationController  # Enable the eviction and migration controller.
    
          pluginConfig:
          - name: MigrationController
            args:
              apiVersion: descheduler/v1alpha2
              kind: MigrationControllerArgs
              defaultJobMode: EvictDirectly
    
          - name: LowNodeLoad
            args:
              apiVersion: descheduler/v1alpha2
              kind: LowNodeLoadArgs
    
              # A node is idle if usage of ALL resources is below lowThresholds.
              lowThresholds:
                cpu: 20    # 20% CPU utilization
                memory: 30 # 30% memory utilization
    
              # A node is a hot spot if usage of ANY resource exceeds highThresholds.
              highThresholds:
                cpu: 50    # 50% CPU utilization
                memory: 60 # 60% memory utilization
    
              # Scopes descheduling to specific namespaces.
              # include and exclude are mutually exclusive—configure only one.
              evictableNamespaces:
                include:
                  - default
                # exclude:
                #   - "kube-system"
                #   - "koordinator-system"
  2. Apply the ConfigMap to the cluster.

    kubectl apply -f koord-descheduler-config.yaml
  3. Restart the Koordinator Descheduler to load the new configuration.

    kubectl -n kube-system scale deploy ack-koord-descheduler --replicas 0
    kubectl -n kube-system scale deploy ack-koord-descheduler --replicas 1

Step 3 (optional): Enable load-aware scheduling

For optimal load balancing, enable load-aware scheduling so the ACK Scheduler avoids placing Pods on hot spot nodes after eviction.

Set loadAwareThreshold to match highThresholds. Mismatched values may cause evicted Pods to be rescheduled onto hot spot nodes, especially with few nodes at similar utilization.

Step 4: Verify descheduling

This example uses a three-node cluster where each node has 104 cores and 396 GiB of memory.

  1. Create a stress-demo.yaml file with the following content.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: stress-demo
      namespace: default
      labels:
        app: stress-demo
    spec:
      replicas: 6
      selector:
        matchLabels:
          app: stress-demo
      template:
        metadata:
          name: stress-demo
          labels:
            app: stress-demo
        spec:
          containers:
            - args:
                - '--vm'
                - '2'
                - '--vm-bytes'
                - '1600M'
                - '-c'
                - '2'
                - '--vm-hang'
                - '2'
              command:
                - stress
              image: polinux/stress
              imagePullPolicy: Always
              name: stress
              resources:
                limits:
                  cpu: '2'
                  memory: 4Gi
                requests:
                  cpu: '2'
                  memory: 4Gi
          restartPolicy: Always
  2. Deploy the stress-testing workload.

    kubectl create -f stress-demo.yaml

    Expected output:

    deployment.apps/stress-demo created
  3. Verify the Pods are running and note their assigned node.

    kubectl get pod -o wide

    Expected output:

    NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE                     NOMINATED NODE   READINESS GATES
    stress-demo-588f9646cf-s****   1/1     Running   0          82s   10.XX.XX.53   cn-beijing.10.XX.XX.53   <none>           <none>
  4. Increase the load on cn-beijing.10.XX.XX.53 and check node utilization.

    kubectl top node

    Expected output:

    NAME                      CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
    cn-beijing.10.XX.XX.215   17611m       17%    24358Mi         6%
    cn-beijing.10.XX.XX.53    63472m       63%    11969Mi         3%

    Node cn-beijing.10.XX.XX.53 is at 63% CPU, exceeding the 50% hot spot threshold. Node cn-beijing.10.XX.XX.215 is at 17% CPU, below the 20% idle threshold.

  5. Enable the LowNodeLoad plugin as described in Step 2: Enable the LowNodeLoad plugin.

  6. Watch for Pod changes.

    By default, a node must exceed highThresholds for five consecutive checks (~10 minutes at the default 120-second interval) to be classified as a hot spot.
    kubectl get pod -w

    Expected output:

    NAME                           READY   STATUS             RESTARTS   AGE   IP             NODE                     NOMINATED NODE   READINESS GATES
    stress-demo-588f9646cf-s****   1/1     Terminating        0          59s   10.XX.XX.53    cn-beijing.10.XX.XX.53   <none>           <none>
    stress-demo-588f9646cf-7****   1/1     ContainerCreating  0          10s   10.XX.XX.215   cn-beijing.10.XX.XX.215  <none>           <none>
  7. Check the eviction event.

    kubectl get event | grep stress-demo-588f9646cf-s****

    Expected output:

    2m14s   Normal   Evicting        podmigrationjob/00fe88bd-****   Pod "default/stress-demo-588f9646cf-s****" evicted from node "cn-beijing.10.XX.XX.53" by the reason "node is overutilized, cpu usage(68.53%)>threshold(50.00%)"
    101s    Normal   EvictComplete   podmigrationjob/00fe88bd-****   Pod "default/stress-demo-588f9646cf-s****" has been evicted
    2m14s   Normal   Descheduled     pod/stress-demo-588f9646cf-s****   Pod evicted from node "cn-beijing.10.XX.XX.53" by the reason "node is overutilized, cpu usage(68.53%)>threshold(50.00%)"
    2m14s   Normal   Killing         pod/stress-demo-588f9646cf-s****   Stopping container stress

    The Pod on the hot spot node has been evicted and migrated to cn-beijing.10.XX.XX.215.

Advanced configuration

All Koordinator Descheduler parameters are configured in the ConfigMap from Step 2.

# koord-descheduler-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: koord-descheduler-config
  namespace: kube-system
data:
  koord-descheduler-config: |
    # System configuration. Do not modify this section.
    apiVersion: descheduler/v1alpha2
    kind: DeschedulerConfiguration
    leaderElection:
      resourceLock: leases
      resourceName: koord-descheduler
      resourceNamespace: kube-system
    deschedulingInterval: 120s  # Execution interval. Must not exceed detectorCacheTimeout.
    dryRun: false               # Set to true for read-only mode (no evictions).
    # End of system configuration.

    profiles:
    - name: koord-descheduler
      plugins:
        deschedule:
          disabled:
            - name: "*"  # All plugins disabled by default (shown for reference only).
        balance:
          enabled:
            - name: LowNodeLoad
        evict:
          disabled:
            - name: "*"  # All plugins disabled by default (shown for reference only).
          enabled:
            - name: MigrationController

      pluginConfig:
      - name: MigrationController
        args:
          apiVersion: descheduler/v1alpha2
          kind: MigrationControllerArgs
          defaultJobMode: EvictDirectly
          maxMigratingPerNode: 1       # Max Pods migrating simultaneously per node. 0 = unlimited.
          maxMigratingPerNamespace: 1  # Max Pods migrating simultaneously per namespace.
          maxMigratingPerWorkload: 1   # Max Pods migrating simultaneously per workload (e.g., Deployment).
          maxUnavailablePerWorkload: 2 # Max unavailable replicas per workload during migration.
          evictLocalStoragePods: false # Whether to evict Pods using HostPath or emptyDir volumes.
          objectLimiters:
            workload:               # At most 1 replica migrated per workload within 5 minutes.
              duration: 5m
              maxMigrating: 1

      - name: LowNodeLoad
        args:
          apiVersion: descheduler/v1alpha2
          kind: LowNodeLoadArgs

          lowThresholds:
            cpu: 20
            memory: 30
          highThresholds:
            cpu: 50
            memory: 60

          anomalyCondition:
            consecutiveAbnormalities: 5  # Number of consecutive checks above highThresholds
                                          # before a node is classified as a hot spot.
                                          # Counter resets after eviction.

          detectorCacheTimeout: "5m"  # Cache duration for hot spot checks.
                                      # Must be >= deschedulingInterval.

          evictableNamespaces:
            include:
              - default
            # exclude:
            #   - "kube-system"
            #   - "koordinator-system"

          nodeSelector:  # Process only the specified nodes.
            matchLabels:
              alibabacloud.com/nodepool-id: np77f520e1108f47559e63809713ce****

          podSelectors:  # Process only the specified Pods.
          - name: lsPods
            selector:
              matchLabels:
                koordinator.sh/qosClass: "LS"

Koordinator Descheduler system configuration

Parameter Type Value Description Example
dryRun boolean true / false (default) Read-only mode switch. When enabled, no Pod migration is initiated. false
deschedulingInterval time.Duration > 0s Execution interval. Must not exceed detectorCacheTimeout in the LowNodeLoad plugin. 120s

Eviction and migration control configuration

Parameter Type Value Description Example
maxMigratingPerNode int64 ≥ 0 (default: 2) Max concurrent migrating Pods per node. 0 = unlimited. 2
maxMigratingPerNamespace int64 ≥ 0 (default: unlimited) Max concurrent migrating Pods per namespace. 0 = unlimited. 1
maxMigratingPerWorkload intOrString ≥ 0 (default: 10%) Max migrating Pods or percentage per workload (e.g., Deployment). 0 = unlimited. Single-replica workloads are excluded. 1 or 10%
maxUnavailablePerWorkload intOrString ≥ 0 (default: 10%), less than total replicas Max unavailable replicas or percentage per workload. 0 = unlimited. 1 or 10%
evictLocalStoragePods boolean true / false (default) Whether to evict Pods with HostPath or emptyDir volumes. Disabled by default for data safety. false
objectLimiters.workload struct Duration > 0s (default: 5m); MaxMigrating ≥ 0 (default: 10%) Workload-level migration throttling. Duration: time window. MaxMigrating: max replicas migrated in that window. Defaults to maxMigratingPerWorkload. duration: 5m / maxMigrating: 1 — max 1 replica per workload within 5 minutes.

LowNodeLoad plugin configuration

Parameter Type Value Description Example
highThresholds map[string]float64 [0, 100] (CPU and memory, as percentages) Hot spot threshold. Pods on nodes above this are eligible for eviction. If all nodes exceed lowThresholds, descheduling is suspended. cpu: 55 / memory: 75
lowThresholds map[string]float64 [0, 100] (CPU and memory, as percentages) Idle threshold. If all nodes exceed this, the overall cluster load is considered high and descheduling is suspended. cpu: 25 / memory: 25
anomalyCondition.consecutiveAbnormalities int64 > 0 (default: 5) Consecutive cycles a node must exceed highThresholds to be classified as a hot spot. Resets after eviction. 5
detectorCacheTimeout \*metav1.Duration See Duration format (default: 5m) Cache duration for hot spot checks. Must be ≥ deschedulingInterval. 1h, 300s, 2m30s
evictableNamespaces include: string / exclude: string Namespaces in the cluster Scopes descheduling to specific namespaces. Leave blank to process all Pods. include and exclude are mutually exclusive. exclude: ["kube-system", "koordinator-system"]
nodeSelector metav1.LabelSelector See Labels and selectors Scopes descheduling to specific nodes using a label selector. Supports single node pool (matchLabels) and multiple node pools (matchExpressions). matchLabels: {alibabacloud.com/nodepool-id: np****}
podSelectors list of PodSelector See Labels and selectors Scopes descheduling to specific Pods using label selectors. Multiple selector groups are supported. matchLabels: {koordinator.sh/qosClass: "LS"}

FAQ

Node utilization exceeds the threshold but Pods are not evicted

The most common cause is an inactive descheduler configuration. Check in order:

  1. Scope not configured: The descheduler only processes explicitly included (or not excluded) namespaces and nodes. Verify evictableNamespaces and nodeSelector.

  2. Descheduler not restarted after config change: Configuration changes require a restart. See Step 2 for instructions.

  3. Interval longer than cache timeout: deschedulingInterval (default: 2 min) must not exceed detectorCacheTimeout (default: 5 min), or hot spot detection fails. Adjust and restart.

  4. Node not consistently above threshold: The descheduler uses a smoothed average. A node is classified as a hot spot only after exceeding highThresholds for consecutiveAbnormalities consecutive cycles (default: 5, ~10 minutes). kubectl top node reflects only the last minute—monitor over a longer period to confirm sustained utilization.

  5. Insufficient cluster capacity: Before evicting a Pod, the descheduler verifies another node has sufficient free capacity. If none does, eviction is skipped. Add nodes to increase capacity.

  6. Single-replica workload: Single-replica Pods are not evicted by default. To override, add annotation descheduler.alpha.kubernetes.io/evict: "true" to the Pod or the workload's spec.template.metadata. Not supported in ack-koordinator v1.3.0-ack1.6 through v1.3.0-ack1.8. Upgrade to the latest version to use this feature.

  7. Pod uses HostPath or emptyDir: Excluded from descheduling by default. Set evictLocalStoragePods: true in MigrationController to enable eviction. See Eviction and migration control configuration.

  8. Too many unavailable or migrating replicas: If unavailable or migrating replicas reach maxUnavailablePerWorkload or maxMigratingPerWorkload, further evictions are blocked. Wait for in-progress evictions or increase these limits.

  9. Replica count ≤ migration limit: If total replicas ≤ maxMigratingPerWorkload or maxUnavailablePerWorkload, the workload is skipped. Decrease these values or use percentages.

The descheduler frequently restarts

An invalid or missing ConfigMap causes restart loops. Verify the ConfigMap format against Advanced configuration, then restart as described in Step 2.

How load-aware scheduling and hot spot descheduling work together

Enable both features for optimal load balancing. Descheduling evicts Pods from overloaded nodes; load-aware scheduling places rescheduled Pods on lower-utilization nodes.

Set loadAwareThreshold to match highThresholds. See Use load-aware scheduling and Scheduling policies.

What utilization data does the descheduler use?

The descheduler calculates a smoothed average over multiple cycles. Eviction triggers only when average usage stays above highThresholds for the configured consecutive cycles (default: ~10 minutes).

The descheduler excludes page cache from memory calculations (reclaimable by the OS). kubectl top node includes page cache—use Managed Service for Prometheus to view the actual metric.

Next steps