Salta al contenuto principale

01 - Kubernetes Networking: CNI, Cilium con eBPF e Network Policy

Kubernetes Networking: CNI, Cilium con eBPF e Network Policy Kubernetes astrae la rete in modo magistrale: ogni Pod ottiene un indirizzo IP routable, i...

Cosa ne pensi di questo articolo?

01 - Kubernetes Networking: CNI, Cilium con eBPF e Network Policy
Kubernetes AvanzatoFoto:Brett SaylesPexels

In Breve

Kubernetes Networking: CNI, Cilium con eBPF e Network Policy Kubernetes astrae la rete in modo magistrale: ogni Pod ottiene un indirizzo IP routable, i...

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):

  1. Il pacchetto esce dal container di Pod A attraverso l'interfaccia eth0
  2. Entra nel namespace del nodo attraverso una veth pair
  3. Il CNI plugin lo intercetta e lo incapsula in VXLAN (o GRE, Geneve...)
  4. Il pacchetto attraversa la rete fisica verso il nodo di Pod B
  5. Il CNI sul nodo di destinazione de-incapsula il pacchetto
  6. 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 test o 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.

Serie Correlate

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti