Kubernetes Networking: CNI, Cilium con eBPF e Network Policy
Kubernetes astrae la rete in modo magistrale: ogni Pod ottiene un indirizzo IP routable, i container all'interno di uno stesso Pod condividono il network namespace, e i Pod possono comunicare tra loro su qualsiasi nodo senza NAT. Eppure, la semplicita apparente nasconde una complessita reale: come funziona effettivamente questo modello? Chi si occupa di assegnare gli IP, di instradare il traffico tra nodi, di implementare le policy di rete?
La risposta sta nel Container Network Interface (CNI), uno standard che definisce come i plugin di rete devono integrarsi con Kubernetes. In questo articolo esploreremo il modello di rete Kubernetes dall'interno: come funziona kube-proxy, perche Cilium con eBPF sta rimpiazzando le soluzioni tradizionali, e come implementare Network Policy per isolare i workload in cluster di produzione.
Cosa Imparerai
- Il modello di rete Kubernetes: IP-per-Pod, flat network, nessun NAT
- Come funziona il Container Network Interface (CNI) e i suoi plugin principali
- La differenza tra kube-proxy (iptables/IPVS) e Cilium con eBPF
- Perche eBPF porta latenza -30% e throughput +60% rispetto a iptables
- Come installare e configurare Cilium come CNI
- Network Policy: sintassi, esempi pratici, default-deny e isolamento namespace
- CiliumNetworkPolicy per regole Layer 7 (HTTP, gRPC, Kafka)
Il Modello di Rete Kubernetes
Kubernetes impone un modello di rete con quattro requisiti fondamentali che qualsiasi implementazione CNI deve rispettare:
- Tutti i Pod possono comunicare con tutti gli altri Pod senza NAT
- Tutti i nodi possono comunicare con tutti i Pod senza NAT
- L'IP che un Pod vede come proprio e lo stesso IP che gli altri usano per raggiungerlo
- I container all'interno di un Pod condividono network namespace e IP
Questo "flat network model" semplifica enormemente il ragionamento applicativo: un microservizio non deve sapere se il servizio che chiama e sullo stesso nodo o su un nodo remoto. La complessita e spostata al layer di rete del cluster.
Come Funziona la Comunicazione tra Pod
Quando un Pod A vuole comunicare con un Pod B su nodi diversi, il flusso tipico e il seguente con una soluzione basata su overlay network (es. VXLAN):
- Il pacchetto esce dal container di Pod A attraverso l'interfaccia
eth0 - Entra nel namespace del nodo attraverso una
veth pair - Il CNI plugin lo intercetta e lo incapsula in VXLAN (o GRE, Geneve...)
- Il pacchetto attraversa la rete fisica verso il nodo di Pod B
- Il CNI sul nodo di destinazione de-incapsula il pacchetto
- Il pacchetto arriva al Pod B attraverso la sua veth pair
Con soluzioni basate su BGP o routing nativo (come Cilium in native routing mode), l'encapsulazione non e necessaria e le performance migliorano significativamente.
Container Network Interface (CNI)
Il CNI e una specifica CNCF che definisce come i runtime di container devono invocare i plugin di rete. Kubernetes usa il CNI per delegare la gestione della rete ai plugin: quando il kubelet crea un Pod, chiama il plugin CNI configurato per allocare l'indirizzo IP e configurare l'interfaccia di rete.
Plugin CNI Principali
| Plugin | Tecnologia | Network Policy | L7 Policy | Use Case |
|---|---|---|---|---|
| Flannel | VXLAN overlay | No (nativo) | No | Sviluppo, cluster semplici |
| Calico | BGP / overlay | Si | No | On-premise, performance |
| Cilium | eBPF | Si | Si (HTTP, gRPC) | Produzione, service mesh |
| AWS VPC CNI | ENI nativo | Si (SG) | No | EKS |
| Azure CNI | VNet nativo | Si (NSG) | No | AKS |
kube-proxy: Il Vecchio Approccio
kube-proxy e il componente Kubernetes responsabile di implementare i Service: quando un client chiama un Service, kube-proxy si assicura che il traffico arrivi a uno dei Pod backend. Tradizionalmente usa iptables per questo scopo.
Il problema con iptables e la complessita che scala linearmente con il numero di regole. In un cluster con 10.000 Service e 100.000 endpoint, iptables gestisce milioni di regole. Ogni pacchetto deve attraversare questa catena di regole, con un impatto significativo sulla latenza e sul CPU del nodo.
L'alternativa in-tree e IPVS (IP Virtual Server), che usa una hash table per la lookup O(1) invece della scansione lineare di iptables. Ma anche IPVS ha limiti: non supporta policy avanzate e richiede comunque la gestione di regole iptables aggiuntive.
iptables Scaling Problem
Con 10.000 Service, kube-proxy con iptables crea circa 40.000 regole solo per i Service. Il tempo di aggiornamento di queste regole cresce da millisecondi a minuti. In cluster grandi, questo porta a latenze elevate durante scaling events e aggiornamenti di deployment. Questo e uno dei motivi principali per cui Cilium sta sostituendo kube-proxy.
Cilium e eBPF: Il Futuro del Networking Kubernetes
eBPF (extended Berkeley Packet Filter) e una tecnologia del kernel Linux che permette di eseguire programmi sandboxed direttamente nel kernel, senza modificarlo e senza moduli kernel. Cilium sfrutta eBPF per intercettare e processare il traffico di rete a livello di kernel, bypassando completamente iptables e kube-proxy.
Vantaggi di Cilium con eBPF
- Performance: Latenza ridotta fino al 30%, throughput aumentato del 60% rispetto a iptables
- Scalabilita: Lookup O(1) con BPF maps invece di scansione lineare iptables
- Osservabilita: Hubble fornisce visibilita del traffico L3/L4/L7 in tempo reale
- Layer 7 Policy: Policy basate su HTTP path, metodo, header, gRPC method, Kafka topic
- Service Mesh senza sidecar: mTLS e load balancing implementati nel kernel, zero overhead sidecar
- Kube-proxy replacement: Cilium puo rimpiazzare completamente kube-proxy
Installazione di Cilium
Installiamo Cilium su un cluster Kubernetes usando Helm, configurandolo per rimpiazzare kube-proxy e abilitare Hubble per l'osservabilita:
# Aggiungi il repo Helm di Cilium
helm repo add cilium https://helm.cilium.io/
helm repo update
# Installa Cilium con kube-proxy replacement e Hubble abilitati
helm install cilium cilium/cilium \
--version 1.16.0 \
--namespace kube-system \
--set kubeProxyReplacement=true \
--set k8sServiceHost=API_SERVER_HOST \
--set k8sServicePort=API_SERVER_PORT \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true \
--set ipam.mode=kubernetes
# Verifica installazione
cilium status --wait
# Verifica connettivita
cilium connectivity test
Cilium in Produzione: Configurazione Avanzata
Per un cluster di produzione, ecco una configurazione Helm values completa con native routing (senza encapsulazione VXLAN) per massimizzare le performance:
# cilium-values-production.yaml
kubeProxyReplacement: true
k8sServiceHost: "10.0.0.1" # indirizzo API server
k8sServicePort: "6443"
# Native routing mode (senza overlay VXLAN)
# Richiede che la rete sottostante supporti il routing dei pod CIDR
tunnel: disabled
autoDirectNodeRoutes: true
ipv4NativeRoutingCIDR: "10.244.0.0/16"
# IPAM
ipam:
mode: kubernetes
# BGP per annunciare i pod CIDR ai router
bgp:
enabled: true
announce:
podCIDR: true
lbIP: true
# Hubble - osservabilita Layer 7
hubble:
enabled: true
metrics:
enabled:
- dns:query;ignoreAAAA
- drop
- tcp
- flow
- icmp
- http
relay:
enabled: true
replicas: 2
ui:
enabled: true
# Monitoring con Prometheus
prometheus:
enabled: true
serviceMonitor:
enabled: true
# Encryption (WireGuard)
encryption:
enabled: true
type: wireguard
# Load Balancing con DSR (Direct Server Return)
loadBalancer:
mode: dsr # elimina un hop di rete nel path di ritorno
# Limita connessioni per Pod
bpf:
mapDynamicSizeRatio: 0.0025
Network Policy in Kubernetes
Per default, tutti i Pod in un cluster Kubernetes possono comunicare liberamente tra loro. Questo e comodo per lo sviluppo ma e un problema di sicurezza in produzione. Le NetworkPolicy permettono di definire regole di ingress e egress per i Pod.
Attenzione: CNI Plugin Richiesto
Le NetworkPolicy sono una risorsa Kubernetes, ma la loro implementazione dipende dal plugin CNI. Flannel non supporta NetworkPolicy nativamente. Per usarle, serve Cilium, Calico, o un altro CNI che le implementi. La risorsa viene accettata dall'API server ma ignorata se il CNI non la supporta.
Default Deny: La Best Practice Fondamentale
Il primo passo per isolare i workload e applicare una policy default-deny su ogni namespace. Questo nega tutto il traffico non esplicitamente autorizzato:
# default-deny-all.yaml
# Nega tutto il traffico ingress e egress nel namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # seleziona TUTTI i pod nel namespace
policyTypes:
- Ingress
- Egress
---
# Permetti il traffico DNS (necessario per la risoluzione dei nomi)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
NetworkPolicy per un'Applicazione Tipica
Vediamo come isolare un'applicazione a tre tier: frontend, backend, database. Solo il frontend accetta traffico dall'esterno, solo il backend puo raggiungere il DB:
# network-policies-app.yaml
# Frontend: accetta traffico dall'ingress controller
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-to-frontend
namespace: production
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 8000
- ports:
- protocol: UDP
port: 53
---
# Backend: accetta solo dal frontend, puo raggiungere DB
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8000
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
- ports:
- protocol: UDP
port: 53
---
# Database: accetta solo dal backend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-database
namespace: production
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432
CiliumNetworkPolicy: Policy Layer 7
Le NetworkPolicy Kubernetes standard operano a Layer 3/4 (IP e porta). Cilium estende questo con CiliumNetworkPolicy, che permette policy basate sul contenuto della comunicazione: HTTP path, gRPC method, Kafka topic, DNS query.
HTTP Policy con Cilium
Permettiamo al frontend di chiamare solo specifici endpoint del backend:
# cilium-http-policy.yaml
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: frontend-to-backend-l7
namespace: production
spec:
endpointSelector:
matchLabels:
app: backend
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
toPorts:
- ports:
- port: "8000"
protocol: TCP
rules:
http:
- method: "GET"
path: "/api/v1/products.*"
- method: "POST"
path: "/api/v1/orders"
- method: "GET"
path: "/health"
DNS Policy con Cilium
Limita i domini DNS che un Pod puo risolvere (utile per prevenire data exfiltration):
# cilium-dns-policy.yaml
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: restrict-dns-egress
namespace: production
spec:
endpointSelector:
matchLabels:
app: backend
egress:
# Permetti solo DNS verso il cluster DNS
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s:k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: ANY
rules:
dns:
- matchPattern: "*.internal.company.com"
- matchPattern: "*.svc.cluster.local"
- matchPattern: "api.stripe.com"
Hubble: Osservabilita della Rete
Hubble e il layer di osservabilita di Cilium. Fornisce visibilita in tempo reale su tutto il traffico di rete del cluster, con identita delle comunicazioni basate su Kubernetes labels invece che su indirizzi IP.
# Installa il CLI di Hubble
export HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
curl -L --fail --remote-name-all \
https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-amd64.tar.gz
tar xzvf hubble-linux-amd64.tar.gz
sudo mv hubble /usr/local/bin
# Port-forward al relay Hubble
cilium hubble port-forward &
# Osserva il traffico in tempo reale
hubble observe --namespace production --follow
# Filtra per Pod specifici
hubble observe \
--namespace production \
--from-pod frontend-7d9d6b8f-abc12 \
--to-pod backend-5c4f8d9-xyz99 \
--follow
# Mostra solo i drop (traffico bloccato dalle policy)
hubble observe \
--namespace production \
--verdict DROPPED \
--follow
# Statistiche per service
hubble observe \
--namespace production \
--output json | jq '.flow.destination.namespace'
Debug e Troubleshooting delle Network Policy
Debuggare le NetworkPolicy e una delle sfide piu comuni in Kubernetes. Ecco un approccio sistematico:
# 1. Verifica quali NetworkPolicy si applicano a un Pod
kubectl get networkpolicies -n production -o wide
# 2. Test di connettivita con un Pod temporaneo
kubectl run test-pod \
--image=nicolaka/netshoot \
--rm \
-it \
--restart=Never \
-n production \
-- bash
# All'interno del Pod:
# Test TCP
nc -zv backend-service 8000
# Test DNS
nslookup backend-service.production.svc.cluster.local
# Test HTTP
curl -v http://backend-service:8000/health
# 3. Con Cilium, usa il tool di policy verification
cilium policy get # mostra tutte le policy caricate
# 4. Testa la connettivita specifica
kubectl exec -n production frontend-pod -- \
curl -v http://backend-service:8000/api/v1/products
# 5. Con Hubble, vedi perche un pacchetto viene droppato
hubble observe \
--namespace production \
--verdict DROPPED \
--from-pod frontend-7d9d6b8f \
--follow
Isolamento Multi-Namespace
In cluster multi-tenant, e fondamentale isolare i namespace. Per default, le NetworkPolicy non bloccano il traffico cross-namespace. Ecco come implementare un isolamento completo:
# Isola completamente un namespace da tutti gli altri
# (ma permette il traffico interno al namespace)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: namespace-isolation
namespace: tenant-a
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
# Permetti solo traffico dallo stesso namespace
- from:
- podSelector: {}
# Permetti da monitoring namespace
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
egress:
# Permetti solo traffico verso lo stesso namespace
- to:
- podSelector: {}
# Permetti DNS
- ports:
- protocol: UDP
port: 53
# Permetti verso monitoring namespace
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
Performance Benchmark: Cilium vs kube-proxy
I benchmark mostrano differenze significative tra Cilium con eBPF e kube-proxy con iptables in cluster con alto numero di Service:
| Metrica | kube-proxy iptables | Cilium eBPF | Miglioramento |
|---|---|---|---|
| Latenza P50 (10K svc) | 450 µs | 130 µs | -71% |
| Latenza P99 (10K svc) | 2.1 ms | 320 µs | -85% |
| Throughput (Gbps) | 22 Gbps | 36 Gbps | +64% |
| CPU update rules (10K svc) | 180 sec | 2 sec | -99% |
| Connessioni/sec | 220K | 380K | +73% |
Best Practices per il Networking Kubernetes
Checklist Networking di Produzione
- Scegli il CNI giusto subito: Migare CNI in produzione e complesso. Valuta Cilium se prevedi network policy avanzate o service mesh
- Default-deny in ogni namespace: Inizia sempre con una policy che nega tutto, poi aggiungi le eccezioni
- Label coerenti sui Pod: Le NetworkPolicy dipendono dai label; usa convenzioni chiare (app, tier, version)
- Testa le policy prima del deploy: Usa
cilium connectivity testo pod di test per verificare - Abilita Hubble: In produzione, la visibilita del traffico e fondamentale per debugging e compliance
- Monitora i drop: Configura alert su Prometheus per traffico droppato inaspettato
- Non bloccare DNS: Ricorda sempre di permettere UDP/TCP 53 verso kube-dns nelle policy egress
- Documenta le policy: Usa annotation Kubernetes per descrivere lo scopo di ogni NetworkPolicy
Anti-Pattern da Evitare
Errori Comuni con le NetworkPolicy
- Policy troppo permissive: Usare
namespaceSelector: {}senza matchLabels permette traffico da TUTTI i namespace, inclusi quelli compromessi - Dimenticare il DNS: Se blocchi tutto l'egress e dimentichi la porta 53, i tuoi Pod non risolveranno piu nessun hostname
- Label non aggiornati: Se aggiungi nuovi Pod senza i label corretti, le NetworkPolicy non li proteggono
- Test solo in sviluppo: Le NetworkPolicy possono bloccare traffico inaspettato in produzione. Testa sempre in staging con traffico realistico
- Usare IP invece di selettori: Gli IP dei Pod cambiano; usa sempre podSelector e namespaceSelector, mai ipBlock per il traffico interno
Conclusioni e Prossimi Passi
Il modello di rete Kubernetes, con il suo approccio flat e IP-per-Pod, e elegante nella sua semplicita ma sofisticato nell'implementazione. La scelta del CNI plugin e una delle decisioni architetturali piu importanti per un cluster di produzione: influenza le performance, la sicurezza e le funzionalita di osservabilita disponibili.
Cilium con eBPF e oggi il CNI piu avanzato disponibile: rimpiazza kube-proxy con performance superiori, offre policy Layer 7, integra l'osservabilita con Hubble e puo sostituire un service mesh tradizionale per il mTLS. Per nuovi cluster in produzione, e la scelta raccomandata dalla community CNCF e da grandi player come Google, Amazon e Microsoft che lo usano nei loro managed Kubernetes services.
Le Network Policy, implementate correttamente con un approccio default-deny, riducono drasticamente la superficie di attacco in caso di compromissione di un Pod. Non sono opzionali in produzione: sono un requisito di sicurezza fondamentale.
Prossimi Articoli della Serie Kubernetes at Scale
Serie Correlate
- MLOps e Machine Learning in Produzione - workload GPU su Kubernetes
- Platform Engineering - Internal Developer Platforms su K8s
- Observability e OpenTelemetry - monitoring del cluster

Commenti
Caricamento commenti...
Accedi per commentare