Descheduler in Kubernetes
The Kubernetes scheduler picks a home for a Pod once and never looks back. The Descheduler is the helper that notices when that choice has gone stale, politely asks the Pod to leave, and lets the scheduler choose again. This guide explains how it works, when to use it and how to use it safely.
Picture a wedding where guests are shown to their tables when they arrive. It is a good plan at the start. Then some guests leave early, a new table is added at the back, two people who should not sit together end up side by side, and one table is crowded while another is half empty. The seating manager has finished the job and walks away, so nothing is ever corrected. Kubernetes has exactly this problem with Pods, and the Descheduler is the person who walks around the room at intervals and gently asks a few guests to stand up so that they can be seated again, this time in a better place.
1What the Descheduler Is and Why It Exists
A short definition first, so every later chapter has something solid to stand on.
Kubernetes runs your applications inside Pods, and Pods run on nodes, which are the computers of the cluster. A component called the scheduler decides which node each new Pod should go to. The Descheduler is a separate, optional program that looks at Pods that are already running and finds the ones whose current placement is no longer a good fit. It then removes those Pods using the safe eviction process, and the normal controller of each Pod, such as a Deployment, creates a fresh replacement. The scheduler places that replacement, and with luck it lands somewhere better.
The key idea is in the name. To schedule is to choose a place. To deschedule is to take a Pod out of its place so that it can be scheduled again. The Descheduler never chooses a node itself. It only decides who should leave.
Think of a school bus driver who drops children at stops (the scheduler) and a teacher who later notices that three children live next door to each other but were left at different stops. The teacher cannot drive the bus. She can only ask the children to get off and wait for the bus to pick them up again. That teacher is the Descheduler.
Pod
The smallest unit that Kubernetes runs. It holds one or more containers that share a network address and storage.
Node
A machine, real or virtual, that provides CPU, memory and storage for Pods to use.
Scheduler
The control plane component that picks a node for each Pod that does not have one yet.
Eviction
The polite way of removing a Pod. It respects safety rules and gives the Pod time to shut down properly.
Descheduler
An add-on program that evicts badly placed Pods so that the scheduler can place them again.
The Descheduler is not part of the core of Kubernetes. It is a separate project from the Kubernetes community that you install on top of a cluster when you need it.
2A Quick Look at How Scheduling Works
To understand what goes stale, we first need to see how a placement decision is made.
When you create a Pod, it first has no node. The scheduler notices it and runs two big steps. The first step is filtering. The scheduler throws out every node that cannot run the Pod, for example because it lacks enough free CPU or memory, because it has a taint that the Pod does not tolerate, or because a rule in the Pod says it must run on a certain kind of machine. The second step is scoring. Among the nodes that remain, the scheduler gives each one a score based on things like how much room is left and whether the Pod would be nicely spread out, and it chooses the highest score.
Once the choice is made, the scheduler writes the node name into the Pod, and the job is finished. The Pod runs there until it ends, crashes or is removed. The scheduler does not wake up later to ask whether the choice still looks right.
A new Pod appears
A controller or a person creates a Pod. It has no node yet, so it waits in the scheduler’s queue.
Filter
Nodes that cannot host the Pod because of capacity, taints or rules are removed from the list.
Score
The remaining nodes are ranked, and the best one is picked.
Bind and forget
The Pod is bound to the chosen node. From now on the scheduler considers the work complete.
This design is deliberate and good. It keeps the scheduler fast and predictable, and it avoids a world where running applications are constantly shuffled around. But it also means that placement can grow old, and that is the gap the Descheduler fills.
Many people believe Kubernetes automatically rebalances Pods when a new node joins. It does not. A new empty node only receives Pods that are created after it joins.
3Why Placement Goes Stale: Cluster Drift
A choice that was perfect at 9 in the morning can be a poor choice by lunchtime.
The state of a cluster keeps changing. A placement that satisfied every rule when the Pod was created may no longer match the cluster today. The difference between the ideal placement and the real placement is often called drift. There are several common reasons for it.
New nodes join the cluster
When an autoscaler adds nodes during a busy period, new Pods use them. When the busy period ends and some Pods go away, the old nodes may stay full while the new nodes stay nearly empty.
Nodes are removed and come back
After a node upgrade, a crash or the loss of a spot machine, Pods move to whichever nodes have room at that moment. When the node returns, the Pods do not move back, and the load stays uneven.
Labels and taints change
An administrator may add a taint to a node to reserve it, or change a label that a Pod’s rule depends on. Pods that were allowed when they arrived may now break the rule, yet they keep running.
Rules are checked only at scheduling time
Many Pod placement rules, such as node affinity, are described as “ignored during execution”. This means they are enforced when the Pod is placed and never again while it runs.
Usage changes over time
A Pod placed on a quiet node may grow hungrier as traffic increases. The node can become crowded without the scheduler ever hearing about it.
Pods fail or restart again and again
Some Pods get stuck in a pattern of repeated restarts or end up in a failed state, and quietly occupy space on a node.
A tidy bookshelf slowly becomes messy. Nobody does anything wrong on any single day, but books are added, borrowed and returned in whatever gap is free. After a year, one shelf is overflowing and another is empty. A regular tidy-up fixes it, and the Descheduler is the tidy-up for your cluster.
| What changed | Resulting problem | Descheduler idea |
|---|---|---|
| New empty nodes added | Old nodes stay overloaded | Move some Pods to the new nodes |
| Taint added to a node | Pods that do not tolerate it still run there | Evict Pods that violate the taint |
| Node label changed | Pods no longer match their affinity rule | Evict Pods that violate the affinity |
| Many replicas on one node | One node failure takes down many copies | Evict duplicates so they spread |
| Pod keeps crashing | Wasted resources and noisy alerts | Evict Pods with too many restarts |
4The Big Picture: Where the Descheduler Fits
Three actors share this job: the Descheduler decides who leaves, a controller makes a replacement, and the scheduler picks the new home.
The easiest way to hold the picture in your head is as a loop. The Descheduler wakes up, reads the state of the cluster from the API server, decides that a particular Pod is poorly placed, and asks the API server to evict it. The Pod’s owner, such as a Deployment, sees that a Pod has disappeared and creates a new one. The scheduler then sees an unplaced Pod and picks a node for it using the rules of the day.
flowchart LR
D["Descheduler"] -->|"1 Read nodes and Pods"| A["API Server"]
D -->|"2 Evict a badly placed Pod"| A
A -->|"3 Pod removed"| C["Owner controller, such as a Deployment"]
C -->|"4 Create replacement Pod"| A
A -->|"5 Unplaced Pod appears"| S["Scheduler"]
S -->|"6 Bind Pod to a better node"| A
This division of labor is important for two reasons. First, the Descheduler needs no special power to place Pods, so it cannot place them anywhere that the scheduler would not. Second, the replacement Pod passes through all the normal checks again, including quotas and admission rules, so nothing is bypassed.
A Pod that has no owner, such as one created by hand, will not come back after eviction, because nothing exists to create a replacement. This is one reason the Descheduler is careful with such Pods by default.
5Ways to Run the Descheduler
The Descheduler is itself an ordinary program running in your cluster, and you choose how often it runs.
The Descheduler is packaged as a container image and is commonly installed with a Helm chart or with the manifests that its project provides. It normally lives in the kube-system namespace and is given a high priority class so that it is not itself removed when the cluster is under pressure. There are three ways to run it.
Job
Runs one time and stops. Useful for a manual clean-up, for testing a new policy, or for being triggered by your own automation.
CronJob
Starts on a schedule, does one pass and exits. A good default because each run starts fresh and the schedule is easy to read.
Deployment
Runs all the time and repeats its work at a chosen interval. Useful when you want leader election and continuous behavior.
Advantages of scheduled runs
- Simple to understand and to pause
- Each run starts with a clean state
- Easy to align with quiet hours
- Failed runs are visible as failed Jobs
Disadvantages / Trade-offs
- Drift stays until the next scheduled run
- A Deployment reacts more steadily but runs all day
- Too frequent runs can cause constant churn
The service account and its permissions
Because the Descheduler talks to the API server, it needs an identity and permissions. It runs under a dedicated service account that is allowed to read nodes, Pods, namespaces and priority classes, and to create evictions. It does not need permission to create Pods or to change nodes. Keeping its rights this narrow means that even a bug in the program cannot do more harm than evicting Pods.
Start with a one-time Job in dry-run mode on a test cluster. Move to a CronJob once you trust the results, and only then think about running it more often.
6The Policy: Profiles, Plugins and Extension Points
All the Descheduler’s behavior is controlled by one policy that you write, and nothing happens that the policy does not ask for.
The Descheduler does nothing on its own initiative beyond reading its policy. The policy is a small configuration object that is stored in a ConfigMap and mounted into the Descheduler Pod. In the current style, the policy is organized into profiles. A profile is a named group of settings that says which plugins to turn on and how each one should behave.
Types of plugin
The behavior of the Descheduler is split into plugins. Plugins fall into a few types, which the project calls extension points.
| Plugin type | Job | Examples |
|---|---|---|
| Deschedule | Looks at Pods one at a time and evicts those that break a rule | Node taints, node affinity, too many restarts, Pod lifetime |
| Balance | Looks at groups of Pods or nodes and evicts some to even things out | Low node utilization, duplicates, topology spread |
| Filter | Decides whether a Pod is allowed to be evicted at all | The default evictor |
| Pre-eviction filter | A last check just before a chosen Pod is evicted | Checks such as whether another node could host the Pod |
Imagine a restaurant manager with a checklist. Some items are about single tables, such as “this table is sitting in the wrong section”. Others are about the whole room, such as “the left side is much busier than the right”. A final rule says who may never be asked to move, like a guest in a wheelchair. Deschedule plugins, Balance plugins and the filter are those three kinds of items.
What a profile contains
A profile lists each plugin it wants, along with settings for each. It also configures the default evictor, which is the safety filter described in the next chapters. Several profiles can exist side by side, for example one for your general workloads and one for a special group of nodes. Within a pass, all Deschedule plugins run before Balance plugins, and the filters are consulted before any Pod is actually evicted.
Turn on only the plugins you need. A short, well understood policy is far easier to reason about than a long one copied from an example.
7Inside One Descheduling Run
Each run follows the same sequence of steps, from reading the cluster to counting what was evicted.
Understanding a single run helps explain why the Descheduler sometimes evicts nothing even when a cluster looks untidy. At each step, rules narrow down the candidates.
Load the policy
The Descheduler reads its policy and builds the profiles with their plugins and settings.
List the nodes
It reads all ready nodes, and if a node selector is set, keeps only the nodes that match.
Run the Deschedule plugins
Each enabled plugin of this type goes through the Pods and nominates the ones that break its rule.
Run the Balance plugins
Each enabled plugin of this type studies groups of Pods or nodes and nominates Pods that would improve balance if moved.
Apply filters and limits
Each nominated Pod is checked against the safety filter and the global limits on how many Pods may be evicted.
Evict through the API
For Pods that pass, the Descheduler sends an eviction request. The API server accepts or refuses it based on disruption budgets.
sequenceDiagram
participant D as Descheduler
participant A as API Server
participant C as Deployment Controller
participant S as Scheduler
D->>A: List ready nodes and Pods
A-->>D: Current cluster state
D->>D: Plugins nominate Pods, filters remove unsafe ones
D->>A: Create eviction for chosen Pod
A->>A: Check disruption budget
A-->>D: Accepted
A-->>C: Pod deleted event
C->>A: Create replacement Pod
A-->>S: New unplaced Pod
S->>A: Bind to a node
At the end of each run the Descheduler reports what it did through its logs and metrics. In Deployment mode, it then sleeps until the next interval. In Job and CronJob modes, it simply exits.
8Eviction Safety: The Default Evictor
A tool that removes running Pods must be very careful, and the Descheduler has several layers of protection.
The Descheduler uses the Eviction API rather than simply deleting Pods. An eviction is a polite request. The API server checks any PodDisruptionBudget that covers the Pod. A PodDisruptionBudget is a rule that says “at least this many copies of my application must stay available”. If removing the Pod would break that promise, the eviction is refused, and the Pod stays. Accepted evictions also respect the Pod’s grace period, so the application has time to finish its requests and shut down cleanly.
What is protected by default
Before the Eviction API is ever called, the Descheduler’s default evictor already filters out Pods that are risky to move. By default these are not evicted.
System-critical Pods
Pods with a very high priority, such as core cluster components, are left alone.
DaemonSet Pods
These exist to run on every node, so moving them would make no sense.
Pods with local storage
Data in a local emptyDir or host path would be lost on eviction, so these are skipped unless you say otherwise.
Pods without an owner
A Pod created by hand would not be recreated, so it is not touched by default.
Static Pods
These are managed directly by the kubelet on a node and cannot be rescheduled.
Pods under a priority threshold
You can set a priority level above which Pods are never evicted, protecting your most important work.
Tuning the evictor
The default evictor has settings you can change. You can allow eviction of Pods with local storage or of failed bare Pods, you can require that another node could actually host the Pod before it is evicted, which is called the node-fit check, you can limit eviction to Pods with a certain label, and you can protect young Pods by setting a minimum age. You can also mark a single Pod with an annotation to say that it should or should not be evicted, which gives application owners a simple way to speak up.
Always turn on the node-fit check. It prevents the Descheduler from evicting a Pod when no other node could take it, which would only leave the Pod stuck in Pending with nowhere to go.
Applications that have only one replica and no disruption budget will be down for a short time every time they are evicted. Run at least two replicas of anything you do not want to interrupt.
9The LowNodeUtilization Plugin
This plugin spreads load by moving Pods from crowded nodes toward empty ones.
This is the plugin that most people think of first. It looks at how much of each node’s capacity is already promised to Pods, and it tries to even things out. It uses two sets of limits that you provide, one called thresholds and one called target thresholds, each given as percentages for resources such as CPU, memory and the number of Pods.
How a node is classified
A node whose use is below all of the thresholds is called underutilized. It has plenty of room. A node whose use is above any of the target thresholds is called overutilized. It is crowded. Nodes in between are considered fine and are left alone.
flowchart TD
N["Each node"] --> Q1{"Below all thresholds?"}
Q1 -->|Yes| U["Underutilized: can receive Pods"]
Q1 -->|No| Q2{"Above any target threshold?"}
Q2 -->|Yes| O["Overutilized: Pods may be evicted"]
Q2 -->|No| OK["Balanced: left alone"]
How it chooses what to evict
The plugin only acts if there is at least one underutilized node and at least one overutilized node. It then evicts Pods from the overutilized nodes until they fall down toward the target level, or until the underutilized nodes have no more room to receive Pods. Among the Pods on an overutilized node, it prefers to evict less important ones first, ordering by priority and then by quality of service class, so that best-effort Pods go before guaranteed ones.
Imagine supermarket checkout lanes. Lane 1 has a queue to the door, while lanes 4 and 5 are empty. A manager asks a few people in lane 1 to move across, starting with those who have the smallest baskets. When lane 1 is shorter, or the empty lanes fill up, the manager stops.
By default this plugin looks at the resources that Pods have requested, not the CPU and memory they are actually using right now. A node can look full on paper and still be quiet in reality. Newer versions can also use live usage metrics, but you need to set this up deliberately.
Useful settings
You can require that a minimum number of underutilized nodes exist before the plugin does anything, which avoids pointless moves in small clusters. You can also choose to measure thresholds as a distance from the average usage of the cluster instead of as fixed numbers, which adapts better when the cluster as a whole is very busy or very quiet.
10The HighNodeUtilization Plugin and Compaction
Sometimes you want the opposite of spreading: you want to pack Pods tightly so that spare nodes can be removed.
Spreading Pods out is good for resilience, but it costs money in a cloud, because you pay for every node even if each one is half empty. If your goal is to save money, you want Pods packed onto as few nodes as possible, so that the empty ones can be shut down by a cluster autoscaler. This is called compaction or bin packing.
The HighNodeUtilization plugin does this. It finds nodes that use less than a given threshold and evicts Pods from them, hoping that those Pods will be placed onto other nodes that are already busier. Over time, lightly used nodes become empty. An autoscaler, seeing an empty node, can then remove it.
A bus company runs five half-empty buses on a quiet evening. By asking passengers to move into three buses, two buses can go back to the depot and save fuel. The passengers still reach their destination, and the company pays less.
This plugin only works well when the scheduler is also set to prefer busy nodes. If the scheduler still spreads Pods evenly, the evicted Pods simply land on the same lightly used nodes, and nothing is gained. The two must agree on the goal.
| Aspect | LowNodeUtilization | HighNodeUtilization |
|---|---|---|
| Goal | Even out load across nodes | Pack Pods onto fewer nodes |
| Evicts from | Overutilized nodes | Underutilized nodes |
| Best for | Performance and resilience | Cost saving with an autoscaler |
| Scheduler needs | Default spreading behavior | A packing preference such as favoring busy nodes |
| Can run together? | No. They pull in opposite directions, so choose one for a given group of nodes. | |
11The RemoveDuplicates Plugin
Several copies of the same application on one node is a hidden risk that this plugin removes.
Applications usually run as several identical copies, called replicas, so that if one fails the others keep serving. This only helps if the copies are on different nodes. If three replicas sit on the same node and that node fails, all three disappear together, and your application is down despite having replicas.
This can happen after node restarts or upgrades, when many Pods land on whichever node is available first. The RemoveDuplicates plugin looks at Pods that share the same owner, such as the same ReplicaSet or StatefulSet, and checks how they are spread. If one node holds more than its fair share, it evicts the extra ones so that the scheduler can place them on other nodes.
A careful traveler never packs all copies of an important document in the same bag. If the bag is lost, every copy is lost. The plugin is like a friend who sees all three copies in one bag and says, “Put one in another bag.”
Fair share rule
If there are fewer nodes than replicas, the plugin does not demand one Pod per node. It works out a fair maximum per node and only evicts Pods above it.
Excluding some owners
You can exclude certain kinds of owners from this check, which is useful for workloads where having several Pods on one node is perfectly acceptable.
Add topology spread constraints or anti-affinity to your Deployments so that the scheduler spreads replicas correctly from the start. Use this plugin as a safety net for older placements, not as the main plan.
12Topology Spread and Anti-Affinity Plugins
Rules about spreading are checked when a Pod is placed, and these plugins check them again later.
Kubernetes lets you describe how Pods should be spread across zones, nodes or other groups. A topology spread constraint says something like “keep the number of Pods in each zone within one of each other”. The amount of difference allowed is called the maximum skew. The scheduler respects this rule at the moment of placement. Afterwards, if Pods in one zone are deleted, the spread can become uneven again, and nothing corrects it.
RemovePodsViolatingTopologySpreadConstraint
This plugin checks whether the current spread breaks the rule, and if so, evicts enough Pods from the most crowded group to bring the difference back within the allowed skew. You can choose whether it should also handle soft constraints, which are preferences rather than strict rules.
RemovePodsViolatingInterPodAntiAffinity
Inter-pod anti-affinity says that certain Pods must not run next to each other, for example two copies of a database on one machine. The scheduler enforces it at placement time. If a conflict appears later, because a label changed or because of an unlucky sequence of events, this plugin evicts the Pods that break the rule so that they can be placed apart.
Two rival fans should not sit in the same row at a football match. The stewards check at the gate, but later two seats are swapped by mistake. A steward walking the rows spots the problem and asks one fan to move.
These plugins only help if your Pods actually carry the spread or anti-affinity rules. Without such rules in the Pod definitions, the scheduler has nothing to enforce when it places the replacements, and Pods may drift back to the same shape.
13Node Affinity and Node Taint Plugins
When the rules about a node change after a Pod arrived, these plugins make sure the Pod still belongs there.
RemovePodsViolatingNodeAffinity
A Pod can say that it must run only on nodes with a certain label, for example nodes with fast disks. This is called node affinity. The common form is written with a long name that ends in “ignored during execution”, and that ending is the catch. It means the rule is checked only at placement time. If an administrator later removes the label from the node, or the Pod’s rule is updated, the Pod keeps running on a node that no longer matches.
This plugin finds such Pods and evicts them, but only when another node exists where the rule would be satisfied. That second check prevents the plugin from evicting a Pod just to leave it homeless. You can choose whether it handles only the required form of affinity, only the preferred form, or both.
RemovePodsViolatingNodeTaints
A taint is a mark on a node that tells Pods to stay away unless they carry a matching toleration. Taints are often used to reserve nodes for special work or to prepare a node for maintenance. A taint with the “no schedule” effect blocks new Pods but does not remove Pods that are already on the node. This plugin removes the Pods that do not tolerate the taint, so the node is truly cleared for its new purpose.
A restaurant turns one room into a private party room and hangs a “reserved” sign on the door. New guests are stopped at the door, but the families already inside keep eating. The staff then politely asks them to move to the main room so the party room is ready. The taint plugin is that polite request.
If you prefer to evict at once, a taint with the “no execute” effect already makes Kubernetes evict Pods that do not tolerate it, without needing the Descheduler. The plugin is mainly useful for the softer “no schedule” kind.
14Lifetime, Restarts and Failed Pods
Three simple plugins deal with Pods that have lived too long, crash too often, or have already failed.
PodLifeTime
Evicts Pods older than a maximum age. Useful when you want applications to be refreshed regularly, for example to pick up new settings or to avoid very long-running memory growth.
RemovePodsHavingTooManyRestarts
Evicts Pods whose containers have restarted more than a set number of times. A fresh Pod on a different node may succeed where this one keeps failing.
RemoveFailedPods
Evicts Pods that are in a failed state, optionally filtered by the reason and by how long ago they failed. This clears clutter that would otherwise stay around.
When these help
PodLifeTime is a gentle way to make sure nothing runs forever without being refreshed. It is best used with a long limit, such as days, and combined with a disruption budget so that restarts happen slowly. The restart plugin is useful for problems tied to one node, such as a faulty disk or a network issue, because moving the Pod away may cure the symptom. It does not fix a bug in the application itself, and an application that crashes everywhere will simply crash on its next node too.
If a printer in one room keeps jamming, it may help to move your document to a printer in another room. But if the document itself is broken, every printer will jam. The restart plugin helps with the first case only.
Setting PodLifeTime to a very short time on important services creates constant restarts that look like instability. Use it deliberately and keep the limit generous.
15Limits, Rate Control and Dry Run
Because eviction causes real interruption, the Descheduler lets you cap how much it can do at once.
Even a correct policy can be too eager. Moving a hundred Pods in a few seconds would put a heavy load on the cluster and would be risky for the applications. The Descheduler therefore provides global limits.
| Setting | What it limits | Why it helps |
|---|---|---|
| Max Pods evicted per node | How many Pods can leave one node in a run | Stops one node from being emptied too quickly |
| Max Pods evicted per namespace | How many Pods can leave one team’s namespace in a run | Protects a single team from being hit hard |
| Max Pods evicted in total | How many Pods can be evicted in the whole run | Puts a ceiling on the overall disruption |
| Node selector | Which nodes the Descheduler considers | Lets you test on a small group of nodes first |
| Interval or schedule | How often runs happen | Spaces out disruption over time |
Dry run
The Descheduler has a dry-run mode. In this mode, it goes through every step and writes in its logs which Pods it would evict, but it does not actually evict anything. This is the best way to learn what a new policy will do, because you can read the planned evictions and decide whether they make sense before any application is touched.
A fire drill with no real fire. Everyone practices the route to the exit and you see where people get stuck, but nobody is actually in danger. Dry run is a drill for your policy.
Begin with low limits, such as a handful of Pods per run, and raise them slowly as you gain confidence. It is easy to allow more later and painful to recover from allowing too much on the first day.
16Descheduler Compared with Related Tools
Several Kubernetes features move or remove Pods, and it helps to know which one does what.
| Tool | Main job | When it acts | Relationship to the Descheduler |
|---|---|---|---|
| Scheduler | Chooses a node for a new Pod | When a Pod is created | Places the replacements the Descheduler asks for |
| Descheduler | Evicts badly placed Pods | On a schedule or interval | Subject of this guide |
| Cluster autoscaler or node provisioner | Adds and removes nodes | When Pods cannot fit or nodes are empty | Removes nodes that compaction has emptied |
| Node drain | Empties one node for maintenance | When an administrator starts it | Uses the same eviction safety rules |
| Vertical Pod Autoscaler | Adjusts Pod resource requests | When usage changes | May evict Pods too, so coordinate the two |
| Taint with no execute effect | Removes Pods that do not tolerate a taint | Immediately when the taint appears | Does part of what the taint plugin does |
A hotel has a front desk that assigns rooms (scheduler), a housekeeping manager who asks guests to switch rooms when something is wrong (Descheduler), a facilities team that opens or closes whole floors as demand rises and falls (autoscaler), and a maintenance crew that clears one room for repairs (drain).
Descheduler and autoscaler often make a good pair. The Descheduler packs Pods onto fewer nodes, and the autoscaler removes the nodes that become empty. Some modern node provisioners also perform their own consolidation, so check what your platform already does before adding a second tool with the same goal.
17A Worked Example: Balancing After a Scale-Up
Here is a story that shows every part working together.
A cluster has three nodes, named A, B and C. During a sale on an online shop, traffic rises and the autoscaler adds two more nodes, D and E. The Pods for the shop’s checkout service are created during the rush, and the scheduler places them wherever there is space. When the sale ends, many Pods are removed, but the old nodes A, B and C still carry most of the remaining Pods. Nodes D and E are almost empty. An engineer has installed the Descheduler as a CronJob with the LowNodeUtilization plugin turned on.
The run starts
The CronJob fires. The Descheduler reads the policy and lists the five ready nodes and their Pods.
Nodes are classified
Nodes D and E are below all thresholds, so they are underutilized. Node A is above a target threshold, so it is overutilized. B and C are in the comfortable middle.
Candidates are picked
On node A, the plugin lines up Pods, with the least important ones first. The safety filter removes a monitoring agent that belongs to a DaemonSet and a Pod with local storage.
Checks and limits
The node-fit check confirms that D or E could host each remaining candidate. The limit of five evictions per node stops the plugin after five Pods.
Evictions and replacements
The Descheduler asks for the eviction of each Pod. Disruption budgets are respected, so one Pod is refused for now. The Deployments create replacements, and the scheduler places most of them on D and E.
flowchart LR
subgraph Before
A1["Node A: crowded"]
B1["Node B: medium"]
D1["Node D: empty"]
end
subgraph After
A2["Node A: comfortable"]
B2["Node B: medium"]
D2["Node D: comfortable"]
end
A1 -->|"Pods evicted and replaced"| D2
After the next run, the cluster is more even. Because the engineer used a small limit, only a few Pods moved at a time, and the checkout service stayed available throughout. If the engineer also uses topology spread constraints in the Deployments, the new placements will be spread across zones as well.
18Making Your Applications Descheduler-Friendly
The Descheduler can only be gentle if the applications it touches are built to be moved.
Any Pod can be evicted at almost any time in Kubernetes, because of node maintenance, autoscaling, or failures. Applications that survive those events will also survive the Descheduler. So the real work of making a cluster safe for descheduling is making each workload robust.
Run several replicas
With at least two copies on different nodes, one copy can leave while the other keeps serving.
Set a disruption budget
A PodDisruptionBudget tells Kubernetes how many copies may be unavailable at once, and the Descheduler respects it.
Handle shutdown signals
The application should finish current work and close connections when it is told to stop, within the grace period.
Use readiness checks
A new Pod should receive traffic only when it is truly ready, so a moved Pod does not serve errors while starting.
Declare placement rules
Put spread and affinity rules in the Pod definition so the scheduler places replacements well.
Mark special Pods
Use annotations or priority levels to protect work that must never be moved, such as long-running batch calculations.
Stateful applications
Databases and other stateful applications need extra care. A StatefulSet Pod usually keeps its storage when it is recreated, but it may take time to reconnect, rejoin a cluster and warm up its caches. Evicting several such Pods quickly can cause trouble. A common approach is to exclude them from descheduling altogether, or to protect them with strict disruption budgets and a long minimum age.
Treating batch jobs like web services. A job that has been running for hours will lose its progress if evicted. Mark such Pods as protected, or keep them out of the Descheduler’s scope with labels.
19Security and Permissions
A program that is allowed to evict Pods is powerful, so its access should be small and well watched.
The Descheduler needs to read information about nodes, Pods, namespaces and priority classes, and to create eviction requests. It does not need to read Secrets, create Pods or change nodes. Granting only those rights through a dedicated service account is the first protection.
| Concern | Risk | Mitigation |
|---|---|---|
| Over-broad permissions | A bug or compromise could change far more than Pods | Use a narrow cluster role with only the needed verbs |
| Wrong policy | A careless rule could cause an outage | Review policy changes like code, test with dry run |
| Policy tampering | Someone edits the ConfigMap to evict important Pods | Restrict who can edit the ConfigMap in kube-system |
| Image trust | A malicious image runs with eviction rights | Pin the image version and verify its source |
| No record of actions | Hard to learn who evicted what | Keep audit logs and Descheduler logs together |
A building manager who can ask tenants to move offices needs a master key to some doors but should never be given the key to the safe. Giving exactly the right keys keeps the building safe even if the manager makes a mistake.
Run the Descheduler in the system namespace with a high priority class, restrict write access to its policy, and alert on any sudden jump in the number of evictions.
20Monitoring, Logging and Metrics
If you cannot see what the Descheduler is doing, you cannot trust it.
Logs
The Descheduler writes logs that explain each run: which plugin ran, which nodes were considered, which Pods were nominated, which were filtered out and why, and which were evicted. The detail depends on the log level, and a higher level is helpful when you are learning or debugging. In dry-run mode, the logs are the main way to see what would have happened.
Events and metrics
Evictions also appear as events attached to the Pods, so you can see them with ordinary Kubernetes tools. The Descheduler exposes metrics in a format that common monitoring systems read. These include counts of evicted Pods, grouped by plugin, namespace and result, and measurements of how long each run and each plugin took.
Eviction count
A steady small number is normal. A sudden spike suggests a policy mistake or a big change in the cluster.
Failed evictions
Many refused evictions can mean disruption budgets are too tight or that Pods have nowhere else to go.
Run duration
Very long runs can mean a large cluster or an expensive policy that should be trimmed.
Repeat evictions of the same Pod
If the same Pod keeps coming back to the same node and being evicted, the plan and the scheduler disagree.
Show node usage and eviction counts on the same dashboard. You then see directly whether the evictions actually made the cluster more balanced, which is the only result that matters.
21Anti-Patterns to Avoid
These mistakes are common and each one has a simple cure.
Problem
Turning on many plugins at once with aggressive settings on the first day.
Why It’s Harmful
It is impossible to tell which rule caused which eviction, and a combined policy can create waves of restarts across the cluster.
Correct Approach
Enable one plugin at a time, test it with dry run, watch the results for a few days, and only then add the next.
Problem
Evicting Pods that the scheduler will place right back on the same node.
Why It’s Harmful
The Pod is restarted for nothing, and the cycle repeats every run. This is often called flapping and creates pure disruption with no benefit.
Correct Approach
Make sure the scheduler’s preferences agree with the plugin’s goal, use the node-fit check, and put spread rules into the Pod definitions.
Problem
Running both LowNodeUtilization and HighNodeUtilization for the same nodes.
Why It’s Harmful
One plugin spreads Pods out and the other packs them in. They undo each other and generate endless churn.
Correct Approach
Choose one goal per group of nodes. Use separate profiles with node selectors if two groups really need different goals.
Problem
Relying on the Descheduler for single-replica applications without disruption budgets.
Why It’s Harmful
Every eviction means a short outage, because there is no other copy to serve traffic while the new Pod starts.
Correct Approach
Run at least two replicas, add a disruption budget, or exclude the application from descheduling.
Problem
Running it very often, such as every minute, with no limits.
Why It’s Harmful
Constant small moves keep applications in permanent turbulence and make problems harder to diagnose.
Correct Approach
Use an interval of tens of minutes or longer, set eviction caps, and avoid peak traffic hours when you can.
22Rolling It Out Safely and Weighing the Trade-Offs
A cautious rollout turns the Descheduler from a risk into a quiet helper.
Prepare the workloads
Check that important applications have replicas, disruption budgets, readiness checks and spread rules.
Start in dry run on a test cluster
Read the logs and compare the planned evictions with what you expect.
Limit the scope
Use a node selector and a namespace limit to restrict the first real runs to a small slice of the cluster.
Watch and tune
Look at the dashboard after each run and adjust thresholds, limits and the schedule.
Widen gradually
Extend to more nodes and more plugins one step at a time, keeping a simple way to pause the Descheduler.
Advantages
- Keeps load more even after the cluster changes
- Restores spread and affinity rules that have drifted
- Enables cost savings when paired with an autoscaler
- Cleans up failed and constantly restarting Pods
- Uses safe eviction with disruption budget support
- Is fully controlled by a readable policy
Disadvantages / Trade-offs
- Every eviction restarts a Pod and risks short interruption
- Needs careful tuning and good workload design
- Does not choose nodes, so the scheduler may disagree
- Default load measurement is based on requests, not live usage
- An extra component to install, upgrade and monitor
Small, stable clusters with few node changes may not need a Descheduler at all. Clusters that scale often, use spot machines, change taints and labels, or care about cost and spread usually benefit the most.
23Troubleshooting Common Problems
Most problems fall into a few patterns, and each has an obvious first check.
| Symptom | Likely cause | What to check first |
|---|---|---|
| Nothing is ever evicted | No node is underutilized, or every Pod is filtered as protected | Read the logs at a higher level to see why Pods were skipped |
| Pods return to the same node | The scheduler’s preferences disagree with the plugin | Compare scheduler scoring with the policy goal and add spread rules |
| Evicted Pods stay Pending | No node has room or matches the rules | Check capacity, taints, quotas and the node-fit setting |
| Evictions are refused | A disruption budget blocks them | Check the budget and the number of ready replicas |
| Descheduler Pod fails to start | Policy file is invalid or permissions are missing | Read its logs and verify the ConfigMap and cluster role |
| Unexpected Pods evicted | Policy is broader than intended | Review labels, namespaces and the evictor settings |
| Too many restarts in one hour | Interval is too short or limits are too high | Lengthen the interval and lower the caps |
A calm way to investigate
First, turn on dry run so nothing else changes. Next, raise the log level and read one full run from start to finish. The logs usually say exactly why a Pod was or was not nominated. Then compare what the plugin expected with what the scheduler did when the replacement appeared. Most puzzling cases turn out to be a disagreement between these two.
Blaming the Descheduler for every Pod restart. Pods are also restarted by node problems, autoscaling, upgrades and application crashes. Check the eviction events and metrics to confirm that the Descheduler was really the cause.
24Real-World Examples
Different teams use the Descheduler for different reasons.
Online shops with sales peaks
After a traffic peak, nodes added by the autoscaler stay under-used while older nodes stay busy. A balancing policy spreads the remaining load, and a compaction policy later lets surplus nodes be removed.
Teams using cheap spot machines
When spot machines disappear and come back, Pods bunch up on the survivors. The Descheduler spreads them again once the new machines are ready.
Platform teams that change node rules
When a platform team adds a taint to reserve nodes for a special workload, the taint and affinity plugins clear out Pods that no longer belong, without a manual drain.
High availability across zones
After a zone outage, Pods may be concentrated in the zones that stayed up. The topology spread plugin restores an even spread once the missing zone returns.
Cost-focused environments
Development and test clusters often run with many half-empty nodes. Compaction followed by node removal can lower the bill without touching production.
Clusters with long-lived Pods
Some teams use the lifetime plugin with a generous limit so that applications are refreshed regularly, picking up the newest images and settings without a manual rollout.
25Frequently Asked Questions
Short answers to questions that beginners often ask.
No. It only evicts Pods. The scheduler is still the one that picks the new node for the replacement.
It can, as a Deployment with an interval, or it can run on a schedule as a CronJob, or once as a Job. The best choice depends on how quickly you need drift to be corrected.
By default it does not evict system-critical Pods, DaemonSet Pods, static Pods, Pods with local storage, or Pods without an owner. You can adjust these rules, so be careful when you do.
Yes, because it uses the Eviction API. If an eviction would break a budget, the API server refuses it, and the Pod stays where it is.
The scheduler decided that the same node was still the best choice. Add spread or affinity rules to the Pod, or make sure the scheduler’s preferences match the Descheduler’s goal.
Yes. Dry-run mode makes the Descheduler write what it would do to its logs without evicting anything.
By default the utilization plugins use the resource requests of Pods. Newer versions can use live metrics, but this must be configured on purpose.
A drain empties one chosen node, usually for maintenance. The Descheduler looks across the whole cluster and evicts only Pods that break a policy.
Not always. The autoscaler adds and removes nodes but does not move running Pods. The Descheduler helps when you need Pods rebalanced or packed so that nodes can actually be removed.
Yes. You can use priority levels, labels used by the evictor, or an annotation on the Pod to tell the Descheduler to leave it alone.
26Summary and Key Takeaways
Kubernetes places each Pod once and then trusts that placement for the Pod’s whole life. As nodes join and leave, rules change and usage shifts, those old decisions become less ideal. The Descheduler closes that gap. It reads a policy, inspects the cluster, nominates Pods that no longer belong where they are, filters out the ones that are unsafe to move, and asks the API server to evict the rest, one careful step at a time. The scheduler then chooses new homes for the replacements. Used with care, small limits and well-built applications, it keeps a cluster balanced, tidy and cost-aware without anyone doing it by hand.
Key Takeaways
- It only evicts — the Descheduler never places Pods. The scheduler picks the new node, so the two must agree on the goal.
- Drift is the problem — new nodes, changed labels and taints, and shifting usage make old placements stale, and core Kubernetes never revisits them.
- Policy drives everything — profiles turn on plugins of the Deschedule and Balance types, and a filter decides who may be evicted.
- Eviction is safe by design — it goes through the Eviction API, respects disruption budgets, and skips critical, DaemonSet, local-storage and ownerless Pods by default.
- Choose spread or pack — LowNodeUtilization spreads load, HighNodeUtilization packs it for cost saving, and they should not be mixed on the same nodes.
- Start small — begin with dry run, one plugin, low limits and a limited set of nodes, and widen only after watching the results.
- Build robust applications — replicas, disruption budgets, readiness checks, graceful shutdown and spread rules make evictions harmless.