DA MASER A MONFUMO | MOTO GUZZI V85TT | PURE SOUND POV 4K

🏍️ Il mio nuovo canale YouTube: giri in moto in POV, solo audio, tra le Dolomiti in 4K. Niente musica, niente parole — solo il motore e le Alpi. Vieni a fare un giro!

Iscriviti

Il networking di Kubernetes spesso sembra un meccanismo complesso. Per capire come funziona, dobbiamo prima guardare i suoi componenti più elementari: i principi fondamentali di TCP/IP e lo stack di networking Linux. Agli albori dell'informatica, le reti erano in gran parte proprietarie, il che significava che hardware e software di un vendor non potevano comunicare con quelli di un altro. Questo "far west" del networking portò allo sviluppo di framework standardizzati per garantire l'interoperabilità. Il più famoso di questi è il modello OSI (Open Systems Interconnection), un modello concettuale a sette livelli che standardizza le funzioni di un sistema di rete. Sebbene sia un ottimo strumento teorico, il modello che ha prevalso in pratica è il più snello modello TCP/IP.

Il modello TCP/IP, che alimenta l'internet moderno, è composto da quattro livelli principali:

  • Link Layer: Questo è il livello più basso, responsabile della trasmissione fisica dei dati sul mezzo di rete, come Ethernet o Wi-Fi. Si occupa degli indirizzi MAC e dell'hardware fisico delle schede di rete.
  • Internet Layer: Questo livello è responsabile dell'indirizzamento logico e dell'instradamento. L'Internet Protocol (IP) opera qui, assegnando indirizzi IP univoci agli host e determinando il percorso migliore per inviare i pacchetti attraverso le reti.
  • Transport Layer: Questo livello garantisce che i dati vengano consegnati tra le applicazioni. I due protocolli più comuni qui sono TCP (Transmission Control Protocol), che fornisce una consegna affidabile e orientata alla connessione (garantendo che i pacchetti arrivino in ordine e senza errori), e UDP (User Datagram Protocol), che offre un servizio più veloce e senza connessione senza tali garanzie.
  • Application Layer: In cima allo stack, qui operano le applicazioni rivolte all'utente come browser web (HTTP), client di posta elettronica (SMTP) e DNS. Queste applicazioni creano e consumano i dati che vengono poi passati giù nello stack per essere inviati attraverso la rete.

Capire questo approccio a livelli è fondamentale, poiché ogni pacchetto di rete in un cluster Kubernetes aderisce a questo modello. Esploreremo questo intero ecosistema in tre parti: le tecnologie fondamentali che rendono tutto possibile, il modello Kubernetes di base, e infine argomenti avanzati e guide pratiche.

Parte I: Comprendere le Fondamenta

Networking Linux

Prima che venga avviato un singolo container, l'intera "realtà" di networking è definita all'interno del kernel Linux. Capire come Linux gestisce pacchetti, interfacce e regole è fondamentale per diagnosticare problemi a qualsiasi livello dello stack. Questi fondamentali sono i mattoni sia per i runtime di container che per Kubernetes.

Il costrutto di networking più elementare in Linux è l'interfaccia di rete. Si tratta di una rappresentazione software di un punto di connessione a una rete, che può essere un dispositivo fisico come una scheda Ethernet (eth0) o uno puramente virtuale, come l'interfaccia di loopback (lo). Un'interfaccia virtuale speciale e di importanza critica è la bridge interface. Un Linux bridge funziona come uno switch Layer 2 virtuale, capace di connettere tra loro più interfacce di rete. Quando un pacchetto proveniente da un'interfaccia connessa arriva al bridge, esso ispeziona l'indirizzo MAC di destinazione del pacchetto e lo inoltra all'interfaccia corretta sullo stesso host. Questo è il meccanismo fondamentale che consente ai container sullo stesso host di comunicare tra loro.

Quando un pacchetto arriva a un'interfaccia, viene passato al kernel per un percorso governato dal framework Netfilter. Netfilter fornisce una serie di "hook" nel percorso di elaborazione della rete del kernel dove altri programmi possono registrarsi per ispezionare e manipolare il pacchetto. Lo strumento più noto per gestire questi hook è iptables, il classico tool firewall per userspace. Usando iptables, potete creare regole che vengono verificate su ogni pacchetto, decidendo se fare ACCEPT, DROP, o modificarlo (per esempio, usando la Network Address Translation — NAT). Insieme a Netfilter lavora conntrack, un sistema che traccia tutte le connessioni di rete. Questo consente al kernel di riconoscere i pacchetti che fanno parte di una connessione esistente e stabilita, che è la base per i firewall stateful.

Mentre il kernel ha una tabella di routing principale, diverse tecnologie si sono evolute per gestire flussi di traffico più complessi. Dopo iptables, il passo successivo fu IPVS (IP Virtual Server). Costruito per il load balancing ad alte prestazioni, IPVS usa hash table in-kernel più efficienti invece delle liste di regole sequenziali di iptables, rendendolo una scelta superiore per ambienti con un gran numero di servizi.

L'ultima evoluzione, eBPF (extended Berkeley Packet Filter), cambia fondamentalmente questa dinamica rendendo il kernel Linux stesso programmabile. Mentre strumenti tradizionali come iptables sono potenti, hanno limitazioni intrinseche in ambienti su larga scala e dinamici. Iptables si affida a lunghe catene sequenziali di regole; man mano che il numero di servizi e policy cresce, attraversare queste catene per ogni pacchetto può introdurre un overhead significativo sulla CPU e aumentare la latenza. eBPF evita questo consentendo a piccoli programmi altamente efficienti e sandboxed di essere allegati direttamente a hook specifici all'interno del kernel — per esempio, nel preciso momento in cui un driver di rete riceve un pacchetto. L'architettura eBPF garantisce la sicurezza attraverso un rigoroso verificatore che analizza ogni programma prima che venga caricato, e un compilatore Just-In-Time (JIT) converte il bytecode eBPF in codice macchina nativo per la massima velocità di esecuzione. Questa programmabilità si estende oltre il networking; allegandosi a tracepoint e system call, eBPF può alimentare strumenti avanzati di sicurezza e osservabilità, rendendolo una tecnologia fondamentale per la prossima generazione di infrastrutture cloud-native.

Per navigare e fare troubleshooting in questo ambiente complesso, Linux fornisce una suite di indispensabili strumenti da riga di comando.

  • ping e traceroute servono per verificare la raggiungibilità di base degli host e mappare il percorso che prendono i pacchetti.
  • dig viene usato per interrogare i server DNS.
  • telnet e netcat (nc) vengono usati per verificare se una specifica porta è aperta e in ascolto.
  • nmap è un potente scanner di rete per scoprire host e servizi.
  • netstat e il più moderno ss mostrano le connessioni di rete locali e le tabelle di routing.
  • curl è il coltellino svizzero per effettuare richieste HTTP/S.
  • openssl può essere usato per eseguire manualmente un TLS handshake e fare debug di problemi complessi con i certificati SSL.

Networking dei Container

Prima di poter capire come i container comunicano tra loro, abbiamo bisogno di una solida comprensione di cosa sia un container e la magia del kernel che rende possibile il suo isolamento. A differenza di un hypervisor, che crea una macchina virtuale (VM) completa con il proprio sistema operativo guest, un container è un costrutto molto più leggero. È essenzialmente solo un processo sandboxed, o gruppo di processi, che gira direttamente sul kernel Linux dell'host. Questo approccio evita l'overhead del boot di un OS separato, rendendo i container incredibilmente veloci da creare e efficienti nelle risorse.

