Lastbalansering och skalning av långlivade anslutningar i Kubernetes

Lastbalansering och skalning av långlivade anslutningar i Kubernetes
Den här artikeln hjälper dig att förstå hur lastbalansering fungerar i Kubernetes, vad som händer när du skalar långlivade anslutningar och varför du bör överväga balansering på klientsidan om du använder HTTP/2, gRPC, RSockets, AMQP eller andra långlivade protokoll . 

Lite om hur trafiken omfördelas i Kubernetes 

Kubernetes tillhandahåller två praktiska abstraktioner för att distribuera applikationer: tjänster och distributioner.

Implementeringar beskriver hur och hur många kopior av din applikation ska köras vid varje given tidpunkt. Varje applikation distribueras som en Pod och tilldelas en IP-adress.

Tjänster liknar sin funktion som en lastbalanserare. De är designade för att fördela trafik över flera pods.

Låt oss se hur det ser ut.

  1. I diagrammet nedan kan du se tre instanser av samma applikation och en lastbalanserare:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  2. Lastbalanseraren kallas för en tjänst och tilldelas en IP-adress. Alla inkommande förfrågningar omdirigeras till en av poddarna:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  3. Distributionsscenariot bestämmer antalet instanser av programmet. Du kommer nästan aldrig behöva expandera direkt under:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  4. Varje pod tilldelas sin egen IP-adress:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

Det är användbart att tänka på tjänster som en samling IP-adresser. Varje gång du använder tjänsten väljs en av IP-adresserna från listan och används som destinationsadress.

Det ser ut så här.

  1. En curl 10.96.45.152-förfrågan tas emot till tjänsten:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  2. Tjänsten väljer en av tre podadresser som destination:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  3. Trafiken omdirigeras till en specifik pod:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

Om din applikation består av en frontend och en backend, kommer du att ha både en tjänst och en distribution för var och en.

När gränssnittet gör en begäran till gränssnittet behöver det inte veta exakt hur många pods gränssnittet betjänar: det kan vara en, tio eller hundra.

Dessutom vet inte gränssnittet något om adresserna till poddarna som betjänar backend.

När frontend gör en förfrågan till backend, använder den backend-tjänstens IP-adress, som inte ändras.

Såhär ser.

  1. Under 1 begär den interna backend-komponenten. Istället för att välja en specifik för backend, gör den en begäran till tjänsten:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  2. Tjänsten väljer en av backend-podarna som destinationsadress:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  3. Trafiken går från Pod 1 till Pod 5, vald av tjänsten:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  4. Under 1 vet inte exakt hur många pods som under 5 som är gömda bakom tjänsten:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

Men exakt hur fördelar tjänsten förfrågningar? Det verkar som om round-robin balansering används? Låt oss ta reda på det. 

Balansering i Kubernetes tjänster

Kubernetes-tjänster finns inte. Det finns ingen process för tjänsten som tilldelas en IP-adress och port.

Du kan verifiera detta genom att logga in på valfri nod i klustret och köra kommandot netstat -ntlp.

Du kommer inte ens att kunna hitta den IP-adress som tilldelats tjänsten.

Tjänstens IP-adress finns i kontrollskiktet, i regulatorn, och registreras i databasen - etcd. Samma adress används av en annan komponent - kube-proxy.
Kube-proxy tar emot en lista med IP-adresser för alla tjänster och genererar en uppsättning iptables-regler på varje nod i klustret.

Dessa regler säger: "Om vi ​​ser IP-adressen för tjänsten måste vi ändra destinationsadressen för begäran och skicka den till en av poddarna."

Tjänstens IP-adress används endast som en ingångspunkt och betjänas inte av någon process som lyssnar på den IP-adressen och porten.

