Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes
Acest articol vă va ajuta să înțelegeți cum funcționează echilibrarea încărcăturii în Kubernetes, ce se întâmplă la scalarea conexiunilor cu durată lungă de viață și de ce ar trebui să luați în considerare echilibrarea pe partea clientului dacă utilizați HTTP/2, gRPC, RSockets, AMQP sau alte protocoale cu durată lungă de viață . 

Câteva despre cum este redistribuit traficul în Kubernetes 

Kubernetes oferă două abstracții convenabile pentru implementarea aplicațiilor: Servicii și Implementări.

Implementările descriu cum și câte copii ale aplicației dvs. ar trebui să ruleze la un moment dat. Fiecare aplicație este implementată ca Pod și i se atribuie o adresă IP.

Serviciile sunt similare în funcție de un echilibrator de încărcare. Sunt concepute pentru a distribui traficul pe mai multe poduri.

Să vedem cum arată.

  1. În diagrama de mai jos puteți vedea trei instanțe ale aceleiași aplicații și un echilibrator de încărcare:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  2. Echilibratorul de încărcare se numește Serviciu și i se atribuie o adresă IP. Orice solicitare primită este redirecționată către unul dintre poduri:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  3. Scenariul de implementare determină numărul de instanțe ale aplicației. Aproape niciodată nu va trebui să vă extindeți direct sub:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  4. Fiecărui pod îi este atribuită propria sa adresă IP:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

Este util să ne gândim la servicii ca la o colecție de adrese IP. De fiecare dată când accesați serviciul, una dintre adresele IP este selectată din listă și utilizată ca adresă de destinație.

Arata cam asa.

  1. Se primește o solicitare curl 10.96.45.152 către serviciu:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  2. Serviciul selectează una dintre cele trei adrese de pod ca destinație:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  3. Traficul este redirecționat către un anumit pod:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

Dacă aplicația dvs. constă dintr-un frontend și un backend, atunci veți avea atât un serviciu, cât și o implementare pentru fiecare.

Când interfața face o solicitare către backend, nu trebuie să știe exact câte poduri servește backend-ul: ar putea fi unul, zece sau o sută.

De asemenea, frontend-ul nu știe nimic despre adresele pod-urilor care servesc backend-ul.

Când interfața face o solicitare către backend, folosește adresa IP a serviciului backend, care nu se modifică.

Așa arată.

  1. Sub 1 solicită componenta backend internă. În loc să selecteze unul specific pentru backend, acesta face o solicitare către serviciu:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  2. Serviciul selectează unul dintre podurile backend ca adresă de destinație:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  3. Traficul merge de la Pod 1 la Pod 5, selectat de serviciu:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  4. Sub 1 nu știe exact câte poduri ca sub 5 sunt ascunse în spatele serviciului:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

Dar cum anume distribuie serviciul cererile? Se pare că se folosește echilibrarea round-robin? Să ne dăm seama. 

Echilibrare în serviciile Kubernetes

Serviciile Kubernetes nu există. Nu există niciun proces pentru serviciul căruia i se atribuie o adresă IP și un port.

Puteți verifica acest lucru conectându-vă la orice nod din cluster și rulând comanda netstat -ntlp.

Nici măcar nu veți putea găsi adresa IP alocată serviciului.

Adresa IP a serviciului este situată în stratul de control, în controler și înregistrată în baza de date - etcd. Aceeași adresă este folosită de o altă componentă - kube-proxy.
Kube-proxy primește o listă de adrese IP pentru toate serviciile și generează un set de reguli iptables pe fiecare nod din cluster.

Aceste reguli spun: „Dacă vedem adresa IP a serviciului, trebuie să modificăm adresa de destinație a cererii și să o trimitem la unul dintre poduri”.

Adresa IP a serviciului este folosită doar ca punct de intrare și nu este deservită de niciun proces care ascultă adresa IP și portul respectiv.

Să ne uităm la asta

  1. Luați în considerare un grup de trei noduri. Fiecare nod are pod-uri:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  2. Păstăile legate vopsite în bej fac parte din serviciu. Deoarece serviciul nu există ca proces, este afișat cu gri:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  3. Primul pod solicită un serviciu și trebuie să meargă la unul dintre podurile asociate:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  4. Dar serviciul nu există, procesul nu există. Cum functioneazã?

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  5. Înainte ca cererea să părăsească nodul, trece prin regulile iptables:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  6. Regulile iptables știu că serviciul nu există și îi înlocuiesc adresa IP cu una dintre adresele IP ale pod-urilor asociate serviciului respectiv:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  7. Solicitarea primește o adresă IP validă ca adresă de destinație și este procesată în mod normal:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  8. În funcție de topologia rețelei, cererea ajunge în cele din urmă la pod:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

