Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes
Denne artikel hjælper dig med at forstå, hvordan belastningsbalancering fungerer i Kubernetes, hvad der sker ved skalering af langlivede forbindelser, og hvorfor du bør overveje klientsidebalancering, hvis du bruger HTTP/2, gRPC, RSockets, AMQP eller andre langlivede protokoller . 

Lidt om hvordan trafikken omfordeles i Kubernetes 

Kubernetes giver to praktiske abstraktioner til implementering af applikationer: Tjenester og implementeringer.

Implementeringer beskriver, hvordan og hvor mange kopier af din applikation skal køre på et givet tidspunkt. Hver applikation implementeres som en Pod og tildeles en IP-adresse.

Tjenester ligner i funktion en belastningsbalancer. De er designet til at fordele trafik på tværs af flere pods.

Lad os se, hvordan det ser ud.

  1. I diagrammet nedenfor kan du se tre forekomster af den samme applikation og en belastningsbalancer:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  2. Loadbalanceren kaldes en Service og tildeles en IP-adresse. Enhver indkommende anmodning omdirigeres til en af ​​pods:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  3. Implementeringsscenariet bestemmer antallet af forekomster af applikationen. Du behøver næsten aldrig at udvide direkte under:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  4. Hver pod tildeles sin egen IP-adresse:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

Det er nyttigt at tænke på tjenester som en samling af IP-adresser. Hver gang du tilgår tjenesten, vælges en af ​​IP-adresserne fra listen og bruges som destinationsadresse.

Det ser sådan ud.

  1. En curl 10.96.45.152 anmodning modtages til tjenesten:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  2. Tjenesten vælger en af ​​tre pod-adresser som sin destination:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  3. Trafik omdirigeres til en bestemt pod:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

Hvis din applikation består af en frontend og en backend, så har du både en service og en udrulning til hver.

Når frontend'en laver en anmodning til backend, behøver den ikke at vide præcis, hvor mange pods backend'en tjener: der kan være én, ti eller hundrede.

Desuden ved frontend ikke noget om adresserne på de pods, der betjener backend.

Når frontenden laver en anmodning til backend, bruger den backend-tjenestens IP-adresse, som ikke ændres.

Her er hvordan det ser ud.

  1. Under 1 anmoder om den interne backend-komponent. I stedet for at vælge en bestemt til backend, sender den en anmodning til tjenesten:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  2. Tjenesten vælger en af ​​backend-podsene som destinationsadresse:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  3. Trafikken går fra Pod 1 til Pod 5, valgt af tjenesten:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  4. Under 1 ved ikke præcis, hvor mange pods som under 5, der er gemt bag tjenesten:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

Men præcis hvordan distribuerer tjenesten anmodninger? Det ser ud til, at der bruges round-robin balancering? Lad os finde ud af det. 

Balancering i Kubernetes-tjenester

Kubernetes-tjenester eksisterer ikke. Der er ingen proces for tjenesten, der er tildelt en IP-adresse og port.

Du kan bekræfte dette ved at logge ind på en hvilken som helst node i klyngen og køre kommandoen netstat -ntlp.

Du vil ikke engang kunne finde den IP-adresse, der er tildelt tjenesten.

Tjenestens IP-adresse er placeret i kontrollaget, i controlleren og registreres i databasen - etcd. Den samme adresse bruges af en anden komponent - kube-proxy.
Kube-proxy modtager en liste over IP-adresser for alle tjenester og genererer et sæt iptables-regler på hver node i klyngen.

Disse regler siger: "Hvis vi ser IP-adressen på tjenesten, skal vi ændre destinationsadressen for anmodningen og sende den til en af ​​pods."

Tjenestens IP-adresse bruges kun som et indgangspunkt og betjenes ikke af nogen proces, der lytter til den pågældende IP-adresse og port.

Lad os se på dette

  1. Overvej en klynge af tre noder. Hver node har pods:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  2. Bundne bælg malet beige er en del af servicen. Fordi tjenesten ikke eksisterer som en proces, vises den med gråt:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  3. Den første pod anmoder om en service og skal gå til en af ​​de tilknyttede pods:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  4. Men tjenesten eksisterer ikke, processen eksisterer ikke. Hvordan virker det?

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  5. Før anmodningen forlader noden, gennemgår den iptables-reglerne:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  6. Iptables-reglerne ved, at tjenesten ikke eksisterer, og erstatter dens IP-adresse med en af ​​IP-adresserne på de pods, der er knyttet til den pågældende tjeneste:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  7. Anmodningen modtager en gyldig IP-adresse som destinationsadresse og behandles normalt:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  8. Afhængigt af netværkstopologien når anmodningen til sidst poden:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

Kan iptables load balance?

Nej, iptables bruges til filtrering og er ikke designet til balancering.

Det er dog muligt at skrive et sæt regler, der fungerer som pseudo-balancer.

Og det er præcis, hvad der er implementeret i Kubernetes.

Hvis du har tre pods, vil kube-proxy skrive følgende regler:

  1. Vælg den første sub med en sandsynlighed på 33 %, ellers gå til næste regel.
  2. Vælg den anden med en sandsynlighed på 50 %, ellers gå til den næste regel.
  3. Vælg den tredje under.

Dette system resulterer i, at hver pod vælges med en sandsynlighed på 33 %.

Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

Og der er ingen garanti for, at Pod 2 bliver valgt næste gang efter Pod 1.

Bemærk: iptables bruger et statistisk modul med tilfældig fordeling. Balanceringsalgoritmen er således baseret på tilfældig udvælgelse.

Nu hvor du forstår, hvordan tjenester fungerer, lad os se på mere interessante servicescenarier.

Forbindelser med lang levetid i Kubernetes skaleres ikke som standard

Hver HTTP-anmodning fra frontend til backend betjenes af en separat TCP-forbindelse, som åbnes og lukkes.

Hvis frontenden sender 100 anmodninger i sekundet til backenden, åbnes og lukkes 100 forskellige TCP-forbindelser.

Du kan reducere anmodningsbehandlingstiden og -belastningen ved at åbne én TCP-forbindelse og bruge den til alle efterfølgende HTTP-anmodninger.

HTTP-protokollen har en funktion kaldet HTTP keep-alive eller genbrug af forbindelse. I dette tilfælde bruges en enkelt TCP-forbindelse til at sende og modtage flere HTTP-anmodninger og -svar:

Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

Denne funktion er ikke aktiveret som standard: både serveren og klienten skal konfigureres i overensstemmelse hermed.

Selve opsætningen er enkel og tilgængelig for de fleste programmeringssprog og miljøer.

Her er nogle links til eksempler på forskellige sprog:

Hvad sker der, hvis vi bruger Keep-alive i en Kubernetes-tjeneste?
Lad os antage, at både frontend og backend understøtter Keep-alive.

Vi har en kopi af frontend og tre kopier af backend. Frontenden laver den første anmodning og åbner en TCP-forbindelse til backend. Anmodningen når tjenesten, en af ​​backend-podsene er valgt som destinationsadresse. Backend sender et svar, og frontend modtager det.

I modsætning til den sædvanlige situation, hvor TCP-forbindelsen lukkes efter at have modtaget et svar, holdes den nu åben for yderligere HTTP-anmodninger.

Hvad sker der, hvis frontenden sender flere anmodninger til backend?

For at videresende disse anmodninger, vil en åben TCP-forbindelse blive brugt, alle anmodninger vil gå til den samme backend, hvor den første anmodning gik.

Bør iptables ikke omfordele trafikken?

Ikke i dette tilfælde.

Når en TCP-forbindelse oprettes, går den igennem iptables-regler, som vælger en specifik backend, hvor trafikken skal gå.

Da alle efterfølgende anmodninger er på en allerede åben TCP-forbindelse, kaldes iptables-reglerne ikke længere.

Lad os se, hvordan det ser ud.

  1. Den første pod sender en anmodning til tjenesten:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  2. Du ved allerede, hvad der vil ske næste gang. Tjenesten eksisterer ikke, men der er iptables-regler, der behandler anmodningen:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  3. En af backend-podsene vil blive valgt som destinationsadresse:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  4. Anmodningen når poden. På dette tidspunkt vil der blive etableret en vedvarende TCP-forbindelse mellem de to pods:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  5. Enhver efterfølgende anmodning fra den første pod vil gå gennem den allerede etablerede forbindelse:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

