
Ziel dieses Artikels ist es, den Lesern die Grundlagen der Netzwerkkommunikation und das Management von Netzwerkrichtlinien in Kubernetes vorzustellen, sowie das externe Plugin Calico, das die Standardfunktionen erweitert. Dabei werden die Benutzerfreundlichkeit der Konfiguration und einige Features anhand realer Beispiele aus unserer Praxis demonstriert.
Schnelle Einführung in die Netzwerktechnologie von Kubernetes
Ein Kubernetes-Cluster ist ohne Netzwerk nicht vorstellbar. Wir haben bereits Materialien zu den Grundlagen veröffentlicht: „» und «».
Im Kontext dieses Artikels ist es wichtig zu betonen, dass nicht Kubernetes selbst die Netzwerkverbindung zwischen Containern und Knoten verwaltet: Hierfür werden verschiedene CNI-Plugins (Container Networking Interface) verwendet. Mehr zu diesem Konzept haben wir .
Zum Beispiel ist eines der am häufigsten verwendeten Plugins – — gewährleistet vollständige Netzwerkverbindung zwischen allen Knoten des Clusters, indem Brücken auf jedem Knoten hergestellt werden und ein Subnetz zugewiesen wird. Allerdings ist vollständige und unregulierte Verfügbarkeit nicht immer vorteilhaft. Um eine gewisse minimale Isolierung im Cluster zu gewährleisten, ist es notwendig, in die Firewall-Konfiguration einzugreifen. Im Allgemeinen liegt dies in der Verantwortung des CNI, weshalb jegliche externen Eingriffe in die iptables möglicherweise fehlerhaft interpretiert oder vollständig ignoriert werden können.
Und 'out of the box' wird zur Verwaltung von Netzwerk-Richtlinien im Kubernetes-Cluster bereitgestellt . Diese Ressource, die auf ausgewählte Namensräume angewendet wird, kann Regeln enthalten, um den Zugriff zwischen verschiedenen Anwendungen zu beschränken. Sie ermöglicht es auch, die Erreichbarkeit zwischen bestimmten Pods, Umgebungen (Namensräumen) oder IP-Adressbereichen zu konfigurieren:
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: 5978Dieses nicht ganz einfache Beispiel wird vielleicht auf ewig die Neugier auf die Logik der Netzwerkrichtlinien mindern. Dennoch versuchen wir, die grundlegenden Prinzipien und Methoden zur Verarbeitung des Datenverkehrs durch Netzwerkrichtlinien zu verstehen…
Es liegt auf der Hand, dass es zwei Arten von Datenverkehr gibt: eingehend in den Pod (Ingress) und aus ihm herausgehend (Egress).

Tatsächlich teilt sich die Richtlinie nach diesen zwei Kategorien des Datenflusses auf.
Ein weiterer notwendiger Attribut ist der Selektor; derjenige, für den die Regel gilt. Dies kann ein Pod (oder eine Gruppe von Pods) oder eine Umgebung (d.h. Namespace) sein. Eine wichtige Detail: Beide Arten dieser Objekte müssen ein Label enthalten (Bezeichnung in der Kubernetes-Terminologie) — damit arbeiten die Richtlinien.
Neben der begrenzten Anzahl von Selektoren, die mit einem bestimmten Label verbunden sind, besteht die Möglichkeit, Regeln wie 'Alles zulassen/alles verbieten' in verschiedenen Variationen zu schreiben. Dazu werden Konstruktionen wie die folgenden verwendet:
podSelector: {}
ingress: []
policyTypes:
- Ingress— in diesem Beispiel wird der eingehende Datenverkehr für alle Pods der Umgebung blockiert. Das Gegenteil kann mit folgender Konstruktion erreicht werden:
podSelector: {}
ingress:
- {}
policyTypes:
- IngressÄhnlich für den ausgehenden Verkehr:
podSelector: {}
policyTypes:
- Egress— um ihn zu deaktivieren. Und so sieht es aus, um ihn zu aktivieren:
podSelector: {}
egress:
- {}
policyTypes:
- EgressZurück zur Auswahl des CNI-Plugins für den Cluster: Es ist wichtig zu beachten, dass nicht jedes Netzwerk-Plugin mit NetworkPolicy arbeitet.Zum Beispiel kann das bereits erwähnte Flannel keine Netzwerkpolitiken konfigurieren, was im offiziellen Repository. Dort wird auch eine Alternative erwähnt — ein Open-Source-Projekt, , das das standardmäßige API-Angebot von Kubernetes in Bezug auf Netzwerkpolitiken erheblich erweitert.

