ProHoster > Bloc > Administració > Equilibri de càrrega i escalat de connexions de llarga durada a Kubernetes
Equilibri de càrrega i escalat de connexions de llarga durada a Kubernetes
Aquest article us ajudarà a entendre com funciona l'equilibri de càrrega a Kubernetes, què passa en escalar connexions de llarga durada i per què hauríeu de considerar l'equilibri del costat del client si feu servir HTTP/2, gRPC, RSockets, AMQP o altres protocols de llarga durada. .
Una mica sobre com es redistribueix el trànsit a Kubernetes
Kubernetes ofereix dues abstraccions convenients per desplegar aplicacions: Serveis i Desplegaments.
Els desplegaments descriuen com i quantes còpies de la vostra aplicació s'han d'executar en un moment donat. Cada aplicació es desplega com a Pod i se li assigna una adreça IP.
Els serveis són similars en funció a un equilibrador de càrrega. Estan dissenyats per distribuir el trànsit entre múltiples pods.
Anem a veure com es veu.
Al diagrama següent podeu veure tres instàncies de la mateixa aplicació i un equilibrador de càrrega:
L'equilibrador de càrrega s'anomena servei i se li assigna una adreça IP. Qualsevol sol·licitud entrant es redirigeix a un dels pods:
L'escenari de desplegament determina el nombre d'instàncies de l'aplicació. Gairebé mai no haureu d'expandir-vos directament sota:
Cada pod té assignada la seva pròpia adreça IP:
És útil pensar en els serveis com una col·lecció d'adreces IP. Cada vegada que accediu al servei, es selecciona una de les adreces IP de la llista i s'utilitza com a adreça de destinació.
Es veu així.
Es rep una sol·licitud curl 10.96.45.152 al servei:
El servei selecciona una de les tres adreces de pod com a destinació:
El trànsit es redirigeix a un pod específic:
Si la vostra aplicació consta d'un front-end i un backend, tindreu un servei i un desplegament per a cadascun.
Quan el frontend fa una sol·licitud al backend, no necessita saber exactament quants pods serveix el backend: n'hi pot haver un, deu o cent.
A més, el frontend no sap res sobre les adreces dels pods que serveixen al backend.
Quan el frontend fa una sol·licitud al backend, utilitza l'adreça IP del servei de backend, que no canvia.
Així és com es veu.
Under 1 sol·licita el component de backend intern. En lloc de seleccionar-ne un específic per al backend, fa una sol·licitud al servei:
El servei selecciona un dels pods de backend com a adreça de destinació:
El trànsit va del Pod 1 al Pod 5, seleccionat pel servei:
Menors d'1 no sap exactament quants pods com els menors de 5 s'amaguen darrere del servei:
Però, com distribueix exactament les peticions el servei? Sembla que s'utilitza l'equilibri round-robin? Anem a esbrinar-ho.
Equilibri en els serveis de Kubernetes
Els serveis de Kubernetes no existeixen. No hi ha cap procés per al servei que tingui assignada una adreça IP i un port.
Podeu verificar-ho iniciant sessió a qualsevol node del clúster i executant l'ordre netstat -ntlp.
Ni tan sols podreu trobar l'adreça IP assignada al servei.
L'adreça IP del servei es troba a la capa de control, al controlador, i es registra a la base de dades, etcd. La mateixa adreça l'utilitza un altre component: kube-proxy.
Kube-proxy rep una llista d'adreces IP per a tots els serveis i genera un conjunt de regles iptables a cada node del clúster.
Aquestes regles diuen: "Si veiem l'adreça IP del servei, hem de modificar l'adreça de destinació de la sol·licitud i enviar-la a un dels pods".
L'adreça IP del servei només s'utilitza com a punt d'entrada i cap procés que escolta aquesta adreça IP i aquest port.
Mirem això.
Considereu un clúster de tres nodes. Cada node té beines:
Les beines lligades pintades de beix formen part del servei. Com que el servei no existeix com a procés, es mostra en gris:
El primer pod sol·licita un servei i ha d'anar a un dels pods associats:
Però el servei no existeix, el procés no existeix. Com funciona?
Abans que la sol·licitud surti del node, passa per les regles d'iptables:
Les regles d'iptables saben que el servei no existeix i substitueixen la seva adreça IP per una de les adreces IP dels pods associades a aquest servei:
La sol·licitud rep una adreça IP vàlida com a adreça de destinació i es processa normalment:
Depenent de la topologia de la xarxa, la sol·licitud arriba finalment al pod:
Es pot equilibrar la càrrega d'iptables?
No, els iptables s'utilitzen per filtrar i no s'han dissenyat per a l'equilibri.
Tanmateix, és possible escriure un conjunt de regles que funcionin com pseudo-equilibrador.
I això és exactament el que s'implementa a Kubernetes.
Si teniu tres pods, kube-proxy escriurà les regles següents:
Seleccioneu el primer sub amb una probabilitat del 33%, en cas contrari aneu a la següent regla.
Trieu el segon amb una probabilitat del 50%, en cas contrari aneu a la següent regla.
Seleccioneu el tercer a sota.
Aquest sistema fa que cada pod sigui seleccionat amb una probabilitat del 33%.
I no hi ha cap garantia que el Pod 2 sigui escollit després del Pod 1.
Nota: iptables utilitza un mòdul estadístic amb distribució aleatòria. Per tant, l'algorisme d'equilibri es basa en la selecció aleatòria.
Ara que enteneu com funcionen els serveis, mirem escenaris de serveis més interessants.
Les connexions de llarga vida a Kubernetes no s'escalen per defecte
Cada sol·licitud HTTP des de l'interfície fins al backend es dóna per una connexió TCP independent, que s'obre i es tanca.
Si la interfície envia 100 sol·licituds per segon al backend, s'obren i es tanquen 100 connexions TCP diferents.
Podeu reduir el temps de processament i la càrrega de les sol·licituds obrint una connexió TCP i utilitzant-la per a totes les sol·licituds HTTP posteriors.
El protocol HTTP té una funció anomenada HTTP keep-alive o reutilització de la connexió. En aquest cas, s'utilitza una única connexió TCP per enviar i rebre diverses sol·licituds i respostes HTTP:
Aquesta característica no està habilitada per defecte: tant el servidor com el client s'han de configurar en conseqüència.
La configuració en si és senzilla i accessible per a la majoria de llenguatges i entorns de programació.
Aquí teniu alguns enllaços a exemples en diferents idiomes:
Què passa si fem servir Keep-alive en un servei de Kubernetes?
Suposem que tant el frontend com el backend admeten es mantenen vius.
Tenim una còpia del frontend i tres còpies del backend. L'interfície fa la primera sol·licitud i obre una connexió TCP al backend. La sol·licitud arriba al servei, es selecciona un dels pods de backend com a adreça de destinació. El backend envia una resposta i el frontend la rep.
A diferència de la situació habitual en què la connexió TCP es tanca després de rebre una resposta, ara es manté oberta per a més sol·licituds HTTP.
Què passa si el frontend envia més sol·licituds al backend?
Per reenviar aquestes sol·licituds, s'utilitzarà una connexió TCP oberta, totes les sol·licituds aniran al mateix backend on va anar la primera sol·licitud.
Els iptables no haurien de redistribuir el trànsit?
No en aquest cas.
Quan es crea una connexió TCP, passa per les regles d'iptables, que seleccionen un backend específic on anirà el trànsit.
Com que totes les peticions posteriors es troben en una connexió TCP ja oberta, les regles d'iptables ja no es criden.
Anem a veure com es veu.
El primer pod envia una sol·licitud al servei:
Ja sabeu què passarà després. El servei no existeix, però hi ha regles d'iptables que processaran la sol·licitud:
Se seleccionarà un dels pods de fons com a adreça de destinació:
La petició arriba al pod. En aquest punt, s'establirà una connexió TCP persistent entre els dos pods:
Qualsevol sol·licitud posterior del primer pod passarà per la connexió ja establerta:
El resultat és un temps de resposta més ràpid i un rendiment més elevat, però perds la capacitat d'escalar el backend.
Fins i tot si teniu dos pods al backend, amb una connexió constant, el trànsit sempre anirà a un d'ells.
Això es pot arreglar?
Com que Kubernetes no sap com equilibrar les connexions persistents, aquesta tasca us correspon.
Els serveis són una col·lecció d'adreces IP i ports anomenats punts finals.
La vostra aplicació pot obtenir una llista de punts finals del servei i decidir com distribuir les sol·licituds entre ells. Podeu obrir una connexió persistent a cada pod i equilibrar les sol·licituds entre aquestes connexions utilitzant el round-robin.
El codi del costat del client responsable de l'equilibri hauria de seguir aquesta lògica:
Obteniu una llista de punts finals del servei.
Obriu una connexió persistent per a cada punt final.
Quan cal fer una sol·licitud, utilitzeu una de les connexions obertes.
Actualitzeu regularment la llista de punts finals, creeu-ne de nous o tanqueu connexions persistents antigues si la llista canvia.
Així es veurà.
En lloc que el primer pod enviï la sol·licitud al servei, podeu equilibrar les sol·licituds al costat del client:
Heu d'escriure un codi que us demani quins pods formen part del servei:
Un cop tingueu la llista, deseu-la al costat del client i utilitzeu-la per connectar-vos als pods:
Sou responsable de l'algorisme d'equilibri de càrrega:
Ara sorgeix la pregunta: aquest problema només s'aplica al manteniment HTTP?
Equilibri de càrrega del costat del client
HTTP no és l'únic protocol que pot utilitzar connexions TCP persistents.
Si la vostra aplicació utilitza una base de dades, no s'obre una connexió TCP cada vegada que necessiteu fer una sol·licitud o recuperar un document de la base de dades.
En canvi, s'obre i s'utilitza una connexió TCP persistent a la base de dades.
Si la vostra base de dades està desplegada a Kubernetes i l'accés es proporciona com a servei, trobareu els mateixos problemes descrits a la secció anterior.
Una rèplica de base de dades estarà més carregada que les altres. Kube-proxy i Kubernetes no ajudaran a equilibrar les connexions. Heu de tenir cura d'equilibrar les consultes a la vostra base de dades.
Depenent de quina biblioteca utilitzeu per connectar-vos a la base de dades, podeu tenir diferents opcions per resoldre aquest problema.
A continuació es mostra un exemple d'accés a un clúster de bases de dades MySQL des de 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
Hi ha molts altres protocols que utilitzen connexions TCP persistents:
WebSockets i WebSockets segurs
HTTP / 2
gRPC
RSockets
AMQP
Ja hauríeu d'estar familiaritzat amb la majoria d'aquests protocols.
Però si aquests protocols són tan populars, per què no hi ha una solució d'equilibri estandarditzada? Per què cal canviar la lògica del client? Hi ha una solució nativa de Kubernetes?
Kube-proxy i iptables estan dissenyats per cobrir els casos d'ús més habituals quan es despleguen a Kubernetes. Això és per comoditat.
Si utilitzeu un servei web que exposa una API REST, esteu de sort; en aquest cas, no s'utilitzen connexions TCP persistents, podeu utilitzar qualsevol servei de Kubernetes.
Però un cop comenceu a utilitzar connexions TCP persistents, haureu d'esbrinar com distribuir uniformement la càrrega entre els backends. Kubernetes no conté solucions preparades per a aquest cas.
No obstant això, sens dubte hi ha opcions que poden ajudar.
Equilibrant connexions de llarga vida a Kubernetes
Hi ha quatre tipus de serveis a Kubernetes:
ClústerIP
Port del node
LoadBalancer
Sense cap
Els tres primers serveis funcionen en funció d'una adreça IP virtual, que kube-proxy utilitza per crear regles d'iptables. Però la base fonamental de tots els serveis és un servei sense cap.
El servei sense cap no té cap adreça IP associada i només proporciona un mecanisme per recuperar una llista d'adreces IP i ports dels pods (punts finals) associats amb ell.
Tots els serveis es basen en el servei sense cap.
El servei ClusterIP és un servei sense cap amb algunes addicions:
La capa de gestió li assigna una adreça IP.
Kube-proxy genera les regles iptables necessàries.
D'aquesta manera, podeu ignorar kube-proxy i utilitzar directament la llista de punts finals obtinguts del servei sense cap per equilibrar la càrrega de la vostra aplicació.
Però, com podem afegir una lògica similar a totes les aplicacions desplegades al clúster?
Si la vostra aplicació ja està implementada, aquesta tasca pot semblar impossible. Tanmateix, hi ha una opció alternativa.
Service Mesh t'ajudarà
Probablement ja us heu adonat que l'estratègia d'equilibri de càrrega del costat del client és bastant estàndard.
Quan s'inicia l'aplicació:
Obté una llista d'adreces IP del servei.
Obre i manté un grup de connexions.
Actualitza periòdicament el grup afegint o eliminant punts finals.
Un cop l'aplicació vol fer una sol·licitud, aquesta:
Selecciona una connexió disponible utilitzant alguna lògica (per exemple, round-robin).
Executa la petició.
Aquests passos funcionen tant per a connexions WebSockets, gRPC i AMQP.
Podeu separar aquesta lògica en una biblioteca independent i utilitzar-la a les vostres aplicacions.
Tanmateix, podeu utilitzar malles de servei com Istio o Linkerd.
Service Mesh augmenta la vostra aplicació amb un procés que:
Cerca automàticament les adreces IP del servei.
Prova connexions com ara WebSockets i gRPC.
Equilibra les sol·licituds utilitzant el protocol correcte.
Service Mesh ajuda a gestionar el trànsit dins del clúster, però requereix força recursos. Altres opcions són utilitzar biblioteques de tercers com Netflix Ribbon o servidors intermediaris programables com Envoy.
Què passa si ignores els problemes d'equilibri?
Podeu optar per no utilitzar l'equilibri de càrrega i encara no noteu cap canvi. Vegem alguns escenaris de treball.
Si teniu més clients que servidors, aquest no és un problema tan gran.
Suposem que hi ha cinc clients que es connecten a dos servidors. Encara que no hi hagi equilibri, s'utilitzaran els dos servidors:
És possible que les connexions no estiguin distribuïdes uniformement: potser quatre clients connectats al mateix servidor, però hi ha moltes possibilitats que s'utilitzin els dos servidors.
El que és més problemàtic és l'escenari contrari.
Si teniu menys clients i més servidors, és possible que els vostres recursos estiguin infrautilitzats i apareixerà un possible coll d'ampolla.
Suposem que hi ha dos clients i cinc servidors. En el millor dels casos, hi haurà dues connexions permanents a dos servidors de cada cinc.
La resta de servidors estaran inactius:
Si aquests dos servidors no poden gestionar les sol·licituds dels clients, l'escala horitzontal no ajudarà.
Conclusió
Els serveis de Kubernetes estan dissenyats per funcionar en la majoria dels escenaris estàndard d'aplicacions web.
Tanmateix, un cop comenceu a treballar amb protocols d'aplicació que utilitzen connexions TCP persistents, com ara bases de dades, gRPC o WebSockets, els serveis ja no són adequats. Kubernetes no proporciona mecanismes interns per equilibrar les connexions TCP persistents.
Això vol dir que heu d'escriure aplicacions tenint en compte l'equilibri del costat del client.