Calico per il networking in Kubernetes: introduzione e un po' di esperienza

Calico per il networking in Kubernetes: introduzione e un po' di esperienza

Lo scopo dell'articolo è introdurre il lettore alle basi del networking e della gestione delle policy di rete in Kubernetes, nonché al plug-in Calico di terze parti che estende le funzionalità standard. Lungo il percorso, la facilità di configurazione e alcune funzionalità verranno dimostrate utilizzando esempi reali tratti dalla nostra esperienza operativa.

Una rapida introduzione all'appliance di rete Kubernetes

Non è possibile immaginare un cluster Kubernetes senza rete. Abbiamo già pubblicato materiali sulle loro basi: “Una guida illustrata al networking in Kubernetes"E"Un'introduzione alle policy di rete Kubernetes per i professionisti della sicurezza'.

Nel contesto di questo articolo, è importante notare che K8s stesso non è responsabile della connettività di rete tra contenitori e nodi: per questo, vari plugin CNI (Interfaccia di rete dei contenitori). Maggiori informazioni su questo concetto noi mi hanno anche detto.

Ad esempio, il più comune di questi plugin è Flanella — fornisce una connettività di rete completa tra tutti i nodi del cluster creando ponti su ciascun nodo e assegnandogli una sottorete. Tuttavia, l’accessibilità completa e non regolamentata non è sempre vantaggiosa. Per garantire una sorta di isolamento minimo nel cluster è necessario intervenire nella configurazione del firewall. Nel caso generale, è posto sotto il controllo dello stesso CNI, motivo per cui eventuali interventi di terzi su iptables possono essere interpretati in modo errato o ignorati del tutto.

Viene inoltre fornita una soluzione "pronta all'uso" per l'organizzazione della gestione delle policy di rete in un cluster Kubernetes API NetworkPolicy. Questa risorsa, distribuita su spazi dei nomi selezionati, può contenere regole per differenziare l'accesso da un'applicazione all'altra. Consente inoltre di configurare l'accessibilità tra pod, ambienti (spazi dei nomi) o blocchi di indirizzi IP specifici:

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ù primitivo di documentazione ufficiale potrebbe scoraggiare una volta per tutte il desiderio di comprendere la logica del funzionamento delle politiche di rete. Tuttavia, cercheremo comunque di comprendere i principi e i metodi di base per elaborare i flussi di traffico utilizzando le politiche di rete...

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

Calico per il networking in Kubernetes: introduzione e un po' di esperienza

In realtà, la politica è divisa in queste 2 categorie in base alla direzione del movimento.

Il successivo attributo richiesto è un selettore; colui al quale si applica la regola. Può essere un pod (o un gruppo di pod) o un ambiente (ovvero uno spazio dei nomi). Un dettaglio importante: entrambe le tipologie di questi oggetti devono contenere un'etichetta (etichetta nella terminologia Kubernetes) - questi sono quelli con cui operano i politici.

Oltre a un numero finito di selettori uniti da una sorta di etichetta, è possibile scrivere regole come “Consenti/nega tutto/tutti” in diverse varianti. A questo scopo vengono utilizzate le costruzioni del modulo:

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

— in questo esempio, tutti i pod nell'ambiente sono bloccati dal traffico in entrata. Il comportamento opposto può essere ottenuto con la seguente costruzione:

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

Allo stesso modo per l'uscita:

  podSelector: {}
  policyTypes:
  - Egress

- per spegnerlo. Ed ecco cosa includere:

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

Tornando alla scelta di un plugin CNI per un cluster, vale la pena sottolinearlo non tutti i plugin di rete supportano NetworkPolicy. Ad esempio, il già citato Flannel non sa come configurare le politiche di rete, che è detto direttamente nel repository ufficiale. Qui viene menzionata anche un'alternativa: un progetto Open Source Calico, che espande in modo significativo il set standard di API Kubernetes in termini di policy di rete.

Calico per il networking in Kubernetes: introduzione e un po' di esperienza

Conoscere Calico: teoria

Il plugin Calico può essere utilizzato in integrazione con Flannel (sottoprogetto Canale) o in modo indipendente, coprendo sia la connettività di rete che le capacità di gestione della disponibilità.

Quali opportunità offre l’utilizzo della soluzione “boxed” di K8 e del set di API di Calico?

Ecco cosa è integrato in NetworkPolicy:

  • i politici sono limitati dall’ambiente;
  • le policy vengono applicate ai pod contrassegnati da etichette;
  • le regole possono essere applicate a pod, ambienti o sottoreti;
  • le regole possono contenere protocolli, specifiche di porta denominate o simboliche.

