Calico per la rete in Kubernetes: introduzione e qualche esperienza

Calico per la rete in Kubernetes: introduzione e qualche esperienza

L'obiettivo dell'articolo è introdurre il lettore alle basi dell'interazione di rete e della gestione delle politiche di rete in Kubernetes, insieme al plugin di terze parti Calico, che amplia le funzionalità standard. Nel corso dell'articolo verranno mostrati la facilità di configurazione e alcune funzionalità con esempi reali tratti dalla nostra esperienza.

Introduzione rapida all'architettura di rete di Kubernetes

Un cluster Kubernetes non può essere immaginato senza una rete. Abbiamo già pubblicato materiali sulle loro basi: «Guida illustrata all'architettura di rete in Kubernetes» e «Introduzione alle politiche di rete di Kubernetes per esperti di sicurezza».

Nel contesto di questo articolo, è importante notare che la connettività di rete tra i contenitori e i nodi non è gestita da K8s stesso: vengono utilizzati vari plugin CNI (Container Networking Interface). Abbiamo anche parlato di questa concezione in precedenza.

Ad esempio, il plugin più comune tra questi è Flannel — garantisce una completa connettività di rete tra tutti i nodi del cluster sollevando ponti su ciascun nodo e assegnando una sottorete ad esso. Tuttavia, la disponibilità completa e non regolata non è sempre utile. Per garantire una minima isolamento nel cluster, è necessario intervenire nella configurazione del firewall. In generale, essa è gestita dal CNI, motivo per cui qualsiasi intervento esterno su iptables può essere interpretato in modo errato o completamente ignorato.

E "di default" per l'organizzazione della gestione delle politiche di rete nel cluster Kubernetes viene fornito NetworkPolicy API. Questa risorsa, che si estende agli spazi dei nomi selezionati, può contenere regole per delimitare l'accesso da una applicazione all'altra. Permette anche di configurare l'accessibilità tra specifici pod, ambienti (spazi dei nomi) o blocchi di indirizzi IP:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978

Questo non è l'esempio più semplice di documentazione ufficiale potrebbe definitivamente scoraggiare l'interesse a comprendere la logica delle politiche di rete. Tuttavia, cercheremo comunque di capire i principi fondamentali e i metodi di gestione del traffico attraverso le politiche di rete…

È logico che ci siano 2 tipi di traffico: quello in ingresso nel pod (Ingress) e quello in uscita (Egress).

Calico per la rete in Kubernetes: introduzione e qualche esperienza

Infatti, la politica viene suddivisa in queste 2 categorie in base alla direzione del traffico.

Il prossimo attributo obbligatorio è il selettore; ovvero, a chi si applica la regola. Questo può essere un pod (o un gruppo di pod) o un ambiente (cioè, uno spazio dei nomi). Un dettaglio importante: entrambi questi tipi di oggetti devono contenere un'etichetta (etichetta nomenclatura in Kubernetes) — è su queste che operano le politiche.

Oltre al numero finale di selettori raggruppati da un'etichetta, esiste la possibilità di scrivere regole come "Permetti/nega tutto/ai tutti" in diverse varianti. A questo proposito si usano costruzioni del tipo:

  podSelector: {}
  ingress: []
  policyTypes:
  - Ingress

— in questo esempio a tutti i pod dell'ambiente viene negato il traffico in entrata. Un comportamento opposto può essere ottenuto con la seguente costruzione:

  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Ingress

Analogamente per l'uscita:

  podSelector: {}
  policyTypes:
  - Egress

— per disabilitarlo. Ecco cosa serve per abilitarlo:

  podSelector: {}
  egress:
  - {}
  policyTypes:
  - Egress

Tornando alla scelta del plugin CNI per il cluster, vale la pena notare che non tutti i plugin di rete supportano la gestione delle NetworkPolicy. Ad esempio, il già citato Flannel non è in grado di configurare le politiche di rete, come è chiaramente indicato nel repository ufficiale. Lì viene menzionata anche un'alternativa: un progetto Open Source Calico, che amplia significativamente il set standard di API di Kubernetes per quanto riguarda le politiche di rete.

Calico per la rete in Kubernetes: introduzione e qualche esperienza

Introduciamo Calico: teoria

Il plugin Calico può essere utilizzato in integrazione con Flannel (sottoprogetto Canal) o autonomamente, coprendo sia le funzioni di connessione di rete che le opzioni di gestione della disponibilità.

Quali vantaggi offre l'uso di una soluzione 'boxata' K8s e del set di API di Calico?

Ecco cosa è integrato in NetworkPolicy:

  • le politiche sono limitate all'ambiente;
  • le politiche si applicano ai pod contrassegnati con etichette;
  • le regole possono essere applicate a pod, ambienti o subnet;
  • le regole possono contenere protocolli, indicazioni di porte nominate o simboliche.

Ecco come Calico espande queste funzionalità:

  • le politiche possono applicarsi a qualsiasi oggetto: pod, contenitore, macchina virtuale o interfaccia;
  • le regole possono contenere un'azione specifica (negazione, autorizzazione, registrazione);
  • come obiettivo o sorgente delle regole può esserci una porta, un intervallo di porte, protocolli, attributi HTTP o ICMP, IP o subnet (di quarta o sesta generazione), selettori vari (di nodi, host, ambienti);
  • inoltre, è possibile regolare il traffico attraverso configurazioni DNAT e politiche di instradamento del traffico.

I primi commit su GitHub nel repository Calico risalgono a luglio 2016, e già un anno dopo il progetto aveva conquistato posizioni di leadership nell’organizzazione della connettività di rete Kubernetes — come indicano ad esempio i risultati di un sondaggio, condotto da The New Stack:

Calico per la rete in Kubernetes: introduzione e qualche esperienza

Molte soluzioni managed di K8s, come Amazon EKS, Azure AKS, Google GKE e altre, hanno iniziato a raccomandarne l'uso.

Per quanto riguarda le prestazioni, sono eccezionali. Durante i test del proprio prodotto, il team di sviluppo di Calico ha mostrato risultati straordinari, eseguendo oltre 50000 container su 500 nodi fisici con una velocità di creazione di 20 container al secondo. Non sono emersi problemi durante lo scaling. Questi risultati sono stati riportati già al momento dell'annuncio della prima versione. Ricerche indipendenti mirate alla larghezza di banda e ai livelli di utilizzo delle risorse confermano anche le prestazioni di Calico, che sono quasi equivalenti a quelle di Flannel. Ad esempio:

Calico per la rete in Kubernetes: introduzione e qualche esperienza

Il progetto si sviluppa molto rapidamente, supportando l'operatività in soluzioni popolari gestite di K8s, OpenShift, OpenStack, ed è possibile utilizzare Calico nel dispiegamento di un cluster tramite kops, si fa riferimento alla costruzione di reti Service Mesh (ecco un esempio di utilizzo con Istio).

Pratica con Calico

In generale, per l'uso di Kubernetes vanilla, l'installazione del CNI si traduce nell'uso del file calico.yaml, scaricato dal sito ufficiale, utilizzando kubectl apply -f.

In genere, l'ultima versione del plugin è compatibile con le ultime 2-3 versioni di Kubernetes: il funzionamento su versioni più vecchie non viene testato né garantito. Secondo gli sviluppatori, Calico funziona su un kernel Linux superiore a 3.10 su CentOS 7, Ubuntu 16 o Debian 8, sopra iptables o IPVS.

Isolamento all'interno dell'ambiente

Per una comprensione generale, consideriamo un semplice caso per capire in cosa differiscono le politiche di rete nella notazione di Calico dalle politiche standard e come l'approccio alla scrittura delle regole ne semplifichi la leggibilità e la flessibilità di configurazione:

Calico per la rete in Kubernetes: introduzione e qualche esperienza

Nel cluster sono distribuite 2 applicazioni web: una in Node.js e una in PHP, di cui una utilizza Redis. Per chiudere l'accesso a Redis da PHP, mantenendo la connettività con Node.js, è sufficiente applicare la seguente politica:

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: allow-redis-nodejs
spec:
  podSelector:
    matchLabels:
      service: redis
  ingress:
  - from:
    - podSelector:
        matchLabels:
          service: nodejs
    ports:
    - protocol: TCP
      port: 6379