Questo potente isolamento viene ottenuto principalmente attraverso due funzionalità del kernel Linux che fungono da muri digitali: control group (cgroup) e namespace. I cgroup sono i contabili delle risorse; controllano quanta CPU, memoria e I/O un container è autorizzato a consumare. I namespace sono gli architetti dell'isolamento; partizionano le risorse del kernel in modo che un container abbia la propria visione privata del sistema. Più importante per il nostro argomento, il network namespace fornisce a un container uno stack di rete completamente nuovo: il proprio set privato di interfacce di rete, indirizzi IP, tabelle di routing e regole del firewall.

Con questa base, possiamo guardare implementazioni pratiche come il modello di networking Docker. Quando si installa Docker, tipicamente crea un bridge virtuale sull'host chiamato docker0. Quando si avvia un container, Docker crea una coppia di interfacce Ethernet virtuali (veth pair). Un'estremità viene posizionata all'interno del nuovo network namespace del container (come eth0), mentre l'altra viene collegata al bridge docker0. Questo consente ai container sullo stesso host di comunicare. Per la comunicazione container-container su host separati, si usa l'overlay networking. Una rete overlay incapsula il traffico del container in un pacchetto che la rete host sa come instradare (usando un protocollo come VXLAN), facendo sembrare che tutti i container siano sulla stessa rete flat.

Per evitare che ogni runtime di container dovesse reinventare questa ruota, la community ha sviluppato la specifica Container Network Interface (CNI). CNI è uno standard semplice che disaccoppia il runtime di container (come containerd o CRI-O) dall'implementazione di networking. Il runtime è solo responsabile della creazione del network namespace e poi chiama un plugin CNI per fare il lavoro effettivo di configurazione della rete, come creare interfacce e assegnare indirizzi IP. Questa architettura pluggabile è una pietra angolare del networking Kubernetes.

Parte II: Il Modello Kubernetes di Base

Networking Kubernetes

Kubernetes porta il networking dei container al livello successivo stabilendo un modello di networking prescrittivo, ma flessibile. Questo modello è costruito su alcuni principi fondamentali: ogni Pod (un gruppo di uno o più container) ottiene il proprio indirizzo IP univoco in tutto il cluster, e tutti i Pod possono comunicare direttamente con tutti gli altri Pod senza necessità di Network Address Translation (NAT). Questo crea uno spazio di rete pulito e flat che si comporta molto come una LAN tradizionale.

Per raggiungere questo, lo spazio degli indirizzi IP del cluster è partizionato. Il kube-controller-manager è responsabile dell'assegnazione di un intervallo di indirizzi IP univoco, chiamato blocco podCIDR, a ogni Node nel cluster. Su ogni Node, il kubelet agisce come agente Kubernetes locale. Quando un nuovo Pod viene schedulato, il kubelet chiama il plugin CNI configurato per collegare il Pod alla rete del cluster. La potenza di questo modello risiede nella sua pluggabilità. Potete scegliere tra dozzine di plugin CNI popolari: Flannel è una scelta semplice che crea una rete overlay; Calico usa il protocollo di routing BGP per un networking non-overlay ad alte prestazioni; Cilium sfrutta eBPF per un networking, osservabilità e sicurezza altamente efficienti.

Un componente chiave per la service discovery è kube-proxy, un daemon che gira su ogni node. Il suo compito è implementare l'astrazione Service di Kubernetes. Quando si crea un Service, ottiene un indirizzo IP virtuale stabile (ClusterIP). Il compito di kube-proxy è garantire che qualsiasi traffico inviato a questo ClusterIP venga intercettato e bilanciato di carico verso uno dei Pod backend sani. Opera in diverse modalità, con la predefinita che è la modalità iptables. Per deployment su larga scala, la modalità ipvs è spesso preferita in quanto usa hash table più efficienti per il load balancing.

Sebbene il modello fornisca comunicazione aperta per impostazione predefinita, NetworkPolicy consente di definire regole firewall per i Pod a livello di indirizzo IP o porta. Queste policy sono applicate dal plugin CNI, consentendo di creare regole di ingress e egress granulari. Infine, nessuna rete moderna è completa senza DNS. Kubernetes fornisce un servizio DNS robusto e consapevole del cluster (tipicamente CoreDNS) che consente ai Pod di scoprirsi a vicenda usando nomi prevedibili invece di indirizzi IP effimeri. Kubernetes supporta anche completamente il networking dual-stack IPv4/IPv6, consentendo a Pod e Service di avere allocati entrambi i tipi di indirizzo senza problemi.

Astrazioni di Networking Kubernetes: Service, Ingress e Mesh

Sebbene i Pod abbiano IP univoci, questi IP sono effimeri. Per costruire applicazioni affidabili, Kubernetes fornisce diverse potenti astrazioni di networking che si collocano sopra questa rete Pod sottostante.

L'astrazione principale è il Service, che fornisce un singolo endpoint stabile per un gruppo di Pod. Kubernetes traccia gli IP dei Pod che supportano un Service usando EndpointSlice (un'evoluzione scalabile dell'oggetto Endpoints originale). Ci sono diversi tipi di Service:

  • ClusterIP: Il tipo predefinito, espone il Service su un indirizzo IP interno solo al cluster. È lo standard per la comunicazione interna servizio-servizio.
  • NodePort: Espone il Service su una porta statica sull'indirizzo IP di ogni Node, rendendolo accessibile dall'esterno del cluster per sviluppo o demo.
  • LoadBalancer: Il modo standard per esporre un servizio a Internet. Esegue il provisioning di un load balancer cloud che dirige il traffico esterno verso il NodePort del Service.
  • Headless: Impostando clusterIP: None, non viene creato alcun IP virtuale. Le query DNS per il servizio restituiscono gli IP di tutti i Pod di supporto, utile per applicazioni stateful dove si vuole connettersi a un'istanza specifica.
  • ExternalName: Mappa un servizio a un nome DNS esterno creando un record CNAME all'interno del DNS del cluster.

Per applicazioni stateful come i database, la risorsa workload StatefulSet fornisce ai Pod identificatori di rete stabili e univoci (es. db-0.my-db-service) che persistono anche se il pod viene rischedulato.

I Service operano al livello 4 (TCP/UDP). Per gestire l'accesso esterno al livello 7 (HTTP/HTTPS), Kubernetes fornisce Ingress. Una risorsa Ingress vi consente di definire regole per instradare il traffico HTTP esterno verso i Service interni in base al nome dell'host o al percorso URL. Un Ingress controller è il motore che lo fa funzionare — un proxy in esecuzione nel cluster che osserva le risorse Ingress e si configura per implementare le regole definite.

Infine, per le architetture a microservizi più esigenti, un service mesh come Istio o Linkerd offre un livello di astrazione ancora più elevato. Un service mesh funziona iniettando un proxy "sidecar" leggero accanto a ogni container applicativo. Questi proxy formano un mesh che fornisce funzionalità avanzate come mTLS per la sicurezza, gestione sofisticata del traffico (canary release, A/B testing) e osservabilità profonda, tutto senza modificare il codice dell'applicazione.

Parte III: Argomenti Avanzati e Applicazioni Pratiche

Sicurezza di Rete Avanzata in Kubernetes

