Calico per la rete in Kubernetes: introduzione e un po' di esperienza

Calico per la rete in Kubernetes: introduzione e un po' di esperienza

L'obiettivo dell'articolo è introdurre il lettore alle basi dell'interazione di rete e della gestione delle politiche di rete in Kubernetes, così come a un plugin esterno Calico che amplia le funzionalità standard. Verranno inoltre dimostrate la comodità di configurazione e alcune funzionalità con esempi reali dalla nostra esperienza.

Introduzione rapida al dispositivo di rete Kubernetes

Un cluster Kubernetes non può essere immaginato senza rete. Abbiamo già pubblicato materiali sulle loro basi: «Guida illustrata sulla struttura della rete in Kubernetes" e "Introduzione alle politiche di rete di Kubernetes per specialisti della sicurezza».

Nel contesto di questo articolo è importante notare che la connettività di rete tra contenitori e nodi non è gestita dal K8s stesso: per questo si utilizzano diversi plugin CNI (Container Networking Interface). Abbiamo anche parlato di questa concetto in precedenza.

Ad esempio, il plugin più comune è Flannel che garantisce una connettività di rete completa tra tutti i nodi del cluster creando ponti su ciascun nodo, associando a ciascuno di essi una sottorete. Tuttavia, una disponibilità completa e non regolamentata non è sempre utile. Per garantire una certa minima isolamento nel cluster, è necessario intervenire nella configurazione del firewall. In generale, questo è lasciato alla gestione di quel CNI, il che significa che qualsiasi intervento esterno in iptables può essere interpretato in modo errato o completamente ignorato.

E '«out of the box» che viene fornita per la gestione delle politiche di rete nel cluster Kubernetes NetworkPolicy API. Questa risorsa, che si applica agli spazi dei nomi selezionati, può contenere regole per limitare l'accesso tra applicazioni diverse. Consente anche di configurare la disponibilità 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 è un esempio non così semplice dalla documentazione ufficiale Questo potrebbe far desistere una volta per tutte dalla voglia di comprendere la logica di funzionamento delle politiche di rete. Tuttavia, cercheremo comunque di capire i principi e i metodi di elaborazione del 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 la rete in Kubernetes: introduzione e un po' di esperienza

Infatti, questa politica si divide in queste 2 categorie in base alla direzione del traffico.

Il prossimo attributo obbligatorio è il selettore; colui a cui 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 i tipi di questi oggetti devono contenere un'etichetta (label nella terminologia di Kubernetes) — sono proprio queste le etichette su cui operano le politiche.

Oltre a un numero finito di selettori raggruppati da qualche etichetta, è possibile scrivere regole come «Permetti/nega tutto/a tutti» in varie forme. A tal fine, si utilizzano costrutti del tipo:

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

— in questo esempio, a tutti i pod dell'ambiente viene negato l'accesso al traffico in ingresso. Un comportamento opposto può essere ottenuto con il seguente costrutto:

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

Analogamente per il traffico in uscita:

  podSelector: {}
  policyTypes:
  - Egress

— per disabilitarlo. Ecco cosa serve per abilitarlo:

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

Tornando alla scelta del plugin CNI per il cluster, va notato che non tutti i plugin di rete supportano l'uso delle NetworkPolicy. Ad esempio, il già citato Flannel non è in grado di configurare le politiche di rete, come è esplicitamente indicato nel repository ufficiale. Qui è anche menzionata un'alternativa: il progetto Open Source Calico, che amplia notevolmente il set di API standard di Kubernetes per quanto riguarda le politiche di rete.

Calico per la rete in Kubernetes: introduzione e un po' di esperienza

Introduzione a Calico: teoria

Il plugin Calico può essere utilizzato in integrazione con Flannel (sotto progetto Canal) o autonomamente, coprendo sia le funzioni per garantire la connettività di rete, sia le possibilità di gestione della disponibilità.

Quali opportunità offre l'uso di una soluzione «preconfezionata» K8s e del set di API di Calico?

Ecco cosa è integrato nelle NetworkPolicy:

  • le politiche sono limitate all'ambiente;
  • le politiche si applicano ai pod contrassegnati da etichette;
  • le regole possono essere applicate a pod, ambienti o sottoreti;
  • le regole possono contenere protocolli, riferimenti a porte nominati o simbolici.

Ecco come Calico amplia queste funzioni:

  • Le politiche possono essere applicate a qualsiasi oggetto: pod, container, macchina virtuale o interfaccia;
  • Le regole possono contenere un'azione specifica (divieto, autorizzazione, log);
  • Come obiettivo o sorgente delle regole può essere una porta, un intervallo di porte, protocolli, attributi HTTP o ICMP, IP o sottorete (versioni 4 o 6), qualsiasi selettore (nodi, host, ambienti);
  • In aggiunta, è possibile regolare il passaggio del traffico tramite impostazioni DNAT e politiche di inoltro del traffico.

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

Calico per la rete in Kubernetes: introduzione e un po' di esperienza

Molte soluzioni gestite popolari con K8s, come Amazon EKS, Azure AKS, Google GKE e altri, hanno iniziato a raccomandarne l'uso.

Per quanto riguarda le prestazioni, qui tutto è straordinario. Durante i test del proprio prodotto, il team di sviluppo di Calico ha dimostrato risultati astronomici, avviando oltre 50000 container su 500 nodi fisici a una velocità di creazione di 20 container al secondo. Non sono stati riscontrati problemi durante il scaling. Questi risultati sono stati annunciati già nell'annuncio della prima versione. Ricercatori indipendenti, impegnati a valutare la capacità di throughput e il consumo di risorse, confermano anche le prestazioni di Calico, che sono praticamente alla pari con Flannel. Ad esempio:

Calico per la rete in Kubernetes: introduzione e un po' di esperienza

Il progetto si sviluppa molto rapidamente, supportando il funzionamento nelle soluzioni gestite K8s, OpenShift, OpenStack, e c'è la possibilità di utilizzare Calico durante il dispiegamento di un cluster tramite kops, ci sono menzioni della costruzione di reti Service Mesh (ecco un esempio di utilizzo congiunto con Istio).

Pratica con Calico

In generale, utilizzare Kubernetes vaniglia richiede l'installazione di un CNI applicando il file calico.yaml, scaricato dal sito ufficiale, tramite kubectl apply -f.

Di norma, l'ultima versione del plugin è compatibile con le ultime 2-3 versioni di Kubernetes: il funzionamento su versioni più vecchie non viene testato e non è garantito. Secondo le dichiarazioni degli sviluppatori, Calico funziona su un kernel Linux superiore alla versione 3.10 sotto CentOS 7, Ubuntu 16 o Debian 8, sopra iptables o IPVS.

Isolamento all'interno dell'ambiente

Per una comprensione generale, consideriamo un caso semplice per capire in cosa differiscono le policy di rete in notazione Calico da quelle standard e come l'approccio alla formulazione delle regole ne semplifica la leggibilità e la flessibilità di configurazione:

Calico per la rete in Kubernetes: introduzione e un po' di esperienza

Nel cluster sono distribuite 2 applicazioni web: una basata su Node.js e l'altra su PHP, una delle quali utilizza Redis. Per limitare l'accesso a Redis da PHP, mantenendo al contempo la connettività con Node.js, è sufficiente 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

In sostanza, abbiamo autorizzato il traffico in ingresso sulla porta di Redis da Node.js. E non abbiamo esplicitamente vietato nulla di diverso. Non appena appare una NetworkPolicy, tutti i selettori menzionati iniziano a essere isolati, a meno che non si disposi diversamente. In questo modo, le regole di isolamento non si applicano ad altri oggetti che non sono coperti dal selettore.

Nell'esempio si utilizza apiVersion di Kubernetes "out of the box", ma nulla impedisce di utilizzare la risorsa omonima fornita da Calico. La sintassi è più dettagliata, quindi sarà necessario riscrivere la regola per il caso sopra descritto 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

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

Isolamento per ambienti

Ora immaginiamo una situazione in cui un'applicazione genera metriche aziendali per la raccolta in Prometheus e una successiva analisi tramite Grafana. Nell'estrazione potrebbero essere presenti dati sensibili, che per impostazione predefinita sono nuovamente disponibili per la visione pubblica. Isoleremo questi dati da sguardi indiscreti:

Calico per la rete in Kubernetes: introduzione e un po' di esperienza

Prometheus è generalmente collocato in un ambiente operativo separato: nell'esempio sarà uno spazio dei nomi del seguente tipo:

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

Campo metadata.labels non è qui per caso. Come già accennato sopra, namespaceSelector (come e podSelector) opera con etichette. Pertanto, per consentire la raccolta delle metriche da tutti i pod su una porta specifica, sarà necessario aggiungere un'etichetta (o prenderne una da quelle esistenti), e poi applicare una configurazione simile a:

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

Nel caso di utilizzo delle 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 per esigenze specifiche, è possibile proteggere il funzionamento delle applicazioni nel cluster da interventi malevoli o accidentali.

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

Applicazione di oggetti Calico aggiuntivi

Ricordo che tramite il set avanzato di API Calico si può regolare l'accessibilità dei nodi, non limitandosi ai pod. Nel seguente esempio tramite GlobalNetworkPolicy viene chiusa la possibilità di ricevere richieste ICMP nel cluster (ad esempio, ping da un pod a un nodo, tra pod o da un nodo all'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

Nel caso sopra indicato, rimane la possibilità per i nodi del cluster di comunicare tra di loro tramite ICMP. Questo problema viene affrontato mediante 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, fornirò un esempio reale di utilizzo delle funzioni Calico per situazioni di interazione attorno al cluster, quando il set standard di politiche non è sufficiente. Per accedere a un'applicazione web da parte dei clienti viene utilizzato un tunnel VPN, e questo accesso è rigorosamente controllato e limitato a un elenco specifico di servizi consentiti:

Calico per la rete in Kubernetes: introduzione e un po' di esperienza

I clienti si connettono al VPN tramite la porta UDP standard 1194 e, al momento della connessione, ricevono percorsi verso le sottoreti cluster dei pod e dei servizi. Le sottoreti vengono pushate interamente, per non perdere i servizi durante i riavvii e il cambio degli indirizzi.

La porta nella configurazione è standard, il che comporta alcune sfumature nel processo di configurazione dell'applicazione e nel suo trasferimento nel cluster Kubernetes. Ad esempio, lo 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 della sua esposizione su tutti i nodi del cluster, rendendo impossibile scalare il numero di istanze del server per motivi di tolleranza ai guasti. Inoltre, sarà necessario cambiare l'intervallo delle porte scelto di default…

Dopo aver considerato le possibili soluzioni, è stata scelta la seguente:

  1. I pod con VPN sono previsti su un nodo in modalità hostNetwork, cioè sull'IP effettivo.
  2. Il servizio è esposto all'esterno tramite ClusterIP. Sul nodo viene fisicamente attivata una porta, che è accessibile dall'esterno con alcune precisazioni (la condizionale presenza di un indirizzo IP reale).
  3. La definizione del nodo su cui è stato attivato il pod va oltre il nostro racconto. Posso solo dire che è possibile "agganciare" rigidamente il servizio a un nodo o scrivere un piccolo servizio sidecar che monitori l'attuale indirizzo IP del servizio VPN e modifichi i record DNS scritti presso i clienti — a seconda della fantasia.

Dal punto di vista della routizzazione, possiamo identificare con certezza il cliente dietro il VPN dal suo indirizzo IP fornito dal server VPN. Di seguito è un semplice esempio di limitazione dell'accesso a tale cliente ai servizi, un'illustrazione sull'anzidetto 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 viene rigorosamente vietata la connessione alla porta 6379, ma nel contempo viene mantenuto il funzionamento del servizio DNS, la cui operatività soffre piuttosto spesso durante la redazione delle regole. Perché, come già accennato, al sorgere del selettore si applica una politica di divieti per default, a meno che non sia specificato diversamente.

Conclusioni

In questo modo, grazie all'API estesa di Calico, è possibile configurare in modo flessibile e modificare dinamicamente il routing all'interno del cluster e attorno ad esso. In generale, il suo utilizzo potrebbe sembrare un colpo di cannone contro un passero, e l'implementazione di una rete L3 con tunneling BGP e IP-IP appare mostruosa in una semplice installazione di Kubernetes in una rete piatta... Tuttavia, a parte ciò, lo strumento si presenta come piuttosto sostenibile e utile.

L'isolamento del cluster per soddisfare i requisiti di sicurezza non sempre è attuabile, ed è proprio in questi casi che Calico (o una soluzione simile) viene in aiuto. Gli esempi forniti nell'articolo (con qualche piccola modifica) sono utilizzati in diverse installazioni dei nostri clienti su AWS.

P.S.

Leggi 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