Fondamentalmente, abbiamo consentito il traffico in entrata sulla porta Redis da Node.js. E chiaramente non abbiamo vietato nient'altro. Non appena viene introdotta una NetworkPolicy, tutti i selettori menzionati iniziano a isolarsi, a meno che non venga specificato diversamente. Le regole di isolamento non si applicano ad altri oggetti non coperti dal selettore.

Nell'esempio viene utilizzato apiVersion di Kubernetes "out of the box", ma nulla impedisce di usare la risorsa omonima fornita da Calico. La sintassi è più dettagliata, quindi sarà necessario riscrivere la regola per il caso sopra descritto nel seguente modo:

apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:
  name: allow-redis-nodejs
spec:
  selector: service == 'redis'
  ingress:
  - action: Allow
    protocol: TCP
    source:
      selector: service == 'nodejs'
    destination:
      ports:
      - 6379

Le strutture sopra menzionate per consentire o vietare tutto il traffico attraverso l'API standard di NetworkPolicy contengono costruzioni complesse da comprendere e memorizzare. Nel caso di Calico, per invertire la logica della regola del firewall, è sufficiente cambiare action: Allow con action: Deny.

Isolamento per ambienti

Immaginiamo una situazione in cui un'applicazione genera metriche aziendali per raccoglierle in Prometheus e successivamente analizzarle tramite Grafana. Nel dump possono essere presenti dati sensibili, che per impostazione predefinita sono di nuovo accessibili a tutti. Chiudiamo questi dati alla vista esterna:

Calico per la rete in Kubernetes: introduzione e qualche esperienza

Prometheus è solitamente collocato in un ambiente di servizio separato — nell'esempio, questo sarà uno namespace del tipo seguente:

apiVersion: v1
kind: Namespace
metadata:
  labels:
    module: prometheus
  name: kube-prometheus

Campo metadata.labels non è casuale. Come già accennato sopra, namespaceSelector (così come podSelector) opera con le etichette. Pertanto, per consentire di prelevare metriche da tutti i pod su una certa porta, sarà necessario aggiungere un'etichetta (o prenderne una da quelle esistenti), e poi applicare una configurazione simile a questa:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-metrics-prom
spec:
  podSelector: {}
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          module: prometheus
    ports:
    - protocol: TCP
      port: 9100

E nel caso in cui si utilizzino politiche Calico, la sintassi sarà la seguente:

apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:
  name: allow-metrics-prom
spec:
  ingress:
  - action: Allow
    protocol: TCP
    source:
      namespaceSelector: module == 'prometheus'
    destination:
      ports:
      - 9100

In generale, aggiungendo politiche di questo tipo alle esigenze specifiche, è possibile proteggere le applicazioni nel cluster da interferenze dannose o accidentali.

La migliore pratica, secondo i creatori di Calico, è l'approccio "Negare tutto e aprire esplicitamente ciò che è necessario", come fissato in documentazione ufficiale (anche altri seguono un approccio simile — in particolare, in nell'articolo già menzionato).

Applicazione di oggetti aggiuntivi di Calico

Ricordo che attraverso un set avanzato di API Calico è possibile regolare la disponibilità dei nodi, senza limitarsi solo ai pod. Nel seguente esempio con GlobalNetworkPolicy viene bloccata la possibilità di passare richieste ICMP nel cluster (ad esempio, ping da un pod a un nodo, tra pod, o da nodo a IP di un pod):

apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: block-icmp
spec:
  order: 200
  selector: all()
  types:
  - Ingress
  - Egress
  ingress:
  - action: Deny
    protocol: ICMP
  egress:
  - action: Deny
    protocol: ICMP

Nell'esempio sopra, rimane la possibilità per i nodi del cluster di comunicare tra loro tramite ICMP. Questa questione viene risolta tramite GlobalNetworkPolicy, applicata all'entità HostEndpoint:

apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: deny-icmp-kube-02
spec:
  selector: "role == 'k8s-node'"
  order: 0
  ingress:
  - action: Allow
    protocol: ICMP
  egress:
  - action: Allow
    protocol: ICMP
---
apiVersion: crd.projectcalico.org/v1
kind: HostEndpoint
metadata:
  name: kube-02-eth0
  labels:
    role: k8s-node
spec:
  interfaceName: eth0
  node: kube-02
  expectedIPs: ["192.168.2.2"]