Una postura di sicurezza robusta in Kubernetes va ben oltre una singola NetworkPolicy. Richiede una strategia di difesa in profondità che protegga l'intero sistema.

  • Proteggere il Control Plane: Questo è fondamentale. Il server API di Kubernetes è il cervello del cluster, e un accesso non autorizzato è catastrofico. Le best practice includono la disabilitazione dell'accesso anonimo, l'uso di autenticazione forte e l'applicazione del principio del minimo privilegio con regole RBAC (Role-Based Access Control) granulari.
  • Sicurezza a Livello di Workload: La sicurezza deve estendersi ai pod stessi. Pod Security Admission (PSA) consente di applicare standard di sicurezza a livello di namespace. Usando un securityContext nella specifica di un pod, potete prevenire operazioni pericolose come girare come root o disabilitare l'escalation dei privilegi, il che riduce drasticamente il raggio d'azione se un container viene compromesso.
  • Comunicazione Zero-Trust con mTLS: In una rete zero-trust, la fiducia non viene mai assunta. Un service mesh può applicare il mutual TLS (mTLS) a livello di cluster, cifrando automaticamente tutto il traffico servizio-servizio e verificando le identità dei workload tramite certificati. Questo avviene automaticamente, proteggendo tutte le comunicazioni servizio-servizio senza richiedere alcuna modifica al codice dell'applicazione.
  • Sicurezza Runtime e Rilevamento delle Minacce: L'ultimo livello è il rilevamento delle minacce in tempo reale. Gli strumenti di runtime security come Falco usano eBPF per monitorare le system call del kernel. Possono rilevare e segnalare comportamenti di rete anomali — come un pod che stabilisce inaspettatamente una connessione in uscita verso un IP sconosciuto — che potrebbe indicare una violazione della sicurezza.

Il Gateway API: L'Evoluzione di Ingress

Con la crescita dell'adozione di Kubernetes, i limiti dell'API Ingress originale sono diventati evidenti. È sottospecificata, portando a implementazioni incoerenti, e manca della capacità espressiva necessaria per un routing del traffico complesso. Per risolvere questo, la community Kubernetes ha sviluppato il Gateway API, un successore moderno, standardizzato e altamente estensibile che fornisce maggiore flessibilità, sicurezza e separazione delle responsabilità.

La potenza del Gateway API risiede nel suo design orientato ai ruoli, che disaccoppia le responsabilità:

  • Infrastructure Provider: Definisce le risorse GatewayClass, che sono template per diversi tipi di load balancer (es. una classe AWS ALB).
  • Cluster Operator: Crea risorse Gateway, che sono istanziazioni specifiche di un GatewayClass, richiedendo un endpoint di load balancing concreto.
  • Application Developer: Gestisce le risorse Route (come HTTPRoute), definendo la logica di routing da un Gateway ai propri servizi.

Questa separazione è un vantaggio significativo. Uno sviluppatore di applicazioni può gestire in sicurezza le regole di routing per il proprio servizio senza poter modificare il gateway condiviso. Il Gateway API introduce anche funzionalità potenti come il cross-namespace routing sicuro e standardizza i pattern avanzati di gestione del traffico come il traffic splitting e il routing basato su header, fornendo una base robusta per il moderno networking Kubernetes.

Networking Multi-Cluster e Federazione

Man mano che le organizzazioni scalano, spesso adottano un'architettura multi-cluster per l'alta disponibilità, la distribuzione geografica o l'isolamento dei workload. Questo introduce la sfida di abilitare i servizi attraverso questi confini di cluster a comunicare in modo sicuro e affidabile.

  • Service Mesh Federation: Questo è un approccio popolare in cui i service mesh come Istio o Linkerd sono configurati per estendersi su più cluster. Stabilendo una root of trust condivisa, creano un service mesh unificato in cui un pod nel Cluster A può scoprire e connettersi in modo sicuro a un servizio nel Cluster B come se fosse locale.
  • Connettività a Livello di Rete: Strumenti come Submariner operano a un livello più basso (L3/L4) per creare una rete flat tra cluster. Stabiliscono tunnel cifrati tra nodi gateway in ogni cluster, unendo effettivamente le reti Pod e Service in modo che qualsiasi pod possa raggiungere qualsiasi altro pod direttamente tramite il suo IP.
  • Il Gateway API: Il Gateway API è stato progettato anche con casi d'uso multi-cluster in mente. Il suo modello gerarchico consente agli amministratori di piattaforma di eseguire il provisioning di risorse Gateway che possono essere implementate da controller capaci di instradare il traffico attraverso i confini del cluster, fornendo una base standardizzata per future soluzioni di ingress multi-cluster.

