Skip to content

Test di connettività da pod

Un “timeout verso X” non è una diagnosi. La diagnosi è a quale livello si ferma il traffico. Questa pagina è organizzata per livelli, dal basso verso l’alto: si sale solo dopo aver chiuso il livello precedente.

0. DOVE eseguire il test → scegliere container e pod corretti
1. DNS → il nome risolve? in quale IP?
2. Connettività L3/L4 → il SYN arriva? torna qualcosa?
3. TLS → handshake, catena, SNI
4. HTTP applicativo → codici, tempi, timeout
5. Service Mesh → il sidecar interferisce?
6. Egress → con quale IP sorgente esco?
7. Policy di rete → NetworkPolicy / EgressFirewall
8. Cattura pacchetti → la prova definitiva

Regola trasversale: un test vale solo se parte dallo stesso network namespace e con lo stesso uid dell’applicazione. Un curl dal bastion non dice nulla sul path del pod.


0. Dove eseguire il test

Prima dei comandi, la scelta del punto di osservazione. È l’errore più frequente: si testa da un posto e si conclude su un altro.

Ricognizione iniziale

Terminal window
NS=<namespace>; POD=<pod>; C=<container-applicativo>
oc -n $NS get pod $POD -o jsonpath='{.spec.containers[*].name}{"\n"}'

Elenca i container del pod. Se compare istio-proxy il pod è in mesh e tutto il traffico in uscita passa da Envoy: il livello 5 è in gioco. Un pod 2/2 Running con un solo container applicativo è quasi sempre questo caso.

Terminal window
oc -n $NS get pod $POD -o jsonpath='{.spec.nodeName}{"\n"}'
oc get ns $NS --show-labels