Se poate echilibra încărcarea iptables?

Nu, iptables sunt folosite pentru filtrare și nu au fost concepute pentru echilibrare.

Cu toate acestea, este posibil să scrieți un set de reguli care să funcționeze ca pseudo-echilibrator.

Și asta este exact ceea ce este implementat în Kubernetes.

Dacă aveți trei poduri, kube-proxy va scrie următoarele reguli:

  1. Selectați primul sub cu o probabilitate de 33%, altfel treceți la următoarea regulă.
  2. Alege-l pe al doilea cu o probabilitate de 50%, altfel treci la următoarea regulă.
  3. Selectați a treia de sub.

Acest sistem are ca rezultat selectarea fiecărui pod cu o probabilitate de 33%.

Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

Și nu există nicio garanție că Podul 2 va fi ales după Podul 1.

Nota: iptables folosește un modul statistic cu distribuție aleatorie. Astfel, algoritmul de echilibrare se bazează pe selecția aleatorie.

Acum că înțelegeți cum funcționează serviciile, să ne uităm la scenarii de servicii mai interesante.

Conexiunile cu durată lungă de viață în Kubernetes nu se scalează în mod implicit

Fiecare cerere HTTP de la front-end la backend este servită de o conexiune TCP separată, care este deschisă și închisă.

Dacă front-end-ul trimite 100 de solicitări pe secundă către backend, atunci 100 de conexiuni TCP diferite sunt deschise și închise.

Puteți reduce timpul de procesare a cererilor și încărcarea prin deschiderea unei conexiuni TCP și folosind-o pentru toate solicitările HTTP ulterioare.

Protocolul HTTP are o caracteristică numită HTTP keep-alive sau reutilizarea conexiunii. În acest caz, o singură conexiune TCP este utilizată pentru a trimite și a primi mai multe solicitări și răspunsuri HTTP:

Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

Această caracteristică nu este activată implicit: atât serverul, cât și clientul trebuie configurate corespunzător.

Configurarea în sine este simplă și accesibilă pentru majoritatea limbajelor și mediilor de programare.

Iată câteva link-uri către exemple în diferite limbi:

Ce se întâmplă dacă folosim keep-alive într-un serviciu Kubernetes?
Să presupunem că atât front-end-ul, cât și backend-ul acceptă menținerea în viață.

Avem o copie a frontend-ului și trei copii a backend-ului. Interfața face prima solicitare și deschide o conexiune TCP la backend. Solicitarea ajunge la serviciu, unul dintre podurile backend este selectat ca adresă de destinație. Backend-ul trimite un răspuns, iar front-end-ul îl primește.

Spre deosebire de situația obișnuită în care conexiunea TCP este închisă după primirea unui răspuns, aceasta este acum menținută deschisă pentru solicitări HTTP ulterioare.

Ce se întâmplă dacă frontend-ul trimite mai multe solicitări către backend?

Pentru a redirecționa aceste solicitări, va fi utilizată o conexiune TCP deschisă, toate cererile vor merge către același backend unde a fost prima solicitare.

Nu ar trebui iptables să redistribuie traficul?

Nu în acest caz.

Când este creată o conexiune TCP, aceasta trece prin regulile iptables, care selectează un backend specific unde va merge traficul.

Deoarece toate cererile ulterioare sunt pe o conexiune TCP deja deschisă, regulile iptables nu mai sunt apelate.

Să vedem cum arată.

  1. Primul pod trimite o solicitare către serviciu:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  2. Știți deja ce se va întâmpla în continuare. Serviciul nu există, dar există reguli iptables care vor procesa cererea:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  3. Unul dintre podurile backend va fi selectat ca adresă de destinație:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  4. Solicitarea ajunge la pod. În acest moment, se va stabili o conexiune TCP persistentă între cele două pod-uri:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  5. Orice cerere ulterioară de la primul pod va trece prin conexiunea deja stabilită:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

