What is this feature?
Active bin-packing (compaction) consolidates your cluster’s workloads onto fewer nodes so that underutilised nodes can be freed for scale-down or cost reduction. Without compaction, workloads that were spread across nodes during a traffic spike stay spread even after load drops — the cluster never self-consolidates.ClusterCompactionPolicy is the Kubex API for declaring compaction intent. Each policy:
- Selects workloads — by namespace, label selector, and workload type.
- Assigns a scheduler to new Pods — the admission webhook directs newly created Pods for matching workloads to the Kubex compaction scheduler (or a nominated external scheduler), which uses a
MostAllocatedpriority to pack them onto already-busy nodes.Note: If your cluster’s default scheduler can be configured to run in
MostAllocatedmode, that is preferable to deploying Kubex’s own scheduler. Managed services such as GKE, AKS, and EKS allow you to change this setting — refer to your managed service documentation for details. - Creates a descheduler — the controller provisions a dedicated descheduler CronJob for the policy. Each run evicts pods that are on underutilised nodes, triggering re-scheduling onto denser nodes.
- Suppresses admission-convergence loops — if runtime replacement repeatedly fails to apply the intended scheduler or labels, Kubex detects the loop fingerprint and temporarily excludes the workload from further compaction eviction.
How to use
1. Create a ClusterCompactionPolicy
setLabelsByEviction: false patches existing Pod labels and annotations without changing their immutable scheduler assignment. Set setLabelsByEviction: true to evict and recreate noncompliant Pods for full scheduler convergence. The descheduler runs every 30 minutes by default (*/30 * * * *).
The controller reconciles the policy, labels matched workloads, and creates the per-policy descheduler CronJob automatically. No further action is needed.
2. Verify
Operational Model
- Helm installs only the shared compaction scheduler and descheduler support objects.
- The controller generates the scheduler config and one descheduler CronJob/ConfigMap pair per active descheduler policy.
ClusterCompactionPolicychooses the effective policy per workload.- Kubex-managed compaction workloads use scheduler name
kubex-compaction-scheduler; external scheduler mode uses the configured external scheduler name instead. - The scheduler image tag auto-defaults from the target cluster version unless explicitly overridden.
Reference Rules
ClusterCompactionPolicyis cluster-scoped.- Multiple policies may match the same workload space.
- If multiple policies overlap, the controller resolves a single effective policy using
weight, then scope specificity, then age, then name. - Each effective policy gets a deterministic workload label:
scheduling.kubex.ai/compaction-policy=<policy-name>. - The shared suppression label is
scheduling.kubex.ai/compaction-suppressed=true. - The controller writes a versioned
scheduling.kubex.ai/compaction-intentannotation to participating workload metadata. The Pod mutating admission webhook resolves the workload owner, reads that annotation, and sets compaction labels andspec.schedulerNameon each new Pod. It does not modify the workload pod template. With the defaultsetLabelsByEviction: false, the controller also patches compaction labels and annotations on existing Pods in place without changing their immutablespec.schedulerName. setLabelsByEviction: trueopts into a Pod runtime-hook recommendation. Existing Running Pods that missed the required admission state may be evicted through the Kubernetes Eviction API one at a time per workload owner and recreated by their controllers; their replacements receive compaction state during admission. This enables full scheduler convergence. PodDisruptionBudgets remain enforced.
Workload type support
Scheduler assignment depends on whether admission can resolve an annotated supported owner. Descheduler support remains narrower because safe eviction and recreation semantics vary by workload controller.
Known limitations
- Scheduler assignment requires the Pod mutating admission webhook. Its failure policy is
Ignore, so a Pod is admitted with its existing schedulerName when the webhook is unavailable. - KAI GPU scheduling takes precedence when the same Pod receives both KAI resize actions and compaction intent. The webhook records this override in the controller log.
- Existing Pods cannot have their immutable
spec.schedulerNamerewritten. With the defaultsetLabelsByEviction: false, the controller patches their compaction labels and annotations in place without replacing them. WithsetLabelsByEviction: true, admission-noncompliant Pods may be evicted and recreated so their scheduler assignment can converge. Scheduled descheduler evictions are independent of this setting. - The compaction controller never modifies pod-template metadata or spec. Top-level workload metadata changes do not trigger a rollout.
- Admission uses the nearest annotated supported owner and falls back up the owner chain, such as from an unannotated Job to its annotated CronJob.
JobandAnalysisRunscheduler assignment is best-effort because their controllers may create the initial Pod before compaction intent is reconciled onto the owner.
spec.setLabelsByEviction. It honors descheduler.nodeSelector, defaultEvictor.evictSystemCriticalPods, defaultEvictor.evictLocalStoragePods, defaultEvictor.ignorePvcPods, and defaultEvictor.evictDaemonSetPods. It deliberately ignores defaultEvictor.nodeFit, defaultEvictor.labelSelector, the maxNoOfPodsToEvict* limits, interval, and highNodeUtilization; those settings govern descheduler balancing runs rather than admission convergence. Unscheduled, terminal, deleting, protected-namespace, and suppressed Pods are not replaced.
Understanding utilization thresholds
HighNodeUtilization uses scheduled resource commitments, not live metrics:
cpuis the sum of Pod CPU requests divided by node allocatable CPU.memoryis the sum of Pod memory requests divided by node allocatable memory.podsis the number of scheduled Pods divided by the node’s allocatable Pod capacity.
kubectl top nodes, which reports recent measured consumption. For example, a node can consume 15% CPU while carrying requests equal to 70% of allocatable CPU; the descheduler uses 70%.
A node is an underutilized eviction source only when all three percentages are at or below their thresholds. With CPU 40, memory 50, and Pods 30, a node at CPU 35, memory 45, and Pods 20 qualifies, while a node at CPU 41, memory 20, and Pods 10 does not.
Raising a threshold generally makes compaction more aggressive because nodes with more committed resources can qualify as sources. Lowering a threshold is more conservative. The relationship is not unlimited: if every selected node qualifies as underutilized, there is no denser destination and the strategy does nothing.
numberOfNodes is an activation gate. The strategy acts only when the count of underutilized nodes is greater than this value:
0allows a run to act when at least one underutilized source exists.1requires at least two underutilized sources.- Higher values are more conservative and are useful when avoiding disruption for a single temporarily sparse node.
nodeFit: true conservatively checks whether a Pod appears able to fit on another ready node before eviction. This is an approximation, not a reservation or placement guarantee. Hard node affinity, anti-affinity, and DoNotSchedule topology-spread constraints can prevent consolidation or cause a replacement to return to its source node. Validate these constraints before enabling frequent descheduler runs.
descheduler.nodeSelector limits which nodes participate as sources and destinations in descheduler classification. It does not add node affinity to replacement Pods; use workload scheduling constraints when Pods must remain in a particular pool.
Field Reference
Configuration examples
Target selected production workloads conservatively
This policy targets only opted-in web workloads. Existing Pods are not replaced solely to add compaction admission state, and a run requires at least two underutilized nodes before evicting one Pod.Limit descheduler activity to a node pool
Wildcard values are expanded against node labels observed in the cluster. This example considers node pools whose label begins withbatch-. The selector scopes descheduler analysis; add equivalent node affinity to the workloads if replacement Pods must stay in these pools.
Use more aggressive consolidation
This example permits nodes carrying up to 60% requested CPU and memory to become sources, runs every 15 minutes, and allows more evictions per run. Use values like these only after validating PodDisruptionBudgets and scheduling constraints. It also setssetLabelsByEviction which uses an initial eviction based approach to bin-pack pods and label them for future bin-packing. All evictions follow the safety mechanisms described in Safety Controls.
Notes
- Set
spec.enabled: falsewhen you want to keep the object without letting it participate in selection. - The scheduler is a dedicated Kubex-managed scheduler when
spec.scheduler.useKubexScheduler=true; otherwise Kubex only targets the named external scheduler. - Scheduler modes:
useKubexScheduler: true-> Kubex-managed schedulerkubex-compaction-scheduleruseKubexScheduler: falseandexternalSchedulerName: <name>-> external scheduler targeting onlyuseKubexScheduler: falseandexternalSchedulerName: ""-> descheduler-only compaction
- Policy status records whether the policy is enabled, the resolved scheduler name, and the expected scheduler/descheduler readiness state.
- The descheduler policy uses the effective policy label selector implicitly by default;
spec.descheduler.defaultEvictor.labelSelectoradds to that selector andspec.descheduler.nodeSelectornarrows the policy to matching nodes. - Starting image versions in this implementation:
- scheduler:
registry.k8s.io/kube-scheduler:<cluster-version>by default - descheduler:
registry.k8s.io/descheduler/descheduler:v0.36.0
- scheduler:
- Set
compactionScheduler.image.tagor.digestto use an explicitly mirrored scheduler image; otherwise the controller derives the scheduler tag from the cluster Kubernetes version. - Scheduler/descheduler pull policies, resources, security contexts, and chart-level
imagePullSecretsare propagated to controller-created runtime Pods. - Suppression blocks runtime replacement and scheduled descheduler eviction; it does not disable workload participation in the scheduler for future Pods.
- The controller reconciles the scheduler ConfigMap and full Pod runtime settings when active policies use the Kubex-managed scheduler.
- External scheduler mode is workload targeting only; Kubex does not manage scheduler runtime/config for those policies.
- The compaction scheduler ConfigMap is a fixed name:
kubex-compaction-scheduler-config. - The controller also reads
COMPACTION_DESCHEDULER_*settings from its Deployment and creates one descheduler CronJob/ConfigMap per active descheduler policy using the fixedkubex-compaction-deschedulerprefix. - A policy with
spec.descheduler.enabled: truebut no managed workloads gets a suspended descheduler CronJob. - With
openshift.enabled=true, controller-created scheduler and descheduler Pods use arbitrary-UID-compatible restricted security contexts. Standard Kubernetes defaults remain unchanged when the switch is false or omitted.

