
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: «" e "».
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 .
Ad esempio, il plugin più comune è 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 . 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: 5978Questo è un esempio non così semplice dalla 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).

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:
- IngressAnalogamente per il traffico in uscita:
podSelector: {}
policyTypes:
- Egress— per disabilitarlo. Ecco cosa serve per abilitarlo:
podSelector: {}
egress:
- {}
policyTypes:
- EgressTornando 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 nel repository ufficiale. Qui è anche menzionata un'alternativa: il progetto Open Source , che amplia notevolmente il set di API standard di Kubernetes per quanto riguarda le politiche di rete.

Introduzione a Calico: teoria
Il plugin Calico può essere utilizzato in integrazione con Flannel (sotto progetto ) 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, :

Molte soluzioni gestite popolari con K8s, come , , 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 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. :

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 , ci sono menzioni della costruzione di reti Service Mesh ( di utilizzo congiunto con Istio).
Pratica con Calico
In generale, utilizzare Kubernetes vaniglia richiede l'installazione di un CNI applicando il file calico.yaml, , 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:

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: 6379In 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 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:

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: 9100Nel 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:
- 9100In 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 (un approccio simile è seguito anche da altri, in particolare, in ).
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:

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:
- I pod con VPN sono previsti su un nodo in modalità
hostNetwork, cioè sull'IP effettivo. - 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). - 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:
- «»;
- «Guida illustrata alla configurazione della rete in Kubernetes»: , ;
- «».
Fonte: habr.com