Caso di VPN

Infine, presenterò un esempio reale dell'utilizzo delle funzionalità di Calico per un caso di interazione tra cluster, quando il set standard delle politiche non è sufficiente. Per accedere all'applicazione web da parte dei clienti viene utilizzato un tunnel VPN, e tale accesso è rigidamente controllato e limitato a un elenco specifico di servizi autorizzati:

Calico per la rete in Kubernetes: introduzione e qualche esperienza

I clienti si connettono alla VPN tramite la porta UDP standard 1194 e, una volta collegati, ricevono le route per le sottoreti dei pod e dei servizi del cluster. Le sottoreti vengono pushate nella loro interezza, per non perdere i servizi durante i riavvii e i cambi di indirizzo.

La configurazione della porta è standard, il che comporta alcune complicazioni nel processo di configurazione dell'applicazione e nel suo trasferimento nel cluster Kubernetes. Ad esempio, nel AWS LoadBalancer, il supporto per UDP è apparso solo alla fine dello scorso anno in un numero limitato di regioni, mentre NodePort non è utilizzabile a causa del suo inoltro su tutti i nodi del cluster, rendendo impossibile scalare il numero di istanze del server per la resilienza. Inoltre, sarà necessario modificare l'intervallo di porte selezionato per impostazione predefinita…

Dopo aver esplorato diverse soluzioni, è stata scelta la seguente:

  1. I Pod con VPN sono pianificati su un nodo in modalità hostNetwork, ovvero sull'IP effettivo.
  2. Il servizio è esposto all'esterno tramite ClusterIP. Su questo nodo viene fisicamente sollevata una porta, accessibile dall'esterno con alcune particolari condizioni (ad esempio, la disponibilità di un vero indirizzo IP).
  3. La definizione del nodo su cui è stato sollevato il pod esula dal nostro racconto. Posso solamente dire che è possibile 'agganciare' rigidamente il servizio al nodo oppure scrivere un piccolo servizio sidecar che monitori l'attuale indirizzo IP del servizio VPN e corregga i record DNS configurati dai clienti — ognuno con la propria creatività.

Dal punto di vista del routing, possiamo identificare in modo univoco un cliente tramite VPN in base al suo indirizzo IP fornito dal server VPN. Di seguito è riportato un esempio semplice di limitazione dell'accesso a tale cliente ai servizi, illustrato con il Redis menzionato sopra:

apiVersion: crd.projectcalico.org/v1
kind: HostEndpoint
metadata:
  name: vpnclient-eth0
  labels:
    role: vpnclient
    environment: production
spec:
  interfaceName: "*"
  node: kube-02
  expectedIPs: ["172.176.176.2"]
---
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: vpn-rules
spec:
  selector: "role == 'vpnclient'"
  order: 0
  applyOnForward: true
  preDNAT: true
  ingress:
  - action: Deny
    protocol: TCP
    destination:
      ports: [6379]
  - action: Allow
    protocol: UDP
    destination:
      ports: [53, 67]

Qui è severamente vietato il collegamento alla porta 6379, ma è stata mantenuta l'attività del servizio DNS, la cui funzionalità spesso soffre quando si creano le regole. Perché, come menzionato in precedenza, quando viene applicato un selettore, la politica restrittiva viene applicata per impostazione predefinita, a meno che non venga specificato diversamente.

Risultati

In questo modo, utilizzando l'API avanzata di Calico, è possibile configurare in modo flessibile e cambiare dinamicamente il routing all'interno del cluster e attorno ad esso. In generale, il suo utilizzo può sembrare come sparare con un cannone per colpire un passero, mentre l'implementazione della rete L3 con tunneling BGP e IP-IP appare mostruosa in un'installazione semplice di Kubernetes in una rete piatta... Tuttavia, al di là di questo, lo strumento appare molto valido e utile.

L'isolamento del cluster per garantire i requisiti di sicurezza potrebbe non sempre essere realizzabile, ed è proprio in questi casi che Calico (o una soluzione simile) viene in aiuto. Gli esempi riportati nell'articolo (con alcune piccole modifiche) sono utilizzati in diverse installazioni dei nostri clienti su AWS.

P.S.

Leggete anche nel nostro blog:

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster