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 corretti1. DNS → il nome risolve? in quale IP?2. Connettività L3/L4 → il SYN arriva? torna qualcosa?3. TLS → handshake, catena, SNI4. HTTP applicativo → codici, tempi, timeout5. Service Mesh → il sidecar interferisce?6. Egress → con quale IP sorgente esco?7. Policy di rete → NetworkPolicy / EgressFirewall8. Cattura pacchetti → la prova definitivaRegola 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
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.
oc -n $NS get pod $POD -o jsonpath='{.spec.nodeName}{"\n"}'oc get ns $NS --show-labelsIl 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
| Punto | Comando base | netns | Passa da Envoy? | IP sorgente | Cosa dimostra |
|---|---|---|---|---|---|
| Container applicativo | oc exec ... -c $C | del pod | sì | EgressIP se presente | il path reale, end-to-end |
Container istio-proxy | oc exec ... -c istio-proxy | del pod | no (uid 1337) | EgressIP se presente | rete senza mesh |
| Ephemeral container | oc debug $POD --target=$C | del pod | sì | EgressIP se presente | come l’app, ma con tool installati |
| Pod nuovo nel ns | oc run | propria | sì (injection) | EgressIP se presente | il namespace, non il pod |
| Pod nuovo senza sidecar | oc run --annotations sidecar.istio.io/inject=false | propria | no | EgressIP se presente | egress senza mesh |
| Nodo | oc debug node/<node> | host | no | IP del nodo | solo 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
IMG=registry.redhat.io/rhel9/support-toolsContiene 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:
oc -n $NS debug $POD --image=$IMG --target=$C -it -- bashb) Pod nuovo nel namespace — eredita EgressIP e injection, ma non le annotation del deployment applicativo:
oc -n $NS run netshoot --image=$IMG --restart=Never -it --rm -- bashc) Pod nuovo senza sidecar — isola la mesh mantenendo l’EgressIP:
oc -n $NS run netshoot --image=$IMG --restart=Never -it --rm \ --annotations sidecar.istio.io/inject=false -- bashSu 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:
pingfunziona: usa un ICMP datagram socket, non richiedeNET_RAWtraceroute -T,tcpdump,hping3falliscono conOperation not permitted: richiedono socket raw
Se ti servono davvero, serve una ServiceAccount con SCC privilegiata:
oc -n $NS create sa netdebugoc 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:
oc adm policy remove-scc-from-user privileged -z netdebug -n $NSoc -n $NS delete sa netdebug1. DNS
Sempre il primo livello: metà dei “problemi di rete” sono nomi che risolvono in ambiente sbagliato o non risolvono affatto.
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.
oc -n $NS exec $POD -c $C -- cat /etc/resolv.confDeve 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:
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:
oc -n $NS exec $POD -c $C -- getent hosts <svc>.<ns>.svc.cluster.localoc -n <ns-backend> get endpointslice -o wideUn 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:
oc -n openshift-dns logs -l dns.operator.openshift.io/daemonset-dns=default --tail=50 | grep -i SERVFAIL2. 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
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:
oc -n $NS exec $POD -c $C -- bash -c 'exec 3<>/dev/tcp/HOST/PORT' 2>&1| Esito | Significato | Dove guardare |
|---|---|---|
Connection refused immediato | il pacchetto è arrivato, nessun listener sulla porta | host vivo, servizio giù o porta sbagliata |
| attesa fino al timeout, nessun messaggio | drop silenzioso | firewall, routing, host inesistente |
No route to host | ICMP unreachable dal gateway | routing o VLAN |
Name or service not known | non sei arrivato al livello 2 | torna 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
oc -n $NS exec $POD -c istio-proxy -- nc -zv -w5 HOST PORTnc -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.
oc -n $NS exec $POD -c istio-proxy -- ping -c3 -W2 HOSTping 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.
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:
# 1. path reale dell'applicazioneoc -n $NS exec $POD -c $C -- bash -c 'timeout 5 bash -c "</dev/tcp/HOST/PORT" && echo OPEN || echo BLOCKED'
# 2. rete dei nodiNODE=$(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. bastiontimeout 5 bash -c '</dev/tcp/HOST/PORT' && echo OPEN || echo BLOCKED
# 4. CONTROPROVA: altra destinazione dallo stesso sorgente del punto 1oc -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.
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:
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 -datesTruststore del container applicativo, che è cosa diversa da quello del sidecar:
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:
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
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/PATHFlag 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.
| Lettura | Diagnosi |
|---|---|
conn=0.000000 | handshake TCP mai completato: è rete, il resto è rumore |
conn valorizzato, tls=0 | TCP ok, TLS fallito: certificati, SNI, cipher, mTLS |
tls ok, ttfb alto | il backend è lento: è tuning di timeout, non un guasto |
code=000 | nessuna risposta HTTP: la richiesta non è mai arrivata a un server HTTP |
code=504 a esattamente 15s | timeout 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:
oc -n $NS get cm | grep -i endpointoc -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:
# 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 meshoc -n $NS exec $POD -c $C -- \ curl -sSko /dev/null --max-time 15 -w 'code=%{http_code} conn=%{time_connect}\n' https://HOST/PATHistio-proxy | container app | Verdetto |
|---|---|---|
| OK | KO | è la mesh: ServiceEntry, outboundTrafficPolicy, mTLS, timeout |
| KO | KO | è rete o destinazione: mesh esclusa, non perderci tempo |
| KO | OK | anomalia di routing o DNS specifica del container, molto raro |
Introspezione di Envoy
oc -n $NS exec $POD -c istio-proxy -- pilot-agent request GET clusters | grep -i HOSTpilot-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.
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.jsonI 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.
oc -n $NS logs $POD -c istio-proxy --since=1h | grep -i HOSTAttenzione: 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:
| Flag | Significato |
|---|---|
NR | nessuna route: ServiceEntry mancante, host fuori registry |
UF | upstream connection failure: rete o firewall verso il backend |
UO | upstream overflow: circuit breaker della DestinationRule |
UT | upstream timeout: timeout di route (default 15s) o backend lento |
URX | limite di retry superato |
UAEX | rifiutato da filtro esterno, tipicamente AuthorizationPolicy |
DC | downstream disconnect: è il client che ha mollato per primo |
Le cinque cause ricorrenti
oc -n istio-system get smcp -o jsonpath='{.items[0].spec.proxy.networking.trafficControl.outbound.policy}{"\n"}'oc get serviceentry -Aoc get destinationrule -A -o yaml | grep -B10 'mode:'oc get peerauthentication -Aoc get virtualservice -A -o yaml | grep -B5 -A2 'timeout:'REGISTRY_ONLYsenza ServiceEntry — tutto ciò che non è dichiarato viene scartato. Sintomo indistinguibile da un timeout di rete, flagNR.- mTLS
STRICTverso un carico fuori mesh — il sidecar presenta un certificato che il backend non capisce. Serve una DestinationRule contls.mode: DISABLEper quell’host. - Port naming — per traffico TCP non HTTP (tutti i database) la porta del Service deve chiamarsi
tcp-<nome>o avereappProtocol: tcp. Con un nome generico Envoy tenta il parsing HTTP e la connessione muore in modo opaco. - 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}'
- Timeout di route — 15 secondi di default. Un
504a 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.
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[*].nodeLa 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:
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
oc -n $NS get networkpolicyoc -n $NS get egressfirewall -o yamlDue 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:
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:
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
ovnkube-trace -src-namespace $NS -src $POD \ -dst-ip <dst-ip> -tcp -dst-port <port> -loglevel 5Simula 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.
oc -n $NS exec $POD -c $C -- getent hosts DBHOSToc -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/nullPorte: 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.
# 1) porta SCAN — di solito passaoc -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 rompeoc -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.
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 BACKENDoc -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/PATHSe il backend è interno al cluster, prima degli strumenti di rete verifica che il Service abbia endpoint:
oc -n <ns-backend> get endpointslice -o wideoc -n <ns-backend> get pod -o wideStorage
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.
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.
oc -n $NS describe pod $POD | sed -n '/Events/,$p'oc -n $NS get pvcoc 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:
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 rgwUn 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
| Sintomo | Causa più probabile | Prossima verifica |
|---|---|---|
conn=0, timeout pieno, ICMP muto | firewall o destinazione inesistente | confronto multi-sorgente, log FW sulla quintupla |
Connection refused immediato | host vivo, servizio giù o porta errata | team applicativo |
OK da istio-proxy, KO dall’app | ServiceEntry / REGISTRY_ONLY / mTLS | pilot-agent request GET clusters |
| Fallisce solo al primo avvio | race sidecar | holdApplicationUntilProxyStarts |
| Errore su un host mai configurato | redirect lato server (SCAN Oracle, F5, 302) | aprire il secondo hop |
| KO dal pod, OK dal nodo | apertura mancante sull’EgressIP | ovn-nbctl lr-nat-list + richiesta FW |
504 a esattamente 15s | timeout di route Envoy | VirtualService.timeout |
| Intermittente, a periodi alterni | un membro del pool o un EgressIP non raggiungibile | testare ogni IP singolarmente |
| DNS risolve ma nessuna risposta | record pubblicato per l’ambiente sbagliato | confronto con l’endpoint di produzione |
| Il Service risolve ma va in timeout | EndpointSlice vuoto | selector 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>) -> esitoSRC <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.