Load-balancing en het schalen van langlevende verbindingen in Kubernetes

Load-balancing en het schalen van langlevende verbindingen in Kubernetes
Dit artikel helpt u te begrijpen hoe taakverdeling werkt in Kubernetes, wat er gebeurt bij het schalen van langlevende verbindingen en waarom u evenwicht aan de clientzijde zou moeten overwegen als u HTTP/2, gRPC, RSockets, AMQP of andere langlevende protocollen gebruikt. . 

Iets over hoe verkeer wordt herverdeeld in Kubernetes 

Kubernetes biedt twee handige abstracties voor het implementeren van applicaties: Services en Implementaties.

Implementaties beschrijven hoe en hoeveel exemplaren van uw applicatie op een bepaald moment moeten worden uitgevoerd. Elke applicatie wordt ingezet als Pod en krijgt een IP-adres toegewezen.

Services zijn qua functie vergelijkbaar met een load balancer. Ze zijn ontworpen om verkeer over meerdere pods te verdelen.

Laten we eens kijken hoe het eruit ziet.

  1. In het onderstaande diagram ziet u drie exemplaren van dezelfde applicatie en een load balancer:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  2. De load balancer wordt een Service genoemd en krijgt een IP-adres toegewezen. Elk inkomend verzoek wordt doorgestuurd naar een van de pods:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  3. Het implementatiescenario bepaalt het aantal exemplaren van de applicatie. U hoeft vrijwel nooit direct uit te breiden onder:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  4. Elke pod krijgt een eigen IP-adres toegewezen:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

Het is nuttig om services te beschouwen als een verzameling IP-adressen. Elke keer dat u de dienst bezoekt, wordt een van de IP-adressen uit de lijst geselecteerd en als bestemmingsadres gebruikt.

Het ziet er zo uit.

  1. Er wordt een curl 10.96.45.152-verzoek ontvangen bij de service:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  2. De service selecteert een van de drie podadressen als bestemming:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  3. Verkeer wordt omgeleid naar een specifieke pod:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

Als uw applicatie uit een frontend en een backend bestaat, heeft u voor elk een service en een implementatie.

Wanneer de frontend een verzoek doet aan de backend, hoeft deze niet precies te weten hoeveel pods de backend bedient: het kunnen er één, tien of honderd zijn.

Ook weet de frontend niets over de adressen van de pods die de backend bedienen.

Wanneer de frontend een verzoek doet aan de backend, gebruikt deze het IP-adres van de backend-service, dat niet verandert.

Hier is hoe het eruit ziet.

  1. Onder 1 wordt de interne backend-component aangevraagd. In plaats van een specifiek exemplaar voor de backend te selecteren, wordt een verzoek aan de service gericht:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  2. De service selecteert een van de backend-pods als bestemmingsadres:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  3. Het verkeer gaat van Pod 1 naar Pod 5, geselecteerd door de service:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  4. Onder 1 weet niet precies hoeveel pods zoals onder 5 er achter de dienst verborgen zitten:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

Maar hoe verdeelt de dienst verzoeken precies? Het lijkt erop dat er gebruik wordt gemaakt van round-robin-balancering? Laten we het uitzoeken. 

Balanceren in Kubernetes-services

Kubernetes-services bestaan ​​niet. Er is geen proces voor de service waaraan een IP-adres en poort is toegewezen.

U kunt dit verifiëren door u aan te melden bij een willekeurig knooppunt in het cluster en de opdracht netstat -ntlp uit te voeren.

U kunt het IP-adres dat aan de service is toegewezen niet eens vinden.

Het IP-adres van de dienst bevindt zich in de controlelaag, in de controller, en vastgelegd in de database - etcd. Hetzelfde adres wordt gebruikt door een ander onderdeel: kube-proxy.
Kube-proxy ontvangt een lijst met IP-adressen voor alle services en genereert een set iptables-regels op elk knooppunt in het cluster.

Deze regels zeggen: “Als we het IP-adres van de dienst zien, moeten we het bestemmingsadres van het verzoek wijzigen en naar een van de pods sturen.”

Het service-IP-adres wordt alleen gebruikt als toegangspunt en wordt niet bediend door enig proces dat naar dat IP-adres en die poort luistert.