Låt oss titta på det här

  1. Betrakta ett kluster med tre noder. Varje nod har pods:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  2. Knyta baljor målade beige ingår i tjänsten. Eftersom tjänsten inte existerar som en process visas den i grått:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  3. Den första podden begär en tjänst och måste gå till en av de associerade podarna:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  4. Men tjänsten finns inte, processen finns inte. Hur fungerar det?

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  5. Innan begäran lämnar noden går den igenom iptables-reglerna:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  6. Iptables-reglerna vet att tjänsten inte existerar och ersätter dess IP-adress med en av IP-adresserna för poddarna som är associerade med den tjänsten:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  7. Begäran får en giltig IP-adress som destinationsadress och behandlas normalt:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  8. Beroende på nätverkstopologin når begäran så småningom podden:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

Kan iptables lastbalansera?

Nej, iptables används för filtrering och är inte designade för balansering.

Det är dock möjligt att skriva en uppsättning regler som fungerar som pseudobalanserare.

Och det är precis vad som är implementerat i Kubernetes.

Om du har tre pods kommer kube-proxy att skriva följande regler:

  1. Välj den första suben med en sannolikhet på 33 %, annars gå till nästa regel.
  2. Välj den andra med en sannolikhet på 50 %, annars gå till nästa regel.
  3. Välj den tredje under.

Detta system resulterar i att varje pod väljs med en sannolikhet på 33 %.

Lastbalansering och skalning av långlivade anslutningar i Kubernetes

Och det finns ingen garanti för att Pod 2 kommer att väljas nästa efter Pod 1.

Notera: iptables använder en statistisk modul med slumpmässig fördelning. Sålunda är balanseringsalgoritmen baserad på slumpmässigt urval.

Nu när du förstår hur tjänster fungerar, låt oss titta på mer intressanta tjänstescenarier.

Långlivade anslutningar i Kubernetes skalas inte som standard

Varje HTTP-begäran från frontend till backend betjänas av en separat TCP-anslutning, som öppnas och stängs.

Om frontend skickar 100 förfrågningar per sekund till backend, öppnas och stängs 100 olika TCP-anslutningar.

Du kan minska bearbetningstiden och belastningen för begäran genom att öppna en TCP-anslutning och använda den för alla efterföljande HTTP-förfrågningar.

HTTP-protokollet har en funktion som kallas HTTP keep-alive, eller anslutningsåteranvändning. I det här fallet används en enda TCP-anslutning för att skicka och ta emot flera HTTP-förfrågningar och svar:

Lastbalansering och skalning av långlivade anslutningar i Kubernetes

Den här funktionen är inte aktiverad som standard: både servern och klienten måste konfigureras därefter.

Själva installationen är enkel och tillgänglig för de flesta programmeringsspråk och miljöer.

Här är några länkar till exempel på olika språk:

Vad händer om vi använder Keep-alive i en Kubernetes-tjänst?
Låt oss anta att både frontend och backend stöder Keep-alive.

Vi har en kopia av frontend och tre kopior av backend. Frontend gör den första begäran och öppnar en TCP-anslutning till backend. Begäran når tjänsten, en av backend-podarna väljs som destinationsadress. Backend skickar ett svar och frontend tar emot det.

Till skillnad från den vanliga situationen där TCP-anslutningen stängs efter att ha mottagit ett svar, hålls den nu öppen för ytterligare HTTP-förfrågningar.

Vad händer om gränssnittet skickar fler förfrågningar till gränssnittet?

För att vidarebefordra dessa förfrågningar kommer en öppen TCP-anslutning att användas, alla förfrågningar kommer att gå till samma backend där den första förfrågan gick.

Borde inte iptables omfördela trafiken?

Inte i det här fallet.

När en TCP-anslutning skapas går den igenom iptables-regler, som väljer en specifik backend dit trafiken ska gå.

Eftersom alla efterföljande förfrågningar är på en redan öppen TCP-anslutning, anropas inte längre iptables-reglerna.

Låt oss se hur det ser ut.

  1. Den första podden skickar en förfrågan till tjänsten:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  2. Du vet redan vad som kommer att hända härnäst. Tjänsten finns inte, men det finns iptables-regler som kommer att behandla begäran:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  3. En av backend-podarna kommer att väljas som destinationsadress:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  4. Förfrågan når podden. Vid denna tidpunkt kommer en beständig TCP-anslutning mellan de två poddarna att upprättas:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  5. Varje efterföljande begäran från den första podden kommer att gå via den redan etablerade anslutningen:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