Lernen Sie Calico kennen: Theorie
Das Calico-Plugin kann in Kombination mit Flannel verwendet werden (Unterprojekt ) oder eigenständig, sowohl für Netzwerkverbindungsfunktionen als auch für Verfügbarkeitsmanagementmöglichkeiten.
Welche Möglichkeiten bietet die Verwendung einer „Box-Lösung“ für K8s und einem API-Set von Calico?
Das ist in NetworkPolicy integriert:
- Politiken sind auf Umgebungen beschränkt;
- Politiken werden auf Pods mit bestimmten Labels angewendet;
- Regeln können auf Pods, Umgebungen oder Subnetze angewendet werden;
- Regeln können Protokolle, benannte oder symbolische Portangaben enthalten.
So erweitert Calico diese Funktionen:
- Politiken können auf jedes Objekt angewendet werden: Pod, Container, virtuelle Maschine oder Schnittstelle;
- Regeln können eine spezifische Aktion enthalten (Verbot, Erlaubnis, Protokollierung);
- Als Ziel oder Quelle von Regeln können Port, Portbereich, Protokolle, HTTP- oder ICMP-Attribute, IP oder Subnetz (4. oder 6. Generation), beliebige Selektoren (Knoten, Hosts, Umgebungen) verwendet werden;
- Zusätzlich kann der Datenverkehr über DNAT-Einstellungen und Traffic-Forwarding-Politiken geregelt werden.
Die ersten Commits auf GitHub im Calico-Repository stammen aus Juli 2016, und bereits ein Jahr später nahm das Projekt eine führende Rolle in der Netzwerkverbindung von Kubernetes ein – dies belegen beispielsweise die Umfrageergebnisse. :

Viele große Managed-Lösungen mit K8s, wie , , und andere, empfehlen dessen Verwendung.
Was die Leistungsfähigkeit betrifft, so ist alles bemerkenswert. Bei Tests ihres Produkts demonstrierte das Calico-Entwicklungsteam astronomische Ergebnisse, indem es über 50.000 Container auf 500 physischen Knoten mit einer Geschwindigkeit von 20 Containern pro Sekunde startete. Es wurden keine Probleme beim Skalieren festgestellt. Solche Ergebnisse Das Projekt entwickelt sich sehr schnell, unterstützt den Betrieb in beliebten Managed-K8s-Lösungen, OpenShift, OpenStack und bietet die Möglichkeit, Calico beim Bereitstellen eines Clusters mit Hilfe von :

kops , es gibt Hinweise auf den Aufbau von Service Mesh-Netzwerken ( in Kombination mit Istio).
Praxis mit Calico
Im allgemeinen Fall der Verwendung von Vanilla Kubernetes reduziert sich die Installation von CNI auf die Anwendung der Datei calico.yaml, , mithilfe von kubectl apply -f.
In der Regel ist die aktuelle Version des Plugins mit den letzten 2-3 Versionen von Kubernetes kompatibel: die Funktionsfähigkeit in älteren Versionen wird nicht getestet oder garantiert. Laut den Entwicklern funktioniert Calico auf Linux-Kernen über 3.10, betrieben mit CentOS 7, Ubuntu 16 oder Debian 8, auf iptables oder IPVS.
Isolation innerhalb der Umgebung
Um ein allgemeines Verständnis zu schaffen, betrachten wir einen einfachen Fall, um zu erkennen, wie sich die Netzwerkpolitiken in der Notation von Calico von den Standardpolicies unterscheiden und wie der Ansatz zur Erstellung von Regeln deren Lesbarkeit und Flexibilität bei der Konfiguration vereinfacht:

Im Cluster sind 2 Webanwendungen ausgeführt: eine auf Node.js und die andere auf PHP, wobei eine davon Redis verwendet. Um den Zugriff auf Redis aus PHP zu verweigern und gleichzeitig die Verbindung zu Node.js aufrechtzuerhalten, reicht es aus, die folgende Policy anzuwenden:
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: 6379Im Grunde haben wir den eingehenden Datenverkehr zu dem Redis-Port aus Node.js erlaubt und explizit nichts anderes verboten. Sobald eine NetworkPolicy in Kraft tritt, werden alle darin genannten Selektoren isoliert, sofern nicht anders angegeben. Die Isolationsregeln gelten dabei nicht für andere Objekte, die durch den Selektor nicht abgedeckt sind.
Im Beispiel wird apiVersion Kubernetes „out of the box“ verwendet, aber es steht nichts im Wege, Die Syntax dort ist umfangreicher, weshalb die Regel für den oben beschriebenen Fall in der folgenden Form umgeschrieben werden muss:
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 Die oben genannten Konstruktionen zur Erlaubnis oder Verweigerung des gesamten Datenverkehrs mit der normalen NetworkPolicy API enthalten komplexe, schwer verständliche Konstruktionen mit Klammern. Bei Calico genügt es, um die Logik der Firewall-Regel umzukehren, lediglich action: Allow findet man action: Deny.
Isolierung nach Umgebungen
Stellen wir uns nun die Situation vor, in der eine Anwendung Geschäftsdaten generiert, um sie in Prometheus zu sammeln und anschließend mit Grafana zu analysieren. In den Exporten könnten sensible Daten enthalten sein, die standardmäßig wieder für jedermann zugänglich sind. Lassen Sie uns diese Daten vor unbefugtem Zugriff schützen:

Prometheus wird in der Regel in eine separate Dienstumgebung überführt – in diesem Beispiel wäre dies ein Namespace folgender Art:
apiVersion: v1
kind: Namespace
metadata:
labels:
module: prometheus
name: kube-prometheus Feld metadata.labels Hier ist das nicht zufällig. Wie bereits erwähnt, namespaceSelector wie auch podSelectorarbeitet mit Labels. Daher müssen wir, um Metriken von allen Pods an einem bestimmten Port abzurufen, ein beliebiges Label hinzufügen (oder ein bestehendes verwenden) und dann eine Konfiguration wie folgt anwenden:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-metrics-prom
spec:
podSelector: {}
ingress:
- from:
- namespaceSelector:
matchLabels:
module: prometheus
ports:
- protocol: TCP
port: 9100Im Fall der Verwendung von Calico-Richtlinien wäre die Syntax wie folgt:
apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:
name: allow-metrics-prom
spec:
ingress:
- action: Allow
protocol: TCP
source:
namespaceSelector: module == 'prometheus'
destination:
ports:
- 9100Durch die Implementierung solcher Richtlinien, die auf spezifische Bedürfnisse zugeschnitten sind, kann man das Risiko böswilliger oder versehentlicher Eingriffe in die Funktionsweise von Anwendungen im Cluster reduzieren.
Die beste Praxis, so die Entwickler von Calico, ist der Ansatz "Alles verbieten und explizit das Notwendige öffnen", wie in beschrieben, der von anderen Ansätzen ebenfalls verfolgt wird — insbesondere in der ).
Die Anwendung zusätzlicher Objekte von Calico
Erinnern Sie sich, dass es mit dem erweiterten API-Satz von Calico möglich ist, die Erreichbarkeit von Knoten zu steuern, ohne sich nur auf Pods zu beschränken. Im nächsten Beispiel wird durch die GlobalNetworkPolicy der Durchlauf von ICMP-Anfragen im Cluster blockiert (zum Beispiel Pings von einem Pod zu einem Knoten, zwischen Pods oder von einem Knoten zur IP eines Pods):
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 In dem oben genannten Fall bleibt den Knoten des Clusters die Möglichkeit, über ICMP miteinander zu kommunizieren. Dieses Problem kann mit Hilfe von GlobalNetworkPolicy, angewendet auf die Entität 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"]Fall mit VPN
Lassen Sie mich ein ganz konkretes Beispiel für die Funktionen von Calico geben, wenn es um die Interaktion innerhalb des Clusters geht, wobei der Standard Satz von Richtlinien nicht ausreicht. Um auf die Webanwendung zuzugreifen, wird ein VPN-Tunnel genutzt, und dieser Zugang wird streng kontrolliert und auf eine bestimmte Liste genehmigter Dienste beschränkt:

Kunden verbinden sich über den Standard-UDP-Port 1194 mit dem VPN und erhalten beim Anschluss Routen zu den Cluster-Subnetzwerken der Pods und Services. Die Subnetze werden vollständig übertragen, um den Verlust von Services bei Neustarts und Adressänderungen zu vermeiden.
Der Standardport in der Konfiguration bringt einige Besonderheiten mit sich, die den Konfigurationsprozess der Anwendung sowie deren Transfer in ein Kubernetes-Cluster beeinflussen. Zum Beispiel wurde der AWS LoadBalancer für UDP erst Ende letzten Jahres in einer begrenzten Anzahl von Regionen eingeführt. NodePort kann nicht verwendet werden, da dieser auf allen Knoten im Cluster weitergeleitet wird und die Anzahl der Serverinstanzen, die aus Redundanzgründen bereitgestellt werden können, nicht skalierbar ist. Zudem muss der standardmäßig gewählte Portbereich geändert werden...
Nach Prüfung möglicher Lösungen wurde Folgendes ausgewählt:
- Die Pods mit VPN sind für einen Knoten im Modus geplant
hostNetwork, also auf der tatsächlichen IP. - Der Dienst wird nach außen über
ClusterIPbereitgestellt. An dem Knoten wird physisch ein Port aktiviert, der unter bestimmten Voraussetzungen (vorhandene reale IP-Adresse) von außen erreichbar ist. - Die Bestimmung des Knotens, auf dem der Pod gestartet wurde, liegt außerhalb unserer Erzählung. Ich möchte nur sagen, dass man den Dienst entweder fest an einen Knoten binden oder einen kleinen Sidecar-Dienst schreiben kann, der die aktuelle IP-Adresse des VPN-Dienstes überwacht und die DNS-Einträge bei den Kunden anpasst – was auch immer die Kreativität hergibt.
In Bezug auf die Routing-Technologie können wir einen VPN-Kunden eindeutig anhand seiner IP-Adresse identifizieren, die vom VPN-Server vergeben wird. Im Folgenden finden Sie ein einfaches Beispiel für die Einschränkung des Zugriffs für einen solchen Kunden auf Dienste, veranschaulicht anhand des zuvor genannten 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]Hier wird die Verbindung zu Port 6379 strikt verweigert, während die Funktionalität des DNS-Services aufrechterhalten bleibt, deren Betrieb häufig bei der Erstellung von Regeln leidet. Denn, wie bereits erwähnt, wird bei Vorhandensein eines Selectors eine restriktive Standardrichtlinie angewendet, es sei denn, es wird etwas anderes angegeben.
Ergebnisse
Mit der erweiterten Calico-API können Sie die Routing-Konfiguration im Cluster flexibel anpassen und dynamisch ändern. Im Allgemeinen fühlt sich die Nutzung wie eine überdimensionale Lösung für kleinere Probleme an, während der Einsatz eines L3-Netzwerks mit BGP- und IP-IP-Tunneln in einer einfachen Kubernetes-Installation in einem flachen Netzwerk recht komplex erscheint… Dennoch erweist sich das Tool insgesamt als durchaus funktionsfähig und nützlich.
Die Isolierung des Clusters, um Sicherheitsanforderungen zu erfüllen, ist nicht immer umsetzbar. In solchen Fällen kommt Calico (oder eine ähnliche Lösung) zur Anwendung. Die in diesem Artikel genannten Beispiele (mit geringfügigen Anpassungen) werden in mehreren Installationen bei unseren Kunden in AWS verwendet.
P.S.
Lesen Sie auch in unserem Blog:
- «»;
- „Illustriertes Handbuch zur Netzwerkausstattung in Kubernetes“: , ;
- «».
Quelle: habr.com