Laten we hier eens naar kijken

  1. Beschouw een cluster van drie knooppunten. Elk knooppunt heeft peulen:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  2. Beige geverfde gebonden peulen maken deel uit van het servies. Omdat de service niet als proces bestaat, wordt deze grijs weergegeven:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  3. De eerste pod vraagt ​​om een ​​dienst en moet naar een van de bijbehorende pods gaan:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  4. Maar de dienst bestaat niet, het proces bestaat niet. Hoe werkt het?

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  5. Voordat het verzoek het knooppunt verlaat, doorloopt het de iptables-regels:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  6. De iptables-regels weten dat de service niet bestaat en vervangen het IP-adres ervan door een van de IP-adressen van de pods die aan die service zijn gekoppeld:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  7. Het verzoek ontvangt een geldig IP-adres als bestemmingsadres en wordt normaal verwerkt:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  8. Afhankelijk van de netwerktopologie bereikt het verzoek uiteindelijk de pod:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

Kan iptables een laadbalans maken?

Nee, iptables worden gebruikt voor filtering en zijn niet ontworpen voor balancering.

Het is echter mogelijk om een ​​reeks regels te schrijven die als volgt werken pseudo-balancer.

En dit is precies wat er in Kubernetes is geïmplementeerd.

Als u drie pods hebt, schrijft kube-proxy de volgende regels:

  1. Selecteer de eerste sub met een waarschijnlijkheid van 33%, ga anders naar de volgende regel.
  2. Kies de tweede met een waarschijnlijkheid van 50%, ga anders naar de volgende regel.
  3. Selecteer de derde onder.

Dit systeem resulteert erin dat elke pod wordt geselecteerd met een waarschijnlijkheid van 33%.

Load-balancing en het schalen van langlevende verbindingen in Kubernetes

En er is geen garantie dat Pod 2 na Pod 1 zal worden gekozen.

Noot: iptables gebruikt een statistische module met willekeurige verdeling. Het balanceringsalgoritme is dus gebaseerd op willekeurige selectie.

Nu u begrijpt hoe services werken, gaan we eens kijken naar interessantere servicescenario's.

Verbindingen met een lange levensduur in Kubernetes schalen niet standaard

Elk HTTP-verzoek van de frontend naar de backend wordt bediend door een afzonderlijke TCP-verbinding, die wordt geopend en gesloten.

Als de frontend 100 verzoeken per seconde naar de backend stuurt, worden er 100 verschillende TCP-verbindingen geopend en gesloten.

U kunt de verwerkingstijd en belasting van verzoeken verkorten door één TCP-verbinding te openen en deze voor alle volgende HTTP-verzoeken te gebruiken.

Het HTTP-protocol heeft een functie die HTTP keep-alive of hergebruik van verbindingen wordt genoemd. In dit geval wordt één TCP-verbinding gebruikt om meerdere HTTP-verzoeken en -antwoorden te verzenden en ontvangen:

Load-balancing en het schalen van langlevende verbindingen in Kubernetes

Deze functie is standaard niet ingeschakeld: zowel de server als de client moeten dienovereenkomstig worden geconfigureerd.

De installatie zelf is eenvoudig en toegankelijk voor de meeste programmeertalen en omgevingen.

Hier zijn enkele links naar voorbeelden in verschillende talen:

Wat gebeurt er als we keep-alive gebruiken in een Kubernetes-service?
Laten we aannemen dat zowel de frontend als de backend ondersteuning in leven blijven.

We hebben één exemplaar van de frontend en drie exemplaren van de backend. De frontend doet het eerste verzoek en opent een TCP-verbinding met de backend. Het verzoek bereikt de service, een van de backend-pods wordt geselecteerd als bestemmingsadres. De backend verzendt een antwoord en de frontend ontvangt deze.

In tegenstelling tot de gebruikelijke situatie waarin de TCP-verbinding wordt gesloten na ontvangst van een antwoord, wordt deze nu opengehouden voor verdere HTTP-verzoeken.

Wat gebeurt er als de frontend meer verzoeken naar de backend stuurt?

Om deze verzoeken door te sturen wordt gebruik gemaakt van een open TCP-verbinding, alle verzoeken gaan naar dezelfde backend waar het eerste verzoek naartoe ging.

Moeten iptables het verkeer niet herverdelen?

In dit geval niet.

Wanneer een TCP-verbinding tot stand wordt gebracht, worden de iptables-regels doorlopen, die een specifieke backend selecteren waar het verkeer naartoe gaat.

Omdat alle volgende verzoeken op een reeds geopende TCP-verbinding plaatsvinden, worden de iptables-regels niet langer aangeroepen.

Laten we eens kijken hoe het eruit ziet.

  1. De eerste pod stuurt een verzoek naar de service:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  2. Je weet al wat er daarna gaat gebeuren. De service bestaat niet, maar er zijn iptables-regels die het verzoek zullen verwerken:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  3. Eén van de backend-pods wordt geselecteerd als bestemmingsadres:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  4. Het verzoek bereikt de pod. Op dit punt wordt een permanente TCP-verbinding tussen de twee pods tot stand gebracht:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  5. Elk volgend verzoek van de eerste pod zal via de reeds tot stand gebrachte verbinding gaan:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

Het resultaat is een snellere responstijd en een hogere doorvoer, maar u verliest de mogelijkheid om de backend te schalen.

Zelfs als je twee pods in de backend hebt, met een constante verbinding, zal het verkeer altijd naar één van hen gaan.

Kan dit gerepareerd worden?

Omdat Kubernetes niet weet hoe persistente verbindingen in evenwicht moeten worden gebracht, ligt deze taak bij u.

Services zijn een verzameling IP-adressen en poorten die eindpunten worden genoemd.

Uw toepassing kan een lijst met eindpunten van de service ophalen en beslissen hoe aanvragen tussen deze eindpunten worden verdeeld. U kunt een permanente verbinding met elke pod openen en aanvragen tussen deze verbindingen verdelen met behulp van round-robin.

Of meer toepassen complexe balanceringsalgoritmen.

De code aan de clientzijde die verantwoordelijk is voor het balanceren moet deze logica volgen:

  1. Haal een lijst met eindpunten op van de service.
  2. Open een permanente verbinding voor elk eindpunt.
  3. Wanneer er een aanvraag gedaan moet worden, maak dan gebruik van één van de open verbindingen.
  4. Werk de lijst met eindpunten regelmatig bij, maak nieuwe of sluit oude persistente verbindingen als de lijst verandert.

Dit is hoe het eruit zal zien.

  1. In plaats van dat de eerste pod het verzoek naar de service verzendt, kunt u verzoeken aan de clientzijde balanceren:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  2. U moet code schrijven die vraagt ​​welke pods deel uitmaken van de service:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  3. Zodra u de lijst heeft, slaat u deze op aan de clientzijde en gebruikt u deze om verbinding te maken met de pods:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

  4. Jij bent verantwoordelijk voor het load-balancing-algoritme:

    Load-balancing en het schalen van langlevende verbindingen in Kubernetes

Nu rijst de vraag: geldt dit probleem alleen voor HTTP keep-alive?

Loadbalancing aan de clientzijde

HTTP is niet het enige protocol dat gebruik kan maken van persistente TCP-verbindingen.

Als uw applicatie gebruik maakt van een database, wordt er niet telkens een TCP-verbinding geopend wanneer u een aanvraag moet indienen of een document uit de database moet ophalen. 

In plaats daarvan wordt een permanente TCP-verbinding met de database geopend en gebruikt.

Als uw database op Kubernetes is geïmplementeerd en de toegang wordt verleend als een service, dan zult u dezelfde problemen tegenkomen als beschreven in de vorige sectie.

Eén databasereplica wordt zwaarder geladen dan de andere. Kube-proxy en Kubernetes helpen niet bij het balanceren van verbindingen. U moet ervoor zorgen dat de zoekopdrachten in uw database in evenwicht zijn.

Afhankelijk van welke bibliotheek u gebruikt om verbinding te maken met de database, heeft u mogelijk verschillende opties om dit probleem op te lossen.

Hieronder ziet u een voorbeeld van toegang tot een MySQL-databasecluster vanuit 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

Er zijn veel andere protocollen die persistente TCP-verbindingen gebruiken:

  • WebSockets en beveiligde WebSockets
  • HTTP / 2
  • gRPC
  • RSockets
  • AMQP

U bent waarschijnlijk al bekend met de meeste van deze protocollen.

Maar als deze protocollen zo populair zijn, waarom is er dan geen gestandaardiseerde balanceringsoplossing? Waarom moet de clientlogica veranderen? Is er een native Kubernetes-oplossing?

Kube-proxy en iptables zijn ontworpen om de meest voorkomende gebruiksscenario's te dekken bij implementatie in Kubernetes. Dit is voor het gemak.

Als u een webservice gebruikt die een REST API beschikbaar stelt, heeft u geluk: in dit geval worden er geen persistente TCP-verbindingen gebruikt, maar kunt u elke Kubernetes-service gebruiken.

Maar zodra u persistente TCP-verbindingen gaat gebruiken, moet u uitzoeken hoe u de belasting gelijkmatig over de backends kunt verdelen. Kubernetes biedt voor dit geval geen kant-en-klare oplossingen.

Er zijn echter zeker opties die kunnen helpen.

Het balanceren van langlevende verbindingen in Kubernetes

Er zijn vier soorten services in Kubernetes:

  1. ClusterIP
  2. Knooppuntpoort
  3. Load Balancer
  4. Zonder hoofd

De eerste drie services werken op basis van een virtueel IP-adres, dat door kube-proxy wordt gebruikt om iptables-regels te bouwen. Maar de fundamentele basis van alle diensten is een dienst zonder hoofd.

Aan de headless-service is geen enkel IP-adres gekoppeld en biedt alleen een mechanisme voor het ophalen van een lijst met IP-adressen en poorten van de pods (eindpunten) die eraan zijn gekoppeld.

Alle diensten zijn gebaseerd op de headless service.

De ClusterIP-service is een headless-service met enkele toevoegingen: 

  1. De beheerlaag wijst er een IP-adres aan toe.
  2. Kube-proxy genereert de benodigde iptables-regels.

Op deze manier kunt u kube-proxy negeren en direct de lijst met eindpunten gebruiken die zijn verkregen van de headless-service om de belasting van uw toepassing te verdelen.

Maar hoe kunnen we soortgelijke logica toevoegen aan alle applicaties die in het cluster worden geïmplementeerd?

Als uw toepassing al is geïmplementeerd, lijkt deze taak misschien onmogelijk. Er is echter een alternatieve optie.

Service Mesh helpt u

Je hebt waarschijnlijk al gemerkt dat de load-balancing-strategie aan de clientzijde vrij standaard is.

Wanneer de applicatie start, doet deze het volgende:

  1. Haalt een lijst met IP-adressen op van de service.
  2. Opent en onderhoudt een verbindingspool.
  3. Werkt de pool periodiek bij door eindpunten toe te voegen of te verwijderen.

Zodra de toepassing een verzoek wil indienen, wordt het volgende gedaan:

  1. Selecteert een beschikbare verbinding met behulp van enige logica (bijvoorbeeld round-robin).
  2. Voert het verzoek uit.

Deze stappen werken voor zowel WebSockets-, gRPC- als AMQP-verbindingen.

U kunt deze logica in een afzonderlijke bibliotheek opdelen en deze in uw toepassingen gebruiken.

U kunt in plaats daarvan echter servicemeshes zoals Istio of Linkerd gebruiken.

Service Mesh breidt uw applicatie uit met een proces dat:

  1. Zoekt automatisch naar service-IP-adressen.
  2. Test verbindingen zoals WebSockets en gRPC.
  3. Balanceert verzoeken met behulp van het juiste protocol.

Service Mesh helpt bij het beheren van het verkeer binnen het cluster, maar vergt behoorlijk wat resources. Andere opties zijn het gebruik van bibliotheken van derden, zoals Netflix Ribbon of programmeerbare proxy's zoals Envoy.

Wat gebeurt er als je evenwichtsproblemen negeert?

U kunt ervoor kiezen om geen gebruik te maken van taakverdeling en toch geen wijzigingen op te merken. Laten we een paar werkscenario's bekijken.

Als je meer clients dan servers hebt, is dit niet zo'n groot probleem.

Laten we zeggen dat er vijf clients zijn die verbinding maken met twee servers. Zelfs als er geen balancering plaatsvindt, worden beide servers gebruikt:

Load-balancing en het schalen van langlevende verbindingen in Kubernetes

Het kan zijn dat de verbindingen niet gelijkmatig verdeeld zijn: wellicht vier clients die op dezelfde server zijn aangesloten, maar de kans is groot dat beide servers worden gebruikt.

Wat problematischer is, is het tegenovergestelde scenario.

Als u minder clients en meer servers heeft, worden uw bronnen mogelijk onderbenut en ontstaat er een potentieel knelpunt.

Laten we zeggen dat er twee clients en vijf servers zijn. In het beste geval zijn er twee permanente verbindingen met twee van de vijf servers.

De overige servers zijn inactief:

Load-balancing en het schalen van langlevende verbindingen in Kubernetes

Als deze twee servers de clientverzoeken niet aankunnen, helpt horizontaal schalen niet.

Conclusie

Kubernetes-services zijn ontworpen om te werken in de meeste standaard webapplicatiescenario's.

Zodra je echter gaat werken met applicatieprotocollen die gebruik maken van persistente TCP-verbindingen, zoals databases, gRPC of WebSockets, zijn diensten niet meer geschikt. Kubernetes biedt geen interne mechanismen voor het balanceren van persistente TCP-verbindingen.

Dit betekent dat u applicaties moet schrijven met balancering aan de clientzijde in gedachten.

Vertaling voorbereid door het team Kubernetes aaS van Mail.ru.

Wat nog meer te lezen over het onderwerp:

  1. Drie niveaus van automatisch schalen in Kubernetes en hoe u deze effectief kunt gebruiken
  2. Kubernetes in de geest van piraterij met een sjabloon voor implementatie.
  3. Ons Telegram-kanaal over digitale transformatie.

Bron: www.habr.com

Voeg een reactie