
Kyçja e këtij artikulli është të kuptoni si funksionon balancimi i ngarkesës në Kubernetes, çfarë ndodh gjatë shkallëzimit të lidhjeve afatgjata dhe pse duhet të shqyrtoni balancimin nga ana e klientit nëse përdorni HTTP/2, gRPC, RSockets, AMQP ose protokolle të tjera afatgjata.
Pak mbi mënyrën se si shpërndahet trafiku në Kubernetes
Kubernetes ofron dy abstraksione të dobishme për deploy-in e aplikacioneve: shërbime (Services) dhe dislokime (Deployments).
Dislokimet përshkruajnë si dhe sa kopje të aplikacionit tuaj duhet të jenë aktivë në çdo moment. Çdo aplikacion dislokohet si një pod (Pod) dhe i jepet një adresë IP.
Shërbimet, në funksion, janë të ngjashme me një balancues ngarkese. Ato janë të dizajnuara për të shpërndarë trafikun nëpër shumë pod-e.
Le të shohim se si duket kjo.
- Në diagramin më poshtë shihni tre kopje të një aplikacioni dhe një balancues ngarkese:

- Balancuesi i ngarkesës quhet shërbim (Service) dhe i jepet një adresë IP. Çdo kërkesë hyrëse drejtohet te një nga pod-et:

- Skenari i dislokimit përcakton numrin e kopjeve të aplikacionit. Duhen të dislokoni shumë rrallë direkt pod-in:

- Çdo pod-i i jepet një adresë IP individuale:

Është e dobishme të shqyrtoni shërbimet si një grup adresash IP. Çdo herë që i qaseni shërbimit, një nga adresat IP zgjidhet nga lista dhe përdoret si adresë destinacioni.
Kjo duket si më poshtë.
- Një kërkesë curl 10.96.45.152 në shërbim:

- Shërbimi zgjidh një nga tre adresat e pod-eve si destinacion:

- Trafiku drejtohet te një pod specifik:

Nëse aplikacioni juaj përbëhet nga një frontend dhe një backend, do të keni një shërbim dhe një dislokim për secilin.
Kur frontend-i bën një kërkesë te backend-i, ai nuk ka nevojë të dijë se sa pod-e shërbejnë backend-in: mund të ketë një, dhjetë ose njëqind.
Po ashtu, frontend-i nuk di asgjë për adresat e pod-eve që shërbejnë backend-in.
Kur frontend-i bën një kërkesë te backend-i, ai përdor adresën IP të shërbimit që është e pandryshueshme.
Kjo është si duket.
- Pod 1 bën një kërkesë te komponenti brenda backend-it. Në vend që të zgjedhë një pod specifik të backend-it, ai bën një kërkesë te shërbimi:

- Shërbimi zgjidh një nga pod-et e backend-it si adresë destinacioni:

- Trafiku shkon nga pod 1 te pod 5, të zgjedhur nga shërbimi:

- Pod 1 nuk di se sa pod-e si pod 5 janë fshehur pas shërbimit:

Por si e shpërndan shërbimi kërkesat? A duket se përdoret balancimi round-robin? Le të e kuptojmë.
Balancimi në shërbimet Kubernetes
Shërbimet Kubernetes nuk ekzistojnë. Nuk ka proces të dedikuar për një shërbim që ka një adresë IP dhe port.
Mund ta konfirmoni këtë duke hyrë në çdo nod të klasterit dhe duke ekzekutuar komandën netstat -ntlp.
As nuk do të jeni në gjendje të gjeni adresën IP që i është dedikuar shërbimit.
Adresa IP e shërbimit ndodhet në shtresën e menaxhimit, në kontrollues, dhe regjistrohet në bazën e të dhënave — etcd. Kjo adresë përdoret gjithashtu nga një komponent tjetër — kube-proxy.
Kube-proxy merr listën e adresave IP për të gjithë shërbimet dhe formon një grup rregullash iptables në çdo nod të klasterit.
Këto rregulla thonë: 'Nëse shohim adresën IP të shërbimit, duhet ta modifikojmë adresën destinacionit të kërkesës dhe ta dërgojmë te një nga pod-et'.
Adresa IP e shërbimit përdoret vetëm si një pikë hyrëse dhe nuk mbështetet nga asnjë proces që dëgjon këtë adresë IP dhe port.
Le të shohim këtë.
- Le të shqyrtojmë një klaster me tre nodë. Në çdo nod ka pod-e:

