Networking in Kubernetes
Kubernetes è una piattaforma di orchestrazione di container che gestisce il ciclo di vita delle applicazioni distribuite. Uno degli aspetti più complessi e fondamentali di Kubernetes è il suo modello di rete. Il networking in Kubernetes si occupa di come i Pod comunicano tra loro, come i servizi vengono esposti all'esterno del cluster, come viene gestita la risoluzione DNS interna e come il traffico viene instradato attraverso l'infrastruttura. Comprendere questi meccanismi è essenziale per progettare, distribuire e operare applicazioni in un cluster Kubernetes in modo affidabile ed efficiente.
Il Modello di Rete di Kubernetes
Kubernetes adotta un modello di rete piatto e non sovrapposto, basato su tre principi fondamentali. Il primo stabilisce che ogni Pod riceve un indirizzo IP univoco all'interno del cluster, eliminando la necessità di mappature di porta tra container. Il secondo afferma che tutti i Pod possono comunicare con tutti gli altri Pod senza NAT (Network Address Translation). Il terzo principio garantisce che i nodi possano comunicare con tutti i Pod, e viceversa, senza NAT.
Questo modello semplifica notevolmente il ragionamento sulla rete rispetto ai tradizionali ambienti virtualizzati, ma richiede una solida implementazione a livello di plugin di rete, realizzata tramite la CNI (Container Network Interface).
Container Network Interface (CNI)
La CNI è la specifica e la libreria che Kubernetes utilizza per configurare le interfacce di rete dei container. Quando un Pod viene creato, il kubelet invoca il plugin CNI configurato per assegnare un indirizzo IP e configurare le route necessarie. Esistono numerosi plugin CNI, tra cui Flannel, Calico, Weave Net, Cilium e Canal, ognuno con caratteristiche diverse in termini di prestazioni, sicurezza e funzionalità avanzate come le Network Policy.
La scelta del plugin CNI ha implicazioni dirette sulle capacità di rete del cluster: alcuni plugin supportano la cifratura del traffico tra nodi, altri offrono osservabilità avanzata tramite eBPF, altri ancora privilegiano la semplicità di configurazione.
Pod Networking
Ogni Pod in Kubernetes è composto da uno o più container che condividono lo stesso namespace di rete. Questo significa che tutti i container di un Pod condividono lo stesso indirizzo IP e lo stesso spazio di porte. I container all'interno di un Pod comunicano tra loro tramite localhost, mentre la comunicazione tra Pod diversi avviene tramite gli indirizzi IP assegnati.
Il seguente manifest definisce un Pod con due container che comunicano internamente tramite localhost:
# Pod con due container che condividono lo stesso namespace di rete
apiVersion: v1
kind: Pod
metadata:
name: multi-container-pod
labels:
app: demo
spec:
containers:
- name: web-server
image: nginx:1.25
ports:
- containerPort: 80
- name: log-sidecar
image: busybox:1.36
# Il sidecar accede al server nginx tramite localhost
command: ["sh", "-c", "while true; do wget -q -O- http://localhost:80; sleep 5; done"]
Services
I Pod sono effimeri: nascono e muoiono continuamente, e i loro indirizzi IP cambiano ad ogni ricreazione. Il concetto di Service in Kubernetes risolve questo problema fornendo un endpoint stabile per accedere a un gruppo di Pod selezionati tramite label selector. Un Service ottiene un indirizzo IP virtuale fisso, chiamato ClusterIP, che rimane costante per tutta la vita del Service stesso.
ClusterIP
Il tipo ClusterIP è il tipo di Service predefinito e rende il servizio accessibile soltanto all'interno del cluster. È la scelta ideale per la comunicazione interna tra microservizi.
# Service di tipo ClusterIP per esposizione interna al cluster
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
selector:
# Seleziona tutti i Pod con questa label
app: backend
ports:
- protocol: TCP
port: 80 # Porta esposta dal Service
targetPort: 8080 # Porta su cui ascolta il container
type: ClusterIP
NodePort
Il tipo NodePort espone il Service su una porta statica di ogni nodo del cluster. Il traffico in arrivo su quella porta viene automaticamente inoltrato al Service. Le porte NodePort sono comprese nell'intervallo 30000-32767 per impostazione predefinita.
# Service di tipo NodePort per esposizione su ogni nodo del cluster
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 3000
nodePort: 30080 # Porta accessibile su ogni nodo
type: NodePort
LoadBalancer
Il tipo LoadBalancer è pensato per i cluster Kubernetes ospitati su cloud provider che supportano il provisioning automatico di bilanciatori di carico esterni. Quando viene creato un Service di questo tipo, il cloud provider alloca un indirizzo IP pubblico e configura il bilanciatore di carico per instradare il traffico verso i nodi del cluster.
# Service di tipo LoadBalancer per esposizione tramite bilanciatore di carico esterno
apiVersion: v1
kind: Service
metadata:
name: public-api-service
spec:
selector:
app: public-api
ports:
- protocol: TCP
port: 443
targetPort: 8443
type: LoadBalancer
ExternalName
Il tipo ExternalName mappa un Service a un nome DNS esterno anziché a un gruppo di Pod. Viene utilizzato per integrare risorse esterne al cluster, come database gestiti o API di terze parti, all'interno del meccanismo di service discovery interno.
# Service di tipo ExternalName che mappa un nome DNS esterno
apiVersion: v1
kind: Service
metadata:
name: external-database
namespace: production
spec:
type: ExternalName
# Il traffico viene reindirizzato a questo hostname esterno
externalName: db.example.com
kube-proxy e il Meccanismo di Forwarding
Il componente kube-proxy è responsabile dell'implementazione delle regole di forwarding del traffico su ogni nodo. Monitora costantemente l'API server di Kubernetes per rilevare la creazione, modifica o eliminazione di Service ed Endpoints, e aggiorna di conseguenza le regole di rete del sistema operativo sottostante.
kube-proxy supporta tre modalità operative: userspace, la più antica e meno efficiente, in cui il proxy gestisce il traffico a livello applicativo; iptables, la modalità predefinita in molte distribuzioni, che sfrutta le regole del kernel Linux per il forwarding diretto; e ipvs (IP Virtual Server), la più performante, che utilizza strutture dati in-kernel ottimizzate per la gestione di migliaia di servizi.
Esempio di verifica delle regole iptables generate da kube-proxy per un Service:
# Visualizzazione delle regole NAT generate da kube-proxy per un Service specifico
iptables -t nat -L KUBE-SERVICES -n --line-numbers | grep backend-service
# Output delle catene di forwarding verso gli endpoint del Service
iptables -t nat -L KUBE-SVC-XXXXXXXXXXXXXXXX -n
DNS Interno del Cluster
Kubernetes include un sistema DNS interno, tipicamente implementato tramite CoreDNS, che consente la risoluzione dei nomi dei Service tramite nomi di dominio completamente qualificati (FQDN). La struttura del FQDN segue il pattern:
<service-name>.<namespace>.svc.cluster.local
I Pod all'interno dello stesso namespace possono fare riferimento a un Service semplicemente tramite il suo nome, senza specificare il namespace o il dominio completo. CoreDNS è configurabile tramite una ConfigMap e supporta plugin per la gestione di zone DNS personalizzate, il forwarding verso resolver esterni e la riscrittura di query.
# ConfigMap di CoreDNS con configurazione personalizzata
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
# Gestione della zona interna del cluster
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
# Inoltro delle query esterne a un resolver specifico
forward . 8.8.8.8 8.8.4.4
cache 30
loop
reload
loadbalance
}
Il seguente esempio mostra come un'applicazione Node.js possa sfruttare la risoluzione DNS interna per connettersi a un Service:
// Connessione a un database PostgreSQL tramite DNS interno del cluster
const { Pool } = require('pg');
const pool = new Pool({
// Risoluzione tramite DNS interno: <service>.<namespace>.svc.cluster.local
host: 'postgres-service.database.svc.cluster.local',
port: 5432,
database: 'appdb',
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
});
// Verifica della connessione al database
async function checkConnection() {
const client = await pool.connect();
try {
const result = await client.query('SELECT NOW()');
console.log('Connessione stabilita:', result.rows[0]);
} finally {
client.release();
}
}
checkConnection();
Ingress e Ingress Controller
Un Ingress è una risorsa Kubernetes che gestisce l'accesso esterno ai servizi del cluster, tipicamente tramite HTTP e HTTPS. Fornisce funzionalità come il routing basato su hostname e path, la terminazione TLS e il bilanciamento del carico a livello applicativo. Un Ingress da solo non è sufficiente: richiede un Ingress Controller che ne implementi effettivamente la logica. I controller più diffusi sono NGINX Ingress Controller, Traefik, HAProxy e i controller nativi dei cloud provider.
# Ingress con routing basato su hostname e percorso, con terminazione TLS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
# Annotazione specifica per NGINX Ingress Controller
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- api.example.com
- app.example.com
# Secret contenente il certificato TLS
secretName: example-tls-secret
rules:
# Routing per il dominio dell'API
- host: api.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
# Routing per il dominio del frontend
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
Il certificato TLS referenziato nell'Ingress viene solitamente creato tramite cert-manager, che automatizza il provisioning e il rinnovo dei certificati da Let's Encrypt o da altre autorità di certificazione.
Network Policy
Per impostazione predefinita, Kubernetes adotta un modello di rete aperto: tutti i Pod possono comunicare con tutti gli altri Pod senza restrizioni. Le Network Policy consentono di definire regole di isolamento del traffico a livello di Pod, specificando quali sorgenti possono comunicare in ingresso (ingress) e verso quali destinazioni il traffico in uscita (egress) è consentito.
Le Network Policy richiedono un plugin CNI che le supporti, come Calico o Cilium. Il plugin Flannel, ad esempio, non supporta nativamente le Network Policy.
# Network Policy che isola i Pod del backend consentendo solo il traffico dall'API gateway
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-isolation-policy
namespace: production
spec:
# Seleziona i Pod a cui applicare questa policy
podSelector:
matchLabels:
role: backend
# La policy si applica sia al traffico in ingresso che in uscita
policyTypes:
- Ingress
- Egress
ingress:
# Consente traffico in ingresso solo dai Pod con label role=api-gateway
- from:
- podSelector:
matchLabels:
role: api-gateway
ports:
- protocol: TCP
port: 8080
egress:
# Consente traffico in uscita verso il database
- to:
- podSelector:
matchLabels:
role: database
ports:
- protocol: TCP
port: 5432
# Consente le query DNS verso kube-dns
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
Headless Services e StatefulSet
Un Headless Service è un Service con il campo clusterIP impostato a None. In questo caso, il DNS interno non restituisce un singolo indirizzo IP virtuale, ma la lista degli indirizzi IP di tutti i Pod selezionati. Questo meccanismo è particolarmente utile con gli StatefulSet, che gestiscono applicazioni stateful come database distribuiti, dove ogni Pod ha una propria identità e deve essere raggiungibile individualmente.
# Headless Service per un cluster Cassandra gestito tramite StatefulSet
apiVersion: v1
kind: Service
metadata:
name: cassandra-headless
labels:
app: cassandra
spec:
selector:
app: cassandra
# ClusterIP impostato a None: nessun IP virtuale, DNS restituisce i singoli Pod
clusterIP: None
ports:
- port: 9042
name: cql
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cassandra
spec:
serviceName: cassandra-headless
replicas: 3
selector:
matchLabels:
app: cassandra
template:
metadata:
labels:
app: cassandra
spec:
containers:
- name: cassandra
image: cassandra:4.1
ports:
- containerPort: 9042
name: cql
env:
# Ogni Pod è raggiungibile tramite nome stabile: cassandra-0, cassandra-1, ...
- name: CASSANDRA_SEEDS
value: "cassandra-0.cassandra-headless.default.svc.cluster.local"
Endpoint e EndpointSlice
Quando viene creato un Service con un selector, Kubernetes crea automaticamente un oggetto Endpoints che mantiene la lista degli indirizzi IP dei Pod selezionati. Con l'introduzione degli EndpointSlice nella versione 1.17, questo meccanismo è stato reso più scalabile: invece di un singolo oggetto Endpoints che può diventare molto grande, il traffico è distribuito su più slice da 100 endpoint ciascuno.
# Visualizzazione degli endpoint associati a un Service
kubectl get endpoints backend-service -o yaml
# Visualizzazione degli EndpointSlice per un Service
kubectl get endpointslices -l kubernetes.io/service-name=backend-service
# Descrizione dettagliata degli EndpointSlice con indirizzi IP e porte
kubectl describe endpointslices -l kubernetes.io/service-name=backend-service
Gateway API
La Gateway API è la nuova generazione di risorse per la gestione del traffico in Kubernetes, progettata come successore dell'Ingress. Offre un modello più espressivo, tipizzato e orientato ai ruoli, separando le responsabilità tra i team infrastrutturali (che configurano i Gateway) e i team applicativi (che configurano le HTTPRoute). È diventata Generally Available a partire da Kubernetes 1.28.
# Gateway che definisce un punto di ingresso HTTP/HTTPS
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: main-gateway
namespace: infra
spec:
# Classe di gateway fornita dal controller installato nel cluster
gatewayClassName: nginx
listeners:
- name: http
protocol: HTTP
port: 80
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- name: example-tls-secret
---
# HTTPRoute che definisce le regole di routing per un'applicazione specifica
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: production
spec:
# Riferimento al Gateway definito dal team infrastrutturale
parentRefs:
- name: main-gateway
namespace: infra
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /v2
backendRefs:
- name: api-v2-service
port: 80
weight: 90
# Canary deployment: il 10% del traffico va alla nuova versione
- name: api-v3-canary-service
port: 80
weight: 10
Service Mesh
Un service mesh è uno strato infrastrutturale dedicato alla gestione della comunicazione tra i microservizi di un cluster. I service mesh più diffusi per Kubernetes sono Istio, Linkerd e Consul Connect. Il pattern architetturale prevalente è quello del sidecar proxy: un container proxy (tipicamente Envoy) viene iniettato automaticamente accanto ad ogni container applicativo e intercetta tutto il traffico in entrata e in uscita.
Questo approccio consente di implementare in modo trasparente funzionalità avanzate come il mutual TLS (mTLS) per la cifratura e l'autenticazione del traffico east-west, il circuit breaking, il retry automatico, la gestione del traffico avanzata (traffic splitting, mirroring) e l'osservabilità distribuita tramite tracing e metriche.
# VirtualService di Istio per il traffic splitting tra due versioni di un servizio
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews-traffic-split
namespace: production
spec:
hosts:
- reviews-service
http:
- route:
# Il 80% del traffico va alla versione stabile
- destination:
host: reviews-service
subset: v1
weight: 80
# Il 20% del traffico va alla versione sperimentale
- destination:
host: reviews-service
subset: v2
weight: 20
---
# DestinationRule che definisce i subset per il servizio reviews
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: reviews-destination-rule
namespace: production
spec:
host: reviews-service
# Abilitazione del mutual TLS per il traffico verso questo servizio
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
subsets:
- name: v1
labels:
version: "1.0"
- name: v2
labels:
version: "2.0"
Osservabilità di Rete
Monitorare il comportamento della rete in un cluster Kubernetes è fondamentale per diagnosticare problemi, ottimizzare le prestazioni e garantire la sicurezza. Kubernetes offre diversi strumenti nativi per l'osservabilità di rete, e l'ecosistema aggiunge ulteriori strumenti specializzati.
Il seguente esempio mostra come utilizzare un Pod temporaneo con curl per verificare la raggiungibilità di un Service dall'interno del cluster:
# Avvio di un Pod temporaneo per debug di rete
kubectl run network-debug --image=nicolaka/netshoot --rm -it --restart=Never -- /bin/bash
# Test di raggiungibilità di un Service tramite DNS interno
curl -v http://backend-service.production.svc.cluster.local/health
# Verifica della risoluzione DNS per un Service specifico
nslookup backend-service.production.svc.cluster.local
# Tracciamento del percorso verso un Service
traceroute backend-service.production.svc.cluster.local
# Analisi delle connessioni attive in un container specifico
kubectl exec -it pod-name -n production -- ss -tulnp
Per una visibilità a lungo termine, strumenti come Prometheus con kube-state-metrics, Grafana con dashboard per il networking, e Cilium Hubble per l'osservabilità a livello di flusso con eBPF, rappresentano lo stack di riferimento per la gestione operativa della rete in ambienti Kubernetes di produzione.
Conclusioni
Il networking in Kubernetes è un sistema articolato e stratificato che comprende la gestione degli indirizzi IP dei Pod, il bilanciamento del carico tramite Services, la risoluzione DNS interna, il routing del traffico esterno tramite Ingress o Gateway API, il controllo delle policy di rete e, nei deployment più avanzati, l'intero piano di controllo del traffico offerto da un service mesh. Ogni livello di questa architettura è progettato per essere modulare e sostituibile, permettendo agli operatori di scegliere i componenti più adatti alle proprie esigenze di sicurezza, prestazioni e osservabilità. Una comprensione solida di questi meccanismi è indispensabile per chiunque operi cluster Kubernetes in produzione.