RCA — Ingress Gateway in OOMKilled
Percorso di RCA per il caso classico: tutti i replica di istio-ingressgateway con restart count alto e crescente, pod vecchi di mesi, nessun rollout in corso.
Sintomo
istio-ingressgateway-759b886f64-255zg 1/1 Running 49 (10m ago) 228distio-ingressgateway-759b886f64-2pnzt 1/1 Running 47 (30m ago) 228distio-ingressgateway-759b886f64-5c4nf 1/1 Running 51 (19m ago) 228dRestart scaglionati nel tempo su tutti i replica → non è un rollout né un problema di un singolo nodo: è il container istio-proxy che muore da solo, indipendentemente su ogni pod.
Trappola: readiness ≠ causa
Negli eventi del namespace si vedono decine di righe come:
Warning Unhealthy pod/istio-ingressgateway-... Readiness probe failed: Get "http://10.131.0.9:15021/healthz/ready": dial tcp ...: connect: connection refusedSono conseguenza, non causa: subito dopo un restart Envoy impiega qualche secondo ad aprire la porta 15021 e la readiness fallisce.
La discriminante è l’assenza di Liveness probe failed e di Killing:
| Eventi presenti | Interpretazione |
|---|---|
Solo Readiness probe failed | Il kubelet non sta uccidendo il container → morte interna (OOM o crash) |
Liveness probe failed + Killing | Restart pilotato dalla liveness → indagare probe e latenza |
Step 1 — Causa del restart
oc get pod -n istio-system -l app=istio-ingressgateway -o custom-columns='NAME:.metadata.name,RC:.status.containerStatuses[*].restartCount,REASON:.status.containerStatuses[*].lastState.terminated.reason,EXIT:.status.containerStatuses[*].lastState.terminated.exitCode,SIG:.status.containerStatuses[*].lastState.terminated.signal,AT:.status.containerStatuses[*].lastState.terminated.finishedAt'Lettura degli exit code:
| Exit | Reason | Significato |
|---|---|---|
| 137 | OOMKilled | Superato il limits.memory del container |
| 137 | Error | Stesso OOM, ma il kubelet non ha attribuito il flag (capita sotto pressione del nodo) — trattalo come OOM |
| 139 | Error | SIGSEGV — crash di Envoy, non memoria |
| 143 | Error | SIGTERM — terminazione pilotata |
Step 2 — Log del container morto
oc logs -n istio-system <pod> -c istio-proxy --previous --tail=200In caso di OOM il log è tipicamente troncato senza errori: Envoy viene ucciso con SIGKILL e non ha modo di loggare nulla. L’assenza di messaggi conferma l’ipotesi invece di smentirla.
Step 3 — Limiti vs consumo reale
oc get deploy istio-ingressgateway -n istio-system -o jsonpath='{.spec.template.spec.containers[?(@.name=="istio-proxy")].resources}{"\n"}'oc adm top pod -n istio-system -l app=istio-ingressgatewayEsempio reale:
{"limits":{"cpu":"2","memory":"1Gi"},"requests":{"cpu":"10m","memory":"128Mi"}}Due problemi in una riga:
- 1Gi di limit è insufficiente per un ingress gateway di un mesh non banale.
requests: 128Micontrolimits: 1Gi(ratio 8x) → QoSBurstablecon scheduling completamente scollegato dal consumo reale: lo scheduler prenota 128Mi, il pod ne usa 700+.
Incrociando oc adm top con l’età dei pod si ricava il rate di crescita:
hvzdg 737Mi (restart 32m fa) → ~10 Mi/minqwfhf 424Mi (restart 7m fa)Step 4 — Dove finisce la memoria
oc exec -n istio-system <pod> -c istio-proxy -- pilot-agent request GET stats | grep -E 'server\.(memory_allocated|memory_heap_size|memory_physical_size|total_connections|concurrency)|listener_manager\.total_listeners_active|cluster_manager\.active_clusters'Output tipico del caso:
cluster_manager.active_clusters: 1564listener_manager.total_listeners_active: 4server.concurrency: 16server.memory_allocated: 673689312server.memory_heap_size: 720371712server.total_connections: 512Come si legge:
memory_allocated / memory_heap_size≈ 93% → heap realmente occupata, non frammentazione tcmalloc. Se invece il rapporto fosse basso (< 60%), il problema sarebbe il rilascio pagine, non il volume di dati.concurrency: 16concpu limit: 2→ Envoy dimensiona i worker thread sui core del nodo, non sul limit del container. Envoy duplica per-worker gran parte dello stato dei cluster.- 1564 cluster × 16 thread con soli 4 listener → la memoria è quasi tutta stato xDS replicato, non buffer di traffico.
Rimedio 1 — Concurrency allineata al CPU limit
È l’intervento a rischio più basso e con l’effetto maggiore. 16 → 4 taglia tipicamente il 40-60% della heap.
oc patch smcp basic -n istio-system --type=merge -p '{"spec":{"gateways":{"ingress":{"runtime":{"pod":{"metadata":{"annotations":{"proxy.istio.io/config":"{\"concurrency\":4}"}}}}}}}}'Rimedio 2 — Resources coerenti
oc patch smcp basic -n istio-system --type=merge -p '{"spec":{"gateways":{"ingress":{"runtime":{"container":{"resources":{"requests":{"cpu":"500m","memory":"1Gi"},"limits":{"cpu":"2","memory":"4Gi"}}}}}}}}'Prima di applicare, verificare che i nodi reggano il nuovo totale (es. 8 repliche × 4Gi = 32Gi di limit):
oc describe node -l node-role.kubernetes.io/infra= | grep -A6 'Allocated resources'Applicare i due rimedi in un unico rollout: se la riduzione di concurrency non bastasse, il limit più alto fa comunque da cuscinetto.
oc rollout status deploy/istio-ingressgateway -n istio-system --timeout=5mVerifica
Attendere almeno il tempo che prima portava all’OOM (nel caso di esempio ~30-60 min), poi:
oc exec -n istio-system $(oc get pod -n istio-system -l app=istio-ingressgateway -o name | head -1 | cut -d/ -f2) -c istio-proxy -- pilot-agent request GET stats | grep -E 'server\.(concurrency|memory_heap_size)|cluster_manager\.active_clusters'oc adm top pod -n istio-system -l app=istio-ingressgatewayoc get pod -n istio-system -l app=istio-ingressgatewayIl restart count deve restare stabile e la heap assestarsi su un plateau invece di crescere linearmente.
Causa di fondo — ridurre i cluster
1564 cluster attivi su 4 listener indicano che il gateway riceve la configurazione di tutti i servizi del mesh, non solo di quelli che espone.
oc get gateway,virtualservice,destinationrule,serviceentry -A --no-headers | wc -loc get smmr default -n istio-system -o jsonpath='{.status.configuredMembers}{"\n"}' | tr ',' '\n' | wc -lSe i namespace membri della SMMR sono molti più di quelli effettivamente serviti dal gateway, il problema è lo scoping del mesh, non Envoy.
La leva tecnica è su istiod:
spec: runtime: components: pilot: container: env: PILOT_FILTER_GATEWAY_CLUSTER_CONFIG: "true"Limita i cluster pushati ai gateway a quelli effettivamente referenziati dai Gateway. Riduzione tipica del 60-80% su mesh grandi.
Checklist riassuntiva
- Restart scaglionati su tutti i replica → morte interna, non rollout.
- Solo eventi di readiness → escludere la liveness.
lastState.terminated→ confermare 137/OOMKilled.--previousvuoto → coerente con SIGKILL.resources+oc adm top→ limit basso e ratio requests/limits sbagliato.server.concurrencyvscpu limit→ moltiplicatore nascosto.cluster_manager.active_clusters→ volume di stato xDS.- Patch SMCP (concurrency + resources) in un solo rollout.
- Scoping del mesh come rimedio strutturale, fuori dall’incidente.