Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes
Dieser Artikel hilft Ihnen zu verstehen, wie der Lastausgleich in Kubernetes funktioniert, was beim Skalieren langlebiger Verbindungen passiert und warum Sie den clientseitigen Ausgleich in Betracht ziehen sollten, wenn Sie HTTP/2, gRPC, RSockets, AMQP oder andere langlebige Protokolle verwenden . 

Ein wenig darüber, wie der Datenverkehr in Kubernetes umverteilt wird 

Kubernetes bietet zwei praktische Abstraktionen für die Bereitstellung von Anwendungen: Dienste und Bereitstellungen.

Bereitstellungen beschreiben, wie und wie viele Kopien Ihrer Anwendung zu einem bestimmten Zeitpunkt ausgeführt werden sollen. Jede Anwendung wird als Pod bereitgestellt und erhält eine IP-Adresse.

Dienste ähneln in ihrer Funktion einem Load Balancer. Sie sind darauf ausgelegt, den Datenverkehr auf mehrere Pods zu verteilen.

Mal sehen, wie es aussieht.

  1. Im Diagramm unten sehen Sie drei Instanzen derselben Anwendung und eines Load Balancers:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  2. Der Load Balancer wird als Dienst bezeichnet und erhält eine IP-Adresse. Jede eingehende Anfrage wird an einen der Pods umgeleitet:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  3. Das Bereitstellungsszenario bestimmt die Anzahl der Instanzen der Anwendung. Sie müssen fast nie direkt erweitern unter:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  4. Jedem Pod wird eine eigene IP-Adresse zugewiesen:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

Es ist sinnvoll, sich Dienste als eine Sammlung von IP-Adressen vorzustellen. Bei jedem Zugriff auf den Dienst wird eine der IP-Adressen aus der Liste ausgewählt und als Zieladresse verwendet.

Es sieht aus wie das.

  1. Eine Curl 10.96.45.152-Anfrage geht beim Dienst ein:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  2. Der Dienst wählt eine von drei Pod-Adressen als Ziel aus:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  3. Der Datenverkehr wird zu einem bestimmten Pod umgeleitet:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

Wenn Ihre Anwendung aus einem Front-End und einem Backend besteht, verfügen Sie jeweils über einen Dienst und eine Bereitstellung.

Wenn das Frontend eine Anfrage an das Backend stellt, muss es nicht genau wissen, wie viele Pods das Backend bedient: Es können eins, zehn oder hundert sein.

Außerdem weiß das Frontend nichts über die Adressen der Pods, die das Backend bedienen.

Wenn das Frontend eine Anfrage an das Backend stellt, verwendet es die IP-Adresse des Backend-Dienstes, die sich nicht ändert.

Hier ist, wie es aussieht.

  1. Unter 1 wird die interne Backend-Komponente angefordert. Anstatt ein bestimmtes Backend auszuwählen, stellt es eine Anfrage an den Dienst:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  2. Der Dienst wählt einen der Backend-Pods als Zieladresse aus:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  3. Der Datenverkehr geht von Pod 1 zu Pod 5, ausgewählt vom Dienst:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  4. Unter 1 weiß man nicht genau, wie viele Pods sich wie unter 5 hinter dem Dienst verbergen:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

Doch wie genau verteilt der Dienst Anfragen? Es sieht so aus, als ob Round-Robin-Balancing verwendet wird? Lass es uns herausfinden. 

Ausgleich in Kubernetes-Diensten

Kubernetes-Dienste existieren nicht. Es gibt keinen Prozess für den Dienst, dem eine IP-Adresse und ein Port zugewiesen werden.

Sie können dies überprüfen, indem Sie sich bei einem beliebigen Knoten im Cluster anmelden und den Befehl netstat -ntlp ausführen.

Sie können nicht einmal die dem Dienst zugewiesene IP-Adresse finden.

Die IP-Adresse des Dienstes befindet sich in der Kontrollschicht, im Controller, und wird in der Datenbank – etcd – aufgezeichnet. Dieselbe Adresse wird von einer anderen Komponente verwendet – kube-proxy.
Kube-Proxy empfängt eine Liste von IP-Adressen für alle Dienste und generiert einen Satz iptables-Regeln auf jedem Knoten im Cluster.

Diese Regeln besagen: „Wenn wir die IP-Adresse des Dienstes sehen, müssen wir die Zieladresse der Anfrage ändern und sie an einen der Pods senden.“

Die Dienst-IP-Adresse dient nur als Einstiegspunkt und wird von keinem Prozess bedient, der diese IP-Adresse und diesen Port überwacht.

Schauen wir uns das an

  1. Betrachten Sie einen Cluster aus drei Knoten. Jeder Knoten hat Pods:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  2. Zusammengebundene, beige bemalte Schoten gehören zum Service. Da der Dienst nicht als Prozess existiert, wird er grau dargestellt:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  3. Der erste Pod fordert einen Dienst an und muss zu einem der zugehörigen Pods gehen:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  4. Aber der Dienst existiert nicht, der Prozess existiert nicht. Wie funktioniert es?

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  5. Bevor die Anfrage den Knoten verlässt, durchläuft sie die iptables-Regeln:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  6. Die iptables-Regeln wissen, dass der Dienst nicht existiert und ersetzen seine IP-Adresse durch eine der IP-Adressen der Pods, die diesem Dienst zugeordnet sind:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  7. Die Anfrage erhält eine gültige IP-Adresse als Zieladresse und wird normal verarbeitet:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  8. Abhängig von der Netzwerktopologie erreicht die Anfrage schließlich den Pod:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

Kann iptables einen Lastausgleich durchführen?

Nein, iptables werden zum Filtern verwendet und sind nicht für den Ausgleich konzipiert.

Es ist jedoch möglich, eine Reihe von Regeln zu schreiben, die wie folgt funktionieren Pseudo-Balancer.

Und genau das ist in Kubernetes umgesetzt.

Wenn Sie drei Pods haben, schreibt kube-proxy die folgenden Regeln:

  1. Wählen Sie das erste Sub mit einer Wahrscheinlichkeit von 33 % aus, andernfalls fahren Sie mit der nächsten Regel fort.
  2. Wählen Sie die zweite Regel mit einer Wahrscheinlichkeit von 50 %, andernfalls fahren Sie mit der nächsten Regel fort.
  3. Wählen Sie unten den dritten aus.

Dieses System führt dazu, dass jeder Pod mit einer Wahrscheinlichkeit von 33 % ausgewählt wird.

Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

Und es gibt keine Garantie dafür, dass Pod 2 als nächstes nach Pod 1 ausgewählt wird.

Beachten: iptables verwendet ein Statistikmodul mit Zufallsverteilung. Somit basiert der Ausgleichsalgorithmus auf einer Zufallsauswahl.

Nachdem Sie nun verstanden haben, wie Dienste funktionieren, schauen wir uns weitere interessante Dienstszenarien an.

Langlebige Verbindungen in Kubernetes werden standardmäßig nicht skaliert

Jede HTTP-Anfrage vom Frontend zum Backend wird von einer separaten TCP-Verbindung bedient, die geöffnet und geschlossen wird.

Wenn das Frontend 100 Anfragen pro Sekunde an das Backend sendet, werden 100 verschiedene TCP-Verbindungen geöffnet und geschlossen.

Sie können die Verarbeitungszeit und -last der Anfrage reduzieren, indem Sie eine TCP-Verbindung öffnen und diese für alle nachfolgenden HTTP-Anfragen verwenden.

Das HTTP-Protokoll verfügt über eine Funktion namens HTTP Keep-Alive oder Verbindungswiederverwendung. In diesem Fall wird eine einzelne TCP-Verbindung zum Senden und Empfangen mehrerer HTTP-Anfragen und -Antworten verwendet:

Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

Diese Funktion ist standardmäßig nicht aktiviert: Sowohl der Server als auch der Client müssen entsprechend konfiguriert werden.

Die Einrichtung selbst ist einfach und für die meisten Programmiersprachen und Umgebungen zugänglich.

Hier sind einige Links zu Beispielen in verschiedenen Sprachen:

Was passiert, wenn wir Keep-Alive in einem Kubernetes-Dienst verwenden?
Nehmen wir an, dass sowohl das Frontend als auch das Backend Keep-Alive unterstützen.

Wir haben eine Kopie des Frontends und drei Kopien des Backends. Das Frontend stellt die erste Anfrage und öffnet eine TCP-Verbindung zum Backend. Die Anfrage erreicht den Dienst, einer der Backend-Pods wird als Zieladresse ausgewählt. Das Backend sendet eine Antwort und das Frontend empfängt sie.

Im Gegensatz zur üblichen Situation, in der die TCP-Verbindung nach dem Empfang einer Antwort geschlossen wird, bleibt sie jetzt für weitere HTTP-Anfragen offen.

Was passiert, wenn das Frontend mehr Anfragen an das Backend sendet?

Um diese Anfragen weiterzuleiten, wird eine offene TCP-Verbindung verwendet. Alle Anfragen gehen an dasselbe Backend, an das die erste Anfrage ging.

Sollte iptables den Datenverkehr nicht neu verteilen?

In diesem Fall nicht.

Wenn eine TCP-Verbindung erstellt wird, durchläuft sie iptables-Regeln, die ein bestimmtes Backend auswählen, an das der Datenverkehr weitergeleitet wird.

Da alle nachfolgenden Anfragen über eine bereits offene TCP-Verbindung erfolgen, werden die iptables-Regeln nicht mehr aufgerufen.

Mal sehen, wie es aussieht.

  1. Der erste Pod sendet eine Anfrage an den Dienst:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  2. Sie wissen bereits, was als nächstes passieren wird. Der Dienst existiert nicht, aber es gibt iptables-Regeln, die die Anfrage verarbeiten:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  3. Einer der Backend-Pods wird als Zieladresse ausgewählt:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  4. Die Anfrage erreicht den Pod. Zu diesem Zeitpunkt wird eine dauerhafte TCP-Verbindung zwischen den beiden Pods hergestellt:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  5. Jede nachfolgende Anfrage vom ersten Pod wird über die bereits hergestellte Verbindung geleitet:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

Das Ergebnis ist eine schnellere Reaktionszeit und ein höherer Durchsatz, aber Sie verlieren die Möglichkeit, das Backend zu skalieren.

Selbst wenn Sie zwei Pods im Backend haben und eine ständige Verbindung besteht, wird der Datenverkehr immer zu einem von ihnen geleitet.

Kann das behoben werden?

Da Kubernetes nicht weiß, wie man persistente Verbindungen ausgleicht, liegt diese Aufgabe bei Ihnen.

Dienste sind eine Sammlung von IP-Adressen und Ports, die als Endpunkte bezeichnet werden.

Ihre Anwendung kann eine Liste der Endpunkte vom Dienst abrufen und entscheiden, wie Anforderungen zwischen ihnen verteilt werden. Sie können eine dauerhafte Verbindung zu jedem Pod öffnen und die Anforderungen mithilfe von Round-Robin zwischen diesen Verbindungen verteilen.

Oder mehr anwenden komplexe Ausgleichsalgorithmen.

Der clientseitige Code, der für den Ausgleich verantwortlich ist, sollte dieser Logik folgen:

  1. Rufen Sie eine Liste der Endpunkte vom Dienst ab.
  2. Öffnen Sie für jeden Endpunkt eine dauerhafte Verbindung.
  3. Wenn eine Anfrage gestellt werden muss, verwenden Sie eine der offenen Verbindungen.
  4. Aktualisieren Sie regelmäßig die Liste der Endpunkte, erstellen Sie neue oder schließen Sie alte dauerhafte Verbindungen, wenn sich die Liste ändert.

So wird es aussehen.

  1. Anstatt dass der erste Pod die Anfrage an den Dienst sendet, können Sie Anfragen auf der Clientseite ausgleichen:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  2. Sie müssen Code schreiben, der fragt, welche Pods Teil des Dienstes sind:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  3. Sobald Sie die Liste haben, speichern Sie sie auf der Clientseite und verwenden Sie sie, um eine Verbindung zu den Pods herzustellen:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

  4. Sie sind für den Load-Balancing-Algorithmus verantwortlich:

    Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

Nun stellt sich die Frage: Betrifft dieses Problem nur HTTP-Keep-Alive?

Clientseitiger Lastausgleich

HTTP ist nicht das einzige Protokoll, das dauerhafte TCP-Verbindungen verwenden kann.

Wenn Ihre Anwendung eine Datenbank verwendet, wird nicht jedes Mal eine TCP-Verbindung geöffnet, wenn Sie eine Anfrage stellen oder ein Dokument aus der Datenbank abrufen müssen. 

Stattdessen wird eine dauerhafte TCP-Verbindung zur Datenbank geöffnet und verwendet.

Wenn Ihre Datenbank auf Kubernetes bereitgestellt wird und der Zugriff als Dienst bereitgestellt wird, treten dieselben Probleme auf, die im vorherigen Abschnitt beschrieben wurden.

Ein Datenbankreplikat wird stärker belastet als die anderen. Kube-Proxy und Kubernetes helfen nicht beim Verbindungsausgleich. Sie müssen darauf achten, die Abfragen auf Ihre Datenbank auszugleichen.

Je nachdem, welche Bibliothek Sie für die Verbindung zur Datenbank verwenden, stehen Ihnen möglicherweise unterschiedliche Möglichkeiten zur Lösung dieses Problems zur Verfügung.

Unten finden Sie ein Beispiel für den Zugriff auf einen MySQL-Datenbankcluster über Node.js:

var mysql = require('mysql');
var poolCluster = mysql.createPoolCluster();

var endpoints = /* retrieve endpoints from the Service */

for (var [index, endpoint] of endpoints) {
  poolCluster.add(`mysql-replica-${index}`, endpoint);
}

// Make queries to the clustered MySQL database

Es gibt viele andere Protokolle, die dauerhafte TCP-Verbindungen verwenden:

  • WebSockets und gesicherte WebSockets
  • HTTP / 2
  • gRPC
  • RSockets
  • AMQP

Mit den meisten dieser Protokolle sollten Sie bereits vertraut sein.

Aber wenn diese Protokolle so beliebt sind, warum gibt es dann keine standardisierte Balancing-Lösung? Warum muss sich die Client-Logik ändern? Gibt es eine native Kubernetes-Lösung?

Kube-Proxy und iptables sind so konzipiert, dass sie die häufigsten Anwendungsfälle bei der Bereitstellung auf Kubernetes abdecken. Dies dient der Bequemlichkeit.

Wenn Sie einen Webdienst verwenden, der eine REST-API bereitstellt, haben Sie Glück – in diesem Fall werden keine dauerhaften TCP-Verbindungen verwendet, Sie können jeden Kubernetes-Dienst verwenden.

Sobald Sie jedoch dauerhafte TCP-Verbindungen verwenden, müssen Sie herausfinden, wie Sie die Last gleichmäßig auf die Backends verteilen. Kubernetes bietet für diesen Fall keine fertigen Lösungen.

Es gibt jedoch durchaus Möglichkeiten, die helfen können.

Ausgleich langlebiger Verbindungen in Kubernetes

In Kubernetes gibt es vier Arten von Diensten:

  1. ClusterIP
  2. KnotenPort
  3. Lastenausgleicher
  4. Headless

Die ersten drei Dienste basieren auf einer virtuellen IP-Adresse, die von kube-proxy zum Erstellen von iptables-Regeln verwendet wird. Die grundlegende Grundlage aller Dienste ist jedoch ein Headless-Dienst.

Dem Headless-Dienst ist keine IP-Adresse zugeordnet und er bietet lediglich einen Mechanismus zum Abrufen einer Liste von IP-Adressen und Ports der damit verbundenen Pods (Endpunkte).

Alle Dienste basieren auf dem Headless-Dienst.

Der ClusterIP-Dienst ist ein Headless-Dienst mit einigen Ergänzungen: 

  1. Die Managementschicht weist ihm eine IP-Adresse zu.
  2. Kube-Proxy generiert die notwendigen iptables-Regeln.

Auf diese Weise können Sie kube-proxy ignorieren und die vom Headless-Dienst erhaltene Liste der Endpunkte direkt zum Lastenausgleich Ihrer Anwendung verwenden.

Aber wie können wir allen im Cluster bereitgestellten Anwendungen eine ähnliche Logik hinzufügen?

Wenn Ihre Anwendung bereits bereitgestellt ist, scheint diese Aufgabe möglicherweise unmöglich zu sein. Es gibt jedoch eine alternative Möglichkeit.

Service Mesh hilft Ihnen dabei

Sie haben wahrscheinlich bereits bemerkt, dass die clientseitige Lastausgleichsstrategie ziemlich Standard ist.

Wenn die Anwendung startet, geschieht Folgendes:

  1. Ruft eine Liste der IP-Adressen vom Dienst ab.
  2. Öffnet und verwaltet einen Verbindungspool.
  3. Aktualisiert den Pool regelmäßig durch Hinzufügen oder Entfernen von Endpunkten.

Sobald die Anwendung eine Anfrage stellen möchte, geschieht Folgendes:

  1. Wählt mithilfe einer Logik (z. B. Round-Robin) eine verfügbare Verbindung aus.
  2. Führt die Anfrage aus.

Diese Schritte funktionieren sowohl für WebSockets-, gRPC- als auch für AMQP-Verbindungen.

Sie können diese Logik in eine separate Bibliothek aufteilen und in Ihren Anwendungen verwenden.

Sie können jedoch stattdessen Service Meshes wie Istio oder Linkerd verwenden.

Service Mesh erweitert Ihre Anwendung um einen Prozess, der:

  1. Sucht automatisch nach Dienst-IP-Adressen.
  2. Testet Verbindungen wie WebSockets und gRPC.
  3. Gleicht Anfragen mit dem richtigen Protokoll aus.

Service Mesh hilft bei der Verwaltung des Datenverkehrs innerhalb des Clusters, ist jedoch recht ressourcenintensiv. Andere Optionen sind die Verwendung von Bibliotheken von Drittanbietern wie Netflix Ribbon oder programmierbaren Proxys wie Envoy.

Was passiert, wenn Sie Balancing-Probleme ignorieren?

Sie können sich dafür entscheiden, den Lastausgleich nicht zu verwenden und trotzdem keine Änderungen zu bemerken. Schauen wir uns einige Arbeitsszenarien an.

Wenn Sie mehr Clients als Server haben, ist das kein so großes Problem.

Nehmen wir an, es gibt fünf Clients, die eine Verbindung zu zwei Servern herstellen. Auch wenn kein Balancing erfolgt, werden beide Server genutzt:

Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

Die Verbindungen sind möglicherweise nicht gleichmäßig verteilt: Vielleicht sind vier Clients mit demselben Server verbunden, aber es besteht eine gute Chance, dass beide Server verwendet werden.

Problematischer ist das umgekehrte Szenario.

Wenn Sie weniger Clients und mehr Server haben, werden Ihre Ressourcen möglicherweise nicht ausgelastet und es entsteht ein potenzieller Engpass.

Nehmen wir an, es gibt zwei Clients und fünf Server. Im besten Fall bestehen zwei permanente Verbindungen zu zwei von fünf Servern.

Die übrigen Server werden im Leerlauf sein:

Lastausgleich und Skalierung langlebiger Verbindungen in Kubernetes

Wenn diese beiden Server keine Client-Anfragen verarbeiten können, hilft die horizontale Skalierung nicht weiter.

Abschluss

Kubernetes-Dienste sind so konzipiert, dass sie in den meisten Standard-Webanwendungsszenarien funktionieren.

Sobald Sie jedoch anfangen, mit Anwendungsprotokollen zu arbeiten, die dauerhafte TCP-Verbindungen verwenden, wie z. B. Datenbanken, gRPC oder WebSockets, sind Dienste nicht mehr geeignet. Kubernetes bietet keine internen Mechanismen zum Ausgleich persistenter TCP-Verbindungen.

Das bedeutet, dass Sie Anwendungen unter Berücksichtigung des clientseitigen Ausgleichs schreiben müssen.

Vom Team erstellte Übersetzung Kubernetes aaS von Mail.ru.

Was gibt es sonst noch zum Thema zu lesen?:

  1. Drei Ebenen der automatischen Skalierung in Kubernetes und wie man sie effektiv nutzt
  2. Kubernetes im Geiste der Piraterie mit Vorlage zur Umsetzung.
  3. Unser Telegram-Kanal zum Thema digitale Transformation.

Source: habr.com

Kommentar hinzufügen