Hoe een Evicted-pod eruitziet
Je draaide kubectl get pods en zag regels zoals deze:
NAME READY STATUS RESTARTS AGE
api-7d4f9b8c6-xvk2p 0/1 Evicted 0 12m
api-7d4f9b8c6-r9k8t 0/1 Evicted 0 11m
api-7d4f9b8c6-2m4qn 1/1 Running 0 3m
Evicted is geen echte pod-fase. Het is de waarde die de kubelet in status.reason schrijft als hij de pod heeft beëindigd om node-pressure-redenen. De daadwerkelijke status.phase op het achtergebleven pod-object is Failed. De containers van de pod zijn weg. Alleen het API-object blijft achter en bezet een slot in de pods-objectquota van de namespace totdat iets het opruimt.
Als een workload-controller (Deployment, ReplicaSet, StatefulSet) eigenaar is van de pod, wordt er vrijwel direct een vervanger gescheduled. De Evicted-pod is een grafsteen, geen draaiende pod. Bare pods (zonder controller) worden niet vervangen.
Eviction is geen OOMKilled en geen preemption
Deze drie failure-modes lijken op elkaar in dashboards en geven overlappende symptomen, maar het zijn verschillende events met verschillende fixes.
| Modus | Wie acteert | Trigger | Resultaat voor pod |
|---|---|---|---|
| Container OOMKilled | Linux-kernel cgroup OOM-killer | Container overschrijdt zijn resources.limits.memory |
Eén container exit code 137; pod blijft, kubelet herstart de container volgens restartPolicy |
| Node-pressure eviction | Kubelet eviction-manager | Node-breed signaal kruist een eviction-drempel | Hele pod beëindigd; status.phase=Failed, status.reason=Evicted |
| Preemption | kube-scheduler | Een pending pod met hogere priority heeft ruimte nodig | Pod met lagere priority gevict om plaats te maken; opnieuw gescheduled via zijn controller |
Drie vuistregels. De kernel killt één container; de kubelet killt de hele pod. OOMKilled vuurt als één container zijn eigen cgroup-limiet overschrijdt; eviction vuurt als de node als geheel onder druk staat. Preemption is een scheduler-beslissing, geen kubelet-beslissing, en staat los van resource-druk op de node.
De eviction-reden lezen in kubectl describe pod
Begin altijd hier:
kubectl describe pod api-7d4f9b8c6-xvk2p -n production
Twee delen van de output zijn belangrijk. De status:
Status: Failed
Reason: Evicted
Message: The node was low on resource: memory. Threshold quantity: 100Mi,
available: 87Mi. Container api was using 312Mi, request is 256Mi,
has larger consumption of memory.
De Message-regel noemt het eviction-signaal dat afging (memory, ephemeral-storage, pid, etc.) en de threshold-hoeveelheid versus het beschikbare op het moment van eviction. Hij vertelt je ook welke container de kubelet als grootste boosdoener heeft aangewezen ten opzichte van zijn request, en dat is precies de centrale input voor de pod-selection-ranking van de kubelet (zie verderop).
Daarna de events:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Evicted 12m kubelet The node was low on resource: memory.
Normal Killing 12m kubelet Stopping container api
Event reason Evicted van kubelet bevestigt het. Zie je in plaats daarvan Preempted van default-scheduler, dan kijk je naar scheduler-preemption en niet naar node-pressure eviction; de oorzaak is een pending pod met hogere priority, niet resource-druk op de node.
Om elke Evicted-pod cluster-breed te vinden:
kubectl get pods --all-namespaces --field-selector=status.phase=Failed -o json \
| jq -r '.items[] | select(.status.reason=="Evicted") | "\(.metadata.namespace)/\(.metadata.name)"'
Of om ze per node te tellen, wat vaak meteen de problematische node aanwijst:
kubectl get pods -A -o json \
| jq -r '.items[] | select(.status.reason=="Evicted") | .spec.nodeName' \
| sort | uniq -c | sort -rn
Een node met tientallen Evicted-pods is de node om te onderzoeken.
Evicted-pods opruimen
Evicted-pods verwijderen zichzelf niet. De kubelet laat het API-object staan zodat je kunt bekijken waarom het gevict is. Lang blijven hangen geeft twee echte problemen.
Het eerste is quota-druk. Failed-pods (waaronder Evicted) tellen mee voor de pods- en count/pods-objectquota, want die quota's zijn fase-onafhankelijk. Ze tellen niet mee voor requests.cpu en requests.memory, want die sommeren alleen pods in een non-terminal state. Een Evicted-pod neemt dus een pod-count-slot in beslag maar geen CPU- of geheugenbudget. In een namespace met pods: 100 blokkeren honderd Evicted-restanten dus nieuwe pod-creatie, ook al heeft het cluster nog ruim CPU en geheugen over.
Het tweede is observability-ruis. Lijsten, dashboards en alerts die filteren op status.phase=Failed of failed-pods tellen, struikelen over de restanten lang nadat de onderliggende druk al weg is.
Om elke Evicted-pod in een namespace te verwijderen:
kubectl get pods -n production --field-selector=status.phase=Failed -o json \
| jq -r '.items[] | select(.status.reason=="Evicted") | .metadata.name' \
| xargs -r kubectl delete pod -n production
Of cluster-breed:
kubectl get pods -A --field-selector=status.phase=Failed -o json \
| jq -r '.items[] | select(.status.reason=="Evicted") | "-n \(.metadata.namespace) \(.metadata.name)"' \
| xargs -r -L1 kubectl delete pod
Verificatie: draai kubectl get pods -n production --field-selector=status.phase=Failed opnieuw en bevestig dat de lijst leeg is. Het pods-quotaverbruik daalt navenant: kubectl describe resourcequota -n production.
Een opruim-loop is een workaround, geen fix. De fix is de onderliggende druk wegnemen (verderop in het artikel).
Eviction-signalen en node-condities
De kubelet bewaakt een vaste set signalen op elke node en vertaalt die naar node-condities die zichtbaar zijn in kubectl describe node.
| Signaal | Wat het meet | Node-conditie | Default hard threshold |
|---|---|---|---|
memory.available |
node.status.capacity[memory] minus de working set |
MemoryPressure |
< 100Mi |
nodefs.available |
Vrije ruimte op het primaire kubelet-filesystem (/var/lib/kubelet, logs, emptyDir) |
DiskPressure |
< 10% |
nodefs.inodesFree |
Vrije inodes op nodefs | DiskPressure |
< 5% |
imagefs.available |
Vrije ruimte op het image-filesystem van de container runtime | DiskPressure |
< 15% |
imagefs.inodesFree |
Vrije inodes op imagefs | DiskPressure |
(geen gedocumenteerde default) |
containerfs.available |
Vrije ruimte op het writable-container-layer filesystem (Linux only, separate-disk feature) | DiskPressure |
(volgt imagefs) |
pid.available |
maxpid minus curproc |
PIDPressure |
(geen gedocumenteerde default) |
memory.available op Linux komt uit cgroup memory accounting, niet uit free -m. Inactieve file-backed pages tellen mee als reclaimable, dus het getal ligt hoger dan wat free als "free" rapporteert. Dat is bewust zo: het kubelet-signaal weerspiegelt wat onder druk daadwerkelijk recupereerbaar is.
Hard thresholds vuren direct, zonder grace period. Soft thresholds (geconfigureerd via evictionSoft) honoreren een per-signaal evictionSoftGracePeriod en de terminationGracePeriodSeconds van de pod, tot maximaal evictionMaxPodGracePeriod. Hard eviction honoreert nooit terminationGracePeriodSeconds of PodDisruptionBudgets.
Om geflapper tussen pressure en geen-pressure te dempen wanneer een signaal vlak rond een threshold zweeft, houdt de kubelet een node-conditie nog evictionPressureTransitionPeriod lang vast nadat het signaal hersteld is. De default is 5m0s. MemoryPressure: True blijft dus minstens vijf minuten actief nadat de geheugendruk al weg is.
Wanneer MemoryPressure: True of DiskPressure: True actief is, wordt de node.kubernetes.io/memory-pressure:NoSchedule- of node.kubernetes.io/disk-pressure:NoSchedule-taint automatisch op de node gezet. Dat voorkomt dat de scheduler nieuwe pods plaatst op een node die juist load aan het afstoten is. Zie je Pending-pods naast Evicted-pods, dan is dit een waarschijnlijke oorzaak; zie Pod blijft in Pending: waarom Kubernetes je workload niet kan schedulen.
Hoe de kubelet kiest welke pod wordt gevict
Dit is het stuk dat de meeste mensen verkeerd hebben. De pod-selection-ranking voor node-pressure eviction van de kubelet is een drie-staps-sortering, in deze volgorde:
- Of het verbruik van de pod voor de schaarse resource zijn request overschrijdt. Pods die meer gebruiken dan ze gerequeststeld hebben, worden eerder gevict dan pods die onder hun request blijven. Een pod met
requests.memory: 256Mien een huidige working set van 312Mi staat hoger op de evictionlijst dan een pod met dezelfde request en 200Mi gebruik. - Pod Priority. Binnen de groep pods die hun request overschrijdt, worden pods met lagere priority eerder gevict dan pods met hogere priority. Priority komt uit de
priorityClassNamevan de pod en lost op tot een integer. De ingebouwde classessystem-cluster-criticalensystem-node-criticalhebben zeer hoge waardes en worden als laatste gevict. - Hoe ver het verbruik boven de request ligt. Binnen dezelfde priority-tier worden pods die hun request met een grotere marge overschrijden eerder gevict dan pods die er net overheen zitten.
QoS-class is geen directe input voor de ranking. Het wordt soms samengevat als "BestEffort gaat eerst, dan Burstable, dan Guaranteed", en dat is meestal de uitkomst maar niet het mechanisme. Het mechanisme is de drie-staps-sortering hierboven. De uitkomst lijkt QoS-vormig omdat:
- BestEffort-pods geen requests hebben, dus elk verbruik overschrijdt hun (nul-)request en zet ze bovenaan in stap 1.
- Guaranteed-pods requests gelijk aan limits hebben, dus per definitie kan hun verbruik onder normale werking de request niet overschrijden, wat ze onderaan stap 1 plaatst.
- Burstable-pods landen in het midden, afhankelijk van of ze toevallig boven hun request uitkomen op het moment dat de druk toeslaat.
Praktische implicatie: een Guaranteed-pod met een hoge priority en bescheiden werkelijk gebruik is de veiligste configuratie tegen eviction. Een BestEffort-pod met lage priority is de minst veilige.
Eén uitzondering is goed om te weten. Static pods, mirror pods en pods met priorityClassName: system-node-critical (of system-cluster-critical) doen niet mee in de ranking; de kubelet kiest ze niet voor eviction, ook niet onder druk. Dat beschermt daemonset-workloads zoals kube-proxy, de CNI-plugin en metrics-agents waar de node afhankelijk van is.
Oorzaak A: geheugendruk op de node
De eviction-message noemt memory.available. De node bereikte MemoryPressure en de kubelet zette pods van het eiland.
Geheugendruk op een node heeft twee veelvoorkomende oorzaken. Ten eerste overcommit: de som van container-memory-limits op de node is veel groter dan de node-capaciteit, en meerdere pods bursten tegelijk richting hun limit. De scheduler kijkt niet naar limits maar alleen naar requests, dus een node kan flink overcommitted zijn op limits terwijl de scheduler hem nog als underutilized ziet. Ten tweede één hete pod zonder limit (BestEffort) of met een veel te hoog limit, die sneller groeit dan de kubelet kan reclaimen.
Diagnose
Vind de node die de pressure veroorzaakte:
kubectl describe node <node-name> | grep -A 5 "Conditions"
# Kijk naar MemoryPressure met een recente transition time, of de conditie die nu
# nog op True staat als je nog midden in de pressure zit.
Vergelijk werkelijk geheugengebruik met capaciteit (vereist metrics-server):
kubectl top node
# NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
# node-3 2400m 60% 14820Mi 92%
Som container-limits op de node om overcommit te zien:
kubectl get pods -A --field-selector=spec.nodeName=<node-name> -o json \
| jq '[.items[].spec.containers[].resources.limits.memory] | length'
Voor fijnere analyse rapporteert kubectl describe node Allocated resources met zowel Requests als Limits als percentage van node allocatable. Limits van 200% of meer is een serieus overcommit-signaal.
Fix
Drie lagen, afhankelijk van wat de diagnose liet zien.
Per pod. Als de eviction-message één container aanwees met "larger consumption of memory", is de request van die pod te laag en zijn werkelijke working set te hoog. Zet resources.requests.memory omhoog tot het waargenomen verbruik zodat hij niet meer als eviction-doelwit fungeert, of fix de onderliggende geheugengroei. Het OOMKilled-artikel loopt door right-sizing van memory-limits en taalspecifieke valkuilen (JVM, Go, Node.js, Python).
Per node. Is overcommit het probleem, verlaag dan aggregate memory-limits over de workloads op die node, of voeg capaciteit toe. Het artikel over resource requests en limits behandelt de request-versus-limit-relatie en de overcommit-rekensom in detail.
Cluster-breed. Raken meerdere nodes MemoryPressure, dan is het cluster te klein. Voeg nodes toe, zet Cluster Autoscaler aan, of verplaats noisy workloads naar een aparte node-pool met taints.
Verificatie: kubectl describe node <node-name> toont MemoryPressure: False, en in kubectl get events -A | grep Evicted verschijnen de komende tien minuten geen nieuwe Evicted-pods (langer dan evictionPressureTransitionPeriod).
Oorzaak B: diskdruk en ephemeral storage
De eviction-message noemt nodefs.available, nodefs.inodesFree of imagefs.available. De node bereikte DiskPressure.
Diskdruk heeft meer bronnen dan geheugendruk. De meest voorkomende: containerlogs die zonder rotatie groeien, emptyDir-volumes die vollopen, de writable container-laag die data verzamelt die de applicatie buiten een volume schreef, image-cache bloat op imagefs, en inode-uitputting (hopen kleine bestanden in logs of caches). Minder vaak maar wel de moeite van het checken: de werk-directory van de kubelet zelf die volloopt omdat checkpoint-bestanden of state van terminated pods niet zijn opgeruimd.
Local ephemeral storage capacity isolation is GA sinds Kubernetes 1.25. Daarmee kun je resources.requests.ephemeral-storage en resources.limits.ephemeral-storage op een container zetten, en dan zal de kubelet eviction-manager pods beëindigen die hun ephemeral-storage limit overschrijden, ongeacht de algehele diskdruk op de node. Dat zet één luidruchtige pod om in een geïsoleerd probleem in plaats van een node-breed probleem.
Diagnose
Identificeer welk signaal afging:
kubectl describe node <node-name> | grep -B 1 -A 5 "DiskPressure"
Op de node zelf (SSH, debug pod, of kubelet-logs):
df -h /var/lib/kubelet /var/lib/docker /var/lib/containerd 2>/dev/null
df -i /var/lib/kubelet /var/lib/docker /var/lib/containerd 2>/dev/null
Vind de grootste verbruikers:
sudo du -sh /var/lib/kubelet/pods/*/volumes/* 2>/dev/null | sort -h | tail
sudo du -sh /var/log/pods/* 2>/dev/null | sort -h | tail
Voor images op imagefs (containerd):
sudo crictl images --no-trunc | sort -k 6 -h
Voor container-diskgebruik (Docker):
docker system df -v
Voor diepere uitleg over ephemeral-storage accounting en wat de kubelet meet, zie Kubernetes ephemeral storage: limieten, eviction en container-disk beheren.
Fix
Per pod. Zet resources.limits.ephemeral-storage op elke container die naar lokale disk schrijft. Verplaats zware writes naar een PersistentVolume (of een tmpfs emptyDir als de data echt vluchtig en klein is). Zorg dat logrotatie geconfigureerd is.
Per node. Draai image garbage collection: de kubelet doet dit automatisch zodra imagefs imagefs.available < 85% raakt (default imageGCHighThresholdPercent), maar je kunt handmatig prunen met crictl rmi --prune of docker image prune -a -f. Vergroot /var/lib/kubelet en het imagefs-volume als de node simpelweg te krap geprovisioneerd is.
Cluster-breed. Forceer een pod-niveau default voor requests.ephemeral-storage via een LimitRange. Zonder default zijn BestEffort-pods voor ephemeral-storage heel gewoon, en die zijn de eerste die gevict worden zodra een willekeurige pod de disk vult.
Verificatie: kubectl describe node <node-name> toont DiskPressure: False. df -h op de node laat ruim vrije ruimte zien. De image-cache na GC is merkbaar kleiner (crictl images | wc -l daalt).
Oorzaak C: PID-druk
De eviction-message noemt pid.available. De node had geen process IDs meer.
PID-uitputting komt minder vaak voor dan geheugen- of diskdruk, maar laat zich in twee verschijningsvormen zien. De eerste is een proceslek: een applicatie die kindprocessen sneller spawnt dan ze opruimt en zo de node-brede PID-ruimte uitput. De tweede is fork-bombs uit verkeerd geconfigureerde workloads (een slecht geschreven shell-loop, een unbounded subprocess-pool in Python, of een shell die zichzelf recursief aanroept).
De kubelet biedt PID-isolatie via Pod PID-limits (SupportPodPidsLimit) en node-level reservations (SystemPidsLimit). Staan beide niet, dan kan één losgeslagen pod de hele node uithongeren.
Diagnose
kubectl describe node <node-name> | grep -B 1 -A 5 "PIDPressure"
Op de node:
cat /proc/sys/kernel/pid_max # plafond
ps -e --no-headers | wc -l # huidig
Procesaantallen per pod (containerd):
sudo crictl ps -q | xargs -I{} sh -c 'echo "$(crictl inspect --output go-template --template '"'"'{{.info.pid}}'"'"' {}) {}"'
Of via kubectl debug met ps:
kubectl debug node/<node-name> -it --image=busybox -- sh -c "ps -ef | wc -l"
Fix
Zet per-pod PID-limits via de kubelet-flag --pod-max-pids (of het bijbehorende kubelet-config-veld), of per-container limits via het resources-veld waar dat ondersteund wordt. Onderzoek de lekkende applicatie: een procesboom die ongebreideld groeit is een bug in de applicatie, geen Kubernetes-probleem. De kubelet-eviction is een vangnet, geen oplossing.
Verificatie: kubectl describe node <node-name> toont PIDPressure: False, en ps -e | wc -l op de node geeft onder belasting een stabiel getal in plaats van een klimmend getal.
Eviction-thresholds tunen
De defaults zijn voorzichtig en redelijk voor de meeste clusters. Tune ze alleen wanneer de diagnose duidelijk wijst op een mismatch met je hardware of workload-patroon.
De kubelet-config (meestal /var/lib/kubelet/config.yaml op een node, of de cluster-brede kubelet-ConfigMap op managed platforms) accepteert deze velden:
# /var/lib/kubelet/config.yaml (Kubernetes 1.29+ KubeletConfiguration)
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
evictionHard:
memory.available: "200Mi" # eerder evicten op kleine nodes
nodefs.available: "10%"
nodefs.inodesFree: "5%"
imagefs.available: "15%"
evictionSoft:
memory.available: "500Mi" # eerder waarschuwen met grace period
evictionSoftGracePeriod:
memory.available: "2m"
evictionMaxPodGracePeriod: 30 # cap op terminationGracePeriodSeconds bij soft eviction
evictionPressureTransitionPeriod: "5m" # default; verhoog om geflapper te dempen
evictionMinimumReclaim:
memory.available: "100Mi" # reclaim minstens dit boven de threshold
nodefs.available: "500Mi"
Drie tuning-patronen zijn de moeite waard.
Eerder hard evicten op kleine nodes. De default memory.available: 100Mi is piepklein op een node van 64 GiB. Verhogen naar 200Mi of 500Mi geeft de kubelet meer marge om te acteren voordat de kernel-OOM-killer afgaat (die is sneller en minder soepel). De afweging: iets meer bruikbare capaciteit gereserveerd.
Soft thresholds met grace periods. evictionSoft met een grace period van enkele minuten geeft tijdelijke spikes de kans te herstellen zonder eviction. Combineer met evictionMaxPodGracePeriod zodat een soft eviction (een gemaximeerde versie van) terminationGracePeriodSeconds respecteert. Vriendelijker voor stateful workloads.
evictionMinimumReclaim. Zonder deze stopt de kubelet met evicten zodra het signaal één byte boven de threshold uitkomt, en dat geeft een ping-pong-patroon. evictionMinimumReclaim zetten zorgt ervoor dat de kubelet doorgaat tot er een betekenisvolle marge boven de threshold is teruggewonnen. Dit is de tuning met de grootste impact voor clusters die herhaald flapperen tussen MemoryPressure: True en MemoryPressure: False.
Op managed Kubernetes (GKE, EKS, AKS) zijn kubelet-config-aanpassingen platform-specifiek en lopen ze vaak via een node-pool-config-object in plaats van directe edits in /var/lib/kubelet/config.yaml. Raadpleeg de docs van het platform voor de juiste plek.
Wanneer escaleren
Heb je de diagnose doorlopen en blijven de evictions terugkomen, verzamel dan dit voordat je hulp inschakelt:
- Volledige output van
kubectl describe pod <evicted-pod-name> -n <namespace> - Output van
kubectl describe node <affected-node-name>, in elk geval de sectiesConditions,Allocated resourcesenEvents kubectl get events -A --sort-by='.lastTimestamp' | grep -i evictvoor het afgelopen uur- Cluster-brede telling van Evicted-per-node (de
jq-snippet eerder in dit artikel) - Kubernetes-versie (
kubectl version) - Of het cluster managed is (GKE, EKS, AKS, OpenShift, k3s, kubeadm, etc.)
- Output van
df -hendf -iop de getroffen node (of viakubectl debug node/<name>) - Output van
kubectl top nodeenkubectl top pod -A | sort -k 4 -h | tail -20 - De kubelet-config in gebruik: op managed platforms de node-pool-config; op self-managed de inhoud van
/var/lib/kubelet/config.yaml - Of er een LimitRange of ResourceQuota geldt:
kubectl get limitrange,resourcequota -A
Genoeg om de eviction te diagnosticeren zonder vervolgvragen.
Hoe je herhaling voorkomt
Een terugkerend eviction-probleem is een sizing-probleem, geen runtime-probleem. Preventie gebeurt op het moment van workload-definitie en cluster-provisioning, niet bij het opruimen van Evicted-pods.
- Zet
resources.requests.memoryop basis van waargenomen p95 working-set, niet op gevoel. Een pod waarvan het werkelijke gebruik zijn request overschrijdt, is het eerste doelwit van de eviction-manager. - Zet
resources.limits.memoryop elke productie-container. BestEffort-QoS is een recept voor eviction zodra een willekeurige buur geheugendruk veroorzaakt. - Zet
resources.requests.ephemeral-storageenresources.limits.ephemeral-storageop elke container die naar lokale disk schrijft (logs naar stdout, emptyDir, scratchbestanden). Zonder die instellingen sleept een noisy neighbor de hele node-disk-budget mee. - Gebruik een LimitRange om defaults voor ephemeral-storage en geheugen-requests/limits op admission-tijd te injecteren, zodat namespaces geen pods zonder die instellingen kunnen draaien.
- Wijs
priorityClassNametoe aan kritieke workloads zodat ze de eviction-ranking overleven, ook wanneer hun verbruik boven de request uitkomt. - Draai image garbage collection agressief op imagefs-zware workloads: verlaag
imageGCHighThresholdPercent(default85) naar75als images zich opstapelen. - Zet een Prometheus-alert op
kube_pod_status_reason{reason="Evicted"} > 0zodat je evictions ziet wanneer ze gebeuren, niet wanneer een klant het meldt. Een tweede alert opkubelet_node_condition{condition="MemoryPressure"} == 1voor langer dan vijf minuten vangt aanhoudende druk. - Voor workloads waar eviction onacceptabel is (databases, persistent queues, alles met een stateful disk), gebruik Guaranteed-QoS (request gelijk aan limit), een hoge
priorityClassNameen een PodDisruptionBudget. Let op: PodDisruptionBudgets beschermen niet tegen hard eviction, alleen tegen voluntary disruption en soft eviction.