- Pod-et e lidhura, të ngjyrosura me ngjyrë të bezhë, janë pjesë e shërbimit. Duke qenë se shërbimi nuk ekziston si proces, ai shfaqet me ngjyrë gri:

- Pod-i i parë bën një kërkesë në shërbim dhe duhet të kalojë te një nga pod-et e lidhura:

- Por shërbimi nuk ekziston, nuk ka proces. Si funksionon kjo?

- Para se kërkesa të largohet nga nodi, ajo kalon përmes rregullave iptables:

- Rregullat iptables e dinë se shërbimi nuk ekziston, dhe zëvendësojnë adresën e tij IP me një nga adresat IP të pod-eve të lidhura me këtë shërbim:

- Kërkesa merr një adresë IP funksionale si adresë destinacioni dhe përpunoset normalisht:

- Në varësi të topologjisë rrjetore, kërkesa në fund arrin në pod:

A dinë iptables të balancojnë ngarkesën?
Jo, iptables përdoren për filtrimin dhe nuk janë projektuar për balancim.
Megjithatë, ekziston mundësia për të shkruar një grup rregullash që funksionon si .
Dhe pikërisht kjo realizohet në Kubernetes.
Nëse keni tri pod-e, kube-proxy do të shkruajë rregullat si më poshtë:
- Zgjidhni pod-in e parë me një probabilitet prej 33%, përndryshe kaloni te rregulla tjetër.
- Zgjidhni pod-in e dytë me një probabilitet prej 50%, përndryshe kaloni te rregulla tjetër.
- Zgjidhni pod-in e tretë.
Kyta sistemë bën që çdo pod të zgjidhet me një probabilitet prej 33%.

Dhe nuk ka asnjë garanci që podi 2 do të zgjedhet pas podit 1.
Shënim: iptables përdor një modul statistik me shpërndarje të rastësishme. Kështu, algoritmi i balancimit bazohet në një zgjedhje të rastit.
Tani, kur kuptoni se si funksionojnë shërbimet, le të shikojmë skenarë më interesantë.
Koneksionet e qëndrueshme në Kubernetes nuk janë të shkallëzuara nga default.
Çdo kërkesë HTTP nga frontend në backend shërbehet nga një koneksion TCP të veçantë që hapet dhe mbyllet.
Nëse frontend dërgon 100 kërkesa në sekondë në backend, atëherë hapen dhe mbyllen 100 koneksione TCP të ndryshme.
Mund të zvogëlohet koha e përpunimit të kërkesës dhe të reduktohet ngarkesa nëse hapet një koneksion TCP dhe përdoret për të gjitha kërkesat e ardhshme HTTP.
Protokolli HTTP përmban një mundësi, të quajtur HTTP keep-alive, ose ribashkimi i koneksionit. Në këtë rast, një koneksion TCP përdoret për dërgimin dhe marrjen e shumë kërkesave dhe përgjigjeve HTTP:

Kjo mundësi nuk është e aktivizuar si default: si serveri, ashtu edhe klienti duhet të jenë të konfiguruar në përputhje me këtë.
Vetë konfigurimi është i thjeshtë dhe i arritshëm për shumicën e gjuhëve të programimit dhe ambienteve.
Ja disa lidhje për shembuj në gjuhë të ndryshme:
Çfarë do të ndodhë nëse përdorim keep-alive në shërbimin Kubernetes?
Le të supozojmë se si frontend-ashtu edhe backend-i mbështesin keep-alive.
Ne kemi një kopje të frontend-it dhe tre instanca të backend-it. Frontend-i bën kërkesën e parë dhe hap një koneksion TCP me backend-in. Kërkesa arrin te shërbimi, një nga pod-et e backend-it zgjidhet si adresë përfundimtare. Pod-i i backend-it dërgon përgjigjen dhe frontend-i e merr atë.
Në përputhje me situatën e zakonshme, kur pas marrjes së përgjigjes koneksioni TCP mbyllet, tani ai mbahet i hapur për kërkesat e ardhshme HTTP.
Çfarë do ndodhë nëse frontend dërgon kërkesa të tjera në backend?
Për të dërguar këto kërkesa do të përdoret koneksioni TCP i hapur, të gjitha kërkesat do të shkojnë në të njëjtin pod të backend-it, ku shkuan kërkesa e parë.
A nuk duhet iptables të redrejtojë trafikun?
Jo në këtë rast.
Kur krijohet një koneksion TCP, ai kalon përmes rregullave të iptables, që zgjedhin pod-in e caktuar të backend-it ku do të shkojë trafiku.
Pasi të gjitha kërkesat e mëpasme shkojnë përmes një koneksioni TCP tashmë të hapur, rregullat e iptables nuk thirren më.
Le të shohim se si duket kjo.
- Pod-i i parë dërgon një kërkesë te shërbimi:

- Tani e dini se çfarë do të ndodhë më pas. Shërbimi nuk ekziston, por ka rregulla iptables që do të trajtojnë kërkesën:

- Një nga pod-et e backend-it do të zgjidhet si adresë e destinacionit:

- Kërkesa arrin te pod-i. Në këtë moment, një koneksion të përhershëm TCP midis dy pod-eve do të vendoset:

- Çdo kërkesë tjetër nga pod-i i parë do të shkojë përmes këtij koneksioni të vendosur tashmë:

Si rezultat, keni marrë një përgjigje më të shpejtë dhe një kapacitet më të lartë, por keni humbur mundësinë për të shkallëzuar backend-in.
Madje edhe nëse keni dy pod-e në backend, nëse keni një koneksion të qëndrueshëm, trafiku do të vazhdojë të shkojë në një prej tyre.
A mund ta rregullojmë këtë?
Duke qenë se Kubernetes nuk di si të balancojë koneksionet e qëndrueshme, kjo detyrë bie mbi ju.
Shërbimet janë një grup IP adresash dhe portesh që quhen pikë fundore.
Aplikacioni juaj mund të marrë një listë pikash fundore nga shërbimi dhe të vendosë se si të shpërndajë kërkesat midis tyre. Mund të hapni një koneksion të qëndrueshëm me secilin pod dhe të balanconi kërkesat midis këtyre koneksioneve me anë të round-robin.
Ose të aplikoni algoritme më .
Kodi në anën e klientit, i cili është përgjegjës për balancimin, duhet të ndjekë logjikën e tillë:
- Merrni një listë pikash fundore nga shërbimi.
- Për çdo pikë fundore hapni një koneksion të qëndrueshëm.
- Kur është e nevojshme të bëni një kërkesë, përdorni një nga koneksionet e hapura.
- Rregullisht përditësoni listën e pikave fundore, krijoni lidhje të reja ose mbyllni ato të vjetra në rast të ndryshimit të listës.
Ja si do të dukej.
- Në vend që pod-i i parë të dërgonte kërkesën te shërbimi, mund të balanconi kërkesat në anën e klientit:

- Duhet të shkruani një kod që pyet se cilat pod-e janë pjesë e shërbimit:

- Sa herë që merrni listën, ruani atë në anën e klientit dhe përdorni për t'u lidhur me pod-et:

- Ju jeni ata që përgjigjeni për algoritmin e balancimit të ngarkesës:

Tani është një pyetje: a i përket kjo problem vetëm HTTP keep-alive?
Balancimi i ngarkesës në anën e klientit
HTTP — nuk është protokolli i vetëm që mund të përdorë lidhje të përhershme TCP.
Nëse aplikacioni juaj përdor një bazë të dhënash, lidhja TCP nuk hapet çdo herë kur ju nevojitet të ekzekutoni një kërkesë ose të merrni një dokument nga baza e të dhënave.
Në vend të kësaj, hapet dhe përdoret një lidhje e përhershme TCP me bazën e të dhënave.
Nëse baza juaj e të dhënave është vendosur në Kubernetes dhe qasja ofrohet në formën e një shërbimi, do të përballeni me të njëjtat probleme të përshkruara në seksionin e mëparshëm.
Një kopje e bazës së të dhënave do të ngarkohet më shumë se të tjerat. Kube-proxy dhe Kubernetes nuk do të ndihmojnë në balancimin e lidhjeve. Ju duhet të merrni përsipër balancimin e kërkesave në bazën tuaj të të dhënave.
Varësisht nga biblioteka që përdorni për t'u lidhur me bazën e të dhënave, mund të keni disa mundësi për zgjidhjen e këtij problemi.
Më poshtë është një shembull i qasjes në klustërin e bazës së të dhënave MySQL nga Node.js:
var mysql = require('mysql');
var poolCluster = mysql.createPoolCluster();
var endpoints = /* merrni endpoint-et nga Shërbimi */
for (var [index, endpoint] of endpoints) {
poolCluster.add(`mysql-replica-${index}`, endpoint);
}
// Bëni kërkesa në bazën e të dhënave MySQL të klustëruarKa shumë protokolle të tjera që përdorin lidhje të përhershme TCP:
- WebSockets dhe WebSockets të siguruara
- HTTP/2
- gRPC
- RSockets
- AMQP
Ju duhet të jeni tashmë të njohur me shumicën e këtyre protokolleve.
Por nëse këto protokolle janë kaq populare, pse nuk ka një zgjidhje të standardizuar për balancimin? Pse kërkohet ndërrimi i logjikës së klientit? A ekziston një zgjidhje native për Kubernetes?
Kube-proxy dhe iptables janë krijuar për të mbuluar shumicën e skenarëve standard të përdorimit gjatë vendosjes në Kubernetes. Kjo është bërë për qenë më e thjeshtë.
Nëse përdorni një shërbim uebi që ofron një REST API, ju jeni me fat — në këtë rast lidhjet e përhershme TCP nuk përdoren, mund të përdorni çdo shërbim të Kubernetes.
Por sapo të filloni të përdorni lidhje të përhershme TCP, do të duhet të kuptoni se si të shpërndani ngarkesën në backend-et. Kubernetes nuk ka zgjidhje të gatshme për këtë rast.
Megjithatë, sigurisht, ka mundësi që mund të ndihmojnë.
Balancimi i lidhjeve të gjatë në Kubernetes
Në Kubernetes ekzistojnë katër lloje shërbimesh:
- ClusterIP
- NodePort
- LoadBalancer
- Headless
Tre shërbimet e para funksionojnë mbi një adresë IP virtuale, e cila përdoret nga kube-proxy për të ndërtuar rregullat iptables. Por baza themelore e të gjitha shërbimeve është shërbimi i tipit headless.
Shërbimi headless nuk është i lidhur me asnjë adresë IP dhe ofron vetëm një mekanizëm për të marrë një listë adresash IP dhe portesh të lidhur me podet e lidhura (pikëfundet).
Të gjitha shërbimet mbështeten në shërbimin headless.
Shërbimi ClusterIP është një shërbim headless me disa shtesa:
- Shtresa e menaxhimit i cakton një adresë IP.
- Kube-proxy krijon rregullat e nevojshme iptables.
Kështu, ju mund të injoroni kube-proxy dhe të përdorni direkt listën e pikëfundit të marra nga shërbimi headless për balancimin e ngarkesës në aplikacionin tuaj.
Por si të shtoni një logjikë të tillë në të gjitha aplikacionet e vendosura në kluster?
Nëse aplikacioni juaj është tashmë i vendosur, një detyrë e tillë mund të duket e pamundur. Megjithatë, ka një variant alternativ.
Service Mesh do t'ju ndihmojë
Ju ndoshta keni vënë re se strategjia e balancimit të ngarkesës në anën e klientit është mjaft standarde.
Kur aplikacioni fillon, ai:
- Merr një listë adresash IP nga shërbimi.
- Hap dhe mban një grup lidhjesh.
- Rregullisht përditëson grupin, duke shtuar ose hequr pikëfundet.
Sa herë që aplikacioni dëshiron të bëjë një kërkesë, ai:
- Zgjedh një lidhje të disponueshme, duke përdorur ndonjë logjikë (për shembull, round-robin).
- Kryen kërkesën.
Këto hapa funksionojnë edhe për lidhjet WebSockets, edhe për gRPC, edhe për AMQP.
Mund ta ndani këtë logjikë në një bibliotekë të veçantë dhe ta përdorni në aplikacionet tuaja.
Megjithatë, për këtë mund të përdorni edhe mesh shërbimesh, si Istio ose Linkerd.
Service Mesh i shton aplikacionit tuaj një proces i cili:
- Automatikisht kërkon adresat IP të shërbimeve.
- Kontrollon lidhjet, si WebSockets dhe gRPC.
- Balanco kërkesat, duke përdorur protokollin e duhur.
Service Mesh ndihmon në menaxhimin e trafikut brenda klustrit, por është mjaft e ndjeshme ndaj burimeve. Mundësi të tjera përfshijnë përdorimin e bibliotekave të palëve të treta, si Netflix Ribbon, ose proxy të programueshëm, si Envoy.
Çfarë do të ndodhë nëse injoroni çështjet e balancimit?
Mund të mos përdorni balancimin e ngarkesës dhe megjithatë të mos vëreni asnjë ndryshim. Le të shohim disa skenarë të funksionimit.
Nëse keni më shumë klientë se serverë, kjo nuk është një problem kaq i madh.
Supozoni se ka pesë klientë që lidhen me dy serverë. Edhe nëse nuk ka balancim, të dy serverët do të përdoren:

Lidhjet mund të jenë të shpërndara në mënyrë të pavendosur: ndoshta katër klientë janë lidhur me të njëjtin server, por ka një mundësi të mirë që të dy serverët të përdoren.
Ajo që është më problematike është skenari i kundërt.
Nëse keni më pak klientë dhe më shumë serverë, burimet tuaja mund të përdoren në mënyrë të pamjaftueshme, duke çuar në një ngushticë të mundshme.
Supozoni se ka dy klientë dhe pesë serverë. Në rastin më të mirë do të kemi dy lidhje të qëndrueshme me dy servera nga pesë.
Serverët e tjerë do të qëndrojnë bosh:

Nëse këta dy serverë nuk munden të përballojnë përpunimin e kërkesave të klientëve, horizonti i shkallëzimit nuk do të ndihmojë.
Përfundimi
Shërbimet e Kubernetes janë krijuar për të punuar në shumicën e skenarëve standard të aplikacioneve web.
Megjithatë, sapo filloni të punoni me protokollet e aplikacioneve që përdorin lidhje të qëndrueshme TCP, si bazat e të dhënave, gRPC ose WebSockets, shërbimet nuk janë më të përshtatshme. Kubernetes nuk ofron mekanizma të brendshëm për balancimin e lidhjeve TCP të qëndrueshme.
Kjo do të thotë se duhet të shkruani aplikacione me marrjen parasysh të mundësisë së balancimit nga ana e klientit.
Përkthimi është përgatitur nga ekipi .
Çfarë tjetër të lexoni në këtë temë:
- .
- .
- .
Burimi: habr.com




























