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
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.
During descheduling, old Pods are evicted before new Pods are created. Ensure your application has enough redundant replicas to maintain availability.
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:
-
Data collection: Fetches node and workload information and resource utilization data.
-
Plugin execution (LowNodeLoad example):
-
Identifies hot spot nodes based on
highThresholdsandlowThresholds. -
Traverses all hot spot nodes, scores the eligible Pods, and sorts them for eviction. See Pod scoring policy.
-
Checks each candidate Pod against migration constraints—cluster capacity, resource utilization, and replica ratio limits. See Load-aware hot spot descheduling policies.
-
Marks passing Pods as migration candidates and skips the rest.
-
-
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%:
-
Idle node: Resource utilization is below
lowThresholds(< 45%). -
Normal node: Resource utilization is between
lowThresholdsandhighThresholds(45%–70%). This is the target range. -
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 exceedlowThresholds, the cluster load is considered high and descheduling is suspended, even if some nodes exceedhighThresholds.
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:
-
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
-
Create a
koord-descheduler-config.yamlConfigMap 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" -
Apply the ConfigMap to the cluster.
kubectl apply -f koord-descheduler-config.yaml -
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.
-
Create a
stress-demo.yamlfile 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 -
Deploy the stress-testing workload.
kubectl create -f stress-demo.yamlExpected output:
deployment.apps/stress-demo created -
Verify the Pods are running and note their assigned node.
kubectl get pod -o wideExpected 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> -
Increase the load on
cn-beijing.10.XX.XX.53and check node utilization.kubectl top nodeExpected 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.53is at 63% CPU, exceeding the 50% hot spot threshold. Nodecn-beijing.10.XX.XX.215is at 17% CPU, below the 20% idle threshold. -
Enable the LowNodeLoad plugin as described in Step 2: Enable the LowNodeLoad plugin.
-
Watch for Pod changes.
By default, a node must exceed
highThresholdsfor five consecutive checks (~10 minutes at the default 120-second interval) to be classified as a hot spot.kubectl get pod -wExpected 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> -
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 stressThe 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:
-
Scope not configured: The descheduler only processes explicitly included (or not excluded) namespaces and nodes. Verify
evictableNamespacesandnodeSelector. -
Descheduler not restarted after config change: Configuration changes require a restart. See Step 2 for instructions.
-
Interval longer than cache timeout:
deschedulingInterval(default: 2 min) must not exceeddetectorCacheTimeout(default: 5 min), or hot spot detection fails. Adjust and restart. -
Node not consistently above threshold: The descheduler uses a smoothed average. A node is classified as a hot spot only after exceeding
highThresholdsforconsecutiveAbnormalitiesconsecutive cycles (default: 5, ~10 minutes).kubectl top nodereflects only the last minute—monitor over a longer period to confirm sustained utilization. -
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.
-
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'sspec.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. -
Pod uses HostPath or emptyDir: Excluded from descheduling by default. Set
evictLocalStoragePods: truein MigrationController to enable eviction. See Eviction and migration control configuration. -
Too many unavailable or migrating replicas: If unavailable or migrating replicas reach
maxUnavailablePerWorkloadormaxMigratingPerWorkload, further evictions are blocked. Wait for in-progress evictions or increase these limits. -
Replica count ≤ migration limit: If total replicas ≤
maxMigratingPerWorkloadormaxUnavailablePerWorkload, 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.