Rezultatul este un timp de răspuns mai rapid și un debit mai mare, dar pierdeți capacitatea de a scala backend-ul.

Chiar dacă aveți două poduri în backend, cu o conexiune constantă, traficul va merge întotdeauna către unul dintre ele.

Se poate remedia acest lucru?

Deoarece Kubernetes nu știe cum să echilibreze conexiunile persistente, această sarcină vă revine.

Serviciile sunt o colecție de adrese IP și porturi numite puncte finale.

Aplicația dvs. poate obține o listă de puncte finale de la serviciu și poate decide cum să distribuie cererile între ele. Puteți deschide o conexiune persistentă la fiecare pod și puteți echilibra cererile între aceste conexiuni folosind round-robin.

Sau aplica mai mult algoritmi complexi de echilibrare.

Codul clientului care este responsabil pentru echilibrare ar trebui să urmeze această logică:

  1. Obțineți o listă de puncte finale de la serviciu.
  2. Deschideți o conexiune persistentă pentru fiecare punct final.
  3. Când trebuie făcută o solicitare, utilizați una dintre conexiunile deschise.
  4. Actualizați în mod regulat lista de puncte finale, creați altele noi sau închideți vechile conexiuni persistente dacă lista se modifică.

Asa va arata.

  1. În loc ca primul pod să trimită cererea către serviciu, puteți echilibra cererile din partea clientului:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  2. Trebuie să scrieți cod care vă întreabă ce poduri fac parte din serviciu:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  3. Odată ce aveți lista, salvați-o pe partea client și utilizați-o pentru a vă conecta la pod-uri:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

  4. Sunteți responsabil pentru algoritmul de echilibrare a sarcinii:

    Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

Acum apare întrebarea: această problemă se aplică numai pentru HTTP keep-alive?

Echilibrarea sarcinii pe partea clientului

HTTP nu este singurul protocol care poate folosi conexiuni TCP persistente.

Dacă aplicația dvs. utilizează o bază de date, atunci o conexiune TCP nu este deschisă de fiecare dată când trebuie să faceți o solicitare sau să preluați un document din baza de date. 

În schimb, o conexiune TCP persistentă la baza de date este deschisă și utilizată.

Dacă baza de date este implementată pe Kubernetes și accesul este oferit ca serviciu, atunci veți întâmpina aceleași probleme descrise în secțiunea anterioară.

O replică a bazei de date va fi mai încărcată decât celelalte. Kube-proxy și Kubernetes nu vor ajuta la echilibrarea conexiunilor. Trebuie să aveți grijă să echilibrați interogările în baza de date.

În funcție de biblioteca pe care o utilizați pentru a vă conecta la baza de date, este posibil să aveți diferite opțiuni pentru a rezolva această problemă.

Mai jos este un exemplu de accesare a unui cluster de baze de date MySQL din 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

Există multe alte protocoale care utilizează conexiuni TCP persistente:

  • WebSockets și WebSockets securizate
  • HTTP / 2
  • gRPC
  • RSockets
  • AMQP

Ar trebui să fiți deja familiarizați cu majoritatea acestor protocoale.

Dar dacă aceste protocoale sunt atât de populare, de ce nu există o soluție standardizată de echilibrare? De ce trebuie schimbată logica clientului? Există o soluție nativă Kubernetes?

Kube-proxy și iptables sunt concepute pentru a acoperi cele mai comune cazuri de utilizare atunci când sunt implementate în Kubernetes. Acest lucru este pentru comoditate.

Dacă utilizați un serviciu web care expune un API REST, aveți noroc - în acest caz, conexiunile TCP persistente nu sunt utilizate, puteți utiliza orice serviciu Kubernetes.

Dar odată ce începeți să utilizați conexiuni TCP persistente, va trebui să vă dați seama cum să distribuiți uniform sarcina pe backend-uri. Kubernetes nu conține soluții gata făcute pentru acest caz.

Cu toate acestea, există cu siguranță opțiuni care vă pot ajuta.

Echilibrarea conexiunilor de lungă durată în Kubernetes

Există patru tipuri de servicii în Kubernetes:

  1. ClusterIP
  2. Portul nodului
  3. Echilibrarea greutății
  4. acefal

Primele trei servicii funcționează pe baza unei adrese IP virtuale, care este utilizată de kube-proxy pentru a construi reguli iptables. Dar baza fundamentală a tuturor serviciilor este un serviciu fără cap.

