Kubernetes pod eviction simulator
When a node runs short of memory, three different things can kill a pod: its own memory limit, the kubelet's eviction manager and the kernel's OOM killer, each with its own rules. Describe the pods on a node and see which one acts, in what order, and what kubectl drain would do with your disruption budgets.
A pod: name app=web prio=100 req=512Mi lim=1Gi use=700Mi (leave out req and lim for BestEffort). A disruption budget: pdb web min=2 elsewhere=1, where elsewhere counts healthy replicas on other nodes.
| Pod | QoS | Priority | Request | Limit | Usage | oom_score_adj | Evict rank | Result |
|---|---|---|---|---|---|---|---|---|
| payments-1 | Guaranteed | 1000 | 1Gi | 1Gi | 900Mi | -997 | 6 | Running |
| web-1 | Burstable | 100 | 512Mi | 1Gi | 900Mi | 928 | 3 | Running |
| web-2 | Burstable | 100 | 512Mi | 1Gi | 450Mi | 928 | 5 | Running |
| cache-1 | Burstable | 500 | 2Gi | 3Gi | 2.78Gi | 714 | 4 | Running |
| batch-1 | BestEffort | 0 | – | – | 400Mi | 1000 | 1 | Evicted |
| reports-1 | Burstable | 0 | 256Mi | – | 600Mi | 964 | 2 | Evicted |
Usage in red is above the pod's request, which puts it first in line for eviction. Rank 1 is evicted first.
- kubeletmemory.available 44Mi < 500Mi: evict batch-1 (BestEffort, priority 0, usage 400Mi over its request of 0). memory.available now 444Mi.
- kubeletmemory.available 444Mi < 500Mi: evict reports-1 (Burstable, priority 0, usage 600Mi over its request of 256Mi). memory.available now 1.02Gi.
How to use the simulator
List the pods on one node with their priority and memory request, limit and current usage, and set the node's memory, what the system and kubelet use, and the hard eviction threshold. The simulator works out each pod's QoS class and oom_score_adj, applies container limits, then the kubelet's eviction if memory.available is below the threshold, and the kernel's OOM killer if memory runs out entirely. Add pdb lines to see what kubectl drain would do.
QoS classes
Kubernetes puts every pod into a quality of service class from its containers' resource requests and limits. A pod is Guaranteed if every container has CPU and memory limits equal to its requests, BestEffort if no container has any request or limit, and Burstable otherwise. If a container sets a limit but no request, the request defaults to the limit. The class is not a priority setting: it describes how much the pod has promised about its use, and Kubernetes uses it to decide whose processes the kernel should kill first.
Node-pressure eviction
The kubelet watches eviction signals such as memory.available. On Linux its default hard thresholds are memory.available<100Mi, nodefs.available<10%, imagefs.available<15% and nodefs.inodesFree<5%. When a signal crosses its threshold, the kubelet ranks the pods and evicts them, re-measuring after each, until the signal recovers. For memory the ranking is:
- pods whose usage exceeds their request come first (BestEffort pods always do);
- then lower priority before higher;
- then the pod furthest above its request.
In the Memory pressure example, with a 500Mi threshold, memory.available is 44Mi. The ranking is batch-1, reports-1, web-1, cache-1, web-2, payments-1; the kubelet evicts batch-1 and then reports-1, after which memory.available is 1.02Gi. The payments pod, which stays within its request, would be evicted last; Guaranteed pods are only evicted when system processes, not other pods, eat into the node's memory. Hard-threshold evictions ignore the pod's grace period and any PodDisruptionBudget.
The OOM killer and oom_score_adj
The kubelet checks memory periodically, so a sudden spike can exhaust the node first. Then the Linux kernel's OOM killer picks a process to kill by its badness, roughly its share of memory in thousandths plus its oom_score_adj, the number shown in /proc/<pid>/oom_score. Kubernetes sets oom_score_adj from the QoS class: −997 for Guaranteed, so they are killed almost never; 1000 for BestEffort; and for Burstable, 1000 − 1000 × request / node memory, kept between 2 and 999, so a larger request means better protection.
The kernel and the kubelet do not agree. In the memory pressure example the highest oom_score belongs to cache-1 (1112), because it uses the most memory, while the kubelet evicts batch-1 first, because it is the lowest-priority pod over its request. In the Out of memory example the pods want more than the node has (memory.available −240Mi), and the kernel kills etl-1.
OOMKilled or Evicted?
A container that goes over its own memory limit is killed by the kernel inside its cgroup, whatever the rest of the node looks like. The pod shows OOMKilled with exit code 137 (128 plus signal 9), and the kubelet restarts the container in place, with back-off if it keeps happening. In the Over its own limit example, api-1 is OOMKilled while the node has plenty of memory free.
An Evicted pod was removed whole by the kubelet because the node was short of a resource; its status says "The node was low on resource: memory." The pod does not restart on that node; its controller creates a new one, which the scheduler may place elsewhere. Frequent evictions usually mean requests are set below real use, so the scheduler packs more onto nodes than they can hold.
Drains and disruption budgets
Evictions for maintenance are voluntary disruptions. kubectl drain cordons the node, then asks the Eviction API to evict each pod. The API checks the pod's PodDisruptionBudget: if evicting it would leave fewer healthy pods than minAvailable (or more unavailable than maxUnavailable), it refuses with HTTP 429, and drain retries. In the Drain with PDBs example, web-1 and batch-1 are evicted and web-2 and zk-1 are blocked until replacements are healthy elsewhere. A budget that can never be met, such as minAvailable equal to the replica count, blocks drains forever.
About the model
Each pod is treated as one container with one memory figure; CPU is assumed to follow the same request and limit pattern when the QoS class is worked out. A container killed for exceeding its limit is counted as using nothing while it restarts. Memory pressure is evaluated once, with the usage you give, rather than over time; soft thresholds, eviction-minimum-reclaim (0 by default) and node-level reclaim before evicting pods, such as freeing unused images for disk pressure, are left out.
Pod eviction questions and answers
Which pods does Kubernetes evict first under memory pressure?
The kubelet ranks pods by three things in order: whether the pod uses more memory than it requested, its priority (lowest first), and how far its usage exceeds its request. BestEffort pods, which request nothing, are always over their request. In the memory pressure example the order is batch-1, reports-1, web-1, cache-1, web-2, payments-1, and the kubelet evicts batch-1 and reports-1 before memory.available is back above 500Mi.
What is the difference between OOMKilled and Evicted?
OOMKilled means a container went over its own memory limit and the kernel killed it inside its cgroup; the kubelet restarts it in the same pod, and the exit code is 137. Evicted means the kubelet removed the whole pod because the node was short of memory (or disk), even if the pod stayed within its limits; a controller such as a Deployment creates a replacement, possibly on another node.
What are the Kubernetes QoS classes?
Guaranteed: every container has memory and CPU limits equal to its requests. Burstable: at least one request or limit is set, but the pod is not Guaranteed. BestEffort: no requests or limits at all. The class sets the oom_score_adj of the pod's processes: −997 for Guaranteed, 1000 for BestEffort, and between 2 and 999 for Burstable depending on the memory request.
What is the default eviction threshold?
On Linux the kubelet's default hard thresholds are memory.available<100Mi, nodefs.available<10%, imagefs.available<15% and nodefs.inodesFree<5%. They can be changed with the evictionHard setting, and soft thresholds with a grace period can be added.
Does node-pressure eviction respect PodDisruptionBudgets?
No. For hard thresholds the kubelet ignores PodDisruptionBudgets and the pod's termination grace period and kills the pod at once. PodDisruptionBudgets apply to voluntary disruptions that go through the Eviction API, such as kubectl drain and the cluster autoscaler.
Why is kubectl drain stuck on "Cannot evict pod as it would violate the pod's disruption budget"?
The Eviction API refuses any eviction that would leave fewer healthy pods than the PodDisruptionBudget's minAvailable, and drain keeps retrying every 5 seconds. It succeeds once a replacement pod is running elsewhere. In the drain example, web-2 and zk-1 are blocked on the first attempt.
Further reading
- Kubernetes documentationNode-pressure EvictionEviction signals, thresholds, the ranking of pods and oom_score_adj.
- Kubernetes documentationPod Quality of Service ClassesThe exact rules for Guaranteed, Burstable and BestEffort.
- Kubernetes documentationDisruptionsVoluntary and involuntary disruptions, and how PodDisruptionBudgets work.
- Linux kernel documentationThe /proc filesystem: oom_score and oom_score_adjHow the kernel scores processes for the OOM killer.