Lasbalansering en skaal langlewende verbindings in Kubernetes
Hierdie artikel sal jou help om te verstaan hoe lasbalansering in Kubernetes werk, wat gebeur wanneer langlewende verbindings skaal en hoekom jy kliënt-kant balansering moet oorweeg as jy HTTP/2, gRPC, RSockets, AMQP of ander langlewende protokolle gebruik .
'n Bietjie oor hoe verkeer in Kubernetes herverdeel word
Kubernetes bied twee gerieflike abstraksies vir die implementering van toepassings: Dienste en Ontplooiings.
Ontplooiings beskryf hoe en hoeveel kopieë van jou toepassing op enige gegewe tydstip moet loop. Elke toepassing word as 'n Pod ontplooi en word 'n IP-adres toegeken.
Dienste is soortgelyk in funksie aan 'n lasbalanseerder. Hulle is ontwerp om verkeer oor verskeie peule te versprei.
Kom ons kyk hoe dit lyk.
In die diagram hieronder kan jy drie gevalle van dieselfde toepassing en 'n lasbalanseerder sien:
Die lasbalanseerder word 'n diens genoem en word 'n IP-adres toegeken. Enige inkomende versoek word na een van die peule herlei:
Die ontplooiingscenario bepaal die aantal gevalle van die toepassing. Jy sal amper nooit direk hoef uit te brei onder:
Elke peul kry sy eie IP-adres:
Dit is nuttig om aan dienste te dink as 'n versameling IP-adresse. Elke keer as jy toegang tot die diens kry, word een van die IP-adresse uit die lys gekies en as die bestemmingsadres gebruik.
Dit lyk so.
'n Krul 10.96.45.152-versoek word aan die diens ontvang:
Die diens kies een van drie pod-adresse as die bestemming:
Verkeer word na 'n spesifieke peul herlei:
As u toepassing uit 'n voorkant en 'n agterkant bestaan, sal u beide 'n diens en 'n ontplooiing vir elkeen hê.
Wanneer die frontend 'n versoek aan die backend rig, hoef dit nie presies te weet hoeveel peule die backend bedien nie: daar kan een, tien of honderd wees.
Die voorkant weet ook niks van die adresse van die peule wat die agterkant bedien nie.
Wanneer die frontend 'n versoek aan die backend rig, gebruik dit die IP-adres van die backend-diens, wat nie verander nie.
Dit is hoe dit lyk.
Onder 1 versoek die interne backend-komponent. In plaas daarvan om 'n spesifieke een vir die agterkant te kies, rig dit 'n versoek aan die diens:
Die diens kies een van die backend-peule as die bestemmingsadres:
Verkeer gaan van Pod 1 na Pod 5, gekies deur die diens:
Onder 1 weet nie presies hoeveel peule soos onder 5 agter die diens versteek is nie:
Maar presies hoe versprei die diens versoeke? Dit wil voorkom asof round-robin-balansering gebruik word? Kom ons vind dit uit.
Balansering in Kubernetes-dienste
Kubernetes-dienste bestaan nie. Daar is geen proses vir die diens wat 'n IP-adres en poort toegeken is nie.
U kan dit verifieer deur by enige nodus in die groep aan te meld en die netstat -ntlp-opdrag uit te voer.
Jy sal nie eers die IP-adres wat aan die diens toegeken is, kan vind nie.
Die diens se IP-adres is geleë in die beheerlaag, in die kontroleerder, en aangeteken in die databasis - ens. Dieselfde adres word deur 'n ander komponent gebruik - kube-proxy.
Kube-proxy ontvang 'n lys van IP-adresse vir alle dienste en genereer 'n stel iptables-reëls op elke nodus in die groepering.
Hierdie reëls sê: "As ons die IP-adres van die diens sien, moet ons die bestemmingsadres van die versoek verander en dit na een van die peule stuur."
Die diens-IP-adres word slegs as 'n toegangspunt gebruik en word nie bedien deur enige proses wat na daardie IP-adres en poort luister nie.
Kom ons kyk hierna.
Oorweeg 'n groep van drie nodusse. Elke nodus het peule:
Gebinde peule wat beige geverf is, is deel van die diens. Omdat die diens nie as 'n proses bestaan nie, word dit in grys getoon:
Die eerste peul versoek 'n diens en moet na een van die geassosieerde peule gaan:
Maar die diens bestaan nie, die proses bestaan nie. Hoe werk dit?
Voordat die versoek die nodus verlaat, gaan dit deur die iptables-reëls:
Die iptables-reëls weet dat die diens nie bestaan nie en vervang sy IP-adres met een van die IP-adresse van die peule wat met daardie diens geassosieer word:
Die versoek ontvang 'n geldige IP-adres as die bestemmingsadres en word normaalweg verwerk:
Afhangende van die netwerktopologie, bereik die versoek uiteindelik die peul:
Kan iptables laaibalans laai?
Nee, iptables word vir filtering gebruik en is nie ontwerp vir balansering nie.
Dit is egter moontlik om 'n stel reëls te skryf wat werk soos pseudo-balanseerder.
En dit is presies wat in Kubernetes geïmplementeer word.
As jy drie peule het, sal kube-proxy die volgende reëls skryf:
Kies die eerste sub met 'n waarskynlikheid van 33%, anders gaan na die volgende reël.
Kies die tweede een met 'n waarskynlikheid van 50%, anders gaan na die volgende reël.
Kies die derde onder.
Hierdie stelsel lei daartoe dat elke peul gekies word met 'n waarskynlikheid van 33%.
En daar is geen waarborg dat Pod 2 volgende na Pod 1 gekies sal word nie.
Let daarop: iptables gebruik 'n statistiese module met ewekansige verspreiding. Die balanseringsalgoritme is dus gebaseer op ewekansige seleksie.
Noudat jy verstaan hoe dienste werk, kom ons kyk na meer interessante diensscenario's.
Langlewende verbindings in Kubernetes skaal nie by verstek nie
Elke HTTP-versoek van die frontend na die backend word bedien deur 'n aparte TCP-verbinding, wat oop- en toegemaak word.
As die frontend 100 versoeke per sekonde na die backend stuur, word 100 verskillende TCP-verbindings oop- en toegemaak.
U kan versoekverwerkingstyd en -lading verminder deur een TCP-verbinding oop te maak en dit vir alle daaropvolgende HTTP-versoeke te gebruik.
Die HTTP-protokol het 'n kenmerk genaamd HTTP keep-alive, of verbinding hergebruik. In hierdie geval word 'n enkele TCP-verbinding gebruik om verskeie HTTP-versoeke en -antwoorde te stuur en te ontvang:
Hierdie kenmerk is nie by verstek geaktiveer nie: beide die bediener en kliënt moet dienooreenkomstig gekonfigureer word.
Die opstelling self is eenvoudig en toeganklik vir die meeste programmeertale en omgewings.
Hier is 'n paar skakels na voorbeelde in verskillende tale:
Wat gebeur as ons Keep-alive in 'n Kubernetes-diens gebruik?
Kom ons neem aan dat beide die voorkant en agterkant aan die lewe hou.
Ons het een kopie van die voorkant en drie kopieë van die agterkant. Die frontend maak die eerste versoek en maak 'n TCP-verbinding na die backend oop. Die versoek bereik die diens, een van die backend-peule word as die bestemmingsadres gekies. Die backend stuur 'n antwoord, en die frontend ontvang dit.
Anders as die gewone situasie waar die TCP-verbinding gesluit word nadat 'n antwoord ontvang is, word dit nou oopgehou vir verdere HTTP-versoeke.
Wat gebeur as die voorkant meer versoeke na die agterkant stuur?
Om hierdie versoeke aan te stuur, sal 'n oop TCP-verbinding gebruik word, alle versoeke sal na dieselfde agterkant gaan waar die eerste versoek gegaan het.
Moet iptables nie die verkeer herverdeel nie?
Nie in hierdie geval nie.
Wanneer 'n TCP-verbinding geskep word, gaan dit deur iptables-reëls, wat 'n spesifieke agterkant kies waarheen die verkeer sal gaan.
Aangesien alle daaropvolgende versoeke op 'n reeds oop TCP-verbinding is, word die iptables-reëls nie meer opgeroep nie.
Kom ons kyk hoe dit lyk.
Die eerste peul stuur 'n versoek aan die diens:
Jy weet reeds wat volgende gaan gebeur. Die diens bestaan nie, maar daar is iptables-reëls wat die versoek sal verwerk:
Een van die backend-peule sal gekies word as die bestemmingsadres:
Die versoek bereik die peul. Op hierdie stadium sal 'n aanhoudende TCP-verbinding tussen die twee peule tot stand gebring word:
Enige daaropvolgende versoek van die eerste pod sal deur die reeds gevestigde verbinding gaan:
Die resultaat is vinniger reaksietyd en hoër deurset, maar jy verloor die vermoë om die agterkant te skaal.
Selfs as jy twee peule in die agterkant het, met 'n konstante verbinding, sal verkeer altyd na een van hulle gaan.
Kan dit reggemaak word?
Aangesien Kubernetes nie weet hoe om aanhoudende verbindings te balanseer nie, val hierdie taak op jou.
Dienste is 'n versameling IP-adresse en poorte wat eindpunte genoem word.
Jou aansoek kan 'n lys eindpunte van die diens kry en besluit hoe om versoeke tussen hulle te versprei. Jy kan 'n aanhoudende verbinding met elke peul oopmaak en versoeke tussen hierdie verbindings balanseer met behulp van round-robin.
Die kliënt-kant kode wat verantwoordelik is vir balansering moet hierdie logika volg:
Kry 'n lys van eindpunte van die diens.
Maak 'n aanhoudende verbinding vir elke eindpunt oop.
Wanneer 'n versoek gedoen moet word, gebruik een van die oop verbindings.
Werk gereeld die lys eindpunte op, skep nuwes of maak ou aanhoudende verbindings toe as die lys verander.
Dit is hoe dit sal lyk.
In plaas daarvan dat die eerste peul die versoek na die diens stuur, kan u versoeke aan die kliëntkant balanseer:
Jy moet kode skryf wat vra watter peule deel van die diens is:
Sodra jy die lys het, stoor dit aan die kliëntkant en gebruik dit om aan die peule te koppel:
Jy is verantwoordelik vir die lasbalanseringsalgoritme:
Nou ontstaan die vraag: is hierdie probleem net van toepassing op HTTP keep-alive?
Kliënt-kant lasbalansering
HTTP is nie die enigste protokol wat volgehoue TCP-verbindings kan gebruik nie.
As jou toepassing 'n databasis gebruik, word 'n TCP-verbinding nie elke keer oopgemaak as jy 'n versoek moet rig of 'n dokument van die databasis moet haal nie.
In plaas daarvan word 'n aanhoudende TCP-verbinding met die databasis oopgemaak en gebruik.
As jou databasis op Kubernetes ontplooi word en toegang as 'n diens verskaf word, sal jy dieselfde probleme ondervind wat in die vorige afdeling beskryf is.
Een databasis replika sal meer gelaai wees as die ander. Kube-proxy en Kubernetes sal nie help om verbindings te balanseer nie. U moet sorg dat u die navrae na u databasis balanseer.
Afhangende van watter biblioteek jy gebruik om aan die databasis te koppel, kan jy verskillende opsies hê om hierdie probleem op te los.
Hieronder is 'n voorbeeld van toegang tot 'n MySQL-databasiskluster vanaf 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
Daar is baie ander protokolle wat aanhoudende TCP-verbindings gebruik:
WebSockets en veilige WebSockets
HTTP / 2
gRPC
Rsokke
AMQP
Jy behoort reeds vertroud te wees met die meeste van hierdie protokolle.
Maar as hierdie protokolle so gewild is, hoekom is daar nie 'n gestandaardiseerde balanseringsoplossing nie? Waarom moet die kliëntlogika verander? Is daar 'n inheemse Kubernetes-oplossing?
Kube-proxy en iptables is ontwerp om die mees algemene gebruiksgevalle te dek wanneer dit na Kubernetes ontplooi word. Dit is vir gerief.
As jy 'n webdiens gebruik wat 'n REST API blootstel, is jy gelukkig - in hierdie geval word volgehoue TCP-verbindings nie gebruik nie, jy kan enige Kubernetes-diens gebruik.
Maar sodra jy begin om aanhoudende TCP-verbindings te gebruik, sal jy moet uitvind hoe om die las eweredig oor die backends te versprei. Kubernetes bevat nie klaargemaakte oplossings vir hierdie saak nie.
Daar is egter beslis opsies wat kan help.
Balansering van langlewende verbindings in Kubernetes
Daar is vier tipes dienste in Kubernetes:
ClusterIP
NodePort
LoadBalancer
onthoofde
Die eerste drie dienste werk gebaseer op 'n virtuele IP-adres, wat deur kube-proxy gebruik word om iptables-reëls te bou. Maar die fundamentele basis van alle dienste is 'n koplose diens.
Die koplose diens het geen IP-adres wat daarmee geassosieer word nie en verskaf slegs 'n meganisme om 'n lys van IP-adresse en poorte van die peule (eindpunte) wat daarmee geassosieer word, op te haal.
Alle dienste is gebaseer op die koplose diens.
Die ClusterIP-diens is 'n koplose diens met 'n paar toevoegings:
Die bestuurslaag ken 'n IP-adres daaraan toe.
Kube-proxy genereer die nodige iptables-reëls.
Op hierdie manier kan jy kube-proxy ignoreer en die lys eindpunte wat van die koplose diens verkry is, direk gebruik om jou aansoek te laai.
Maar hoe kan ons soortgelyke logika by alle toepassings wat in die groepering ontplooi word, byvoeg?
As jou toepassing reeds ontplooi is, kan hierdie taak onmoontlik lyk. Daar is egter 'n alternatiewe opsie.
Service Mesh sal jou help
U het waarskynlik al opgemerk dat die kliënt-kant-lasbalanseringstrategie redelik standaard is.
Wanneer die toepassing begin, dit:
Kry 'n lys van IP-adresse van die diens.
Maak 'n verbindingspoel oop en hou dit in stand.
Dateer die swembad periodiek op deur eindpunte by te voeg of te verwyder.
Sodra die aansoek 'n versoek wil rig, dit:
Kies 'n beskikbare verbinding met behulp van een of ander logika (bv. round-robin).
Voer die versoek uit.
Hierdie stappe werk vir beide WebSockets-, gRPC- en AMQP-verbindings.
Jy kan hierdie logika in 'n aparte biblioteek skei en dit in jou toepassings gebruik.
U kan egter eerder diensnetwerke soos Istio of Linkerd gebruik.
Service Mesh vul jou aansoek aan met 'n proses wat:
Soek outomaties vir diens IP-adresse.
Toets verbindings soos WebSockets en gRPC.
Balanseer versoeke deur die korrekte protokol te gebruik.
Service Mesh help om verkeer binne die groepering te bestuur, maar dit is redelik hulpbron-intensief. Ander opsies is om derdeparty-biblioteke soos Netflix Ribbon of programmeerbare gevolmagtigdes soos Envoy te gebruik.
Wat gebeur as jy balanseringskwessies ignoreer?
Jy kan kies om nie lasbalansering te gebruik nie en steeds geen veranderinge opmerk nie. Kom ons kyk na 'n paar werkscenario's.
As jy meer kliënte as bedieners het, is dit nie so 'n groot probleem nie.
Kom ons sê daar is vyf kliënte wat aan twee bedieners koppel. Selfs al is daar geen balansering nie, sal beide bedieners gebruik word:
Verbindings is dalk nie eweredig versprei nie: miskien vier kliënte wat aan dieselfde bediener gekoppel is, maar daar is 'n goeie kans dat albei bedieners gebruik sal word.
Wat meer problematies is, is die teenoorgestelde scenario.
As jy minder kliënte en meer bedieners het, kan jou hulpbronne onderbenut word en 'n potensiële bottelnek sal verskyn.
Kom ons sê daar is twee kliënte en vyf bedieners. In die beste geval sal daar twee permanente verbindings met twee uit vyf bedieners wees.
Die oorblywende bedieners sal ledig wees:
As hierdie twee bedieners nie kliëntversoeke kan hanteer nie, sal horisontale skaal nie help nie.
Gevolgtrekking
Kubernetes-dienste is ontwerp om in die meeste standaard webtoepassingscenario's te werk.
Sodra jy egter begin werk met toepassingsprotokolle wat aanhoudende TCP-verbindings gebruik, soos databasisse, gRPC of WebSockets, is dienste nie meer geskik nie. Kubernetes verskaf nie interne meganismes om volgehoue TCP-verbindings te balanseer nie.
Dit beteken dat jy aansoeke moet skryf met kliënt-kant-balansering in gedagte.