Scenari Pratici di Troubleshooting

Anche in un cluster ben configurato, i problemi di rete sono una realtà. Un container potrebbe non avviarsi, o un servizio potrebbe diventare irraggiungibile. Quando ciò accade, un approccio sistematico e a livelli al debugging è il modo più rapido per trovare la causa principale. Di seguito sono riportate guide passo-passo per due dei più comuni scenari di fallimento che è probabile incontrare.

Sintomo: La Connettività Pod-a-Pod Fallisce

Questo è uno dei problemi più fondamentali: un pod è in esecuzione, ma non riesce a comunicare con un altro pod sulla rete. Il fallimento potrebbe essere nel CNI, in una NetworkPolicy, o nell'applicazione stessa. Ecco come tracciare il problema:

  • Verificare lo stato e la posizione del Pod: Eseguire kubectl get pods -o wide. Entrambi i pod sono in stato Running? Sono sullo stesso node o su node diversi?
  • Esaminare eventi e log: Usare kubectl describe pod <pod-name> per cercare eventi recenti come FailedCreatePodSandBox. Controllare i log dell'applicazione con kubectl logs <pod-name> per escludere errori a livello applicativo.
  • Verificare la NetworkPolicy: Eseguire kubectl get networkpolicy -n <namespace>. Se sono presenti policy, ispezionarle per assicurarsi che non stiano bloccando il traffico involontariamente.
  • Isolare con un container di debug: Avviare un pod temporaneo con strumenti di networking: kubectl run -it --rm --image=nicolaka/netshoot network-debug -- /bin/bash. Dall'interno di questo pod di debug, usare ping o curl per cercare di raggiungere l'indirizzo IP del pod di destinazione. Se funziona, il livello di rete è probabilmente a posto.

Sintomo: La Service Discovery Fallisce

Un problema comune e spesso frustrante è quando i pod riescono a connettersi tra loro tramite indirizzo IP, ma la service discovery fallisce quando usano un nome di servizio. Questo punta quasi sempre a un problema con il servizio DNS del cluster, tipicamente CoreDNS.

  • Verificare lo stato di CoreDNS: Eseguire kubectl get pods -n kube-system -l k8s-app=kube-dns per vedere se i pod CoreDNS sono in esecuzione. Controllare i loro log per eventuali errori.
  • Ispezionare il resolv.conf del Pod: Exec in un pod problematico (kubectl exec <pod-name> -- cat /etc/resolv.conf) per assicurarsi che il nameserver punti all'IP del servizio kube-dns.
  • Testare la risoluzione direttamente: Da un container di debug, usare nslookup <service-name> per testare la risoluzione interna, poi nslookup google.com per testare la risoluzione esterna. Questo permetterà di individuare la fonte del fallimento.

Conclusione

Se siete arrivati fin qui, avete compiuto l'intero percorso — da un singolo pacchetto che colpisce una scheda di rete fino a un service mesh che gestisce il traffico su una flotta globale. Il concetto chiave è che il networking di Kubernetes non è una magia incomprensibile; è un potente stack di astrazioni costruite sopra strumenti familiari e collaudati. Tutto inizia con le solide fondamenta delle capacità di networking del kernel Linux. I container usano poi primitivi come i namespace per ottenere la propria porzione isolata di quello stack. Kubernetes semplicemente orchestra questo concetto su scala massiva, dando a ogni pod un indirizzo IP tramite CNI e fornendo endpoint stabili con i Service. Quando si capisce come questi livelli si connettono — come una richiesta fluisce attraverso un Service, viene gestita da kube-proxy, e raggiunge infine un pod su una rete gestita da CNI — non si usa più solo una scatola nera. Si è equipaggiati per diagnosticare, fare troubleshooting e costruire sistemi più resilienti. Speriamo che questo approfondimento vi aiuti a fare esattamente questo!