
Das Ziel des Artikels ist es, den Leser mit den Grundlagen der Netzwerkinteraktion und dem Management von Netzwerkrichtlinien in Kubernetes sowie mit dem Drittanbieter-Plugin Calico, das die Standardfunktionen erweitert, vertraut zu machen. Nebenbei werden die Benutzerfreundlichkeit der Konfiguration und einige Funktionen anhand realer Beispiele aus unserer Erfahrung demonstriert.
Schnelle Einführung in das Netzwerkgerät 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 beachten, dass die Netzwerkverbindung zwischen Containern und Knoten nicht von K8s selbst verantwortet wird: Hierfür kommen verschiedene CNI-Plugins (Container Networking Interface) zum Einsatz. Mehr über dieses Konzept haben wir .
Das am weitesten verbreitete dieser Plugins ist – es gewährleistet die vollständige Netzwerkverbindung zwischen allen Knoten des Clusters, indem es Brücken auf jedem Knoten aufbaut und diesem ein Subnetz zuweist. Eine vollständige und unregulierte Erreichbarkeit ist jedoch nicht immer vorteilhaft. Um eine gewisse minimale Isolation im Cluster zu gewährleisten, ist es erforderlich, in die Firewall-Konfiguration einzugreifen. Im Allgemeinen liegt dies in der Verantwortung des genannten CNI, weshalb alle externen Eingriffe in iptables möglicherweise falsch interpretiert oder sogar völlig ignoriert werden.
Da "out of the box" eine Verwaltung von Netzwerkrichtlinien im Kubernetes-Cluster bereitgestellt wird, gibt es eine . Dieser Ressource, die auf ausgewählte Namensräume anwendbar ist, können Regeln zur Zugangstrennung von einer Anwendung zur anderen beigefügt werden. Sie ermöglicht auch die Konfiguration der Erreichbarkeit zwischen bestimmten Pods, Umgebungen (Namensräumen) oder IP-Adressblöcken:
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 gerade einfache Beispiel stammt aus Es könnte endgültig das Verlangen dämpfen, die Logik der Netzwerkrichtlinien zu verstehen. Dennoch werden wir versuchen, die grundlegenden Prinzipien und Methoden der Verarbeitung von Datenverkehr durch Netzwerkrichtlinien zu verstehen…
Es ist logisch, dass es 2 Arten von Datenverkehr gibt: eingehender (Ingress) und ausgehender (Egress).

Tatsächlich wird die Richtlinie nach diesen 2 Kategorien der Bewegungsrichtung unterteilt.
Das nächste erforderliche Attribut ist der Selector; das ist der, auf den die Regel angewendet wird. Das kann ein Pod (oder eine Gruppe von Pods) oder ein Namespace sein. Wichtig ist: Beide Typen dieser Objekte müssen ein Label enthalten (label in der Kubernetes-Terminologie) – genau diese haben Einfluss auf die Richtlinien.
Neben der endlichen Anzahl an Selectoren, die durch ein bestimmtes Label verbunden sind, gibt es die Möglichkeit, Regeln wie "Alles erlauben/verbieten" in verschiedenen Variationen zu schreiben. Hierfür werden Konstruktionen wie diese verwendet:
podSelector: {}
ingress: []
policyTypes:
- Ingress– In diesem Beispiel wird allen Pods im Namespace der eingehende Datenverkehr verwehrt. Um das Gegenteil zu erreichen, kann folgende Konstruktion verwendet werden:
podSelector: {}
ingress:
- {}
policyTypes:
- IngressÄhnlich für den ausgehenden Datenverkehr:
podSelector: {}
policyTypes:
- Egress– um ihn auszuschalten. Und das ist für die Aktivierung:
podSelector: {}
egress:
- {}
policyTypes:
- EgressWenn wir zur Auswahl des CNI-Plugins für den Cluster zurückkehren, ist zu beachten, dass nicht jedes Netzwerk-Plugin die Verwendung von NetworkPolicy unterstützt. Zum Beispiel kann das bereits erwähnte Flannel keine Netzwerkrichtlinien konfigurieren, was im offiziellen Repository. Dort wird auch eine Alternative erwähnt – ein Open Source-Projekt , das die Standard-API von Kubernetes im Hinblick auf Netzwerkrichtlinien erheblich erweitert.

Lernen wir Calico kennen: Theorie
Das Calico-Plugin kann in Kombination mit Flannel (Unterprojekt ) oder eigenständig verwendet werden und deckt sowohl die Funktionen zur Gewährleistung der Netzwerkverbindung als auch die Möglichkeiten zur Verwaltung der Verfügbarkeit ab.
Welche Möglichkeiten bietet die Verwendung eines "Out-of-the-Box"-K8s und des API-Sets aus Calico?
Das ist in NetworkPolicy integriert:
- Richtlinien sind auf den Namespace beschränkt;
- Richtlinien werden auf Pods angewendet, die mit Labels versehen sind;
- Regeln können auf Pods, Namespaces oder Subnetze angewendet werden;
- Regeln können Protokolle, benannte oder symbolische Portangaben enthalten.
Und so erweitert Calico diese Funktionen:
- Richtlinien können auf jedes Objekt angewendet werden: Pod, Container, virtuelle Maschine oder Schnittstelle;
- Die Regeln können eine spezifische Aktion enthalten (Verbot, Erlaubnis, Protokollierung);
- Als Ziel oder Quelle der Regeln können Port, Portbereiche, Protokolle, HTTP- oder ICMP-Attribute, IP oder Subnetz (Version 4 oder 6) sowie beliebige Selektoren (Knoten, Hosts, Umgebungen) verwendet werden;
- Zusätzlich kann der Datenverkehr durch DNAT-Einstellungen und Traffic-Forwarding-Richtlinien reguliert werden.
Die ersten Commits auf GitHub im Calico-Repository stammen aus dem Juli 2016, und bereits ein Jahr später belegte das Projekt führende Positionen in der Organisation des Netzwerkbetriebs von Kubernetes – wie beispielsweise die Ergebnisse einer Umfrage belegen, :

Viele große Managed-Lösungen mit K8s, wie , , und andere haben empfohlen, es zu verwenden.
Was die Leistung betrifft, so ist alles hervorragend. Bei den Tests ihres Produkts zeigte das Calico-Entwicklungsteam astronomische Ergebnisse, indem es über 50.000 Container auf 500 physischen Knoten mit einer Geschwindigkeit von 20 Containern pro Sekunde startete. Bei der Skalierung traten keine Probleme auf. Solche Ergebnisse Unabhängige Studien zur Bandbreite und Ressourcennutzung bestätigen ebenfalls die Leistung von Calico, die praktisch gleichwertig mit Flannel ist. :

Das Projekt entwickelt sich sehr schnell, es wird in beliebten Managed-K8s-Lösungen, OpenShift, OpenStack unterstützt, und es besteht die Möglichkeit, Calico beim Bereitstellen eines Clusters mit Hilfe von , wobei auch von der Erstellung von Service-Mesh-Netzwerken die Rede ist ( für die Verwendung zusammen mit Istio).
Praxis mit Calico
Im allgemeinen Fall der Verwendung von Vanilla Kubernetes beschränkt sich die Installation von CNI auf die Verwendung 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: Einstellungen in älteren Versionen werden nicht getestet und nicht garantiert. Den Angaben der Entwickler zufolge funktioniert Calico auf einem Linux-Kernel höher als 3.10 unter CentOS 7, Ubuntu 16 oder Debian 8, über iptables oder IPVS.
Isolation innerhalb der Umgebung
Um ein allgemeines Verständnis zu schaffen, betrachten wir einen einfachen Fall, um zu verstehen, wie sich Netzwerkrichtlinien in der Calico-Notation von den Standardrichtlinien unterscheiden und wie der Ansatz zur Erstellung von Regeln ihre Lesbarkeit und Konfigurationsflexibilität vereinfacht:

Im Cluster sind 2 Webanwendungen bereitgestellt: eine auf Node.js und eine auf PHP, von denen eine Redis verwendet. Um den Zugriff auf Redis aus PHP zu sperren und gleichzeitig die Verbindung zu Node.js aufrechtzuerhalten, reicht es, die folgende Richtlinie 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 eingehenden Datenverkehr auf den Redis-Port aus Node.js erlaubt. Und wir haben ausdrücklich nichts anderes verboten. Sobald eine NetworkPolicy vorhanden ist, beginnen alle darin erwähnten Selektoren sich zu isolieren, sofern nicht anders angegeben. Dabei gelten die Isolierungsregeln nicht für andere Objekte, die nicht durch den Selektor abgedeckt sind.
Im Beispiel wird verwendet apiVersion von Kubernetes "out-of-the-box", aber nichts hindert daran, . Die Syntax dort ist umfangreicher, daher muss die Regel für den oben beschriebenen Fall wie folgt umgeschrieben werden:
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 zum Erlauben oder Verhindern des gesamten Datenverkehrs über die Standard NetworkPolicy API enthalten komplexe, schwer verständliche Konstruktionen mit Klammern. Im Fall von Calico genügt es, um die Logik der Firewall-Regel umzukehren, einfach action: Allow auf action: Deny.
Isolierung nach Umgebungen
Stellen wir uns nun die Situation vor, dass eine Anwendung Geschäftsdaten für deren Sammlung in Prometheus und anschließende Analyse über Grafana generiert. In den Exports können sensible Daten enthalten sein, die standardmäßig wiederum für alle zugänglich sind. Lassen Sie uns diese Daten vor neugierigen Augen schützen:

Prometheus wird in der Regel in eine separate Serviceumgebung ausgelagert – im Beispiel wird dies ein Namespace des folgenden Typs sein:
apiVersion: v1
kind: Namespace
metadata:
labels:
module: prometheus
name: kube-prometheus Feld metadata.labels ist hier nicht zufällig. Wie bereits oben erwähnt, namespaceSelector (wie auch podSelector) arbeitet mit Labels. Daher muss, um Metriken von allen Pods an einem bestimmten Port abzurufen, irgendein Label hinzugefügt werden (oder eines aus den bestehenden genommen) und dann eine Konfiguration wie folgt angewendet werden:
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 Falle 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:
- 9100Insgesamt kann man durch das Hinzufügen solcher Richtlinien für spezifische Bedürfnisse vor böswilligem oder unbeabsichtigtem Eingreifen in die Funktion von Anwendungen im Cluster schützen.
Das beste Vorgehen, so die Entwickler von Calico, ist der Ansatz "Verbot alles und öffne explizit, was nötig ist", verankert in (diesem Ansatz folgen auch andere, insbesondere in der ).
Anwendung zusätzlicher Calico-Objekte
Ich erinnere daran, dass man mit dem erweiterten API-Satz von Calico die Verfügbarkeit von Knoten regulieren kann, ohne sich auf Pods zu beschränken. Im folgenden Beispiel wird dies durch GlobalNetworkPolicy verhindert, dass ICMP-Anfragen im Cluster durchkommen (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 Im obigen Fall bleibt es den Knoten des Clusters möglich, über ICMP miteinander zu kommunizieren. Und dieses Problem wird durch 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
Abschließend möchte ich ein ganz reales Beispiel für die Nutzung der Funktionen von Calico im Fall der internen Cluster-Interaktion anführen, wenn der Standard-Satz an Richtlinien nicht ausreicht. Für den Zugriff von Clients auf die Webanwendung wird ein VPN-Tunnel verwendet, und dieser Zugriff wird streng kontrolliert und auf eine bestimmte Liste von zugelassenen Diensten beschränkt:

Kunden verbinden sich über den standardmäßigen UDP-Port 1194 mit dem VPN und erhalten beim Verbindungsaufbau Routen zu den Cluster-Subnets der Pods und Dienste. Die Subnetze werden vollständig gepusht, um Dienste bei Neustarts und Adressänderungen nicht zu verlieren.
Der Port in der Konfiguration ist standardmäßig, was einige Nuancen im Konfigurationsprozess der Anwendung und deren Migration in den Kubernetes-Cluster mit sich bringt. Zum Beispiel wurde der UDP-Pool im AWS LoadBalancer erst Ende letzten Jahres in einer begrenzten Anzahl von Regionen eingeführt, und NodePort kann nicht verwendet werden, da er auf allen Knoten des Clusters weitergeleitet wird und es unmöglich ist, die Anzahl der Serverinstanzen zur Gewährleistung der Ausfallsicherheit zu skalieren. Zudem muss der standardmäßig gewählte Portbereich geändert werden...
Nach Durchsicht der möglichen Lösungen wurde Folgendes ausgewählt:
- Pods mit VPN sind für den Knoten im Modus
hostNetwork, das heißt, sie verwenden die tatsächliche IP. - Der Dienst wird nach außen über
ClusterIPbereitgestellt. Auf dem Knoten wird physisch ein Port aktiviert, der von außen unter bestimmten Vorbedingungen (vor allem hinsichtlich einer realen IP-Adresse) zugänglich ist. - Die Bestimmung des Knotens, auf dem der Pod läuft, liegt außerhalb unserer Erzählung. Ich kann nur sagen, dass der Dienst fest an einen Knoten gebunden werden kann oder ein kleiner Sidecar-Dienst geschrieben werden kann, der die aktuelle IP-Adresse des VPN-Dienstes überwacht und die DNS-Einträge von Kunden anpasst – je nachdem, wer welche Ideen hat.
Im Hinblick auf die Routenidentifikation können wir den Kunden im VPN eindeutig anhand seiner vom VPN-Server zugewiesenen IP-Adresse identifizieren. Unten ist ein einfaches Beispiel zur Einschränkung des Zugriffs für einen solchen Kunden auf Dienste, dargestellt am zuvor erwähnten 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 der Zugang zu Port 6379 strikt untersagt, während der Betrieb des DNS-Dienstes erhalten bleibt, dessen Funktionalität häufig bei der Erstellung von Regeln leidet. Denn, wie bereits erwähnt, wird bei Vorhandensein eines Selektors automatisch eine restriktive Richtlinie angewendet, es sei denn, es ist etwas anderes angegeben.
Ergebnisse
Auf diese Weise kann das erweiterte Calico-API flexibel konfiguriert und die Routing-Strategien im Cluster und darüber hinaus dynamisch geändert werden. Im Allgemeinen kann die Nutzung so wirken, als würde man mit Kanonen auf Spatzen schießen, und die Implementierung eines L3-Netzwerks mit BGP- und IP-IP-Tunneln wirkt in einer einfachen Kubernetes-Installation in einem flachen Netzwerk monströs... Dennoch scheint das Werkzeug in anderen Bereichen durchaus funktionsfähig und nützlich zu sein.
Die Isolation eines Clusters zur Gewährleistung von Sicherheitsanforderungen ist nicht immer umsetzbar, und genau in solchen Fällen kommt Calico (oder eine ähnliche Lösung) ins Spiel. Die im Artikel angegebenen Beispiele (mit geringfügigen Anpassungen) werden in mehreren Installationen unserer Kunden in AWS verwendet.
P.S.
Lesen Sie auch in unserem Blog:
- «»;
- „Illustriertes Handbuch zum Netzwerkaufbau in Kubernetes“: , ;
- «».
Quelle: habr.com
