Späť na blog

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 Terminating dlhšie než X minút (X podľa tvojich grace periodov).
  • Alert na attach/detach chyby a FailedMount.
  • Alert na nody v NotReady / Unreachable dlhš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:

  1. Overím stav nodu a či je kubelet reachable.
  2. Pozriem finalizery a rozdelím ich (system/storage vs custom controller).
  3. Ak sú v hre volume, beriem to ako CSI / node cleanup problém, nie ako “kubectl problém”.
  4. 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

Citujte tento článok

Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.

Michal Drozd. "Pod zaseknutý v Terminating: produkčný rozhodovací strom pre finalizery, volume a mŕtve nody". https://www.michal-drozd.com/sk/blog/kubernetes-pod-zaseknuty-terminating-playbook/ (Publikované 26. novembra 2025).