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.