Serviciul fără cap nu are nicio adresă IP asociată și oferă doar un mecanism pentru preluarea unei liste de adrese IP și porturi ale podurilor (punctele finale) asociate cu acesta.

Toate serviciile se bazează pe serviciul fără cap.

Serviciul ClusterIP este un serviciu fără cap cu câteva completări: 

  1. Nivelul de management îi atribuie o adresă IP.
  2. Kube-proxy generează regulile iptables necesare.

În acest fel, puteți ignora kube-proxy și puteți utiliza direct lista de puncte finale obținute de la serviciul headless pentru a echilibra încărcarea aplicației dvs.

Dar cum putem adăuga o logică similară tuturor aplicațiilor implementate în cluster?

Dacă aplicația dvs. este deja implementată, această sarcină poate părea imposibilă. Cu toate acestea, există o opțiune alternativă.

Service Mesh vă va ajuta

Probabil ați observat deja că strategia de echilibrare a sarcinii la nivelul clientului este destul de standard.

Când pornește aplicația, aceasta:

  1. Obține o listă de adrese IP de la serviciu.
  2. Deschide și menține un pool de conexiuni.
  3. Actualizează periodic pool-ul prin adăugarea sau eliminarea punctelor finale.

Odată ce aplicația dorește să facă o cerere, aceasta:

  1. Selectează o conexiune disponibilă folosind o anumită logică (de exemplu, round-robin).
  2. Execută cererea.

Acești pași funcționează atât pentru conexiunile WebSockets, gRPC și AMQP.

Puteți separa această logică într-o bibliotecă separată și o puteți utiliza în aplicațiile dvs.

Cu toate acestea, puteți utiliza în schimb rețele de servicii precum Istio sau Linkerd.

Service Mesh vă îmbunătățește aplicația cu un proces care:

  1. Caută automat adrese IP de serviciu.
  2. Testează conexiuni precum WebSockets și gRPC.
  3. Echilibrează cererile folosind protocolul corect.

Service Mesh ajută la gestionarea traficului din cluster, dar necesită destul de mult resurse. Alte opțiuni folosesc biblioteci terță parte, cum ar fi Netflix Ribbon sau proxy programabile precum Envoy.

Ce se întâmplă dacă ignori problemele de echilibrare?

Puteți alege să nu utilizați echilibrarea încărcăturii și să nu observați nicio modificare. Să ne uităm la câteva scenarii de lucru.

Dacă aveți mai mulți clienți decât servere, aceasta nu este o problemă atât de mare.

Să presupunem că există cinci clienți care se conectează la două servere. Chiar dacă nu există echilibrare, ambele servere vor fi utilizate:

Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

Este posibil ca conexiunile să nu fie distribuite uniform: poate patru clienți conectați la același server, dar există șanse mari ca ambele servere să fie utilizate.

Ceea ce este mai problematic este scenariul opus.

Dacă aveți mai puțini clienți și mai multe servere, resursele dvs. pot fi subutilizate și va apărea un potențial blocaj.

Să presupunem că există doi clienți și cinci servere. În cel mai bun caz, vor exista două conexiuni permanente la două servere din cinci.

Serverele rămase vor fi inactive:

Echilibrarea sarcinii și scalarea conexiunilor de lungă durată în Kubernetes

Dacă aceste două servere nu pot gestiona solicitările clientului, scalarea orizontală nu va ajuta.

Concluzie

Serviciile Kubernetes sunt concepute pentru a funcționa în majoritatea scenariilor de aplicații web standard.

Cu toate acestea, odată ce începeți să lucrați cu protocoale de aplicație care utilizează conexiuni TCP persistente, cum ar fi baze de date, gRPC sau WebSockets, serviciile nu mai sunt potrivite. Kubernetes nu oferă mecanisme interne pentru echilibrarea conexiunilor TCP persistente.

Aceasta înseamnă că trebuie să scrieți aplicații având în vedere echilibrarea pe partea clientului.

Traducere pregătită de echipă Kubernetes aaS de la Mail.ru.

Ce să mai citești pe subiect:

  1. Trei niveluri de autoscaling în Kubernetes și cum să le folosiți eficient
  2. Kubernetes în spiritul pirateriei cu un șablon pentru implementare.
  3. Canalul nostru Telegram despre transformarea digitală.

Sursa: www.habr.com

Adauga un comentariu