Resultatet er hurtigere responstid og højere gennemløb, men du mister evnen til at skalere backend.

Selvom du har to pods i backend, med en konstant forbindelse, vil trafikken altid gå til en af ​​dem.

Kan dette rettes?

Da Kubernetes ikke ved, hvordan man balancerer vedvarende forbindelser, falder denne opgave til dig.

Tjenester er en samling af IP-adresser og porte kaldet endepunkter.

Din applikation kan få en liste over slutpunkter fra tjenesten og bestemme, hvordan anmodninger skal fordeles mellem dem. Du kan åbne en vedvarende forbindelse til hver pod og balancere anmodninger mellem disse forbindelser ved hjælp af round-robin.

Eller ansøg mere komplekse balanceringsalgoritmer.

Den klientsidekode, der er ansvarlig for balancering, skal følge denne logik:

  1. Få en liste over endepunkter fra tjenesten.
  2. Åbn en vedvarende forbindelse for hvert endepunkt.
  3. Når der skal foretages en anmodning, skal du bruge en af ​​de åbne forbindelser.
  4. Opdater jævnligt listen over endepunkter, opret nye eller luk gamle vedvarende forbindelser, hvis listen ændres.

Sådan kommer det til at se ud.

  1. I stedet for at den første pod sender anmodningen til tjenesten, kan du afbalancere anmodninger på klientsiden:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  2. Du skal skrive kode, der spørger, hvilke pods der er en del af tjenesten:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  3. Når du har listen, skal du gemme den på klientsiden og bruge den til at oprette forbindelse til pods:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

  4. Du er ansvarlig for belastningsbalanceringsalgoritmen:

    Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

Nu opstår spørgsmålet: gælder dette problem kun for HTTP keep-alive?

Lastbalancering på klientsiden

HTTP er ikke den eneste protokol, der kan bruge vedvarende TCP-forbindelser.

Hvis din applikation bruger en database, åbnes der ikke en TCP-forbindelse, hver gang du skal lave en anmodning eller hente et dokument fra databasen. 

I stedet åbnes og bruges en vedvarende TCP-forbindelse til databasen.

Hvis din database er implementeret på Kubernetes, og adgangen leveres som en tjeneste, vil du støde på de samme problemer, som beskrevet i det foregående afsnit.

En databasereplika vil være mere indlæst end de andre. Kube-proxy og Kubernetes hjælper ikke med at balancere forbindelser. Du skal sørge for at balancere forespørgslerne til din database.

Afhængigt af hvilket bibliotek du bruger til at oprette forbindelse til databasen, kan du have forskellige muligheder for at løse dette problem.

Nedenfor er et eksempel på adgang til en MySQL-databaseklynge fra 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

Der er mange andre protokoller, der bruger vedvarende TCP-forbindelser:

  • WebSockets og sikrede WebSockets
  • HTTP / 2
  • gRPC
  • RSockets
  • AMQP

Du burde allerede være bekendt med de fleste af disse protokoller.

Men hvis disse protokoller er så populære, hvorfor findes der så ikke en standardiseret balanceringsløsning? Hvorfor skal klientlogikken ændres? Er der en indbygget Kubernetes-løsning?

Kube-proxy og iptables er designet til at dække de fleste almindelige brugssager, når de implementeres til Kubernetes. Dette er for nemheds skyld.

Hvis du bruger en webtjeneste, der afslører en REST API, er du heldig - i dette tilfælde bruges vedvarende TCP-forbindelser ikke, du kan bruge enhver Kubernetes-tjeneste.

Men når du først begynder at bruge vedvarende TCP-forbindelser, bliver du nødt til at finde ud af, hvordan du fordeler belastningen jævnt på tværs af backends. Kubernetes indeholder ikke færdige løsninger til denne sag.

Der er dog helt sikkert muligheder, der kan hjælpe.

Afbalancering af langlivede forbindelser i Kubernetes

Der er fire typer tjenester i Kubernetes:

  1. ClusterIP
  2. Node Port
  3. LoadBalancer
  4. Hovedløse

De første tre tjenester fungerer baseret på en virtuel IP-adresse, som bruges af kube-proxy til at bygge iptables-regler. Men det grundlæggende grundlag for alle tjenester er en hovedløs tjeneste.

Den hovedløse tjeneste har ingen IP-adresse tilknyttet og giver kun en mekanisme til at hente en liste over IP-adresser og porte for de pods (endepunkter), der er knyttet til den.

Alle tjenester er baseret på den hovedløse tjeneste.

ClusterIP-tjenesten er en hovedløs tjeneste med nogle tilføjelser: 

  1. Administrationslaget tildeler det en IP-adresse.
  2. Kube-proxy genererer de nødvendige iptables-regler.

På denne måde kan du ignorere kube-proxy og direkte bruge listen over endepunkter opnået fra den hovedløse tjeneste til at indlæse din applikation.

Men hvordan kan vi tilføje lignende logik til alle applikationer, der er implementeret i klyngen?

Hvis din applikation allerede er implementeret, kan denne opgave virke umulig. Der er dog en alternativ mulighed.

Service Mesh vil hjælpe dig

Du har sikkert allerede bemærket, at belastningsbalanceringsstrategien på klientsiden er ret standard.

Når applikationen starter, er den:

  1. Får en liste over IP-adresser fra tjenesten.
  2. Åbner og vedligeholder en forbindelsespool.
  3. Opdaterer puljen med jævne mellemrum ved at tilføje eller fjerne slutpunkter.

Når applikationen vil fremsætte en anmodning, skal den:

  1. Vælger en tilgængelig forbindelse ved hjælp af en vis logik (f.eks. round-robin).
  2. Eksekverer anmodningen.

Disse trin fungerer for både WebSockets-, gRPC- og AMQP-forbindelser.

Du kan adskille denne logik i et separat bibliotek og bruge den i dine applikationer.

Du kan dog bruge servicemasker som Istio eller Linkerd i stedet.

Service Mesh udvider din ansøgning med en proces, der:

  1. Søger automatisk efter tjenestens IP-adresser.
  2. Tester forbindelser som WebSockets og gRPC.
  3. Balancerer anmodninger ved hjælp af den korrekte protokol.

Service Mesh hjælper med at administrere trafik i klyngen, men det er ret ressourcekrævende. Andre muligheder er at bruge tredjepartsbiblioteker som Netflix Ribbon eller programmerbare proxyer som Envoy.

Hvad sker der, hvis du ignorerer balanceringsproblemer?

Du kan vælge ikke at bruge belastningsbalancering og stadig ikke bemærke nogen ændringer. Lad os se på et par arbejdsscenarier.

Hvis du har flere klienter end servere, er dette ikke så stort et problem.

Lad os sige, at der er fem klienter, der forbinder til to servere. Selvom der ikke er nogen balancering, vil begge servere blive brugt:

Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

Forbindelser er muligvis ikke jævnt fordelt: måske fire klienter forbundet til den samme server, men der er en god chance for, at begge servere vil blive brugt.

Hvad der er mere problematisk er det modsatte scenario.

Hvis du har færre klienter og flere servere, kan dine ressourcer blive underudnyttet, og en potentiel flaskehals vil dukke op.

Lad os sige, at der er to klienter og fem servere. I bedste tilfælde vil der være to permanente forbindelser til to ud af fem servere.

De resterende servere vil være inaktive:

Belastningsbalancering og skalering af langlivede forbindelser i Kubernetes

Hvis disse to servere ikke kan håndtere klientanmodninger, hjælper horisontal skalering ikke.

Konklusion

Kubernetes-tjenester er designet til at fungere i de fleste standard webapplikationsscenarier.

Men når du begynder at arbejde med applikationsprotokoller, der bruger vedvarende TCP-forbindelser, såsom databaser, gRPC eller WebSockets, er tjenester ikke længere egnede. Kubernetes leverer ikke interne mekanismer til afbalancering af vedvarende TCP-forbindelser.

Det betyder, at du skal skrive applikationer med klientsidebalancering i tankerne.

Oversættelse udarbejdet af teamet Kubernetes aaS fra Mail.ru.

Hvad skal man ellers læse om emnet:

  1. Tre niveauer af autoskalering i Kubernetes og hvordan man bruger dem effektivt
  2. Kubernetes i pirateriets ånd med en skabelon til implementering.
  3. Vores Telegram-kanal om digital transformation.

Kilde: www.habr.com

Tilføj en kommentar