Pod zaseknutý v Terminating: produkčný rozhodovací strom pre finalizery, volume a mŕtve nody
Pod zaseknutý v Terminating je v produkcii často viac než len “otravnosť”. Vie:
- blokovať rollouty (minie sa budget maxSurge/maxUnavailable),
- vyčerpávať kvóty (CPU/memory, IP adresy, PVC attachmenty),
- držať “identity” stateful workloadov obsadené (najhoršie: split brain),
- maskovať väčší problém v clustri (mŕtvy node, CSI bug, zaseknutý kubelet cleanup).
Tento runbook je zámerne konzervatívny: najprv minimalizuj riziko straty dát, až potom “odstraň Terminating”.
Testované na: Kubernetes 1.27–1.30 (managed + self-managed), containerd a bežné CSI drivery. Niektoré príkazy vyžadujú cluster-admin RBAC.
Čo “Terminating” reálne znamená (to, čo je dôležité operatívne)
Keď zmažeš objekt, ktorý má finalizery, Kubernetes nastaví .metadata.deletionTimestamp, vráti sa rýchlo a objekt ostane existovať dovtedy, kým kontroléry neodstránia finalizery po úspešnom cleanup-e. Ak cleanup nevie dobehnúť, Pod môže ostať “terminating” donekonečna.
Pointa: problém nie je “kubectl delete”, ale čo presne sa nevie upratať.
Rýchly triage: je bezpečné použiť force delete?
Predtým než použiješ --force, odpovedz si:
Väčšinou relatívne bezpečné (aj tak over)
- Stateless replika z Deploymentu, bez dôležitého lokálneho stavu, bez exkluzívnych lockov.
- Vieš potvrdiť, že Pod už neobsluhuje traffic (alebo obsluhovať nevie).
Vysoké riziko (ber ako incident)
- StatefulSet (stabilná identita + stabilný storage).
- Quorum systémy (databázy, Kafka, etcd, RabbitMQ, …).
- Pod na node, ktorý môže byť stále “živý” (network partition scenár).
Pri StatefulSet-e môže force delete uvoľniť meno v API a vytvoriť náhradu aj vtedy, keď starý Pod ešte beží. To je typický spôsob, ako si vyrobíš porušenie “at most one”.
Zober minimum dôkazov (nehádaj)
Nastav si premenné:
NS=default
POD=my-pod-abc123
1) Snapshot Podu a nodu
kubectl get pod -n "$NS" "$POD" -o wide
kubectl describe pod -n "$NS" "$POD"
2) deletionTimestamp + finalizery (rýchly signál)
kubectl get pod -n "$NS" "$POD" -o jsonpath='{.metadata.deletionTimestamp}{"\n"}{.metadata.finalizers}{"\n"}'
Ak vidíš finalizery, si vo vetve “finalizer”.
3) Posledné eventy v časovom poradí
kubectl get events -n "$NS" --sort-by=.lastTimestamp | tail -n 60
Pri storage problémoch často uvidíš FailedMount, FailedAttachVolume, UnmountVolume, Device or resource busy, atď.
Rozhodovací strom (copy/paste do runbookov)
Krok 1 — Je node reachable?
Zisti node:
NODE="$(kubectl get pod -n "$NS" "$POD" -o jsonpath='{.spec.nodeName}')"
kubectl get node "$NODE" -o wide
kubectl describe node "$NODE"
Ak je node NotReady / Unreachable:
- Najprv rieš node incident.
- Ak je to prechodný partition, počkajte alebo opravte konektivitu.
- Ak je node potvrdene mŕtvy, často je najčistejšie node nahradiť/odstrániť, aby sa control plane vedel posunúť ďalej.
Force delete na unreachable node znamená, že tvrdíš “tento proces sa už nikdy nevráti”. Nerob to ľahkovážne.
Krok 2 — Blokujú delete finalizery?
Ak finalizery existujú, vypíš ich:
kubectl get pod -n "$NS" "$POD" -o jsonpath='{range .metadata.finalizers[*]}{.}{"\n"}{end}'
Potom ich klasifikuj:
A) Systémové finalizery (často storage)
Tie môžu znamenať, že volume je stále “in use”. Ak Pod používa PVC, vypíš ich:
kubectl get pod -n "$NS" "$POD" -o jsonpath='{range .spec.volumes[*]}{.persistentVolumeClaim.claimName}{"\n"}{end}'
kubectl get pvc -n "$NS"
kubectl get pv
Ak máš oprávnenia, pozri VolumeAttachments (CSI):
kubectl get volumeattachments
Ak to cez RBAC nevieš, spoľahni sa na eventy + CSI controller logy (podľa clustra).
B) Finalizery vlastného kontroléra (napr. example.com/...)
Zvyčajne to znamená, že kontrolér, ktorý finalizer vlastní, je:
- down,
- zaseknutý,
- alebo blokovaný externou závislosťou (cloud API, webhook, …).
Bežná oprava:
- obnoviť kontrolér,
- umožniť cleanup,
- nechať finalizer odstrániť automaticky.
Manualné odstránenie finalizerov rob len vtedy, keď presne chápeš dopad.
Krok 3 — CSI/volume cleanup je zaseknutý (najčastejší reálny koreň)
Ak eventy ukazujú volume problémy, rieš:
- zaseknutý detach/attach,
- kubelet nevie unmount, lebo proces drží mount,
- node filesystem je v zlom stave.
Ak máš prístup na node, najrýchlejšie to potvrdíš cez kubelet logy + mount tabuľku:
journalctl -u kubelet --since "30 min ago" --no-pager | tail -n 200
mount | grep -E "kubelet/pods|plugins/kubernetes.io|plugins/kubernetes.io/csi" || true
Ak prístup na node nemáš (managed cluster), typicky zostáva:
- eventy,
- CSI controller logy (ak sú dostupné),
- a výmena nodu, ak je node unhealthy.
Eskalácia: force delete (posledná možnosť, rob to vedome)
1) Force delete API objektu
kubectl delete pod -n "$NS" "$POD" --grace-period=0 --force
Toto odstráni objekt z API hneď, ale nezaručuje, že proces na node reálne skončil.
2) Nukleárna možnosť: odstránenie finalizerov
Iba ak rozumieš následkom (resource leak / rozbité invariants):
kubectl patch pod -n "$NS" "$POD" -p '{"metadata":{"finalizers":null}}' --type=merge
Prevencia: sprav z Terminating nudnú vec
Guardrails
- Alert na Pody v
Terminatingdlhšie než X minút (X podľa tvojich grace periodov). - Alert na attach/detach chyby a
FailedMount. - Alert na nody v
NotReady/Unreachabledlhšie než očakávaš.
Engineering hygiena
- Finalizery sú produkčný kód: timeouts, retry s backoffom, viditeľnosť “cleanup failed”.
- Pre Stateful workloady maj dopredu spísanú policy na force delete (kto, kedy, a ako overiť, že starý člen je naozaj mŕtvy).
- Rehearsal: výpadok nodu a CSI fail scenáre v stage prostredí.
Čo by som spravil v produkcii
Keď som on-call a terminuje sa toho veľa, idem defaultne:
- Overím stav nodu a či je kubelet reachable.
- Pozriem finalizery a rozdelím ich (system/storage vs custom controller).
- Ak sú v hre volume, beriem to ako CSI / node cleanup problém, nie ako “kubectl problém”.
- Force delete robím až keď to viem obhájiť (a explicitne priznám riziko, najmä pri StatefulSet).
FAQ
Prečo kubectl delete pod skončí rýchlo, ale Pod ostane Terminating?
Lebo s finalizermi je delete asynchrónny: API označí objekt na zmazanie a čaká, kým kontroléry finalizery odstránia.
Zaručí kubectl delete --force --grace-period=0, že kontajner zomrie?
Nie. Odstráni objekt z API, ale proces môže na node stále bežať, ak je kubelet/runtime v zlom stave.
Kedy je OK patchnúť finalizery na null?
Len keď prijímaš následky (leaky, porušené invariants) a vieš, že kontrolér cleanup už neurobí.
Prečo je force delete pri StatefulSet nebezpečný?
Lebo môže porušiť “at most one”: nový Pod sa môže vytvoriť, aj keď starý ešte beží a komunikuje.
Súvisiace
/sk/blog/kubernetes-graceful-shutdown-rollouty/(termination semantika a bursty chýb pri rolloute)/sk/blog/kubernetes-inode-exhaustion-overlayfs/(filesystem problémy na node, ktoré vyzerajú “random”)/sk/blog/etcd-compaction-quota-alarm/(control-plane symptómy, ktoré často idú spolu s väčším incidentom)
Ďalšie čítanie
Súvisiace články
Kubernetes graceful shutdown ako kontrakt: nula 502 počas rolloutov (HTTP + gRPC)
Reprodukovateľný postup ako odstrániť 502/ECONNRESET pri rolloute: readiness-driven draining, preStop, SIGTERM a merateľný drain budget.
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.
Ephemeral-storage evictions v Kubernetes: logová búrka, ktorá vyhodila zdravé pody
Pody sú evicted kvôli ephemeral-storage aj keď disk vyzerá voľný. Runbook: nodefs/imagefs, logy, kubelet GC a nastavenie budgetov + log rotácia.
Prometheus remote_write backpressure: keď monitoring zaplní disk a ešte aj stratí dáta
Runbook pre výpadky remote_write: ako zmerať lag, odhadnúť time-to-disk-full, bezpečne ladiť queue_config a vedome zvoliť trade-off medzi prežitím a stratou.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.