Kubernetes Networks: Ingress

Heute veröffentlichen wir eine Übersetzung des dritten Teils des Kubernetes Networking Guide. Der erste Teil befasste sich mit Pods, der zweite mit Diensten, und heute werden wir über Lastausgleich und Kubernetes-Ressourcen vom Typ Ingress sprechen.

Routing ist kein Lastausgleich


Im vorherigen Artikel dieser Serie haben wir eine Konfiguration betrachtet, die aus zwei Herden und einem Dienst besteht, dem eine IP-Adresse zugewiesen wurde, die als "Cluster-IP" bezeichnet wird. Anfragen für Herde wurden an diese Adresse gesendet. Hier werden wir weiter an unserem Trainingssystem arbeiten, beginnend dort, wo wir das letzte Mal unseren Abschluss gemacht haben. Denken Sie daran, dass die Cluster-IP-Adresse des Dienstes 10.3.241.152 zu einem Bereich von IP-Adressen gehört, der sich von dem im 10.3.241.152 und dem in dem Netzwerk verwendeten Netzwerk unterscheidet, in dem sich die Knoten befinden. Ich habe das durch diesen Adressraum definierte Netzwerk als "Dienstnetzwerk" bezeichnet, obwohl es kaum einen besonderen Namen verdient, da keine Geräte mit diesem Netzwerk verbunden sind und sein Adressraum tatsächlich ausschließlich aus Routing-Regeln besteht. Es wurde zuvor gezeigt, wie dieses Netzwerk auf der Basis der Kubernetes-Komponente namens kube-proxy implementiert wird und mit dem Linux-Kernel- Netfilter- Modul interagiert, um den an den IP-Cluster gesendeten Datenverkehr abzufangen und umzuleiten, unter dem gearbeitet werden soll.


Netzwerkdiagramm

Bisher haben wir über „Verbindungen“ und „Anfragen“ gesprochen und sogar das schwer zu interpretierende Konzept des „Verkehrs“ verwendet. Um jedoch die Funktionen des Kubernetes Ingress-Mechanismus zu verstehen, müssen wir genauere Begriffe verwenden. Verbindungen und Anforderungen funktionieren also auf der 4. Ebene des OSI-Modells (tcp) oder auf der 7. Ebene (http, rpc usw.). Netfilter-Regeln sind Routing-Regeln und funktionieren mit IP-Paketen auf der dritten Ebene. Alle Router, einschließlich Netfilter, treffen Entscheidungen mehr oder weniger nur auf der Grundlage der im Paket enthaltenen Informationen. Im Allgemeinen interessieren sie sich dafür, woher das Paket kommt und wohin es geht. Um dieses Verhalten in Bezug auf die dritte Ebene des OSI-Modells zu beschreiben, muss daher gesagt werden, dass jedes Paket, das für den um 10.3.241.152:80 Dienst 10.3.241.152:80 und an der Schnittstelle des eth0 Knotens eth0 , von netfilter verarbeitet wird und gemäß Die für unseren Service festgelegten Regeln werden an die IP-Adresse eines funktionsfähigen Herdes umgeleitet.

Es liegt auf der Hand, dass jeder Mechanismus, mit dem externe Clients auf Pods zugreifen können, dieselbe Routing-Infrastruktur verwenden sollte. Infolgedessen greifen diese externen Clients auf die IP-Adresse und den Port des Clusters zu, da sie der „Zugangspunkt“ zu allen Mechanismen sind, über die wir bisher gesprochen haben. Sie erlauben uns, uns keine Gedanken darüber zu machen, wo genau es zu einem bestimmten Zeitpunkt ausgeführt wird. Es ist jedoch keineswegs klar, wie alles funktioniert.

Der Cluster-IP-Dienst ist nur über die Ethernet-Schnittstelle des Knotens erreichbar. Nichts außerhalb des Clusters weiß, was mit Adressen aus dem Bereich zu tun ist, zu dem diese Adresse gehört. Wie kann ich Datenverkehr von einer öffentlichen IP-Adresse zu einer Adresse umleiten, die nur erreichbar ist, wenn das Paket bereits beim Host angekommen ist?

Wenn wir versuchen, eine Lösung für dieses Problem zu finden, können Sie bei der Suche nach einer Lösung unter anderem die Netzfilterregeln mit dem Dienstprogramm iptables untersuchen . Wenn Sie dies tun, können Sie etwas entdecken, das auf den ersten Blick ungewöhnlich erscheint: Die Regeln für den Dienst sind nicht auf ein bestimmtes Quellnetzwerk beschränkt. Dies bedeutet, dass alle Pakete, die irgendwo generiert werden und auf der Ethernet-Schnittstelle des Knotens ankommen und eine Zieladresse von 10.3.241.152:80 haben, als regelkonform erkannt und an das Sub weitergeleitet werden. Können wir den Clients einfach einen IP-Cluster geben, indem wir ihn möglicherweise an einen geeigneten Domänennamen binden, und dann eine Route einrichten, mit der wir die Zustellung dieser Pakete an einen der Knoten organisieren können?


Externer Client und Cluster

Wenn alles auf diese Weise eingerichtet ist, wird sich herausstellen, dass ein solches Design funktioniert. Clients greifen auf die Cluster-IP zu, Pakete folgen der Route, die zum Host führt, und werden dann nach unten umgeleitet. In diesem Moment scheint es Ihnen, dass eine solche Lösung begrenzt sein kann, aber sie leidet unter einigen ernsthaften Problemen. Das erste ist, dass die Knoten, in der Tat das Konzept der Vergänglichkeit, sie unterscheiden sich in dieser Hinsicht nicht besonders von den Herden. Sie sind der materiellen Welt natürlich etwas näher als Pods, aber sie können auf neue virtuelle Maschinen migrieren, Cluster können vergrößert oder verkleinert werden und so weiter. Router arbeiten auf der dritten Ebene des OSI-Modells, und Pakete können nicht zwischen normal funktionierenden Diensten und solchen unterscheiden, die nicht ordnungsgemäß funktionieren. Sie erwarten, dass der nächste Übergang auf der Route zugänglich und stabil sein wird. Wenn der Knoten nicht erreichbar ist, wird die Route funktionsunfähig und bleibt in den meisten Fällen viel Zeit. Selbst wenn die Route fehleranfällig ist, führt ein solches Schema dazu, dass der gesamte externe Verkehr über einen einzelnen Knoten geleitet wird, was wahrscheinlich nicht optimal ist.

Unabhängig davon, wie wir den Clientverkehr zum System bringen, müssen wir dies tun, damit er nicht vom Status eines einzelnen Clusterknotens abhängt. Tatsächlich gibt es keine zuverlässige Möglichkeit, dies nur mit Routing zu tun, ohne den Router aktiv zu verwalten. Tatsächlich spielt kube-proxy in Bezug auf den Netzfilter genau diese Rolle, die Rolle des Steuerungssystems. Die Ausweitung der Verantwortung von Kubernetes auf die Verwaltung eines externen Routers war für Systemarchitekten wahrscheinlich nicht sehr sinnvoll, zumal wir bereits bewährte Tools zur Verteilung des Clientverkehrs auf mehrere Server haben. Sie werden als Load Balancer bezeichnet, und es ist nicht verwunderlich, dass sie die wirklich zuverlässige Lösung für Kubernetes Ingress sind. Um genau zu verstehen, wie dies geschieht, müssen wir von der dritten OSI-Ebene aufstehen und erneut über Verbindungen sprechen.

Um den Load Balancer zum Verteilen des Clientverkehrs zwischen Clusterknoten verwenden zu können, benötigen wir eine öffentliche IP-Adresse, mit der Clients eine Verbindung herstellen können, sowie die Adressen der Knoten selbst, an die der Load Balancer Anforderungen umleiten kann. Aus den oben genannten Gründen können wir nicht einfach eine stabile statische Route zwischen dem Gateway-Router und den Knoten mithilfe eines dienstbasierten Netzwerks (IP-Cluster) erstellen.

Unter den anderen Adressen, mit denen Sie arbeiten können, können nur die Adressen des Netzwerks notiert werden, mit dem die Ethernet-Schnittstellen der Knoten verbunden sind, in diesem Beispiel 10.100.0.0/24 . Der Router weiß bereits, wie Pakete an diese Schnittstellen weitergeleitet werden, und die vom Load Balancer an den Router gesendeten Verbindungen werden dorthin geleitet, wo sie hingehört. Wenn der Client jedoch über Port 80 eine Verbindung zu unserem Dienst herstellen möchte, können wir nicht einfach Pakete an diesen Port über die Netzwerkschnittstellen der Knoten senden.


Load Balancer, erfolgloser Versuch, auf Port 80 der Host-Netzwerkschnittstelle zuzugreifen

Der Grund, warum dies nicht möglich ist, liegt auf der Hand. Wir sprechen nämlich über die Tatsache, dass um 10.100.0.3:80 kein Prozess auf Verbindungen wartet (und wenn 10.100.0.3:80 , dann ist dies definitiv nicht der gleiche Prozess), und über die Netfilter-Regeln, die, wie wir gehofft hatten, die Anforderung und abfangen würden Sie senden es ihm. Sie arbeiten nicht an dieser Zieladresse. Sie antworten nur auf ein Cluster-IP-Netzwerk, das auf Diensten basiert, 10.3.241.152:80 auf die Adresse 10.3.241.152:80 . Infolgedessen können diese Pakete bei ihrer Ankunft nicht an die Zieladresse ECONNREFUSED , und der Kernel gibt eine ECONNREFUSED Antwort aus. Dies bringt uns in eine verwirrende Position: Es ist nicht einfach, mit einem Netzwerk für die Paketumleitung zu arbeiten, zu dem Netfilter konfiguriert ist, wenn Daten vom Gateway zu Knoten umgeleitet werden, und ein Netzwerk, für das das Routing einfach zu konfigurieren ist, ist nicht das Netzwerk, zu dem Netfilter Pakete umleitet. Um dieses Problem zu lösen, können Sie eine Brücke zwischen diesen Netzwerken erstellen. Genau das macht Kubernetes mit einem Dienst wie NodePort.

Dienste wie NodePort


Dem Dienst, den wir beispielsweise im vorherigen Artikel erstellt haben, ist kein Typ zugewiesen, daher wurde der Standardtyp ClusterIP . Es gibt zwei weitere Arten von Diensten, die sich in zusätzlichen Funktionen unterscheiden, und der, an dem wir NodePort interessiert sind, ist NodePort . Hier ist ein Beispiel für eine Beschreibung eines Dienstes dieses Typs:

 kind: Service apiVersion: v1 metadata: name: service-test spec: type: NodePort selector:   app: service_test_pod ports: - port: 80   targetPort: http 

Dienste vom Typ NodePort sind Dienste vom Typ ClusterIP , die eine zusätzliche Möglichkeit bieten: Der Zugriff auf diese Dienste kann sowohl über die dem Host zugewiesene IP-Adresse als auch über die dem Cluster im ClusterIP zugewiesene Adresse erfolgen. Dies wird auf relativ einfache Weise erreicht: Wenn Kubernetes einen NodePort-Dienst erstellt, weist kube-proxy einen Port im Bereich von 30000-32767 zu und öffnet diesen Port auf der eth0 Schnittstelle jedes Knotens (daher der Name des NodePort - NodePort ). Verbindungen zu diesem Port (wir werden solche NodePort Ports nennen) werden an die Cluster-IP des Dienstes umgeleitet. Wenn wir den oben beschriebenen Dienst erstellen und den kubectl get svc service-test , können wir den ihm zugewiesenen Port sehen.

 $ kubectl get svc service-test NAME           CLUSTER-IP EXTERNAL-IP   PORT(S) AGE service-test   10.3.241.152 <none>        80:32213/TCP 1m 

In diesem Fall wird dem Dienst NodePort 32213 zugewiesen. Dies bedeutet, dass wir jetzt über einen beliebigen Knoten in unserem experimentellen Cluster um 10.100.0.2:32213 oder um 10.100.0.3:32213 Verbindung zum Dienst herstellen können. In diesem Fall wird der Datenverkehr an den Dienst umgeleitet.

Nachdem dieser Teil des Systems seinen Platz eingenommen hat, verfügen wir über alle Fragmente der Pipeline, um die durch Clientanforderungen verursachte Last auf alle Knoten des Clusters auszugleichen.


NodePort-Dienst

In der vorherigen Abbildung stellt der Client über eine öffentliche IP-Adresse eine Verbindung zum Load Balancer her. Der Load Balancer wählt den Knoten aus und stellt um 10.100.0.3:32213 Verbindung zu ihm her. Kube-proxy akzeptiert diese Verbindung und leitet sie an den Dienst weiter, auf den über Cluster IP 10.3.241.152:80 . Hier wird die Anforderung gemäß den von netfilter festgelegten Regeln erfolgreich verarbeitet und unter der Adresse 10.0.2.2:8080 an den Server-Pod 10.0.2.2:8080 . Vielleicht sieht das alles etwas kompliziert aus und ist es bis zu einem gewissen Grad auch, aber es ist nicht einfach, eine einfachere Lösung zu finden, die all die großartigen Funktionen unterstützt, die uns Pods und service-basierte Netzwerke bieten.

Dieser Mechanismus ist jedoch nicht ohne eigene Probleme. Durch die Verwendung von Diensten wie NodePort Kunden über einen nicht standardmäßigen Port auf Dienste zugreifen. Oft ist dies kein Problem, da der Load Balancer ihnen einen regulären Port zur Verfügung stellen und NodePort vor Endbenutzern verbergen kann. In einigen Szenarien, z. B. bei Verwendung eines externen Load Balancers für die Google Cloud-Plattform, kann es jedoch erforderlich sein, NodePort Clients NodePort . Es sollte beachtet werden, dass solche Ports zusätzlich begrenzte Ressourcen darstellen, obwohl 2768 Ports wahrscheinlich selbst für die größten Cluster ausreichen. In den meisten Fällen können Sie Kubernetes Portnummern zufällig auswählen lassen, diese jedoch bei Bedarf selbst festlegen. Ein weiteres Problem sind einige Einschränkungen hinsichtlich der Speicherung von Quell-IP-Adressen in Anforderungen. Informationen zur Lösung dieser Probleme finden Sie in der Kubernetes-Dokumentation.

Ports NodePorts ist der grundlegende Mechanismus, mit dem der gesamte externe Datenverkehr in den Kubernetes-Cluster gelangt. Sie selbst präsentieren uns jedoch keine vorgefertigte Lösung. Aus den oben genannten Gründen ist vor dem Cluster immer eine Art Load Balancer erforderlich, unabhängig davon, ob es sich bei den Clients um interne oder externe Entitäten in einem öffentlichen Netzwerk handelt.

Die Plattformarchitekten stellten dies fest und stellten zwei Möglichkeiten zur Verfügung, um den Load Balancer über die Kubernetes-Plattform selbst zu konfigurieren. Lassen Sie uns das diskutieren.

Dienste wie LoadBalancer und Ressourcen vom Typ Ingress


Dienste wie LoadBalancer und Ressourcen vom Typ Ingress gehören zu den komplexesten Kubernetes-Mechanismen. Wir werden jedoch nicht zu viel Zeit mit ihnen verbringen, da ihre Verwendung nicht zu grundlegenden Änderungen in allem führt, worüber wir bisher gesprochen haben. Der gesamte externe Datenverkehr gelangt nach wie vor über NodePort in den Cluster.

Architekten könnten hier aufhören und denjenigen, die Cluster erstellen, erlauben, sich nur um öffentliche IP-Adressen und Load Balancer zu kümmern. In bestimmten Situationen, z. B. beim Starten eines Clusters auf normalen Servern oder zu Hause, ist dies genau das, was sie tun. In Umgebungen, die API-gesteuerte Netzwerkressourcenkonfigurationen unterstützen, können Sie mit Kubernetes alles, was Sie benötigen, an einem Ort konfigurieren.

Der erste und einfachste Ansatz zur Lösung dieses Problems ist die Verwendung von Kubernetes-Diensten wie LoadBalancer . Solche Dienste verfügen über alle Funktionen von Diensten wie NodePort und können darüber hinaus vollständige Pfade für eingehenden Datenverkehr erstellen, basierend auf der Annahme, dass der Cluster in Umgebungen wie GCP oder AWS ausgeführt wird, die die Konfiguration von Netzwerkressourcen über die API unterstützen.

 kind: Service apiVersion: v1 metadata: name: service-test spec: type: LoadBalancer selector:   app: service_test_pod ports: - port: 80   targetPort: http 

Wenn wir den Dienst aus unserem Beispiel in der Google Kubernetes Engine löschen und neu erstellen, können wir kurz darauf mit dem kubectl get svc service-test überprüfen, ob die externe IP zugewiesen ist.

 $ kubectl get svc service-test NAME      CLUSTER-IP      EXTERNAL-IP PORT(S)          AGE openvpn   10.3.241.52     35.184.97.156 80:32213/TCP     5m 

Es wurde oben erwähnt, dass wir die Tatsache der Zuweisung einer externen IP-Adresse „bald“ überprüfen können, obwohl die Zuweisung einer externen IP-Adresse einige Minuten dauern kann, was angesichts der Menge an Ressourcen, die in einen gesunden Zustand gebracht werden müssen, nicht überraschend ist. Auf der GCP-Plattform muss das System beispielsweise eine externe IP-Adresse, Verkehrsumleitungsregeln, einen Ziel-Proxy-Server, einen Back-End-Dienst und möglicherweise eine Instanz einer Gruppe erstellen. Nachdem Sie eine externe IP-Adresse zugewiesen haben, können Sie über diese Adresse eine Verbindung zum Dienst herstellen, ihm einen Domänennamen zuweisen und Clients informieren. Bis der Dienst zerstört und neu erstellt wird (dazu selten, wenn es einen guten Grund gibt), ändert sich die IP-Adresse nicht.

Dienste wie LoadBalancer haben einige Einschränkungen. Ein solcher Dienst kann nicht zum Entschlüsseln des HTTPS-Verkehrs konfiguriert werden. Sie können keine virtuellen Hosts erstellen oder pfadbasiertes Routing konfigurieren. Daher können Sie in praktischen Konfigurationen keinen einzelnen Load Balancer mit vielen Diensten verwenden. Diese Einschränkungen führten zur Einführung von Kubernetes 1.1. Eine spezielle Ressource zum Konfigurieren von Load Balancern. Dies ist eine Ressource vom Typ Ingress . Dienste wie LoadBalancer zielen darauf ab, die Funktionen eines einzelnen Dienstes zur Unterstützung externer Clients zu erweitern. Im Gegensatz dazu sind Ingress Ressourcen spezielle Ressourcen, mit denen Sie Load Balancer flexibel konfigurieren können. Die Ingress-API unterstützt die Entschlüsselung von TLS-Verkehr, virtuellen Hosts und pfadbasiertem Routing. Mit dieser API kann der Load Balancer einfach für die Arbeit mit mehreren Backend-Diensten konfiguriert werden.

Die Ressourcen-API des Ingress Typs ist zu groß, um ihre Funktionen hier zu erläutern. Darüber hinaus hat dies keinen besonderen Einfluss auf die Funktionsweise der Ingress-Ressourcen auf Netzwerkebene. Die Implementierung dieser Ressource folgt dem üblichen Kubernetes-Muster: Es gibt einen Ressourcentyp und einen Controller, um diesen Typ zu steuern. Die Ressource in diesem Fall ist die Ingress Ressource, die Anforderungen an Netzwerkressourcen beschreibt. So könnte die Beschreibung einer Ingress Ressource aussehen.

 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test-ingress annotations:   kubernetes.io/ingress.class: "gce" spec: tls:   - secretName: my-ssl-secret rules: - host: testhost.com   http:     paths:     - path: /*       backend:         serviceName: service-test         servicePort: 80 

Der Ingress-Controller ist für die Ausführung dieser Anforderungen verantwortlich, indem er andere Ressourcen in den gewünschten Zustand versetzt. Bei Verwendung von Ingress werden Dienste wie NodePort erstellt. NodePort der Ingress-Controller entscheiden, wie der Datenverkehr zu Knoten geleitet werden soll. Es gibt eine Implementierung des Ingress-Controllers für GCE-Load-Balancer, für AWS-Balancer und für beliebte Proxy-Server wie Nginx und Haproxy. Beachten Sie, dass das Mischen von Ingress-Ressourcen und -Diensten wie LoadBalancer in einigen Umgebungen zu geringfügigen Problemen LoadBalancer kann. Sie sind einfach zu handhaben, aber im Allgemeinen ist es am besten, Ingress auch nur für einfache Dienste zu verwenden.

HostPort und HostNetwork


Worüber wir jetzt sprechen werden, nämlich HostPort und HostNetwork , kann eher der Kategorie interessanter Raritäten und nicht nützlichen Werkzeugen zugeschrieben werden. Tatsächlich verpflichte ich mich zu behaupten, dass in 99,99% der Fälle ihre Verwendung als Anti-Pattern angesehen werden kann und jedes System, in dem sie verwendet werden, einer obligatorischen Überprüfung seiner Architektur unterzogen werden muss.

Ich dachte, dass es sich überhaupt nicht lohnt, über sie zu sprechen, aber sie sind so etwas wie die Tools, die von Ingress-Ressourcen verwendet werden, um eingehenden Datenverkehr zu verarbeiten. Deshalb habe ich beschlossen, dass es sich lohnt, sie zumindest kurz zu erwähnen.

HostPort über HostPort sprechen. Dies ist eine Containereigenschaft (in der ContainerPort Struktur deklariert). Wenn eine bestimmte Portnummer darin geschrieben ist, führt dies zur Öffnung dieses Ports auf dem Knoten und zu seiner Umleitung direkt zum Container. Es gibt keine Proxy-Mechanismen und der Port wird nur auf den Knoten geöffnet, auf denen der Container ausgeführt wird. In den frühen Tagen der Plattform, bevor die DaemonSet- und StatefulSet- Mechanismen darin auftauchten, war HostPort ein Trick, mit dem nur ein Container eines bestimmten Typs auf einem Knoten gestartet werden konnte. Ich habe dies beispielsweise einmal verwendet, um einen Elasticsearch-Cluster zu erstellen, indem HostPort auf 9200 und so viele Replikate angegeben wurden, wie Knoten vorhanden waren. , Kubernetes, - HostPort .

NostNetwork , , Kubernetes , HostPort . true , - network=host docker run . , . eth0 . , . , , , Kubernetes, - .

Zusammenfassung


Kubernetes, , Ingress. , , , Kubernetes.

Liebe Leser! Ingress?

Source: https://habr.com/ru/post/de442646/


All Articles