Resultatet är snabbare svarstid och högre genomströmning, men du förlorar förmågan att skala backend.

Även om du har två pods i backend, med en konstant anslutning, kommer trafiken alltid att gå till en av dem.

Kan detta åtgärdas?

Eftersom Kubernetes inte vet hur man balanserar ihållande anslutningar faller denna uppgift på dig.

Tjänster är en samling IP-adresser och portar som kallas slutpunkter.

Din applikation kan få en lista över slutpunkter från tjänsten och bestämma hur förfrågningar ska fördelas mellan dem. Du kan öppna en beständig anslutning till varje pod och balansera förfrågningar mellan dessa anslutningar med round-robin.

Eller ansök mer komplexa balanseringsalgoritmer.

Koden på klientsidan som är ansvarig för balansering bör följa denna logik:

  1. Få en lista över slutpunkter från tjänsten.
  2. Öppna en beständig anslutning för varje slutpunkt.
  3. När en begäran behöver göras, använd en av de öppna anslutningarna.
  4. Uppdatera regelbundet listan över slutpunkter, skapa nya eller stäng gamla beständiga anslutningar om listan ändras.

Så här kommer det att se ut.

  1. Istället för att den första podden skickar förfrågan till tjänsten kan du balansera förfrågningar på klientsidan:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  2. Du måste skriva kod som frågar vilka poddar som ingår i tjänsten:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  3. När du har listan sparar du den på klientsidan och använder den för att ansluta till poddarna:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

  4. Du ansvarar för lastbalanseringsalgoritmen:

    Lastbalansering och skalning av långlivade anslutningar i Kubernetes

Nu uppstår frågan: gäller detta problem bara för HTTP keep-alive?

Lastbalansering på klientsidan

HTTP är inte det enda protokollet som kan använda beständiga TCP-anslutningar.

Om din applikation använder en databas, öppnas inte en TCP-anslutning varje gång du behöver göra en begäran eller hämta ett dokument från databasen. 

Istället öppnas och används en beständig TCP-anslutning till databasen.

Om din databas är distribuerad på Kubernetes och åtkomst tillhandahålls som en tjänst, kommer du att stöta på samma problem som beskrivs i föregående avsnitt.

En databasreplik kommer att vara mer laddad än de andra. Kube-proxy och Kubernetes hjälper inte till att balansera anslutningar. Du måste vara noga med att balansera frågorna till din databas.

Beroende på vilket bibliotek du använder för att ansluta till databasen kan du ha olika alternativ för att lösa detta problem.

Nedan är ett exempel på åtkomst till ett MySQL-databaskluster från 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

Det finns många andra protokoll som använder beständiga TCP-anslutningar:

  • WebSockets och säkrade WebSockets
  • HTTP / 2
  • gRPC
  • RSockets
  • AMQP

Du bör redan vara bekant med de flesta av dessa protokoll.

Men om dessa protokoll är så populära, varför finns det inte en standardiserad balanseringslösning? Varför måste klientlogiken ändras? Finns det en inbyggd Kubernetes-lösning?

Kube-proxy och iptables är utformade för att täcka de vanligaste användningsfallen vid distribution till Kubernetes. Detta är för bekvämlighets skull.

Om du använder en webbtjänst som exponerar ett REST API har du tur - i det här fallet används inte beständiga TCP-anslutningar, du kan använda vilken Kubernetes-tjänst som helst.

Men när du väl börjar använda beständiga TCP-anslutningar måste du ta reda på hur du ska fördela belastningen jämnt över backends. Kubernetes innehåller inga färdiga lösningar för detta fall.

Men det finns säkert alternativ som kan hjälpa.

Balanserar långlivade anslutningar i Kubernetes

Det finns fyra typer av tjänster i Kubernetes:

  1. ClusterIP
  2. Nodport
  3. Lastbalanserare
  4. Huvudlös

De tre första tjänsterna fungerar baserat på en virtuell IP-adress, som används av kube-proxy för att bygga iptables-regler. Men den grundläggande basen för alla tjänster är en huvudlös tjänst.