Il nodo serve per i test a livello host e per la cattura pacchetti. Le label del namespace dicono tre cose: istio-injection=enabled e maistra.io/member-of confermano l’appartenenza alla mesh; una label tipo eg-<ns>=enable segnala un EgressIP, quindi l’IP sorgente non sarà quello del nodo del pod; le label pod-security.kubernetes.io/* anticipano quali capability avrai a disposizione nei pod di debug.

I sei punti di osservazione

PuntoComando basenetnsPassa da Envoy?IP sorgenteCosa dimostra
Container applicativooc exec ... -c $Cdel podEgressIP se presenteil path reale, end-to-end
Container istio-proxyoc exec ... -c istio-proxydel podno (uid 1337)EgressIP se presenterete senza mesh
Ephemeral containeroc debug $POD --target=$Cdel podEgressIP se presentecome l’app, ma con tool installati
Pod nuovo nel nsoc runpropriasì (injection)EgressIP se presenteil namespace, non il pod
Pod nuovo senza sidecaroc run --annotations sidecar.istio.io/inject=falseproprianoEgressIP se presenteegress senza mesh
Nodooc debug node/<node>hostnoIP del nodosolo la rete nodi

Il punto chiave della seconda riga: il traffico originato dal container istio-proxy gira con uid 1337, che è l’uid escluso dalle regole iptables di intercettazione della mesh. Non viene re-catturato da Envoy, ma resta nella netns del pod, quindi mantiene EgressIP e routing dell’applicazione. È l’unico modo di isolare la mesh senza cambiare nient’altro.

Immagine di debug e capability

Terminal window
IMG=registry.redhat.io/rhel9/support-tools

Contiene curl, nc, dig, openssl, tcpdump, traceroute. Le immagini applicative in genere non hanno nulla di tutto questo.

a) Ephemeral container — si attacca al pod esistente senza riavviarlo, condividendo netns e (con --target) namespace dei processi. È il test più fedele all’applicazione:

Terminal window
oc -n $NS debug $POD --image=$IMG --target=$C -it -- bash

b) Pod nuovo nel namespace — eredita EgressIP e injection, ma non le annotation del deployment applicativo:

Terminal window
oc -n $NS run netshoot --image=$IMG --restart=Never -it --rm -- bash

c) Pod nuovo senza sidecar — isola la mesh mantenendo l’EgressIP:

Terminal window
oc -n $NS run netshoot --image=$IMG --restart=Never -it --rm \
--annotations sidecar.istio.io/inject=false -- bash

Su namespace con PodSecurity restricted in modalità warn/audit vedrai un warning ma il pod parte comunque. L’SCC restricted-v2 però droppa tutte le capability, con una conseguenza pratica:

  • ping funziona: usa un ICMP datagram socket, non richiede NET_RAW
  • traceroute -T, tcpdump, hping3 falliscono con Operation not permitted: richiedono socket raw

Se ti servono davvero, serve una ServiceAccount con SCC privilegiata:

Terminal window
oc -n $NS create sa netdebug
oc adm policy add-scc-to-user privileged -z netdebug -n $NS
oc -n $NS run netshoot --image=$IMG --restart=Never -it --rm \
--annotations sidecar.istio.io/inject=false \
--overrides='{"spec":{"serviceAccountName":"netdebug","containers":[{"name":"netshoot","image":"registry.redhat.io/rhel9/support-tools","stdin":true,"tty":true,"command":["bash"],"securityContext":{"capabilities":{"add":["NET_RAW","NET_ADMIN"]}}}]}}' -- bash

--overrides sovrascrive il manifest generato da oc run con il frammento JSON indicato: qui serve perché SA e capability non sono esprimibili con i flag. A fine analisi rimuovi il grant:

Terminal window
oc adm policy remove-scc-from-user privileged -z netdebug -n $NS
oc -n $NS delete sa netdebug

1. DNS

Sempre il primo livello: metà dei “problemi di rete” sono nomi che risolvono in ambiente sbagliato o non risolvono affatto.

Terminal window
oc -n $NS exec $POD -c $C -- getent hosts <fqdn>

getent hosts interroga il name service switch del container, cioè lo stesso percorso che userà l’applicazione: /etc/hosts, poi il resolver. È preferibile a dig e nslookup proprio per questo — quelli parlano direttamente col server DNS e possono dare un risultato che l’app non vedrà mai. In più getent c’è ovunque, dig quasi mai nelle immagini applicative.

Nessun output significa risoluzione fallita. Un output significa nome risolto: annota l’IP, servirà in tutti i livelli successivi.

Terminal window
oc -n $NS exec $POD -c $C -- cat /etc/resolv.conf

Deve puntare al ClusterIP del DNS di cluster (172.30.0.10 in configurazione standard) con i search domain del namespace. Un resolv.conf con resolver esterni indica una dnsPolicy personalizzata sul pod: da verificare, perché cambia tutta la risoluzione.

Con l’immagine di debug puoi entrare nel dettaglio:

Terminal window
oc -n $NS debug $POD --image=$IMG --target=$C -it -- \
bash -c 'dig +short <fqdn>; dig +short <fqdn> @172.30.0.10; dig +trace <fqdn> | tail -20'

dig @172.30.0.10 forza la query sul DNS di cluster: se questa risponde e getent no, il problema è nel resolver del container. +trace segue la delega dai root server e serve quando il record esiste in un ambiente e non in un altro.

Servizi interni al cluster:

Terminal window
oc -n $NS exec $POD -c $C -- getent hosts <svc>.<ns>.svc.cluster.local
oc -n <ns-backend> get endpointslice -o wide

Un Service risolve sempre, anche senza pod dietro. Per questo la seconda riga è indispensabile: se l’EndpointSlice è vuoto il ClusterIP non ha nessuno a cui inoltrare e il sintomo è un timeout identico a quello di un firewall. Controlla il selector prima di scomodare la rete.

Errori CoreDNS lato cluster:

Terminal window
oc -n openshift-dns logs -l dns.operator.openshift.io/daemonset-dns=default --tail=50 | grep -i SERVFAIL

2. Connettività L3/L4

Qui si decide se il problema è di rete o applicativo. Il tempo che serve è al massimo cinque secondi per test.

Il metodo senza tool

Terminal window
oc -n $NS exec $POD -c $C -- bash -c \
'timeout 5 bash -c "</dev/tcp/HOST/PORT" && echo OPEN || echo BLOCKED'

/dev/tcp/host/porta è uno pseudo-device di bash: non esiste sul filesystem, è bash che intercetta la redirezione e apre una socket TCP. Aprirla in lettura tenta il three-way handshake e non invia nulla. timeout 5 impedisce che il comando resti appeso per il timeout di sistema (oltre due minuti). È il test che funziona anche in un container distroless-ish, purché ci sia bash.

Per la semantica del fallimento, che è il dato che conta davvero, lascia parlare stderr:

Terminal window
oc -n $NS exec $POD -c $C -- bash -c 'exec 3<>/dev/tcp/HOST/PORT' 2>&1
EsitoSignificatoDove guardare
Connection refused immediatoil pacchetto è arrivato, nessun listener sulla portahost vivo, servizio giù o porta sbagliata
attesa fino al timeout, nessun messaggiodrop silenziosofirewall, routing, host inesistente
No route to hostICMP unreachable dal gatewayrouting o VLAN
Name or service not knownnon sei arrivato al livello 2torna al DNS

La distinzione tra le prime due righe è quella che decide se il ticket va al team di rete o a quello applicativo. Un reject è una risposta: qualcuno ha ricevuto il pacchetto. Un timeout è silenzio.

Con i tool disponibili

Terminal window
oc -n $NS exec $POD -c istio-proxy -- nc -zv -w5 HOST PORT

nc -z apre e chiude subito senza inviare dati (scan mode), -v stampa l’esito, -w5 è il timeout. Output tipico: Ncat: TIMEOUT. oppure Connection refused oppure Connected to.

Terminal window
oc -n $NS exec $POD -c istio-proxy -- ping -c3 -W2 HOST

ping aggiunge un’informazione che TCP non dà: se l’host risponde a ICMP ma non alla porta, l’host esiste ed è raggiungibile e il filtro è sulla porta. Se non risponde nemmeno a ICMP, o l’host non c’è o il drop è totale. Attenzione: molti apparati bloccano ICMP per policy, quindi un ping perso da solo non prova nulla — vale come conferma di un test TCP già fallito, non come test primario.

Terminal window
oc -n $NS exec $POD -c netshoot -- traceroute -T -p PORT -n -m 8 HOST

-T usa pacchetti TCP SYN verso la porta reale invece dei datagram UDP di default, quindi segue lo stesso percorso e gli stessi filtri del traffico applicativo. -n evita il reverse DNS su ogni hop (molto più veloce), -m 8 limita gli hop. Richiede NET_RAW: senza, risponde You do not have enough privileges to use this traceroute method. L’hop dopo il quale compaiono solo asterischi è il punto in cui il traffico muore.

Confronto multi-sorgente

Un singolo test fallito non è una diagnosi. Servono tre esiti più una controprova:

Terminal window
# 1. path reale dell'applicazione
oc -n $NS exec $POD -c $C -- bash -c 'timeout 5 bash -c "</dev/tcp/HOST/PORT" && echo OPEN || echo BLOCKED'
# 2. rete dei nodi
NODE=$(oc -n $NS get pod $POD -o jsonpath='{.spec.nodeName}')
oc debug node/$NODE -- chroot /host bash -c \
'timeout 5 bash -c "</dev/tcp/HOST/PORT" && echo OPEN || echo BLOCKED; ip route get <dst-ip>'
# 3. bastion
timeout 5 bash -c '</dev/tcp/HOST/PORT' && echo OPEN || echo BLOCKED
# 4. CONTROPROVA: altra destinazione dallo stesso sorgente del punto 1
oc -n $NS exec $POD -c $C -- bash -c 'timeout 5 bash -c "</dev/tcp/ALTRO-HOST/ALTRA-PORTA" && echo OPEN || echo BLOCKED'

oc debug node crea un pod privilegiato sulla network namespace dell’host; chroot /host dà accesso ai binari del sistema operativo del nodo invece di quelli dell’immagine di debug. ip route get mostra quale interfaccia e quale gateway verranno usati per quella destinazione: su OCP con OVN il traffico verso l’esterno esce da br-ex.

Il punto 4 è quello che rende il ticket inattaccabile: se un’altra destinazione risponde dallo stesso identico sorgente, l’uscita dal cluster funziona e il problema è a valle. Senza controprova, la rete rimanderà indietro il ticket.


3. TLS

Si arriva qui solo con il livello 2 verde. Un errore TLS con handshake TCP fallito non esiste: è sempre un problema di rete travestito.

Terminal window
oc -n $NS exec $POD -c istio-proxy -- \
openssl s_client -connect HOST:443 -servername HOST -brief </dev/null

-connect apre la sessione, -servername imposta l’SNI: senza, un load balancer o un Ingress che ospita più siti restituisce il certificato sbagliato o chiude. </dev/null chiude subito lo stdin, altrimenti il comando resta interattivo. -brief riassume protocollo, cipher e verifica.

Catena e scadenze:

Terminal window
oc -n $NS exec $POD -c istio-proxy -- \
openssl s_client -connect HOST:443 -servername HOST -showcerts </dev/null 2>/dev/null | \
openssl x509 -noout -subject -issuer -dates

Truststore del container applicativo, che è cosa diversa da quello del sidecar:

Terminal window
oc -n $NS exec $POD -c $C -- bash -c \
'ls -l /etc/pki/ca-trust/extracted/pem/ 2>/dev/null; ls /etc/ssl/certs | head'

Per applicazioni Java il truststore rilevante è quello della JVM, non quello di sistema:

Terminal window
oc -n $NS exec $POD -c $C -- bash -c \
'keytool -list -cacerts -storepass changeit 2>/dev/null | grep -ci "trusted"'

Errori tipici e traduzione: unable to get local issuer certificate = CA aziendale non nel truststore; certificate verify failed: Hostname mismatch = SNI o SAN errati; tlsv1 alert unknown ca lato server = il server chiede mTLS e non gli stai presentando un certificato client — caso frequente quando la mesh applica mTLS STRICT verso un carico che non ne fa parte.


4. HTTP applicativo

Terminal window
oc -n $NS exec $POD -c $C -- curl -sSvko /dev/null --max-time 20 \
-w 'code=%{http_code} dns=%{time_namelookup} conn=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} tot=%{time_total}\n' \
https://HOST/PATH

Flag per flag: -s silenzia la progress bar, -S mantiene comunque gli errori, -v mostra la negoziazione (è lì che vedi Trying <ip>... e la conferma dell’IP realmente contattato), -k ignora la validazione del certificato — utile per separare un problema TLS da uno di raggiungibilità, mai da usare come soluzione — -o /dev/null scarta il body, --max-time è il tetto complessivo.

La -w è la parte che vale: ogni campo è cumulativo dall’inizio della richiesta, quindi le differenze tra campi adiacenti sono le durate delle singole fasi.

LetturaDiagnosi
conn=0.000000handshake TCP mai completato: è rete, il resto è rumore
conn valorizzato, tls=0TCP ok, TLS fallito: certificati, SNI, cipher, mTLS
tls ok, ttfb altoil backend è lento: è tuning di timeout, non un guasto
code=000nessuna risposta HTTP: la richiesta non è mai arrivata a un server HTTP
code=504 a esattamente 15stimeout di route di Envoy, non del backend

Il caso conn=0 con tot pari al --max-time impostato è la firma esatta di un drop silenzioso.

Per i microgateway WSO2 gli endpoint di backend non stanno nel codice ma in ConfigMap:

Terminal window
oc -n $NS get cm | grep -i endpoint
oc -n $NS get cm <app>-endpoints-cm -o yaml | grep -iE '<nome-api>|endpoint_0'

Prima di testare, verifica quale URL l’applicazione chiama davvero: molto spesso è diverso da quello che l’AM ha provato a mano.


5. Service Mesh

Da percorrere solo se il pod ha istio-proxy.

Il test discriminante

Stesso comando, stesso pod, due container diversi:

Terminal window
# fuori dalla mesh (uid 1337)
oc -n $NS exec $POD -c istio-proxy -- \
curl -sSko /dev/null --max-time 15 -w 'code=%{http_code} conn=%{time_connect}\n' https://HOST/PATH
# dentro la mesh
oc -n $NS exec $POD -c $C -- \
curl -sSko /dev/null --max-time 15 -w 'code=%{http_code} conn=%{time_connect}\n' https://HOST/PATH
istio-proxycontainer appVerdetto
OKKOè la mesh: ServiceEntry, outboundTrafficPolicy, mTLS, timeout
KOKOè rete o destinazione: mesh esclusa, non perderci tempo
KOOKanomalia di routing o DNS specifica del container, molto raro

Introspezione di Envoy

Terminal window
oc -n $NS exec $POD -c istio-proxy -- pilot-agent request GET clusters | grep -i HOST

pilot-agent request interroga l’admin interface di Envoy su localhost:15000, che non è esposta fuori dal pod. clusters elenca gli upstream conosciuti: se l’host di destinazione non compare, per Envoy quella destinazione non esiste e il traffico finisce nel blackhole. È la verifica diretta della presenza di una ServiceEntry.

Terminal window
oc -n $NS exec $POD -c istio-proxy -- pilot-agent request GET stats | \
grep -E 'upstream_cx_connect_fail|upstream_cx_connect_timeout|upstream_rq_timeout|upstream_cx_none_healthy'
oc -n $NS exec $POD -c istio-proxy -- pilot-agent request GET config_dump > /tmp/cd.json

I contatori distinguono un fallimento di connessione (connect_fail, problema di rete a valle del proxy) da un timeout di richiesta (rq_timeout, il backend risponde troppo tardi). config_dump è la configurazione completa che il control plane ha spinto sul proxy: utile quando sospetti che una risorsa non sia stata propagata.

Terminal window
oc -n $NS logs $POD -c istio-proxy --since=1h | grep -i HOST

Attenzione: per traffico HTTPS in uscita Envoy vede TCP opaco e logga per SNI e porta, non per path. Cercare il path applicativo nei log del sidecar non produce mai risultati e fa concludere erroneamente che il traffico non passi.

response_flags nei log di accesso:

FlagSignificato
NRnessuna route: ServiceEntry mancante, host fuori registry
UFupstream connection failure: rete o firewall verso il backend
UOupstream overflow: circuit breaker della DestinationRule
UTupstream timeout: timeout di route (default 15s) o backend lento
URXlimite di retry superato
UAEXrifiutato da filtro esterno, tipicamente AuthorizationPolicy
DCdownstream disconnect: è il client che ha mollato per primo

Le cinque cause ricorrenti

Terminal window
oc -n istio-system get smcp -o jsonpath='{.items[0].spec.proxy.networking.trafficControl.outbound.policy}{"\n"}'
oc get serviceentry -A
oc get destinationrule -A -o yaml | grep -B10 'mode:'
oc get peerauthentication -A
oc get virtualservice -A -o yaml | grep -B5 -A2 'timeout:'
  1. REGISTRY_ONLY senza ServiceEntry — tutto ciò che non è dichiarato viene scartato. Sintomo indistinguibile da un timeout di rete, flag NR.
  2. mTLS STRICT verso un carico fuori mesh — il sidecar presenta un certificato che il backend non capisce. Serve una DestinationRule con tls.mode: DISABLE per quell’host.
  3. Port naming — per traffico TCP non HTTP (tutti i database) la porta del Service deve chiamarsi tcp-<nome> o avere appProtocol: tcp. Con un nome generico Envoy tenta il parsing HTTP e la connessione muore in modo opaco.
  4. Race di startup — l’applicazione apre le connessioni prima che Envoy sia pronto. Sintomo tipico: fallisce solo al primo avvio del pod, poi funziona.
    proxy.istio.io/config: '{"holdApplicationUntilProxyStarts": true}'
  5. Timeout di route — 15 secondi di default. Un 504 a esattamente 15s è di Envoy, non del backend.

Bypass mirato

Solo per validare l’ipotesi, non come soluzione definitiva:

traffic.sidecar.istio.io/excludeOutboundIPRanges: "10.10.10.0/24"
traffic.sidecar.istio.io/excludeOutboundPorts: "1521,1546"

Escludono destinazioni o porte dall’intercettazione iptables del sidecar. Se con l’esclusione il traffico passa, il problema è confermato nella mesh: a quel punto la fix corretta è la ServiceEntry o la DestinationRule, non l’esclusione permanente.


6. Egress: con quale IP esco

Con EgressIP attivo il traffico non esce dal nodo che ospita il pod: viene rediretto sui nodi che detengono l’IP e nattato lì. È il fraintendimento più comune nei ticket di rete.

Terminal window
oc get ns $NS --show-labels | tr ',' '\n' | grep -i '^eg-'
oc get egressip -o custom-columns=NAME:.metadata.name,SPEC:.spec.egressIPs,ASSIGNED:.status.items[*].egressIP,NODE:.status.items[*].node

La prima riga estrae la label di selezione del namespace, la seconda mappa ogni oggetto EgressIP sugli IP dichiarati (spec), su quelli effettivamente assegnati (status) e sui nodi che li ospitano. Una divergenza tra SPEC e ASSIGNED è già un’anomalia da segnalare.

Verifica che il NAT sia realmente programmato in OVN:

Terminal window
EGNODE=<nodo-assegnato>
OVNPOD=$(oc -n openshift-ovn-kubernetes get pod -l app=ovnkube-node \
--field-selector spec.nodeName=$EGNODE -o name | head -1)
oc -n openshift-ovn-kubernetes rsh -c nbdb $OVNPOD ovn-nbctl lr-nat-list GR_$EGNODE | grep <egress-ip>

GR_<nodo> è il gateway router OVN di quel nodo, e lr-nat-list ne elenca le regole NAT. Il container nbdb è quello che espone il database Northbound, dove ovn-nbctl legge. Se per l’EgressIP non compare nessuna entry e vedi solo lo SNAT di default verso l’IP del nodo, la riconciliazione è rotta: il workaround è delete e recreate dell’oggetto EgressIP, poi ticket a Red Hat perché è un problema del control plane, non di configurazione.

Conseguenza operativa da tenere sempre presente quando si chiede un’apertura: il sorgente da autorizzare sono gli EgressIP, non l’IP del nodo che ospita il pod.


7. Policy di rete

Terminal window
oc -n $NS get networkpolicy
oc -n $NS get egressfirewall -o yaml

Due oggetti diversi con effetti opposti. NetworkPolicy è namespace-scoped e in OCP agisce prevalentemente sul traffico in ingresso tra pod: se un namespace ha almeno una policy con podSelector: {}, tutto ciò che non è esplicitamente permesso viene negato. EgressFirewall (uno per namespace, di nome default) filtra invece le destinazioni esterne: le regole sono valutate in ordine e la prima che fa match vince.

Un items: [] su entrambe esclude il livello 7 in un colpo solo, ed è un’informazione che va sempre nel ticket: dimostra che il blocco non è nel cluster.


8. Cattura pacchetti e simulazione

tcpdump nella netns del pod

Quando serve la prova che il SYN esce e non torna nulla:

Terminal window
NODE=$(oc -n $NS get pod $POD -o jsonpath='{.spec.nodeName}')
oc debug node/$NODE -- chroot /host bash -c "
POD_ID=\$(crictl pods -q --name $POD --namespace $NS)
PID=\$(crictl inspectp \$POD_ID | jq -r '.info.pid')
nsenter -t \$PID -n tcpdump -nni any host <dst-ip> -c 50"

crictl è la CLI del container runtime sul nodo: pods -q ricava l’ID del sandbox, inspectp ne estrae il PID del processo di pausa. nsenter -t <pid> -n entra nella network namespace di quel processo, cioè quella del pod, ed esegue lì tcpdump. È il modo di catturare il traffico del pod senza installare nulla nel pod e senza toccare la mesh.

Sul nodo egress, per vedere il pacchetto già nattato:

Terminal window
oc debug node/$EGNODE -- chroot /host bash -c \
'tcpdump -nni br-ex host <dst-ip> and port <dst-port> -c 50'

Lettura: SYN ripetuti senza SYN-ACK significa drop a valle. Nessun SYN in uscita significa che il problema è dentro il pod — mesh, policy, o DNS che non ha risolto.

ovnkube-trace

Terminal window
ovnkube-trace -src-namespace $NS -src $POD \
-dst-ip <dst-ip> -tcp -dst-port <port> -loglevel 5

Simula il percorso nella logica OVN senza generare traffico reale. Utile per validare policy e routing quando non puoi permetterti di toccare l’applicazione.


9. Ricette per caso d’uso

Database

Ordine: DNS → TCP sulla porta → TLS del listener → mesh se presente.

Terminal window
oc -n $NS exec $POD -c $C -- getent hosts DBHOST
oc -n $NS exec $POD -c $C -- bash -c 'timeout 5 bash -c "</dev/tcp/DBHOST/PORT" && echo OPEN || echo BLOCKED'
oc -n $NS exec $POD -c istio-proxy -- openssl s_client -connect DBHOST:PORT -servername DBHOST -brief </dev/null

Porte: Oracle 1521/1526/1546 · PostgreSQL 5432 · MySQL/MariaDB 3306 · MSSQL 1433 · MongoDB 27017 · Redis 6379 e 16379 (cluster bus) · Kafka 9092/9093.

Oracle RAC e Exadata — il doppio hop. Il client si collega allo SCAN listener; questo fa load balancing e risponde con un redirect verso il VIP del nodo scelto, sulla porta del local listener. Il secondo indirizzo non compare mai nella connect string né nel codice applicativo, e quasi sempre il firewall è aperto solo sulla porta SCAN.

Terminal window
# 1) porta SCAN — di solito passa
oc -n $NS exec $POD -c $C -- bash -c 'timeout 5 bash -c "</dev/tcp/scan-host/1546" && echo OPEN || echo BLOCKED'
# 2) VIP del nodo, porta del local listener — è qui che si rompe
oc -n $NS exec $POD -c $C -- bash -c 'timeout 5 bash -c "</dev/tcp/vip-host/1521" && echo OPEN || echo BLOCKED'

Firma dell’errore lato applicativo: java.io.IOException: Operation timed out, socket connect lapse <ms> con un host diverso da quello della connect string. Risoluzione: aprire il firewall verso tutti i VIP dei nodi RAC sulla porta del local listener, oppure far allineare LOCAL_LISTENER alla porta già aperta. Da chiedere al DBA per la conferma:

show parameter local_listener;
show parameter remote_listener;

In mesh, per qualsiasi database: porta del Service nominata tcp-* e, se il DB è esterno alla mesh, DestinationRule con tls.mode: DISABLE. Il connection pool applicativo, inoltre, va tenuto d’occhio insieme a holdApplicationUntilProxyStarts: i pool aprono connessioni all’avvio, cioè nel momento peggiore.

Backend HTTP/HTTPS

Ordine: quale URL viene chiamato davvero → DNS → TCP → TLS → tempi.

Terminal window
oc -n $NS get cm <app>-endpoints-cm -o yaml | grep -iE '<nome-api>|endpoint_0'
oc -n $NS exec $POD -c $C -- getent hosts BACKEND
oc -n $NS exec $POD -c $C -- curl -sSvko /dev/null --max-time 20 \
-w 'code=%{http_code} conn=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer}\n' \
https://BACKEND/PATH

Se il backend è interno al cluster, prima degli strumenti di rete verifica che il Service abbia endpoint:

Terminal window
oc -n <ns-backend> get endpointslice -o wide
oc -n <ns-backend> get pod -o wide

Storage

Distinzione decisiva: NFS, iSCSI e FC sono montati dal kubelet, non dal pod. Se il PVC non monta, il pod non esiste ancora e testare “dal pod” è impossibile: il test va fatto dal nodo.

Terminal window
NODE=<nodo>
oc debug node/$NODE -- chroot /host bash -c '
timeout 5 bash -c "</dev/tcp/NFSHOST/2049" && echo NFS-OPEN || echo NFS-BLOCKED
timeout 5 bash -c "</dev/tcp/NFSHOST/111" && echo RPC-OPEN || echo RPC-BLOCKED
showmount -e NFSHOST 2>/dev/null | head
mount | grep -i nfs | head'

La 2049 è NFS vero e proprio, la 111 è il portmapper/rpcbind necessario a NFSv3. showmount -e elenca gli export e le ACL di host: se la porta è aperta ma l’export non è visibile, è un problema di autorizzazione lato filer, non di rete.

Terminal window
oc -n $NS describe pod $POD | sed -n '/Events/,$p'
oc -n $NS get pvc
oc get pv <pv> -o yaml | grep -A10 'nfs:\|csi:'

Gli eventi del pod distinguono con precisione un FailedMount per timeout di rete da uno per permessi o per StorageClass errata.

S3, ODF RGW e MinIO invece si testano dal pod, perché è l’applicazione a parlarci direttamente:

Terminal window
oc -n $NS exec $POD -c $C -- bash -c 'timeout 5 bash -c "</dev/tcp/S3HOST/443" && echo OPEN || echo BLOCKED'
oc -n $NS exec $POD -c istio-proxy -- curl -sSko /dev/null -w '%{http_code}\n' --max-time 10 https://S3HOST/
oc -n openshift-storage get route,svc | grep -i rgw

Un 403 o un 404 sono buone notizie: significa che il servizio S3 ha risposto e il problema è di credenziali o di bucket. Attenzione al path-style contro il virtual-host-style: con quest’ultimo bucket.s3host deve risolvere in DNS e, in mesh, serve una ServiceEntry con wildcard.


10. Matrice sintomo → causa

SintomoCausa più probabileProssima verifica
conn=0, timeout pieno, ICMP mutofirewall o destinazione inesistenteconfronto multi-sorgente, log FW sulla quintupla
Connection refused immediatohost vivo, servizio giù o porta erratateam applicativo
OK da istio-proxy, KO dall’appServiceEntry / REGISTRY_ONLY / mTLSpilot-agent request GET clusters
Fallisce solo al primo avviorace sidecarholdApplicationUntilProxyStarts
Errore su un host mai configuratoredirect lato server (SCAN Oracle, F5, 302)aprire il secondo hop
KO dal pod, OK dal nodoapertura mancante sull’EgressIPovn-nbctl lr-nat-list + richiesta FW
504 a esattamente 15stimeout di route EnvoyVirtualService.timeout
Intermittente, a periodi alterniun membro del pool o un EgressIP non raggiungibiletestare ogni IP singolarmente
DNS risolve ma nessuna rispostarecord pubblicato per l’ambiente sbagliatoconfronto con l’endpoint di produzione
Il Service risolve ma va in timeoutEndpointSlice vuotoselector del Service, stato dei pod backend

11. Evidenze per l’handoff a un team di rete

Un ticket viene accettato solo se contiene tutte e quattro le righe:

DST <ip>:<porta> (<fqdn>) -> esito
SRC <EgressIP del ns> -> esito [path reale dell'applicazione]
SRC <IP nodo> / bastion -> esito [conferma multi-sorgente]
CTRL <altra destinazione, stesso sorgente> -> OK [l'uscita dal cluster funziona]

Più la riga di configurazione: presenza o assenza di NetworkPolicy ed EgressFirewall, e la nota che la mesh è stata esclusa con il test da istio-proxy.

Alla rete non si chiede “aprite”: si chiede il log del firewall sulla quintupla. È l’unico dato che distingue un deny esplicito da un permit senza risposta del destinatario, cioè che decide se il ticket resta al team di rete o passa a quello applicativo.