Ecco come Calico estende queste funzioni:

  • le policy possono essere applicate a qualsiasi oggetto: pod, contenitore, macchina virtuale o interfaccia;
  • le regole possono contenere un'azione specifica (divieto, autorizzazione, registrazione);
  • la destinazione o sorgente delle regole può essere una porta, un range di porte, protocolli, attributi HTTP o ICMP, IP o sottorete (4a o 6a generazione), eventuali selettori (nodi, host, ambienti);
  • Inoltre, puoi regolare il passaggio del traffico utilizzando le impostazioni DNAT e le politiche di inoltro del traffico.

I primi commit su GitHub nel repository Calico risalgono a luglio 2016 e un anno dopo il progetto ha assunto una posizione di leadership nell'organizzazione della connettività di rete Kubernetes - ciò è evidenziato, ad esempio, dai risultati del sondaggio, condotto da The New Stack:

Calico per il networking in Kubernetes: introduzione e un po' di esperienza

Molte soluzioni gestite di grandi dimensioni con K8, come Amazon EX, AKS di Azure, Google GKE e altri iniziarono a raccomandarlo per l'uso.

Per quanto riguarda le prestazioni, qui è tutto fantastico. Nel testare il proprio prodotto, il team di sviluppo di Calico ha dimostrato prestazioni astronomiche, eseguendo più di 50000 contenitori su 500 nodi fisici con una velocità di creazione di 20 contenitori al secondo. Non sono stati identificati problemi con il ridimensionamento. Tali risultati sono stati annunciati già all'annuncio della prima versione. Studi indipendenti focalizzati sulla produttività e sul consumo di risorse confermano inoltre che le prestazioni di Calico sono buone quasi quanto quelle di Flannel. Per esempio:

Calico per il networking in Kubernetes: introduzione e un po' di esperienza

Il progetto si sta sviluppando molto rapidamente, supporta il lavoro nelle soluzioni più diffuse gestite K8s, OpenShift, OpenStack, è possibile utilizzare Calico durante la distribuzione di un cluster utilizzando kop, sono presenti riferimenti alla realizzazione di reti Service Mesh (ecco un esempio utilizzato insieme a Istio).

Esercitati con Calico

Nel caso generale di utilizzo di Vanilla Kubernetes, l'installazione di CNI si riduce all'utilizzo del file calico.yaml, scaricato dal sito ufficiale, usando kubectl apply -f.

Di norma la versione attuale del plugin è compatibile con le ultime 2-3 versioni di Kubernetes: il funzionamento nelle versioni precedenti non è testato e non è garantito. Secondo gli sviluppatori, Calico funziona su kernel Linux superiori a 3.10 con CentOS 7, Ubuntu 16 o Debian 8, oltre a iptables o IPVS.

Isolamento nell'ambiente

Per una comprensione generale, diamo un’occhiata ad un semplice caso per capire come le policy di rete nella notazione Calico differiscono da quelle standard e come l’approccio alla creazione delle regole ne semplifica la leggibilità e la flessibilità di configurazione:

Calico per il networking in Kubernetes: introduzione e un po' di esperienza

Sono presenti 2 applicazioni web distribuite nel cluster: in Node.js e PHP, una delle quali utilizza Redis. Per bloccare l'accesso a Redis da PHP, mantenendo la connettività con Node.js, basta applicare la seguente policy:

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

Essenzialmente abbiamo consentito il traffico in entrata sulla porta Redis da Node.js. E chiaramente non hanno proibito nient’altro. Non appena viene visualizzato NetworkPolicy, tutti i selettori in esso menzionati iniziano a essere isolati, se non diversamente specificato. Tuttavia, le regole di isolamento non si applicano ad altri oggetti non coperti dal selettore.

L'esempio utilizza apiVersion Kubernetes pronto all'uso, ma nulla ti impedisce di usarlo risorsa con lo stesso nome dalla consegna Calico. La sintassi è più dettagliata, quindi dovrai riscrivere la regola per il caso precedente nella seguente forma:

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

I costrutti sopra menzionati per consentire o negare tutto il traffico attraverso la normale API NetworkPolicy contengono costrutti con parentesi difficili da comprendere e ricordare. Nel caso di Calico, per cambiare la logica di una regola firewall in quella opposta, basta cambiare action: Allow su action: Deny.

Isolamento per ambiente

Ora immagina una situazione in cui un'applicazione genera metriche aziendali per la raccolta in Prometheus e ulteriori analisi utilizzando Grafana. Il caricamento potrebbe contenere dati sensibili, che sono nuovamente visibili pubblicamente per impostazione predefinita. Nascondiamo questi dati da occhi indiscreti:

Calico per il networking in Kubernetes: introduzione e un po' di esperienza

Prometheus, di regola, viene inserito in un ambiente di servizio separato: nell'esempio sarà uno spazio dei nomi come questo:

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

Campo metadata.labels questo si è rivelato non un caso. Come menzionato sopra, namespaceSelector (così come podSelector) funziona con le etichette. Pertanto, per consentire l'acquisizione delle metriche da tutti i pod su una porta specifica, dovrai aggiungere qualche tipo di etichetta (o prelevare da quelle esistenti), quindi applicare una configurazione come:

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 se usi le policy Calico, la sintassi sarà così:

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 questo tipo di policy per esigenze specifiche, è possibile proteggersi da interferenze dannose o accidentali nel funzionamento delle applicazioni nel cluster.

La migliore pratica, secondo i creatori di Calico, è l'approccio "Blocca tutto e apri esplicitamente ciò di cui hai bisogno", documentato in documentazione ufficiale (altri seguono un approccio simile - in particolare, in articolo già citato).

Utilizzo di oggetti Calico aggiuntivi

Lascia che ti ricordi che attraverso il set esteso di API Calico puoi regolare la disponibilità dei nodi, non solo dei pod. Nell'esempio seguente utilizzando GlobalNetworkPolicy la capacità di passare richieste ICMP nel cluster è chiusa (ad esempio, ping da un pod a un nodo, tra pod o da un nodo a un pod IP):

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

Nel caso precedente, è ancora possibile che i nodi del cluster si “contattino” tra loro tramite ICMP. E questo problema è risolto con i mezzi GlobalNetworkPolicy, applicato a un'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"]

Il caso VPN

Infine, fornirò un esempio molto reale di utilizzo delle funzioni Calico nel caso di interazione quasi cluster, quando un insieme standard di politiche non è sufficiente. Per accedere all'applicazione web, i client utilizzano un tunnel VPN e questo accesso è strettamente controllato e limitato a un elenco specifico di servizi consentiti per l'utilizzo:

Calico per il networking in Kubernetes: introduzione e un po' di esperienza

I client si connettono alla VPN tramite la porta UDP standard 1194 e, una volta connessi, ricevono percorsi verso le sottoreti del cluster di pod e servizi. Intere sottoreti vengono inviate in modo da non perdere servizi durante i riavvii e i cambiamenti di indirizzo.

La porta nella configurazione è standard, il che impone alcune sfumature al processo di configurazione dell'applicazione e al suo trasferimento al cluster Kubernetes. Ad esempio, nello stesso AWS LoadBalancer per UDP è apparso letteralmente alla fine dello scorso anno in un elenco limitato di regioni e NodePort non può essere utilizzato a causa del suo inoltro su tutti i nodi del cluster ed è impossibile ridimensionare il numero di istanze del server per scopi di tolleranza agli errori. Inoltre, dovrai modificare l'intervallo predefinito di porte...

Dopo aver esaminato le possibili soluzioni, è stato scelto quanto segue:

  1. I pod con VPN sono pianificati per nodo in hostNetwork, ovvero all'IP effettivo.
  2. Il servizio è affisso all'esterno tramite ClusterIP. Sul nodo è fisicamente installata una porta, accessibile dall'esterno con piccole riserve (presenza condizionata di un indirizzo IP reale).
  3. Determinare il nodo su cui è sorto il baccello va oltre lo scopo della nostra storia. Dirò solo che puoi cablare il servizio a un nodo o scrivere un piccolo servizio sidecar che monitorerà l'attuale indirizzo IP del servizio VPN e modificherà i record DNS registrati con i client, chiunque abbia abbastanza immaginazione.

Dal punto di vista del routing, possiamo identificare in modo univoco un client VPN tramite il suo indirizzo IP rilasciato dal server VPN. Di seguito è riportato un esempio primitivo di limitazione dell'accesso di tale cliente ai servizi, illustrato sul suddetto Redis:

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 connettersi alla porta 6379, ma allo stesso tempo viene preservato il funzionamento del servizio DNS, il cui funzionamento molto spesso soffre durante la stesura delle regole. Perché, come accennato in precedenza, quando viene visualizzato un selettore, ad esso viene applicata la politica di rifiuto predefinita, se non diversamente specificato.

Risultati di

Pertanto, utilizzando l'API avanzata di Calico, puoi configurare in modo flessibile e modificare dinamicamente il routing all'interno e attorno al cluster. In generale, il suo utilizzo può sembrare come sparare ai passeri con un cannone, e l'implementazione di una rete L3 con tunnel BGP e IP-IP sembra mostruosa in una semplice installazione Kubernetes su una rete piatta... Tuttavia, per il resto lo strumento sembra abbastanza praticabile e utile .

Isolare un cluster per soddisfare i requisiti di sicurezza potrebbe non essere sempre fattibile, ed è qui che Calico (o una soluzione simile) viene in soccorso. Gli esempi forniti in questo articolo (con piccole modifiche) vengono utilizzati in diverse installazioni dei nostri clienti in AWS.

PS

Leggi anche sul nostro blog:

Fonte: habr.com

Aggiungi un commento