
Dit artikel helpt u begrijpen hoe load balancing werkt in Kubernetes, wat er gebeurt bij het schalen van langdurige verbindingen en waarom u client-side load balancing zou moeten overwegen als u HTTP/2, gRPC, RSockets, AMQP of andere langdurige protocollen gebruikt.
Een beetje over hoe verkeer wordt herverdeeld in Kubernetes
Kubernetes biedt twee handige abstracties voor het implementeren van applicaties: Services en Deployments.
Implementaties beschrijven hoeveel kopieën van uw applicatie er op een bepaald moment actief moeten zijn en hoe vaak. Elke applicatie wordt geïmplementeerd als een Pod en krijgt een IP-adres toegewezen.
Services hebben een vergelijkbare functionaliteit als een load balancer. Ze zijn ontworpen om het verkeer over meerdere pods te verdelen.
Laten we eens kijken hoe het eruit ziet.
- In het onderstaande diagram ziet u drie exemplaren van dezelfde applicatie en een load balancer:

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

- Het implementatiescenario bepaalt het aantal exemplaren van de applicatie. U zult vrijwel nooit rechtstreeks onder een van de volgende systemen hoeven te implementeren:

- Aan elke pod wordt een eigen IP-adres toegewezen:

Het is nuttig om diensten te beschouwen als een verzameling IP-adressen. Elke keer dat u toegang krijgt tot de service, wordt een van de IP-adressen uit de lijst geselecteerd en gebruikt als bestemmingsadres.
Het ziet er zo uit.
- Er wordt een curl 10.96.45.152-verzoek ontvangen bij de service:

- De service selecteert een van de drie pod-adressen als bestemming:

- Verkeer wordt omgeleid naar een specifieke pod:

Als uw applicatie uit een frontend en een backend bestaat, dan hebt u voor elk een service en een implementatie.
Wanneer de frontend een verzoek aan de backend doet, hoeft deze niet precies te weten hoeveel pods de backend bedient: het kunnen er één, tien of honderd zijn.
Bovendien weet de frontend niets over de adressen van de pods die de backend bedienen.
Wanneer de frontend een verzoek doet aan de backend, wordt het IP-adres van de backend-service gebruikt. Dit IP-adres verandert niet.
Dit is hoe het eruit ziet.
- Sub 1 vraagt om een intern backend component. In plaats van een specifieke backend-pod te selecteren, wordt er een verzoek aan de service gedaan:

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

- Het verkeer gaat van pod 1 naar pod 5, geselecteerd door de service:

- Pod 1 weet niet precies hoeveel pods er net als pod 5 achter de service verborgen zitten:

Maar hoe verdeelt de dienst verzoeken precies? Het lijkt erop dat er gebruik wordt gemaakt van 'round-robin balancing'? Laten we het eens uitzoeken.
Balanceren in Kubernetes-services
Kubernetes-services bestaan niet. Er is geen proces toegewezen aan een IP-adres en een poort voor de service.
U kunt dit verifiëren door naar een willekeurig clusterknooppunt te gaan en de opdracht netstat -ntlp uit te voeren.
U zult zelfs het IP-adres dat aan de service is toegewezen, niet kunnen vinden.
Het IP-adres van de service bevindt zich in de besturingslaag, in de controller, en wordt naar de database geschreven - 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 clusterknooppunt.
Deze regels luiden als volgt: "Als we het IP-adres van een service zien, moeten we het bestemmingsadres van de aanvraag wijzigen en deze naar een van de pods sturen."
Het service-IP-adres wordt alleen gebruikt als toegangspunt en wordt niet bediend door een proces dat op dat IP-adres en die poort luistert.
Laten we eens kijken hoe dat zit.
- Laten we eens een cluster van drie knooppunten bekijken. Elk knooppunt bevat de volgende pods:

- De gekoppelde pods, beige gekleurd, maken deel uit van de service. Omdat de service niet als proces bestaat, wordt deze grijs weergegeven:

- De eerste pod vraagt de service aan en moet worden toegewezen aan een van de bijbehorende pods:

- Maar de service bestaat niet, er is geen proces. Hoe werkt het?

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

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

- De aanvraag ontvangt een geldig IP-adres als bestemmingsadres en wordt normaal verwerkt:

- Afhankelijk van de netwerktopologie bereikt het verzoek uiteindelijk de pod:

Kan iptables load balancing uitvoeren?
Nee, iptables wordt gebruikt voor filtering en is niet ontworpen voor load balancing.
Het is echter mogelijk om een reeks regels te schrijven die als volgt werken: .
En dit is precies wat in Kubernetes is geïmplementeerd.
Als u drie pods hebt, schrijft kube-proxy de volgende regels:
- Kies de eerste pod met een waarschijnlijkheid van 33%. Anders ga je door naar de volgende regel.
- Kies de tweede pod met een waarschijnlijkheid van 50%. Anders ga je naar de volgende regel.
- Selecteer de derde.
Dit systeem zorgt ervoor dat elke pod met een waarschijnlijkheid van 33% wordt geselecteerd.

En er is geen garantie dat pod 2 na pod 1 gekozen wordt.
Noot:iptables gebruikt een statistische module met willekeurige verdeling. Het balanceringsalgoritme is dus gebaseerd op willekeurige selectie.
Nu u begrijpt hoe diensten werken, kunnen we nog een paar interessante scenario's bekijken.
Langdurige verbindingen in Kubernetes schalen standaard niet
Elk HTTP-verzoek van de frontend naar de backend wordt via een aparte TCP-verbinding verwerkt, 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 van aanvragen verkorten en de belasting verminderen door één TCP-verbinding te openen en deze voor alle daaropvolgende HTTP-aanvragen te gebruiken.
Het HTTP-protocol bevat een functie genaamd HTTP keep-alive, of hergebruik van verbindingen. In dit geval wordt één enkele TCP-verbinding gebruikt om meerdere HTTP-verzoeken en -reacties te verzenden en ontvangen:

Deze functie is standaard niet ingeschakeld: zowel de server als de client moeten hierop worden geconfigureerd.
De installatie zelf is eenvoudig en beschikbaar voor de meeste programmeertalen en -omgevingen.
Hier zijn enkele links naar voorbeelden in verschillende talen:
Wat gebeurt er als we keep-alive gebruiken in de Kubernetes-service?
Laten we aannemen dat zowel de frontend als de backend keep-alive ondersteunen.
We hebben één kopie van de frontend en drie kopieën van de backend. De frontend doet de eerste aanvraag en opent een TCP-verbinding met de backend. Het verzoek bereikt de service en een van de backend-pods wordt geselecteerd als bestemmingsadres. De backend stuurt een antwoord en de frontend ontvangt dit.
In tegenstelling tot de gebruikelijke situatie, waarbij de TCP-verbinding wordt gesloten na ontvangst van een antwoord, blijft deze nu open voor verdere HTTP-verzoeken.
Wat gebeurt er als de frontend meer verzoeken naar de backend stuurt?
Er wordt een open TCP-verbinding gebruikt om deze verzoeken door te sturen en alle verzoeken gaan naar dezelfde backendpod als waar het eerste verzoek naartoe ging.
Zouden iptables het verkeer niet moeten herverdelen?
In dit geval is dat niet het geval.
Wanneer er een TCP-verbinding wordt gemaakt, doorloopt deze de iptables-regels, die een specifieke backend-pod selecteren waar het verkeer naartoe moet.
Omdat alle volgende aanvragen via de reeds geopende TCP-verbinding verlopen, worden iptables-regels niet meer aangeroepen.
Laten we eens kijken hoe het eruit ziet.
- De eerste pod stuurt een verzoek naar de service:

- Je weet al wat er vervolgens gaat gebeuren. De service bestaat niet, maar er zijn iptables-regels die de aanvraag afhandelen:

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

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

- Elk volgend verzoek van de eerste pod verloopt via de reeds tot stand gebrachte verbinding:

Het resultaat was snellere responstijden en een hogere doorvoer, maar u verloor de mogelijkheid om de backend te schalen.
Zelfs als u twee pods in uw backend hebt, zal het verkeer bij een constante verbinding altijd naar één van de twee pods gaan.
Kan dit gerepareerd worden?
Omdat Kubernetes niet weet hoe permanente verbindingen in balans moeten worden gebracht, is dit jouw taak.
Services bestaan uit een reeks IP-adressen en poorten, ook wel eindpunten genoemd.
Uw toepassing kan een lijst met eindpunten van de service ophalen en bepalen hoe verzoeken tussen de eindpunten moeten worden verdeeld. U kunt een permanente verbinding met elke pod openen en verzoeken tussen deze verbindingen verdelen met behulp van round-robin.
Of vraag meer aan .
De client-side code die verantwoordelijk is voor het balanceren, moet de volgende logica volgen:
- Haal een lijst met eindpunten op van een service.
- Open voor elk eindpunt een permanente verbinding.
- Wanneer er een verzoek gedaan moet worden, gebruik dan één van de open verbindingen.
- Werk de lijst met eindpunten regelmatig bij, maak nieuwe eindpunten of sluit oude permanente verbindingen als de lijst verandert.
Dit is hoe het eruit zal zien.
- In plaats van dat de eerste pod het verzoek naar de service stuurt, kunt u de verzoeken aan de clientzijde in evenwicht brengen:

- Je moet code schrijven die vraagt welke pods deel uitmaken van de service:

- Zodra u de lijst hebt, slaat u deze op aan de clientzijde en gebruikt u deze om verbinding te maken met de pods:

- Jij bent verantwoordelijk voor het load balancing-algoritme:

Nu rijst de vraag: heeft dit probleem alleen te maken met HTTP keep-alive?
Client-side load balancing
HTTP is niet het enige protocol dat permanente TCP-verbindingen kan gebruiken.
Als uw toepassing een database gebruikt, wordt er niet elke keer dat u een aanvraag moet uitvoeren of een document uit de database moet ophalen, een TCP-verbinding geopend.
In plaats daarvan wordt een permanente TCP-verbinding met de database geopend en gebruikt.
Als uw database is geïmplementeerd in Kubernetes en beschikbaar is als een service, zult u dezelfde problemen tegenkomen die in de vorige sectie zijn beschreven.
Één replica van de database wordt meer geladen dan de andere. Kube-proxy en Kubernetes helpen niet bij het verdelen van de belasting van verbindingen. U moet ervoor zorgen dat de query's naar uw database in evenwicht zijn.
Afhankelijk van de bibliotheek die u gebruikt om verbinding te maken met de database, hebt u verschillende opties om dit probleem op te lossen.
Hieronder ziet u een voorbeeld van hoe u toegang krijgt 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 databaseEr zijn veel andere protocollen die gebruikmaken van persistente TCP-verbindingen:
- WebSockets en beveiligde WebSockets
- HTTP / 2
- gRPC
- RSockets
- AMQP
De meeste van deze protocollen zijn waarschijnlijk al bekend.
Maar als deze protocollen zo populair zijn, waarom is er dan geen gestandaardiseerde oplossing voor het balanceren? Waarom is het nodig om de clientlogica te wijzigen? Bestaat er een native Kubernetes-oplossing?
Kube-proxy en iptables zijn ontworpen om de meeste gangbare use cases te dekken bij implementaties in Kubernetes. Dit doen we voor het gemak.
Als u gebruikmaakt van een webservice die een REST API biedt, hebt u geluk. In dat geval worden er geen persistente TCP-verbindingen gebruikt en kunt u elke Kubernetes-service gebruiken.
Maar zodra u persistente TCP-verbindingen gaat gebruiken, moet u uitzoeken hoe u de belasting gelijkmatig over de back-ends kunt verdelen. Kubernetes biedt geen kant-en-klare oplossingen voor dit geval.
Er zijn natuurlijk wel opties die kunnen helpen.
Balanceren van langdurige verbindingen in Kubernetes
Er zijn vier soorten services in Kubernetes:
- ClusterIP
- Knooppuntpoort
- Load Balancer
- Zonder hoofd
De eerste drie services werken op een virtueel IP-adres, dat door kube-proxy wordt gebruikt om iptables-regels te bouwen. Maar de fundamentele basis van alle diensten is de headless-dienst.
Aan de headless-service zijn geen IP-adressen gekoppeld en deze biedt alleen een mechanisme om een lijst met IP-adressen en poorten van de gekoppelde pods (eindpunten) op te halen.
Alle services zijn gebaseerd op de headless service.
De ClusterIP-service is een headless-service met enkele toevoegingen:
- De besturingslaag wijst er een IP-adres aan toe.
- Kube-proxy genereert de benodigde iptables-regels.
Op deze manier kunt u kube-proxy negeren en rechtstreeks de lijst met eindpunten gebruiken die u van de headless service hebt verkregen om de belasting van uw toepassing te verdelen.
Maar hoe voeg je vergelijkbare logica toe aan alle applicaties die in het cluster zijn geïmplementeerd?
Als uw applicatie al is geïmplementeerd, kan deze taak onmogelijk lijken. Er is echter een alternatieve optie.
Service Mesh helpt u
U hebt waarschijnlijk opgemerkt dat de client-side load balancing-strategie vrij standaard is.
Wanneer de applicatie start, gebeurt het volgende:
- Haalt een lijst met IP-adressen op van de service.
- Opent en onderhoudt een verbindingenpool.
- Werkt de pool regelmatig bij door eindpunten toe te voegen of te verwijderen.
Wanneer een applicatie een verzoek wil doen, gebeurt het volgende:
- Selecteert een beschikbare verbinding met behulp van logica (bijv. round-robin).
- Voert het verzoek uit.
Deze stappen werken voor WebSockets-, gRPC- en AMQP-verbindingen.
U kunt deze logica in een aparte bibliotheek opslaan en in uw toepassingen gebruiken.
U kunt echter ook gebruik maken van service meshes zoals Istio of Linkerd.
Een service mesh breidt uw applicatie uit met een proces dat:
- Zoekt automatisch naar IP-adressen van services.
- Controleert verbindingen zoals WebSockets en gRPC.
- Verwerkt verzoeken in evenwicht met behulp van het juiste protocol.
Service Mesh helpt bij het beheren van het verkeer binnen een cluster, maar is vrij resource-intensief. Andere opties zijn het gebruik van bibliotheken van derden, zoals Netflix Ribbon of programmeerbare proxy's zoals Envoy.
Wat gebeurt er als je balansproblemen negeert?
Het kan zijn dat u geen load balancing gebruikt en toch geen verschil merkt. Laten we eens naar enkele werkscenario's kijken.
Als u meer clients dan servers hebt, is dit geen groot probleem.
Stel dat er vijf clients verbinding maken met twee servers. Zelfs als er geen balancering plaatsvindt, worden beide servers gebruikt:

De verbindingen zijn mogelijk niet gelijkmatig verdeeld: misschien zijn er vier clients verbonden met dezelfde server, maar de kans is groot dat beide servers worden gebruikt.
Problematischer is het omgekeerde scenario.
Als u minder clients en meer servers hebt, worden uw bronnen mogelijk niet optimaal benut en ontstaat er een potentieel knelpunt.
Stel dat er twee clients en vijf servers zijn. Er zijn maximaal twee permanente verbindingen met twee van de vijf servers.
De overige servers blijven inactief:

Als deze twee servers de clientverzoeken niet aankunnen, heeft horizontaal schalen geen zin.
Conclusie
Kubernetes-services zijn ontworpen om te werken in de meest voorkomende webapplicatiescenario's.
Zodra u echter begint te werken met toepassingsprotocollen die gebruikmaken van persistente TCP-verbindingen, zoals databases, gRPC of WebSockets, zijn de services niet meer geschikt. Kubernetes biedt geen interne mechanismen voor load balancing van persistente TCP-verbindingen.
Dit betekent dat u applicaties moet schrijven met client-side load balancing in gedachten.
Vertaling voorbereid door het team .
Wat nog meer te lezen over het onderwerp:
- .
- .
- .
Bron: www.habr.com




























