Ephemeral-storage evictions v Kubernetes: logová búrka, ktorá vyhodila zdravé pody
Ak ste to ešte nezažili, znie to ako bug:
- pody restartujú s dôvodom Evicted
- správa hovorí “node was low on resource: ephemeral-storage”
- prihlásite sa na node,
df -ha… disk vyzerá voľný
Prvýkrát som si myslel, že kubelet je pokazený. Nebol. Boli sme to my.
Toto je runbook, ktorý mi vtedy chýbal: nodefs vs imagefs, rast logov, kubelet garbage collection a ephemeral-storage ako kontrakt.
Testované na: Kubernetes 1.29–1.31, containerd 1.7, Linux 6.1–6.6, mix nodefs/imagefs setupov.
Incident (anonymizovaný)
Počas produkčného incidentu som zapol veľmi verbose logging v jednej službe, aby som chytil raritný edge case. Incident skončil. Debug logging nie.
O pár hodín:
- kontajner logy narástli na desiatky GB na časti nodov
- kubelet nastavil
DiskPressure - začali evictions — aj na úplne nesúvisiacich službách
- rollouty boli noisy, lebo evictions vyzerali ako “random restarty”
Blast radius: viacero služieb malo vyšší error rate kvôli restartom a cold cache.
Constraint: nemohli sme odstaviť node pool. Potrebovali sme zastaviť krvácanie a pridať guardrails, aby sa “debug logging” už nikdy nezmenil na cluster incident.
Timeline
- T-0: alerty: rastú restarty podov naprieč namespace.
- T+10m:
kubectl describe podukážeEvicteda “low on ephemeral-storage”. - T+20m:
kubectl describe nodeukážeDiskPressure=Truena rovnakých nodoch. - T+30m: na node
duodhalí, že/var/log/containersdominuje rast. - T+40m: mitigácia: znížiť verbosity + reštart noisy podov + drain najhorších nodov.
- T+2h: DiskPressure zmizne; evictions prestanú.
- T+1d: vynútime log rotáciu v kubelete + per-pod ephemeral-storage limity + alerty.
Mechanizmus: prečo “disk vyzerá voľný”, ale kubelet evictuje
Kubelet evictuje podľa thresholdov, nie až pri 100% plnosti
Eviction sa spúšťa pri prekročení/priblížení sa ku thresholdom (evictionHard, evictionSoft) a sleduje konkrétne filesystemy:
- nodefs (root FS; často tu žijú logy a emptyDir)
- imagefs (images/snapshots; môže byť samostatná partícia)
Takže môžete mať:
- voľno na
/, ale imagefs je plný - alebo naopak
- alebo “voľno celkovo”, ale pod hard thresholdom kubeletu
Container logy sú ephemeral-storage
Logy žijú na node (typicky /var/log/containers a /var/log/pods). Bez kubelet-level rotácie vám jeden chatty kontajner zožerie disk až do DiskPressure a evictionov.
Evictions nie sú “graceful”, ak ste ich nenavrhovali
Pod dostane SIGTERM. Ak nemáte graceful shutdown, uvidíte:
- user-visible chyby
- connection storm po restartoch
- nestabilné rollouty
Runbook: diagnostika a zastavenie evictionov
Čo skontrolovať ako prvé
- Potvrdiť dôvod
kubectl -n <ns> describe pod <pod>
Hľadajte:
Reason: Evicted- “low on resource: ephemeral-storage”
- niekedy aj usage per container
- Nájsť node a node condition
kubectl get pod -n <ns> <pod> -o wide
kubectl describe node <node> | grep -n "DiskPressure" -n
- Rozlíšiť nodefs vs imagefs
Na node (alebo cez
kubectl debug node/...):
df -h
df -h /var/log
df -h /var/lib/containerd
Ako potvrdiť hypotézu (rýchla forenzika disku)
A. Čo žerie nodefs (logy, emptyDir, tmp)
sudo du -xh /var/log | sort -h | tail -n 20
sudo du -xh /var/log/containers | sort -h | tail -n 20
sudo du -xh /var/log/pods | sort -h | tail -n 20
B. Čo žerie imagefs (images/snapshots)
sudo du -xh /var/lib/containerd | sort -h | tail -n 20
C. Namapovať veľký log na konkrétny pod
Súbor v /var/log/containers typicky obsahuje názov podu v názve.
Bezpečné mitigácie
- Vypnúť log firehose
- revert debug logging
- obmedziť per-request logovanie
- znížiť log volume na zdroji
- Drain najhorších nodov Je to často bezpečnejšie než “ručné mazanie” v containerd.
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
-
Zapnúť log rotáciu v kubelete Toto je reálna oprava.
-
Pridať ephemeral-storage requests/limits Aby jeden pod nemohol ticho zožrať disk.
Rizikové mitigácie
- ručné mazanie v
/var/lib/containerdbez pochopenia containerd GC - “rm -rf” snapshot/overlay adresárov
- náhodný restart containerd na horúcom node (vie spustiť ďalšiu vlnu problémov)
Čo sme zmenili (konkrétne)
1) Vynútená kubelet log rotácia
KubeletConfiguration snippet:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
containerLogMaxSize: "50Mi"
containerLogMaxFiles: 5
2) Ephemeral-storage ako kontrakt pre noisy službu
Predtým (bez budgetu):
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"
Potom:
resources:
requests:
cpu: "200m"
memory: "256Mi"
ephemeral-storage: "512Mi"
limits:
cpu: "1"
memory: "512Mi"
ephemeral-storage: "2Gi"
3) Guardrail proti “debug logging navždy”
- debug režim auto-expiruje po čase
- CI check failne build, ak je debug default v produkčných configoch
Ako verifikovať
- DiskPressure zmizne
kubectl describe node <node> | grep -n "DiskPressure" -n
- Nevznikajú nové evictions
kubectl get events -A --sort-by=.metadata.creationTimestamp | tail -n 50
- Log adresáre nerastú bez limitu Na node:
sudo du -sh /var/log/containers
- Workload sa stabilizuje
- restarty späť na baseline
- SLO bez periodických spikes
Prevencia / guardrails
- Ephemeral-storage budgety
- definovať pre kritické workloady
- vynucovať (šablóny, policy, review)
- Alerty na disk
- nodefs a imagefs utilization
- growth rate alerty sú praktickejšie než absolútne %
- Logging budgety
- max logov na request
- sampling v produkcii
- Graceful shutdown
- aj pri eviction má byť dopad na užívateľa minimálny
Súvisiace čítanie
- ‘No space left on device’ s 40% voľného disku: Inode a OverlayFS Death Spiral
- Structured Logging Performance: Keď Sa Logger Stane Bottleneckom
- Kubernetes graceful shutdown ako kontrakt: nula 502 počas rolloutov (HTTP + gRPC)
- Kubernetes OOM Killer: Prečo Kontajner Zomiera pri 50% Pamäte
- Linux Page Cache Thrashing v Kontajneroch: Keď Voľná Pamäť Nie Je Voľná
- Prometheus remote_write backpressure: keď monitoring zaplní disk a ešte aj stratí dáta
Súvisiace články
Pod zaseknutý v Terminating: produkčný rozhodovací strom pre finalizery, volume a mŕtve nody
Konzervatívny runbook na bezpečné odblokovanie Terminating Podov: finalizery, CSI/volume cleanup, mŕtve nody a kedy (a ako) použiť force delete.
CSI VolumeAttachment zaseknutý: pody v ContainerCreating a drain, ktorý sa nepohne
Pody zaseknuté v ContainerCreating často skrývajú stuck CSI VolumeAttachment. Runbook na diagnostiku, bezpečné detach, prevenciu data loss a alerty.
Cilium BPF conntrack map full: náhodné resetovania aj keď conntrack vyzerá OK
Náhodné resetovania s Cilium? Ako sa zaplnia eBPF conntrack (CT) mapy, prečo netfilter conntrack vyzerá OK, a runbook na sizing a verifikáciu v Kubernetes.
Redis AOF fsync latency špičky: keď sa durabilita stane tvojím p99
Redis AOF vie spraviť z durability p99 špičky: fsync tlak a BGREWRITEAOF fork CoW. Runbook na dôkaz, bezpečné mitigácie a guardrails.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.