Den huvudlösa tjänsten har ingen IP-adress associerad med den och tillhandahåller endast en mekanism för att hämta en lista över IP-adresser och portar för de pods (endpoints) som är associerade med den.

Alla tjänster är baserade på den huvudlösa tjänsten.

ClusterIP-tjänsten är en huvudlös tjänst med några tillägg: 

  1. Hanteringsskiktet tilldelar den en IP-adress.
  2. Kube-proxy genererar de nödvändiga iptables-reglerna.

På så sätt kan du ignorera kube-proxy och direkt använda listan över slutpunkter som erhålls från den huvudlösa tjänsten för att belasta din applikation.

Men hur kan vi lägga till liknande logik till alla applikationer som distribueras i klustret?

Om din applikation redan är distribuerad kan den här uppgiften verka omöjlig. Det finns dock ett alternativ.

Service Mesh hjälper dig

Du har säkert redan märkt att belastningsbalanseringsstrategin på klientsidan är ganska standard.

När applikationen startar:

  1. Får en lista över IP-adresser från tjänsten.
  2. Öppnar och underhåller en anslutningspool.
  3. Uppdaterar poolen regelbundet genom att lägga till eller ta bort slutpunkter.

När applikationen vill göra en begäran gör den:

  1. Väljer en tillgänglig anslutning med hjälp av någon logik (t.ex. round-robin).
  2. Utför begäran.

Dessa steg fungerar för både WebSockets, gRPC och AMQP-anslutningar.

Du kan separera denna logik i ett separat bibliotek och använda den i dina applikationer.

Däremot kan du använda servicenät som Istio eller Linkerd istället.

Service Mesh utökar din ansökan med en process som:

  1. Söker automatiskt efter tjänstens IP-adresser.
  2. Testar anslutningar som WebSockets och gRPC.
  3. Balanserar förfrågningar med rätt protokoll.

Service Mesh hjälper till att hantera trafik inom klustret, men det är ganska resurskrävande. Andra alternativ är att använda tredjepartsbibliotek som Netflix Ribbon eller programmerbara proxyservrar som Envoy.

Vad händer om du ignorerar balanseringsproblem?

Du kan välja att inte använda lastbalansering och ändå inte märka några förändringar. Låt oss titta på några arbetsscenarier.

Om du har fler klienter än servrar är detta inte ett så stort problem.

Låt oss säga att det finns fem klienter som ansluter till två servrar. Även om det inte finns någon balansering kommer båda servrarna att användas:

Lastbalansering och skalning av långlivade anslutningar i Kubernetes

Anslutningar kanske inte är jämnt fördelade: kanske fyra klienter anslutna till samma server, men det finns en god chans att båda servrarna kommer att användas.

Vad som är mer problematiskt är det motsatta scenariot.

Om du har färre klienter och fler servrar kan dina resurser vara underutnyttjade och en potentiell flaskhals dyker upp.

Låt oss säga att det finns två klienter och fem servrar. I bästa fall kommer det att finnas två permanenta anslutningar till två servrar av fem.

De återstående servrarna kommer att vara inaktiva:

Lastbalansering och skalning av långlivade anslutningar i Kubernetes

Om dessa två servrar inte kan hantera klientförfrågningar, hjälper horisontell skalning inte.

Slutsats

Kubernetes tjänster är utformade för att fungera i de flesta vanliga webbapplikationsscenarier.

Men när du börjar arbeta med applikationsprotokoll som använder beständiga TCP-anslutningar, såsom databaser, gRPC eller WebSockets, är tjänster inte längre lämpliga. Kubernetes tillhandahåller inga interna mekanismer för att balansera beständiga TCP-anslutningar.

Det betyder att du måste skriva applikationer med klientsidans balansering i åtanke.

Översättning utarbetad av teamet Kubernetes aaS från Mail.ru.

Vad mer att läsa om ämnet:

  1. Tre nivåer av autoskalning i Kubernetes och hur man använder dem effektivt
  2. Kubernetes i piratkopieringens anda med en mall för implementering.
  3. Vår Telegram-kanal om digital transformation.

Källa: will.com

Lägg en kommentar