RCA guidata — Pod in CrashLoop
RCA guidata — Pod in CrashLoop
Runbook operativo per la root cause analysis di un pod che restarta ripetutamente. Struttura a “5 whys”: ogni step approfondisce il precedente, e alla fine si arriva alla vera causa (config, memory, dipendenza, node).
Applicabile sia a CrashLoopBackOff che a OOMKilled che a Error puro.
Prerequisiti: accesso
cluster-admino RBAC che permettaoc get events,oc logs,oc debug node. Console Observe → Metrics accessibile.
Fase 0 — Sintomo iniziale
Un pod è stato segnalato come “non funziona”. Alla prima verifica:
oc get pod -n <ns> <pod>Se vedi:
NAME READY STATUS RESTARTS AGEmyapp-xxx-yyy 0/1 CrashLoopBackOff 17 2hSei nel caso classico. Procedi.
Fase 1 — Chi, dove, quante volte
1.1 Su quale node gira?
oc get pod -n <ns> <pod> -o jsonpath='{.spec.nodeName}{"\n"}'Salva il nome del node in una variabile: NODE=$(oc get pod -n <ns> <pod> -o jsonpath='{.spec.nodeName}')
1.2 Quanti restart?
oc get pod -n <ns> <pod> \ -o custom-columns=NAME:.metadata.name,\RESTARTS:.status.containerStatuses[*].restartCount,\STATUS:.status.containerStatuses[*].state,\LAST_STATE:.status.containerStatuses[*].lastStateL’output .lastState mostra il motivo dell’ultimo exit. Se è terminated con reason=OOMKilled, salta direttamente alla Fase 3 — OOM.
1.3 Alternativa PromQL (utile per screenshot / audit)
Chi è, dove sta, quanti restart nelle ultime 6 ore:
sum by (namespace, pod, container, node) ( increase(kube_pod_container_status_restarts_total{ namespace="<ns>", pod=~"<pod>.*" }[6h]) * on(pod, namespace) group_left(node) kube_pod_info)Fase 2 — Perché è morto (log e events)
2.1 Log del container morto (crucial: --previous)
Il container attualmente in stato waiting/CrashLoopBackOff non ha log (non sta girando). Ti serve il log del ciclo precedente:
oc logs -n <ns> <pod> -c <container> --previousoc logs -n <ns> <pod> -c <container> --previous --tail=200Cosa cerchi qui:
- Stack trace applicativo (Java:
Exception in thread, Python:Traceback, Go:panic:) - Errori di connessione a dipendenze (DB, cache, altri servizi)
OutOfMemoryError(in Java può essere sia JVM OOM sia container OOM)- Errori di parsing config (variabili d’ambiente mancanti, file mount errati)
2.2 Events Kubernetes recenti sul pod
oc get events -n <ns> --field-selector=involvedObject.name=<pod> --sort-by=.lastTimestampCosa cerchi:
Failed to pull image→ problema registry / pull-secretBack-off restarting failed container→ conferma CLBOReadiness probe failed→ probe troppo aggressive o app troppo lenta a partireLiveness probe failed→ probe uccide app che sta caricando lentamenteMountVolume.SetUp failed→ problema PV / configMap / secret
2.3 Describe completo del pod
oc describe pod -n <ns> <pod>L’output è lungo, ma le sezioni chiave sono:
- Conditions — stato attuale
- Containers → Last State — dettaglio dell’ultimo exit (reason, exit_code, started/finished)
- Events (in fondo) — cronologia recente
Esempio di Last State: Terminated diagnostico:
Last State: Terminated Reason: OOMKilled Exit Code: 137 # 128 + SIGKILL(9) Started: Mon, 06 May 2026 14:12:03 +0200 Finished: Mon, 06 May 2026 14:15:47 +0200Exit code 137 = SIGKILL = kernel OOM (o kill esplicito).
Exit code 139 = SIGSEGV = crash applicativo (segfault, tipico C/C++).
Exit code 143 = SIGTERM = kill “gentile” (di solito rolling update, non un bug).
Exit code 1 = errore generico applicativo.
Fase 3 — Se è OOMKilled
Se Fase 2 dice Reason: OOMKilled (o exit code 137 con contesto memory), sei sul path OOM.
3.1 Quanto memoria stava usando
# Ultimi 30 minuti di working-set memorymax_over_time( container_memory_working_set_bytes{ namespace="<ns>", pod=~"<pod>.*", container="<container>" }[30m]) / 1024 / 1024Restituisce il picco in MiB.
3.2 Quanto limit era configurato
oc get pod -n <ns> <pod> -o jsonpath='{.spec.containers[?(@.name=="<container>")].resources}{"\n"}'Confronta memoria usata con limits.memory. Se sono vicini, è memory pressure interna al container.
3.3 Conferma kernel-side
# Ottieni il nodeNODE=$(oc get pod -n <ns> <pod> -o jsonpath='{.spec.nodeName}')
# Cerca OOM nel kernel logoc debug node/$NODE -- chroot /host journalctl --since "1 hour ago" --grep "oom.*kill|Killed process"Nell’output cerca Memory cgroup out of memory (= OOM cgroup del container, causa il tuo limit) vs Out of memory: Killed process senza cgroup (= OOM del node intero, causa memory pressure sul node).
3.4 Se è OOM cgroup (tuo limit)
Fix: alzi il limits.memory. Calcola il nuovo valore su 7 giorni:
max_over_time( container_memory_working_set_bytes{ namespace="<ns>", pod=~"<pod>.*", container="<container>" }[7d:5m]) / 1024 / 1024Prendi il picco, aggiungi 20-30% margin. Se l’app è in Java, considera anche -Xmx = 70-75% del limit del container.
3.5 Se è OOM node (pressione globale)
Fix: liberare il node o aumentare i request degli altri pod (per fargli evacuare via scheduler). Vedi anche OOM Killed § 3.
Vedi chi sta consumando sul node:
topk(20, sum by (namespace, pod) ( container_memory_working_set_bytes{ container!="", container!="POD" } * on(pod, namespace) group_left(node) kube_pod_info{node="<NODE>"} )) / 1024 / 1024Fase 4 — Se non è OOM (probabilmente config / dipendenza)
Se il container muore con exit code non-137 e i log non parlano di memoria, è quasi sicuramente un problema applicativo. Le cause più frequenti:
4.1 ConfigMap / Secret mancante o cambiato
oc get pod -n <ns> <pod> -o json | jq '.spec.volumes[] | select(.configMap or .secret)'oc get configmap,secret -n <ns>Verifica che le referenze siano coerenti.
4.2 Env var mancanti (crash all’avvio)
oc get pod -n <ns> <pod> -o json | jq '.spec.containers[].env'Cerca env che referenziano secretKeyRef o configMapKeyRef che potrebbero non esistere.
4.3 Dipendenza esterna non raggiungibile
Se l’app chiama un DB / API esterno all’avvio e non ha timeout robusti:
# Log del container: se vedi "connection refused" o "timeout" nei primi secondioc logs -n <ns> <pod> -c <container> --previous | head -50
# Test dalla stessa network del podoc debug -n <ns> <pod> -- curl -v http://<db-service>:54324.4 Readiness/Liveness probe troppo aggressive
Se il pod muore ripetutamente ma i log non mostrano errori applicativi, sospetta probe:
oc get pod -n <ns> <pod> -o yaml | grep -A5 "livenessProbe\|readinessProbe\|startupProbe"Sintomi tipici:
initialDelaySecondstroppo basso → probe partono prima che l’app sia prontatimeoutSecondstroppo basso → una response lenta di 3 secondi killa il pod- Manca
startupProbeper app slow-start (Java Spring, IIS, ecc.)
4.5 Init container fallisce
oc get pod -n <ns> <pod> -o json | jq '.status.initContainerStatuses'Se un init container non passa, il container principale non parte mai. Log:
oc logs -n <ns> <pod> -c <init-container-name>4.6 SCC / SELinux / permessi
Se il container gira come root ma la SCC del namespace non lo permette:
oc logs -n <ns> <pod> -c <container> --previous | grep -i "permission denied\|operation not permitted"oc describe pod -n <ns> <pod> | grep -i "scc\|security"Fase 5 — Se il node stesso è il problema
A volte il pod restarta perché il node ha problemi. Verifiche:
5.1 Condizioni del node
oc get node $NODEoc describe node $NODE | grep -A20 "Conditions:"Cerca MemoryPressure=True, DiskPressure=True, PIDPressure=True, NetworkUnavailable=True.
5.2 Kubelet in errore
oc debug node/$NODE -- chroot /host journalctl -u kubelet --since "1 hour ago" | grep -iE "error|failed" | tail -305.3 CRI-O in errore (container runtime)
oc debug node/$NODE -- chroot /host journalctl -u crio --since "1 hour ago" | grep -iE "error|failed" | tail -305.4 Se solo pod su UN node hanno il problema
Sposta il pod di test su un altro node per confermare l’ipotesi:
# Cordon del node sospetto (blocca nuova schedulazione)oc adm cordon $NODE
# Delete del pod: il ReplicaSet lo riprogramma su un altro nodeoc delete pod -n <ns> <pod>
# Se sul nuovo node funziona, il node originale ha un problema# Non dimenticare di uncordon dopo!oc adm uncordon $NODEFase 6 — Report finale
Una volta capita la causa, documenta l’RCA. Template minimale:
# RCA — <pod> restart in loop
**Ambiente**: <cluster>, namespace `<ns>`**Pod**: `<pod>`**Container**: `<container>`**Node**: `<node>`**Durata sintomo**: <ora inizio> → <ora fix>
## Sintomo- <pod> in `CrashLoopBackOff` con <N> restart- Impact: <servizio giù / degradato / nessuno>
## Root cause<Config errata / OOM cgroup / dipendenza X non raggiungibile / probe troppo aggressive>
## Evidenza- Log applicativo: `<estratto rilevante>`- Events K8s: `<estratto>`- Metriche: <link a Grafana / query PromQL>- Kernel (se OOM): `<estratto journalctl>`
## Fix applicato- <es: aumentato limits.memory da 512Mi a 1Gi>- <es: fixato ConfigMap `app-config` chiave `db.url`>- <es: aumentato startupProbe initialDelaySeconds a 60s>
## Prevenzione- <es: alert PrometheusRule su restart > 5/1h>- <es: aggiunta review su change config>Salvarlo (Confluence, wiki, cartella RCA/ del repo openshift-cheatsheet) ti costruisce nel tempo un catalogo di casi che è oro puro per la squadra e per l’audit.
Vedi anche
- Restart & Pod Lifecycle — query di partenza
- OOM Killed — Troubleshooting — approfondimento OOM
- Alerting operativo — alert essenziali da attivare