Taints and Tolerations in Kubernetes
Learn how nodes can push unwanted pods away, how pods can earn the right to stay, and how this simple pair of ideas protects expensive hardware, isolates teams, and keeps your cluster calm when a machine starts to fail.
Imagine a large airport with a quiet lounge reserved for gold-card members. A sign on the lounge door says “Gold members only”. Ordinary travellers read the sign and walk past. Travellers who carry a gold card are allowed in, but holding the card does not force them to go there. They are free to sit at any gate they like. The sign keeps the wrong people out, and the card gives the right people permission.
Kubernetes uses exactly this idea to control where your applications run. A taint is the sign on the door of a computer, and a toleration is the gold card carried by an application. This tutorial explains both from the ground up, shows what happens inside the cluster when they meet, and gives you the patterns and warnings that real teams rely on in production.
1Why Scheduling Needs Rules
Before we meet taints, we need to understand how Kubernetes decides where each application runs, and why its default answer, “anywhere”, is sometimes wrong.
Pods, nodes, and the scheduler
Kubernetes is a system that runs your applications across many computers and keeps them healthy. Each of those computers is called a node. A node can be a physical server in a data centre or a virtual machine in the cloud. All the nodes together form a cluster.
Your application runs inside a pod, which is the smallest unit Kubernetes manages. A pod wraps one or more containers, and a container is a sealed package holding your program and everything it needs to run. When you ask Kubernetes to run a pod, something must decide which node will host it. That decision-maker is a control plane component called the scheduler.
The scheduler watches for new pods that have no node yet. For each one it picks a node and records that choice. Then a small agent called the kubelet, which runs on every node, notices the assignment and starts the containers. So the scheduler chooses, and the kubelet carries out the choice.
How the scheduler chooses by default
The scheduler works in two steps. First it filters, which means it removes every node that cannot run the pod, for example because it does not have enough free memory. Second it scores, which means it ranks the remaining nodes and picks the best one. By default, the scoring tends to spread pods around so no single node is overloaded.
This default is good for ordinary applications. It treats all nodes as equal servants of the cluster and places pods wherever there is room. The trouble starts when your nodes are not equal, or when some nodes should not be used by some pods at all.
Think of a hotel receptionist who hands out rooms. By default, the receptionist gives a guest any free room that fits. That works until the hotel has a penthouse suite, a maintenance room with a broken heater, and rooms reserved for a visiting film crew. Now the receptionist needs rules about who may go where.
When “anywhere” is the wrong answer
In real clusters, some placements are clearly bad. A small web server should not occupy a very expensive graphics card machine. A pod from one team should not land on nodes bought for another team. No new pod should be started on a node whose disk is failing. The list below shows the main problems that taints and tolerations were created to solve.
Expensive Hardware
Nodes with GPUs or very fast disks cost a lot. Ordinary pods should not waste them, so they need to be kept for the workloads that truly need them.
Team or Tenant Isolation
A team may pay for its own group of nodes and expect only its own applications to run there.
Unhealthy Nodes
A node that is running out of memory or has lost its network should stop receiving new pods, and sometimes its current pods should leave.
Interruptible Capacity
Cheap cloud machines, often called spot or preemptible nodes, can be taken away with little warning. Only pods that can survive that should run on them.
Maintenance
Before upgrading or repairing a node, operators need to stop new pods arriving and move the existing ones away gracefully.
Protecting the Control Plane
The nodes that run the cluster’s brain should be kept for that job and not crowded out by ordinary applications.
Kubernetes lets you steer pods in two opposite ways. You can attract a pod toward certain nodes, which is done with node selectors and node affinity. Or you can repel pods away from a node, which is done with taints. Tolerations are how a pod says “I am allowed near this repellent”. Keep these two directions separate in your mind, because mixing them up is the most common beginner mistake.
2Taints and Tolerations: The Core Idea
A taint is a mark on a node that says “keep away”. A toleration is a note on a pod that says “I can put up with that mark”.
The idea in plain words
A taint is a piece of information attached to a node. It tells the scheduler that ordinary pods should not be placed on this node. A pod that has a matching toleration is allowed to ignore the taint. Pods without a matching toleration are turned away.
The most important detail is that a toleration only gives permission. It never pulls a pod toward the node. A pod that tolerates a taint may still be placed on any other node that has room. If you want a pod to go to a particular node, you must combine the toleration with a separate attraction rule, which we explore in Chapter 8.
Return to the airport lounge. The sign on the door is the taint. The gold card in a traveller’s wallet is the toleration. The card lets the traveller enter the lounge but does not drag them there, and travellers without a card simply cannot go in. To make gold members actually prefer the lounge, the airline would need to send them an invitation, which is the attraction rule.
Where the names come from
The word “taint” means to mark something as spoiled or unwelcome, which is exactly the effect on a node: it is marked as unwelcome for ordinary pods. The word “toleration” means putting up with something. A pod tolerates a taint the way a person tolerates a strong smell: it does not mind, so it can stay.
The vocabulary you will use
Taint
A key, an optional value, and an effect, stored on a node, that repels pods which do not tolerate it.
Toleration
A rule inside a pod’s definition that matches a taint and lets the pod ignore it.
Effect
What the taint does to pods that do not tolerate it: refuse them, discourage them, or evict them.
Eviction
Forcing a running pod to stop and leave its node so that it can be started elsewhere.
Dedicated Node Pool
A group of nodes kept for one purpose, such as machine learning jobs, usually protected by a taint.
Cordon
Marking a node so that no new pods are scheduled to it, while existing pods keep running.
Drain
Cordoning a node and then gently moving its running pods to other nodes, usually before maintenance.
Toleration Seconds
How long a pod is willing to stay on a node after an eviction-type taint appears, before it is removed.
Taints belong to nodes. Tolerations belong to pods. If you ever forget which is which, remember that a node gets tainted, like a spoiled dish, and a pod learns to tolerate it.
3Kubernetes Building Blocks You Need First
Taints and tolerations touch several parts of Kubernetes, so let us meet each part before we watch them work together.
Node
A worker computer, physical or virtual, that runs pods. Taints are stored on nodes.
Pod
The smallest unit Kubernetes runs. Tolerations are written in the pod’s specification.
API Server
The front door of the control plane. Every change, including adding a taint, goes through it and is saved.
Scheduler
The component that picks a node for each new pod. It reads taints and tolerations while deciding.
Kubelet
The agent on each node that starts and stops containers and reports the node’s health.
Node Lifecycle Controller
A controller that watches node health, adds automatic taints when a node has problems, and evicts pods when needed.
Label
A name tag attached to a node or pod. Labels are used to attract pods to nodes, unlike taints, which repel.
Node Selector and Node Affinity
Rules inside a pod that say which nodes it wants, based on the labels those nodes carry.
DaemonSet
A workload that runs one pod on every node, such as a log collector. It needs special handling around taints.
Admission Controller
A checkpoint that can inspect or change objects as they enter the cluster, including adding tolerations to pods.
Labels versus taints
Labels and taints both live on nodes, and both influence scheduling, but they behave in opposite ways. A label is descriptive and neutral, saying something like “this node has a GPU”. It does nothing by itself. Pods can choose to look for it. A taint is a restriction, saying “do not come here unless you are allowed”. It acts on every pod automatically, whether the pod asked or not.
A label is like a name badge on a shop door reading “Bakery”. It tells everyone what is inside, and people who want bread walk toward it. A taint is like a velvet rope across the door with a sign saying “Staff only”. People who are not staff are stopped no matter what they wanted.
Who does what during scheduling
When you create a pod, the API server saves it without a node. The scheduler notices it, filters nodes using several checks, and one of those checks is the taint check. The scheduler then writes the chosen node into the pod record, and the kubelet on that node starts the containers. Later, if the node develops a problem, the node lifecycle controller may add a taint to it, and that new taint can even lead to the pod being removed again.
This means taints are used at two different moments. They influence where a pod is first placed, and they can force a pod out of a place it already occupies. We will study both moments carefully in the coming chapters.
Assuming that adding a toleration to a pod will send it to the tainted node. It will not. The toleration only removes the barrier. Without an attraction rule, the pod may end up anywhere in the cluster, including nodes you never intended.
4The Anatomy of a Taint
A taint has three parts: a key, an optional value, and an effect. The effect is the part that decides how strongly the node pushes pods away.
Key and value
The key is a short name that describes why the node is marked, for example “dedicated” or “hardware”. The value is an optional extra detail, for example “machine-learning” or “gpu”. Together they form a label-like pair. You choose these words yourself for your own taints, and Kubernetes chooses them for the automatic taints it adds when a node is unhealthy.
A useful habit is to pick keys that explain the reason for the taint, so anyone looking at a node later can understand it without asking around. A key such as “workload-type” with the value “batch” tells a story. A key such as “x1” tells nothing.
The three effects
The effect decides what happens to pods that do not tolerate the taint. There are exactly three effects, and understanding the difference between them is the heart of this topic.
NoSchedule: the firm door
The scheduler will not place any new pod on this node unless the pod tolerates the taint. Pods that are already running on the node when the taint is added are left alone. Use this when you want to protect a node from future placements without disturbing current work.
PreferNoSchedule: the polite request
The scheduler tries to avoid placing pods on this node, but it is not a rule. If no other node has room, the pod may still land here. Use this for a soft preference, such as keeping a node lightly used without making it completely off limits.
NoExecute: the firm door plus eviction
This is the strongest effect. New pods without a matching toleration are refused, and pods already running without a matching toleration are evicted. A pod that tolerates it may stay, for ever or for a limited time, depending on its settings.
Picture a restaurant. NoSchedule is a host at the door who stops seating new customers at a table but lets the current diners finish. PreferNoSchedule is a host who says “that table is not ideal, maybe try another”, but will seat you there if the restaurant is full. NoExecute is a host who stops new customers and also politely asks the current diners to leave.
| Effect | New pods without toleration | Running pods without toleration | Strength |
|---|---|---|---|
| NoSchedule | Refused | Left alone | Firm rule for future placement |
| PreferNoSchedule | Avoided if possible | Left alone | Soft preference |
| NoExecute | Refused | Evicted | Strongest, affects current pods too |
Several taints on one node
A node can carry many taints at once. The scheduler treats them like a series of locked doors. A pod must have a toleration for every firm taint, meaning every NoSchedule and every NoExecute taint, or it is rejected. If a pod fails to tolerate only PreferNoSchedule taints, it is still allowed, but the node becomes less attractive and scores lower.
If a node has taints for both “dedicated” and “hardware”, a pod that only tolerates “dedicated” will be turned away. This lets you build layered protection, where a pod must show several permissions before it can enter.
Adding a NoSchedule taint to a busy node does not clear it. Pods already there keep running. People often add the taint and are surprised that unwelcome pods are still present. If you want them gone, you need NoExecute, or you must drain the node.
5The Anatomy of a Toleration
A toleration describes which taints a pod can put up with. It has five parts, and small differences in them change what matches.
Key
The name of the taint the pod can tolerate. It must match the taint’s key.
Operator
Either “Equal”, meaning the value must match, or “Exists”, meaning any value is fine as long as the key is present.
Value
The taint value the pod expects. It is used with the Equal operator and left out with Exists.
Effect
Which effect the toleration covers. If left empty, it covers all effects for that key.
Toleration Seconds
Only meaningful for NoExecute. How long the pod may remain after the taint appears before it is evicted.
How matching works
A taint and a toleration match when three things agree. The keys must be the same. The effects must be the same, unless the toleration leaves the effect empty, which means it matches every effect. And the values must agree, according to the operator. With Equal, the toleration’s value must be identical to the taint’s value. With Exists, the value is ignored.
There is one special case worth remembering. A toleration with an empty key and the Exists operator matches every taint on every node. Such a pod ignores all restrictions. This is used by a few system components that must run everywhere, and it should be used with great care in your own applications.
Think of a security guard checking wristbands. With Equal, the guard checks that the wristband is exactly the right colour and number. With Exists, the guard only checks that some wristband of the right type is worn, and does not care about the number. A toleration with no key at all is a master pass that opens every door in the building.
Toleration seconds: staying for a while
When a NoExecute taint appears on a node, a pod that tolerates it can remain. Without a time limit, it stays for ever. With a toleration seconds value, it stays only for that many seconds and is then evicted. This gives applications a grace period, which is useful when a node problem is likely to be short-lived.
For example, if a node briefly loses contact with the cluster, it is better not to move every pod immediately, because the node may come back a few seconds later. A limited toleration says, “Give this node a little while to recover before you kick me out”.
| Situation | Toleration style | Result |
|---|---|---|
| Pod must run only where a specific key and value exist | Equal with key and value | Tolerates only that exact taint |
| Pod can run wherever a key is present, any value | Exists with key only | Tolerates every value for that key |
| Pod must survive any taint on any node | Exists with no key | Ignores all taints, use with great care |
| Pod should leave a failing node after a short delay | NoExecute with toleration seconds | Stays briefly, then is evicted |
| Pod should stay on a tainted node indefinitely | NoExecute without toleration seconds | Never evicted by that taint |
Write tolerations that match exactly the taints you intend. A very broad toleration, such as matching every key, is like handing out master keys. It works, but it removes the protection the taint was supposed to give.
6Architecture: The Parts That Make It Work
Taints and tolerations are not a single program. They are a small agreement between the node, the pod, the scheduler, and a few controllers.
Everything starts with two pieces of data. The node carries its taints, and the pod carries its tolerations. Both are stored in the cluster database through the API server. Two different components then read this data at different moments. The scheduler reads it when placing a new pod, and a controller reads it to decide whether running pods must leave. The figure below shows the placement path.
flowchart TD
P["New pod without a node"] --> S["Scheduler"]
S --> F["Filter step"]
F --> T{"Does the pod tolerate every firm taint on the node?"}
T -->|"No"| R["Node rejected"]
T -->|"Yes"| O["Other checks: resources and affinity"]
O --> C["Score step: prefer nodes with fewer ignored soft taints"]
C --> B["Best node chosen and recorded"]
B --> K["Kubelet starts the pod"]
Read the figure from top to bottom. The scheduler takes each candidate node and asks a simple question: does this pod tolerate every firm taint on the node? If not, the node is dropped from the list. If yes, the node continues through the other checks, such as free memory and affinity rules. During scoring, nodes that carry soft taints the pod does not tolerate are ranked lower, so the pod prefers cleaner nodes.
The components you will meet
Tainted Node
A node whose record lists one or more taints, added by an operator, a node pool setting, or a controller.
Pod with Tolerations
A pod whose specification lists the taints it can put up with. Pods are usually created by a Deployment or Job template.
Scheduler Taint Plugin
A built-in scheduler plugin that rejects nodes with untolerated firm taints and lowers the score of nodes with untolerated soft taints.
Node Lifecycle Controller
Watches node health and adds automatic taints such as “not ready” or “unreachable” when a node misbehaves.
Taint-Based Eviction
The logic that compares NoExecute taints with each running pod’s tolerations and deletes pods whose time has run out.
Admission Plugins
Checkpoints that can add tolerations automatically, such as a default five-minute allowance for unhealthy nodes.
Node Pool Configuration
Cloud and autoscaler settings that stamp taints and labels onto every node they create, so they survive replacement.
Observability Stack
Events, metrics, and logs that show why a pod is pending or why it was evicted.
Consider an office building. The taint is a sign on a floor saying “Finance team only”. The toleration is the finance badge on an employee. The scheduler is the receptionist who checks badges before sending visitors to a floor. The node lifecycle controller is the facilities team that hangs a “Closed for repairs” sign when the lights fail, and the eviction logic is the guard who escorts people out.
Keep in mind that the scheduler only cares about taints at placement time. Eviction is done by a different piece of logic, and only for the NoExecute effect. This is why NoSchedule never removes anything, while NoExecute can.
7Internal Working: Placement and Eviction
Let us look inside the two moments when taints matter: when a pod is placed, and when a pod is pushed out.
Moment one: placement
The scheduler is built from many small plugins that each handle one concern. One of them is the taint and toleration plugin. In the filter step it looks at each node’s firm taints, which are the NoSchedule and NoExecute ones, and checks whether the pod has a matching toleration for each. A single unmatched firm taint is enough to reject the node for that pod.
In the score step, the same plugin counts how many soft taints, meaning PreferNoSchedule taints, the pod does not tolerate on each node. The fewer such taints a node has, the higher it scores. This is why a soft taint discourages placement without forbidding it: it only pulls a node’s score down.
Moment two: eviction
Eviction only happens for the NoExecute effect. When a NoExecute taint appears on a node, the eviction logic, which lives inside the node lifecycle controller, looks at every pod on that node. For each pod it works out one of three outcomes.
No matching toleration
The pod cannot tolerate the taint at all, so it is deleted immediately.
Matching toleration without a time limit
The pod tolerates the taint for ever, so it is never evicted because of this taint.
Matching toleration with a time limit
The controller starts a timer for that many seconds. If the taint is still present when the timer ends, the pod is deleted. If the taint is removed first, the timer is cancelled and the pod stays.
When a pod is deleted this way, the pod itself is only removed. If it belongs to a Deployment, a Job, or a similar owner, the owner notices that the pod count has dropped and creates a replacement. The scheduler then places that replacement, and it will avoid the tainted node because the new pod, like the old one, does not tolerate the taint. A pod created directly with no owner is simply gone, which is one reason to always run pods through a controller.
sequenceDiagram
participant O as Operator or Controller
participant A as API Server
participant T as Taint Eviction Logic
participant K as Kubelet
participant R as ReplicaSet
participant S as Scheduler
O->>A: Add NoExecute taint to node
A->>T: Node change event
T->>T: Compare taint with each pod's tolerations
T->>A: Delete pod after its allowed time
A->>K: Pod deletion, stop containers gracefully
A->>R: Pod count is below desired
R->>A: Create replacement pod
S->>A: Place replacement on a suitable node
The sequence shows that eviction is a chain, not a single action. The taint is written, a controller reacts, the pod is deleted, the kubelet stops the containers, the owner replaces the pod, and the scheduler finds a new home. Each link takes a moment, so eviction is quick but not instantaneous.
Imagine a fire alarm in an office. The alarm sounds, the fire warden checks each person’s badge, and anyone not on the emergency team is asked to leave right away. People on the emergency team may stay for a set number of minutes. Meanwhile, the manager arranges for the departing staff to continue their work in another building.
Why timing is not perfect
The scheduler works from a slightly delayed picture of the cluster. If a taint is added at almost the same moment that a pod is being placed, the pod may land on the node just before the taint takes effect. For NoSchedule, that pod is then left alone. For NoExecute, the eviction logic will catch it shortly afterward. This small window is normal in a distributed system and is one more reason to prefer NoExecute when you truly need a node cleared.
An evicted pod is not killed instantly. Kubernetes sends it a termination signal and gives it a grace period to finish its work. Applications that ignore the signal, or that need longer than the grace period, can lose in-flight requests. Make sure your applications handle shutdown properly, because taints will trigger it more often than you expect.
8Taints Versus Node Selectors and Affinity
Taints repel and affinity attracts. Real designs almost always need both, and knowing when to use which prevents most scheduling surprises.
Two opposite ideas
A node selector is the simplest attraction rule. The pod says “I only want nodes carrying this label”. Node affinity is a more expressive version. It can say a rule is required or merely preferred, and it can use richer matching. Both attract pods toward nodes and both are written in the pod, using labels on the nodes.
A taint works from the other side. It is written on the node and repels every pod that lacks a matching toleration. The pod does not need to say anything for the taint to act on it, which is why taints are good at protecting nodes from the pods you forgot about.
| Question | Taint and Toleration | Node Selector or Affinity |
|---|---|---|
| Where is the rule written? | Taint on the node, toleration on the pod | On the pod, matching node labels |
| Direction | Repels pods from nodes | Attracts pods to nodes |
| Who must cooperate? | The node owner sets the taint, the pod owner adds the toleration | Only the pod owner |
| Protects a node from unknown pods? | Yes, automatically | No, other pods can still land there |
| Can remove running pods? | Yes, with NoExecute | No, only affects placement |
| Guarantees a pod goes to a specific node group? | No | Yes, when the rule is required |
Why you usually need both
Suppose you have a group of GPU nodes. If you only add a label and a node selector, GPU jobs will go to those nodes, but ordinary pods can still land there too, wasting the expensive machines. If you only add a taint and a toleration, ordinary pods stay away, but the GPU jobs are not forced to go there and may land on a regular node with no GPU and fail.
The solution is to use both. The taint keeps out the wrong pods, the toleration lets the right pods in, and the node affinity pulls the right pods to exactly those nodes. Together they give a group of nodes that is reserved for one workload and used only by that workload.
flowchart LR
GP["GPU job pod: tolerates the taint and asks for the GPU label"] --> GN["GPU node: has the taint and has the label"]
WP["Web pod: no toleration"] -.->|"repelled by the taint"| GN
WP --> GEN["General node: no taint"]
In the figure, the GPU job pod carries a toleration and an attraction rule, so it reaches the GPU node. The web pod has no toleration, so the solid line to the GPU node is blocked and it flows to a general node instead. Each mechanism does one job, and none of them alone would give the full result.
Think of a private members’ club. The velvet rope at the door is the taint, which keeps everyone else out. The membership card is the toleration, which lets members through the rope. The invitation that tells members where the club is located is the affinity rule. Without the rope, strangers walk in. Without the card, members are stopped. Without the invitation, members never find the door.
Use taints to protect the node, and use affinity to guide the pod. Whenever you dedicate nodes, ask yourself two questions: how do I keep everyone else off, and how do I make sure my own pods arrive. The first is a taint, the second is affinity.
9Built-In Taints and Automatic Behaviour
Kubernetes uses taints on its own, all the time. Knowing these built-in taints explains many events you will see in a real cluster.
Taints added automatically
The node lifecycle controller adds taints to nodes whenever their condition changes, and removes them again when the problem clears. You do not need to manage these, but you should recognise them, because they explain why pods suddenly move or stop being scheduled.
| Built-in taint | Effect | When it appears |
|---|---|---|
| node.kubernetes.io/not-ready | NoExecute | The node reports that it is not ready |
| node.kubernetes.io/unreachable | NoExecute | The control plane can no longer reach the node |
| node.kubernetes.io/memory-pressure | NoSchedule | The node is running low on memory |
| node.kubernetes.io/disk-pressure | NoSchedule | The node is running low on disk space |
| node.kubernetes.io/pid-pressure | NoSchedule | The node has too many processes |
| node.kubernetes.io/network-unavailable | NoSchedule | The node’s network is not yet set up |
| node.kubernetes.io/unschedulable | NoSchedule | The node has been cordoned |
| node-role.kubernetes.io/control-plane | NoSchedule | Set on control plane nodes to keep ordinary pods off |
Why unhealthy nodes wait five minutes
By default, an admission plugin adds two tolerations to every pod for the not-ready and unreachable taints, each with a limit of three hundred seconds. This means a pod on a failing node is not evicted the instant the taint appears. It has five minutes for the node to recover. If the node returns within that time, nothing moves. If not, the pod is evicted and replaced on a healthy node.
This default is a sensible compromise for many applications, but not for all. A shopping checkout service may want a much shorter wait, so users are not stuck on a dead node. A database with heavy local data may want a longer wait, because moving it is expensive. You can override the default by writing your own tolerations with your own seconds.
flowchart TD
A["Node stops reporting"] --> B["After a short grace period the node is marked unhealthy"]
B --> C["Controller adds the unreachable taint with NoExecute"]
C --> D{"Does the pod have its own toleration for it?"}
D -->|"Yes, with a longer time"| E["Pod stays until its time runs out"]
D -->|"No, the default applies"| F["Pod removed after about 300 seconds"]
E --> G["Replacement created on a healthy node"]
F --> G
The figure shows that node failure is handled with the same tools you use yourself. The controller simply adds a taint, and each pod’s tolerations decide how long it waits. Nothing special is needed for failures, which is an elegant design: one mechanism handles both planned restrictions and unplanned problems.
Cordon, drain, and taints
When an operator cordons a node, Kubernetes marks it as unschedulable, and the effect on scheduling is the same as a NoSchedule taint. Draining goes one step further. It cordons the node and then asks the running pods to leave using the eviction process, which respects disruption budgets that protect applications from losing too many pods at once. Note that draining is not the same as a NoExecute taint. A drain is a gentle, controlled request, while a NoExecute taint evicts on its own schedule and does not consult those budgets in the same way.
DaemonSets and system pods
A DaemonSet needs a pod on every node, even nodes that are unhealthy or restricted. To make this work, the DaemonSet controller automatically adds tolerations for several of the built-in node condition taints to the pods it creates. That is why log collectors and network agents keep running on nodes where ordinary pods are kept away.
Pods that request special hardware such as GPUs benefit from a similar helper. An admission plugin can add a toleration automatically for pods that ask for such extended resources, when the nodes are tainted with the resource name. This saves developers from having to remember the toleration themselves.
The taint on control plane nodes keeps ordinary applications from competing with the components that run the cluster. In a small test cluster with one node you may remove it, but in production, doing so lets a runaway application starve the control plane and can bring down the whole cluster.
10Data Flow and Lifecycle of a Taint
A taint has a life: it is created, it acts, it is checked, and eventually it is removed. Each stage has traps worth knowing.
Where a taint lives matters. When you add a taint to a node by hand, it is saved on that node’s record in the cluster database. But if the node is replaced, for example by an autoscaler or after an upgrade, the new node starts life with none of your manual changes. The taint is gone, and unwanted pods can flood in. This is why long-lived taints should be set in the node pool configuration, so every node the pool creates receives them automatically.
flowchart TD
A["Taint added to node"] --> B{"Which effect?"}
B -->|"NoSchedule"| C["New pods without toleration refused, running pods stay"]
B -->|"PreferNoSchedule"| D["New pods avoid the node if possible, running pods stay"]
B -->|"NoExecute"| E["New pods refused, running pods without toleration evicted"]
E --> F["Tolerating pods stay, forever or until their time ends"]
C --> G["Taint removed later, node accepts pods again"]
D --> G
F --> G
Decide the purpose
Write down why the node group exists and which workloads may use it. A taint without a clear owner and purpose becomes a mystery within months.
Configure the node pool
Set the taint and a matching label in the node pool definition, so every node, including replacements, receives them.
Prepare the workloads
Add the toleration and the node affinity to the pod templates of the applications that should use the pool, before or together with adding the taint.
Check the placement
Confirm that the intended pods landed on the pool and that no unintended pods did. Look at pending pods, because a mismatch often shows up there first.
Clean up existing pods
If nodes already held unwanted pods before the taint, remove them by draining the node or using NoExecute, because NoSchedule alone will not touch them.
Observe over time
Watch for pending pods, eviction events, and unexpected tolerations creeping into applications. Chapter 13 shows what to track.
Change or remove with care
Changing a taint key or effect affects every pod that relied on the old one. Add the new toleration first, then change the taint, then remove the old toleration.
A taint set by hand on one node is like a handwritten “Staff only” note stuck on a door. If the door is replaced, the note is lost. A taint set in the node pool is like an instruction to the door factory to print the sign on every door it makes. The sign survives because it is part of the manufacturing.
If you rename a taint key while pods depend on the old key, those pods become unable to be scheduled onto the nodes that changed. New pods stay pending, and with NoExecute, existing pods may be evicted. Always roll toleration changes out before the taint change.
11Advantages, Disadvantages, and Trade-offs
Taints and tolerations are simple and powerful, but they are also easy to misuse. Here is an honest view of both sides.
Advantages
- Protects special nodes automatically from pods that never asked to be there.
- Lets node owners set rules without editing every application.
- One mechanism handles planned restrictions and unplanned node failures.
- Supports gentle preferences through PreferNoSchedule and firm control through the other two effects.
- Works with autoscalers and managed node pools, so rules follow new nodes.
- Controls how long a pod waits before leaving a failing node.
Disadvantages / Trade-offs
- A toleration never attracts, so you must add a second mechanism to reach the right nodes.
- Missing tolerations leave pods stuck in a pending state, which can be confusing to debug.
- Broad tolerations quietly remove the protection a taint was meant to give.
- Manual taints are lost when nodes are replaced.
- Many taints across many teams become hard to track and reason about.
- Taints alone do not give strong security isolation between tenants.
The cost of complexity
Every taint you add creates a small piece of shared knowledge. Someone must remember it, every application team must know it, and every new cluster must repeat it. A small cluster with two node groups can hold this in one person’s head. A large platform with dozens of pools and hundreds of teams needs documentation, naming rules, and automated checks, or the taints turn into a maze.
The good news is that most of this cost can be avoided by keeping the design small. Use a handful of clearly named taints with clear purposes, and prefer defaults that work for most applications over special cases.
Choosing the right tool
| Goal | Best tool | Why |
|---|---|---|
| Keep ordinary pods off special nodes | Taint with NoSchedule | Acts on all pods automatically |
| Send specific pods to specific nodes | Node affinity or node selector | Attracts, and can be made mandatory |
| Reserve a node group for one workload | Taint plus toleration plus affinity | Repels others and pulls the right pods |
| Empty a node for maintenance | Cordon and drain | Respects disruption budgets |
| React to a failing node | Built-in NoExecute taints | Handled automatically by the controller |
| Strong isolation between tenants | Separate clusters or stronger isolation, plus taints | Taints alone do not stop all cross-tenant risks |
| Spread pods across failure zones | Topology spread rules | Taints are about eligibility, not evenness |
Teams that adopt taints successfully usually begin with a single, obvious case, such as reserving GPU nodes, and only add more once the first is well understood and documented.
12Security and Multi-Tenancy
Taints look like a security feature, but on their own they are a scheduling feature. Understanding the gap is essential before you rely on them to separate teams.
The central weakness: pods choose their own tolerations
A taint is set by whoever controls the node. A toleration is written by whoever creates the pod. In a cluster shared by many teams, that means any team that can create pods can also write any toleration it likes, including the one that matches a taint reserved for someone else. The taint stops accidental placement, but it does not stop a determined or careless team from adding the matching toleration.
To close this gap, cluster administrators add a second layer of control at the point where pods enter the cluster. This layer can restrict which tolerations each namespace may use, and can add required rules automatically. Only then does the taint start to behave like a real boundary.
Imagine a building where each floor has a sign saying “Finance team only”, but employees print their own badges. Anyone can print a finance badge and walk in. The sign is only useful if the badge office checks who is allowed to print which badge. Admission control is that badge office.
Restrict Who Edits Nodes
Use role-based access control so that only administrators and automation can add or remove taints and labels on nodes.
Limit Tolerations per Namespace
Use an admission plugin or policy engine to allow each namespace only the tolerations that belong to its team.
Force Attraction Rules
Have policy add the correct node selector for each namespace, so pods cannot skip the attraction half of the design.
Ban Wildcard Tolerations
Reject pods whose tolerations match every taint, except for a short list of trusted system components.
Protect Node Labels
Use label prefixes that a node’s own agent cannot change, so a compromised node cannot relabel itself to attract sensitive pods.
Audit Changes
Record every change to node taints and every pod that carries a broad toleration, and review them regularly.
Scheduling separation is not runtime isolation
Even a perfect taint design only decides where pods are placed. Pods from one team that share a node with pods from another still share the same operating system kernel and the same hardware. Taints can put different teams on different nodes, which greatly reduces the risk, but they do not add the strong walls that sandboxed runtimes or separate clusters provide.
For sensitive workloads, such as payment processing or regulated data, many organisations use dedicated node pools protected by taints as one layer, and add further layers such as network rules, stricter pod security settings, and sometimes separate clusters.
Log collectors, network agents, and monitoring agents often carry very broad tolerations so they can run on every node. That means they also run on your reserved nodes, and they may hold high privileges. Review these components carefully, because they cross every boundary your taints create.
13Monitoring, Logging, and Metrics
Taint problems rarely crash anything. They show up as pods that quietly wait, or pods that move when nobody expected. Good signals make both visible.
A traffic control room does not wait for a crash to notice trouble. It watches for cars stuck at a red light for too long and for roads that were closed by mistake. Scheduling signals do the same job for your cluster: they show pods stuck waiting and nodes closed to traffic.
What to watch
Pending Pods
Count pods that stay pending, and how long. The scheduler exposes its own queue length, and long waits often point to a missing toleration.
Scheduling Failure Events
When a pod cannot be placed, an event explains how many nodes were rejected and why, including nodes that had a taint the pod did not tolerate.
Node Taint Inventory
Metric exporters such as kube-state-metrics publish the taints on every node, so you can graph them and alert on unexpected ones.
Eviction Events
Events recorded when the taint logic deletes a pod tell you which pod left, from which node, and which taint caused it.
Node Conditions
Track how many nodes are not ready, unreachable, or under memory or disk pressure, since these conditions produce the built-in taints.
Dedicated Pool Utilisation
Watch how busy your reserved nodes are. Expensive nodes sitting idle mean money is being wasted.
Reading a pending pod
The single most useful skill is reading the reason a pod is pending. When the scheduler fails, it writes an event that summarises every node it considered. The message tells you, node by node, whether the problem was a taint, a lack of resources, or an affinity rule. A message saying that several nodes had a taint the pod did not tolerate is a direct pointer to the missing toleration.
Read the pod’s events
Look at the scheduling failure message and note which taint keys and values are named.
Inspect the node taints
Check the exact key, value, and effect on the nodes you expected the pod to reach. Look for typos and differences in capital letters.
Compare with the pod’s tolerations
Check the key, operator, value, and effect. A value mismatch with the Equal operator is the most common cause.
Check the other filters
Confirm that resources, affinity rules, and topology rules are not also blocking the pod. Several causes often appear together.
Fix one side and recheck
Correct the toleration or the taint, and watch the pod get scheduled. Avoid changing both at once, so you know which change worked.
Logs and alerts worth setting up
Send cluster events and audit logs to a central store, because events in Kubernetes expire after a short time. The audit log records who changed a taint and when, which is often the first question in an incident. Useful alerts include pods pending for longer than a chosen time with a taint-related reason, nodes that carry a taint nobody expects, sudden jumps in the number of evictions, and dedicated pools that are nearly empty or completely full.
A new taint appearing on a production node is worth a notification even if nothing is broken yet. It is much cheaper to check an unexpected taint at once than to investigate a wave of pending pods an hour later.
14Design Patterns and Anti-patterns
A few well-proven designs cover most needs. A few tempting shortcuts cause most of the pain.
Patterns that work well
Dedicated Node Pool
A taint, a label, a toleration, and an affinity rule together reserve a pool of nodes for one kind of workload.
Interruptible Tier
Put cheap, interruptible nodes behind a taint, and let only fault-tolerant workloads tolerate it.
System and Workload Split
Keep cluster add-ons on their own small pool, protected by a taint, so application load cannot starve them.
Maintenance Taint
Before a planned repair, add a taint with a clear name so operators and tools can recognise the state.
Tuned Toleration Seconds
Shorten the wait for stateless services that recover quickly elsewhere, and lengthen it for heavy stateful services that are costly to move.
Policy-Guarded Tenant Pools
Combine per-team pools with admission rules so each team can only tolerate and select its own pool.
Anti-patterns to avoid
Problem
Application teams add a toleration that matches every taint, so their pods “always schedule”.
Why It’s Harmful
The pods now ignore every restriction. They can land on reserved GPU nodes, on nodes under pressure, and on nodes marked for maintenance, undoing the reasons the taints exist.
Correct Approach
Write narrow tolerations that name only the taints the workload really needs, and use policy to reject wildcard tolerations except for trusted system components.
Problem
A team adds a toleration and expects the pod to be placed on the tainted nodes.
Why It’s Harmful
A toleration only permits. The pod may land on any node, including ones without the required hardware, and then fail or run slowly in a confusing way.
Correct Approach
Pair every toleration with an attraction rule, such as required node affinity on the node pool label.
Problem
Taints are added by hand to individual nodes and are never recorded anywhere else.
Why It’s Harmful
When a node is replaced by an upgrade or an autoscaler, the taint disappears silently. Reserved capacity fills with the wrong pods, and nobody knows why.
Correct Approach
Define taints and labels in the node pool configuration, and keep that configuration in version control.
Problem
Every node in the cluster is tainted, and no ordinary workload has a matching toleration.
Why It’s Harmful
New pods have nowhere to go and stay pending. Only pods with broad tolerations, usually system agents, keep running, so the cluster looks alive while nothing new can start.
Correct Approach
Always leave a general pool without restrictive taints, where ordinary workloads can run.
15Best Practices and Common Mistakes
A short field guide collected from teams that run taints in production, and from the ways beginners get caught.
Best practices
- Name taints by purpose — a key such as “workload-type” tells the next person why it exists.
- Set taints at the node pool level — so replacement nodes inherit them automatically.
- Always pair with affinity — the taint keeps others out, the affinity brings your pods in.
- Keep tolerations narrow — match exact keys, values, and effects, and avoid master-key tolerations.
- Prefer NoSchedule for dedicated pools — it is predictable, and does not evict work by surprise.
- Use NoExecute deliberately — apply it only when you truly want running pods removed, and check disruption plans first.
- Roll toleration changes out first — add the new toleration to workloads before changing or adding the taint.
- Keep a general pool — make sure ordinary workloads always have somewhere to run.
- Tune toleration seconds by workload — do not accept the default five minutes for services where it is clearly wrong.
- Guard with admission policy — restrict which namespaces may use which tolerations.
- Document every taint — record its owner, purpose, and the workloads allowed to use it.
- Watch pending pods and evictions — treat them as the health signals of your scheduling design.
Common mistakes
Taint keys and values are case-sensitive and must match exactly. A toleration for “GPU” will not match a taint for “gpu”. The pod stays pending, and the message names the taint, so read it carefully.
Adding NoExecute to a node full of running pods evicts every pod that does not tolerate it, all at once. If many of those pods belong to the same application, you can lose most of its capacity in seconds. Prefer draining, which respects disruption budgets, when you are emptying a busy node.
Built-in agents get automatic tolerations for the standard node condition taints, but not for your own custom taints. A monitoring or security agent that must run on the reserved nodes needs its own explicit toleration, or those nodes will silently go unmonitored.
Try a new taint design on a test cluster with a few nodes. Watch where pods land, cause a node failure on purpose, and confirm that both placement and eviction behave the way you predicted.
16Real-World and Industry Examples
Taints and tolerations appear in almost every serious Kubernetes platform, often behind settings that teams never see directly.
Machine learning and GPU pools
Organisations that train models keep GPU machines in a separate pool with a taint. Only training and inference jobs carry the toleration and the affinity for the pool, so expensive accelerators are never wasted on ordinary web servers. Some clusters add the toleration automatically for pods that request GPU resources.
Spot and preemptible capacity
Cloud providers sell spare machines at a large discount, but may reclaim them on short notice. Platforms place these machines behind a taint, and only fault-tolerant workloads such as batch processing tolerate it. Some managed services, for example spot pools on Azure Kubernetes Service, add such a taint to spot nodes automatically.
Control plane protection
Cluster installers commonly place a NoSchedule taint on control plane nodes so ordinary applications stay away, and only system components carry the matching toleration. This keeps the cluster’s brain calm even when applications are busy.
System pools in managed services
Managed Kubernetes services often let you create a small pool reserved for critical add-ons, using a taint that only those add-ons tolerate. Application pods then live on separate user pools, so a runaway application cannot starve the components that keep the cluster healthy.
Autoscalers and node templates
Cluster autoscalers and node provisioners such as Karpenter let you define taints on the templates they use to create nodes. Every new node then appears already protected, which solves the problem of manual taints disappearing when nodes are replaced.
Multi-team platforms
Internal platforms often give each business unit or important customer its own pool, guarded by a taint and by admission rules that decide which namespace may use which toleration. This mixes shared administration with clear cost attribution.
Edge and unreliable networks
Where nodes sit in shops, vehicles, or remote sites and lose connectivity often, teams lengthen the toleration seconds for the unreachable taint. Pods then stay on a node through a short outage instead of being moved away and back, avoiding needless churn.
17Frequently Asked Questions
Quick answers to the questions that come up most often when teams start using taints and tolerations.
No. A toleration only permits the pod to be placed there. To make the pod prefer or require those nodes, you need node affinity or a node selector that matches a label on the nodes.
NoSchedule only affects new placements and leaves running pods alone. NoExecute also evicts running pods that do not tolerate the taint, immediately or after their allowed time.
Yes. A pod can list any number of tolerations. If a node carries several firm taints, the pod needs a matching toleration for every one of them.
The pod stays pending. The scheduling failure event lists the taints that blocked it, which tells you exactly what to add or which taint to remove.
Only the NoExecute effect does. NoSchedule and PreferNoSchedule influence future placement and leave running pods where they are.
Read the events on the pod. They summarise how many nodes were rejected and for what reasons, including untolerated taints, lack of resources, and affinity mismatches.
Yes. Managed services and autoscalers let you set taints and labels in the node pool or node template, so every node they create receives them automatically.
Not by themselves. Teams that can create pods can write their own tolerations. Combine taints with admission policy, network rules, and stronger isolation where the risk is high.
It is very close. Cordoning marks the node as unschedulable, which has the same effect on new pods as a NoSchedule taint. Draining adds a controlled removal of running pods on top of that.
By default, pods receive tolerations for the not-ready and unreachable taints with a three-hundred-second limit. You can set shorter or longer limits in your own pod definitions.
They do, but the DaemonSet controller automatically adds tolerations for several built-in node condition taints. For your own custom taints, you must add the tolerations yourself.
No. An unused toleration simply does nothing. That is useful when you prepare workloads in advance of adding a taint, which is the safe order for rolling out changes.
18Summary and Key Takeaways
Taints and tolerations are a small pair of ideas with a very wide reach. A taint marks a node as unwelcome to ordinary pods, and a toleration gives a pod permission to ignore that mark. Three effects give you a soft preference, a firm refusal, and a refusal that also removes running pods. Kubernetes uses the same mechanism for its own housekeeping, from protecting control plane nodes to reacting to failing machines.
The power comes with responsibility. Tolerations permit but never attract, so real designs pair them with node affinity. Taints alone are not a security wall, so shared clusters need admission policy on top. And taints that live only on hand-edited nodes vanish when the nodes are replaced. Teams that respect these limits get cleaner clusters, lower costs, and calmer maintenance.
Key Takeaways
- Taints repel, tolerations permit — a taint lives on a node, a toleration lives on a pod, and neither one attracts a pod to a node.
- Pick the effect deliberately — use NoSchedule for firm protection, PreferNoSchedule for a soft hint, and NoExecute only when running pods must leave.
- Pair taints with affinity — the taint keeps the wrong pods out and node affinity pulls the right pods in.
- Learn the built-in taints — node failures, resource pressure, cordons, and control plane nodes all use them, and toleration seconds decide how long pods wait.
- Set taints where they survive — define them in node pools or templates, not by hand on single nodes.
- Guard tolerations with policy — anyone who can create pods can write tolerations, so restrict them per namespace and ban wildcards.
- Watch pending pods and evictions — these are the signals that tell you your scheduling rules are working, or quietly failing.