Lastbalansering og skalering av langtidsforbindelser i Kubernetes

Lastbalansering og skalering av langtidsforbindelser i Kubernetes
Denne artikkelen vil hjelpe deg å forstå hvordan lastbalansering fungerer i Kubernetes, hva som skjer når du skalerer langlivede tilkoblinger, og hvorfor du bør vurdere klientsidebalansering hvis du bruker HTTP/2, gRPC, RSockets, AMQP eller andre langlivede protokoller . 

Litt om hvordan trafikken omfordeles i Kubernetes 

Kubernetes tilbyr to praktiske abstraksjoner for distribusjon av applikasjoner: Tjenester og distribusjoner.

Implementeringer beskriver hvordan og hvor mange kopier av applikasjonen din skal kjøre til enhver tid. Hver applikasjon distribueres som en Pod og tildeles en IP-adresse.

Tjenester ligner i funksjon på en lastbalanser. De er utformet for å distribuere trafikk over flere pods.

La oss se hvordan det ser ut.

  1. I diagrammet nedenfor kan du se tre forekomster av samme applikasjon og en lastbalanser:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  2. Lastbalanseren kalles en tjeneste og er tildelt en IP-adresse. Enhver innkommende forespørsel omdirigeres til en av podene:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  3. Distribusjonsscenariet bestemmer antall forekomster av applikasjonen. Du trenger nesten aldri å utvide direkte under:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  4. Hver pod er tildelt sin egen IP-adresse:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

Det er nyttig å tenke på tjenester som en samling av IP-adresser. Hver gang du bruker tjenesten, velges en av IP-adressene fra listen og brukes som destinasjonsadresse.

Det ser slik ut.

  1. En curl 10.96.45.152-forespørsel er mottatt til tjenesten:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  2. Tjenesten velger en av tre pod-adresser som destinasjon:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  3. Trafikk blir omdirigert til en bestemt pod:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

Hvis applikasjonen din består av en frontend og en backend, vil du ha både en tjeneste og en distribusjon for hver.

Når frontend sender en forespørsel til backend, trenger den ikke å vite nøyaktig hvor mange pods backend serverer: det kan være én, ti eller hundre.

Frontend vet heller ikke noe om adressene til podene som betjener backend.

Når frontend sender en forespørsel til backend, bruker den IP-adressen til backend-tjenesten, som ikke endres.

Slik ser det ut.

  1. Under 1 ber om den interne backend-komponenten. I stedet for å velge en spesifikk for backend, sender den en forespørsel til tjenesten:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  2. Tjenesten velger en av backend-podene som destinasjonsadresse:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

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

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  4. Under 1 vet ikke nøyaktig hvor mange pods som under 5 som er skjult bak tjenesten:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

Men hvordan distribuerer tjenesten forespørsler? Det virker som om round-robin balansering brukes? La oss finne ut av det. 

Balansering i Kubernetes-tjenester

Kubernetes-tjenester eksisterer ikke. Det er ingen prosess for tjenesten som er tildelt en IP-adresse og port.

Du kan bekrefte dette ved å logge på hvilken som helst node i klyngen og kjøre kommandoen netstat -ntlp.

Du vil ikke engang kunne finne IP-adressen som er tildelt tjenesten.

Tjenestens IP-adresse er plassert i kontrolllaget, i kontrolleren, og registrert i databasen - etcd. Den samme adressen brukes av en annen komponent - kube-proxy.
Kube-proxy mottar en liste over IP-adresser for alle tjenester og genererer et sett med iptables-regler på hver node i klyngen.

Disse reglene sier: "Hvis vi ser IP-adressen til tjenesten, må vi endre destinasjonsadressen til forespørselen og sende den til en av podene."

Tjenestens IP-adresse brukes bare som et inngangspunkt og betjenes ikke av noen prosess som lytter til den IP-adressen og porten.

La oss se på dette

  1. Tenk på en klynge med tre noder. Hver node har pods:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  2. Knyttede poder malt beige er en del av tjenesten. Fordi tjenesten ikke eksisterer som en prosess, vises den i grått:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  3. Den første poden ber om en tjeneste og må gå til en av de tilknyttede podene:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

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

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  5. Før forespørselen forlater noden, går den gjennom iptables-reglene:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  6. Iptables-reglene vet at tjenesten ikke eksisterer og erstatter IP-adressen med en av IP-adressene til podene som er knyttet til den tjenesten:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  7. Forespørselen mottar en gyldig IP-adresse som destinasjonsadresse og behandles normalt:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  8. Avhengig av nettverkstopologien, når forespørselen til slutt poden:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

Kan iptables lastebalanse?

Nei, iptables brukes til filtrering og ble ikke designet for balansering.

Det er imidlertid mulig å skrive et sett med regler som fungerer som pseudo-balanserer.

Og det er nettopp dette som er implementert i Kubernetes.

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

  1. Velg første sub med en sannsynlighet på 33 %, ellers gå til neste regel.
  2. Velg den andre med en sannsynlighet på 50 %, ellers gå til neste regel.
  3. Velg den tredje under.

Dette systemet resulterer i at hver pod velges med en sannsynlighet på 33 %.

Lastbalansering og skalering av langtidsforbindelser i Kubernetes

Og det er ingen garanti for at Pod 2 blir valgt neste etter Pod 1.

Note: iptables bruker en statistisk modul med tilfeldig distribusjon. Dermed er balansealgoritmen basert på tilfeldig utvalg.

Nå som du forstår hvordan tjenester fungerer, la oss se på mer interessante tjenestescenarier.

Langtidsforbindelser i Kubernetes skaleres ikke som standard

Hver HTTP-forespørsel fra frontend til backend betjenes av en separat TCP-tilkobling, som åpnes og lukkes.

Hvis frontend sender 100 forespørsler per sekund til backend, åpnes og lukkes 100 forskjellige TCP-forbindelser.

Du kan redusere forespørselsbehandlingstiden og belastningen ved å åpne én TCP-tilkobling og bruke den for alle påfølgende HTTP-forespørsler.

HTTP-protokollen har en funksjon kalt HTTP keep-alive, eller gjenbruk av tilkobling. I dette tilfellet brukes en enkelt TCP-tilkobling til å sende og motta flere HTTP-forespørsler og svar:

Lastbalansering og skalering av langtidsforbindelser i Kubernetes

Denne funksjonen er ikke aktivert som standard: både serveren og klienten må konfigureres tilsvarende.

Selve oppsettet er enkelt og tilgjengelig for de fleste programmeringsspråk og miljøer.

Her er noen lenker til eksempler på forskjellige språk:

Hva skjer hvis vi bruker Keep-alive i en Kubernetes-tjeneste?
La oss anta at både frontend og backend støtter Keep-alive.

Vi har en kopi av frontend og tre kopier av backend. Frontend gjør den første forespørselen og åpner en TCP-tilkobling til backend. Forespørselen når tjenesten, en av backend-podene er valgt som destinasjonsadresse. Backend sender et svar, og frontend mottar det.

I motsetning til den vanlige situasjonen der TCP-tilkoblingen er stengt etter å ha mottatt et svar, holdes den nå åpen for ytterligere HTTP-forespørsler.

Hva skjer hvis frontend sender flere forespørsler til backend?

For å videresende disse forespørslene vil en åpen TCP-forbindelse bli brukt, alle forespørsler vil gå til samme backend der den første forespørselen gikk.

Bør ikke iptables omfordele trafikken?

Ikke i dette tilfellet.

Når en TCP-tilkobling opprettes, går den gjennom iptables-regler, som velger en spesifikk backend hvor trafikken skal gå.

Siden alle påfølgende forespørsler er på en allerede åpen TCP-tilkobling, kalles ikke lenger iptables-reglene.

La oss se hvordan det ser ut.

  1. Den første poden sender en forespørsel til tjenesten:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  2. Du vet allerede hva som vil skje videre. Tjenesten eksisterer ikke, men det er iptables-regler som vil behandle forespørselen:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  3. En av backend-podene vil bli valgt som destinasjonsadresse:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  4. Forespørselen når poden. På dette tidspunktet vil en vedvarende TCP-forbindelse mellom de to podene bli etablert:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  5. Enhver påfølgende forespørsel fra den første poden vil gå gjennom den allerede etablerte tilkoblingen:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

Resultatet er raskere responstid og høyere gjennomstrømning, men du mister muligheten til å skalere backend.

Selv om du har to pods i backend, med en konstant tilkobling, vil trafikken alltid gå til en av dem.

Kan dette fikses?

Siden Kubernetes ikke vet hvordan man balanserer vedvarende tilkoblinger, faller denne oppgaven på deg.

Tjenester er en samling av IP-adresser og porter som kalles endepunkter.

Applikasjonen din kan få en liste over endepunkter fra tjenesten og bestemme hvordan forespørsler skal fordeles mellom dem. Du kan åpne en vedvarende tilkobling til hver pod og balansere forespørsler mellom disse tilkoblingene ved å bruke round-robin.

Eller søk mer komplekse balansealgoritmer.

Koden på klientsiden som er ansvarlig for balansering bør følge denne logikken:

  1. Få en liste over endepunkter fra tjenesten.
  2. Åpne en vedvarende tilkobling for hvert endepunkt.
  3. Når en forespørsel må gjøres, bruk en av de åpne tilkoblingene.
  4. Oppdater listen over endepunkter regelmessig, opprett nye eller lukk gamle vedvarende tilkoblinger hvis listen endres.

Slik vil det se ut.

  1. I stedet for at den første poden sender forespørselen til tjenesten, kan du balansere forespørsler på klientsiden:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  2. Du må skrive kode som spør hvilke poder som er en del av tjenesten:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  3. Når du har listen, lagrer du den på klientsiden og bruker den til å koble til podene:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

  4. Du er ansvarlig for lastbalanseringsalgoritmen:

    Lastbalansering og skalering av langtidsforbindelser i Kubernetes

Nå oppstår spørsmålet: gjelder dette problemet bare for HTTP keep-alive?

Lastbalansering på klientsiden

HTTP er ikke den eneste protokollen som kan bruke vedvarende TCP-tilkoblinger.

Hvis applikasjonen din bruker en database, åpnes ikke en TCP-tilkobling hver gang du trenger å sende en forespørsel eller hente et dokument fra databasen. 

I stedet åpnes og brukes en vedvarende TCP-tilkobling til databasen.

Hvis databasen din er distribuert på Kubernetes og tilgang tilbys som en tjeneste, vil du møte de samme problemene som beskrevet i forrige seksjon.

En databasereplika vil være mer lastet enn de andre. Kube-proxy og Kubernetes hjelper ikke med å balansere tilkoblinger. Du må passe på å balansere spørringene til databasen din.

Avhengig av hvilket bibliotek du bruker for å koble til databasen, kan du ha forskjellige alternativer for å løse dette problemet.

Nedenfor er et eksempel på tilgang 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

Det er mange andre protokoller som bruker vedvarende TCP-tilkoblinger:

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

Du bør allerede være kjent med de fleste av disse protokollene.

Men hvis disse protokollene er så populære, hvorfor finnes det ikke en standardisert balanseløsning? Hvorfor må klientlogikken endres? Finnes det en innebygd Kubernetes-løsning?

Kube-proxy og iptables er designet for å dekke de fleste vanlige brukstilfellene når de distribueres til Kubernetes. Dette er for enkelhets skyld.

Hvis du bruker en nettjeneste som avslører en REST API, er du heldig - i dette tilfellet brukes ikke vedvarende TCP-tilkoblinger, du kan bruke hvilken som helst Kubernetes-tjeneste.

Men når du begynner å bruke vedvarende TCP-tilkoblinger, må du finne ut hvordan du fordeler belastningen jevnt over backends. Kubernetes inneholder ikke ferdige løsninger for denne saken.

Imidlertid er det absolutt alternativer som kan hjelpe.

Balansering av langvarige forbindelser i Kubernetes

Det er fire typer tjenester i Kubernetes:

  1. ClusterIP
  2. Nodeport
  3. LoadBalancer
  4. Hodeløs

De tre første tjenestene opererer basert på en virtuell IP-adresse, som brukes av kube-proxy for å bygge iptables-regler. Men det grunnleggende grunnlaget for alle tjenester er en hodeløs tjeneste.

Den hodeløse tjenesten har ingen IP-adresse knyttet til seg og gir bare en mekanisme for å hente en liste over IP-adresser og porter til podene (endepunktene) knyttet til den.

Alle tjenester er basert på den hodeløse tjenesten.

ClusterIP-tjenesten er en hodeløs tjeneste med noen tillegg: 

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

På denne måten kan du ignorere kube-proxy og direkte bruke listen over endepunkter hentet fra den hodeløse tjenesten for å lastebalanse applikasjonen din.

Men hvordan kan vi legge til lignende logikk til alle applikasjoner som er distribuert i klyngen?

Hvis applikasjonen din allerede er distribuert, kan denne oppgaven virke umulig. Det finnes imidlertid et alternativ.

Service Mesh vil hjelpe deg

Du har sikkert allerede lagt merke til at belastningsbalanseringsstrategien på klientsiden er ganske standard.

Når applikasjonen starter, gjør den:

  1. Får en liste over IP-adresser fra tjenesten.
  2. Åpner og vedlikeholder et tilkoblingsbasseng.
  3. Oppdaterer bassenget med jevne mellomrom ved å legge til eller fjerne endepunkter.

Når applikasjonen ønsker å sende en forespørsel, gjør den:

  1. Velger en tilgjengelig tilkobling ved hjelp av noe logikk (f.eks. round-robin).
  2. Utfører forespørselen.

Disse trinnene fungerer for både WebSockets-, gRPC- og AMQP-tilkoblinger.

Du kan dele denne logikken i et eget bibliotek og bruke den i applikasjonene dine.

Du kan imidlertid bruke tjenestenett som Istio eller Linkerd i stedet.

Service Mesh forsterker søknaden din med en prosess som:

  1. Søker automatisk etter tjenestens IP-adresser.
  2. Tester tilkoblinger som WebSockets og gRPC.
  3. Balanserer forespørsler ved å bruke riktig protokoll.

Service Mesh hjelper med å administrere trafikk i klyngen, men det er ganske ressurskrevende. Andre alternativer er å bruke tredjepartsbiblioteker som Netflix Ribbon eller programmerbare proxyer som Envoy.

Hva skjer hvis du ignorerer balanseproblemer?

Du kan velge å ikke bruke lastbalansering og likevel ikke merke noen endringer. La oss se på noen få arbeidsscenarier.

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

La oss si at det er fem klienter som kobler til to servere. Selv om det ikke er balansering, vil begge serverne bli brukt:

Lastbalansering og skalering av langtidsforbindelser i Kubernetes

Tilkoblinger er kanskje ikke jevnt fordelt: kanskje fire klienter koblet til samme server, men det er en god sjanse for at begge serverne vil bli brukt.

Det som er mer problematisk er det motsatte scenarioet.

Hvis du har færre klienter og flere servere, kan ressursene dine bli underutnyttet og en potensiell flaskehals vil dukke opp.

La oss si at det er to klienter og fem servere. I beste fall vil det være to permanente tilkoblinger til to servere av fem.

De gjenværende serverne vil være inaktive:

Lastbalansering og skalering av langtidsforbindelser i Kubernetes

Hvis disse to serverne ikke kan håndtere klientforespørsler, hjelper ikke horisontal skalering.

Konklusjon

Kubernetes-tjenester er utviklet for å fungere i de fleste standard webapplikasjonsscenarier.

Men når du begynner å jobbe med applikasjonsprotokoller som bruker vedvarende TCP-tilkoblinger, for eksempel databaser, gRPC eller WebSockets, er ikke tjenester egnet lenger. Kubernetes tilbyr ikke interne mekanismer for å balansere vedvarende TCP-forbindelser.

Dette betyr at du må skrive søknader med klientsidebalansering i tankene.

Oversettelse utarbeidet av teamet Kubernetes aaS fra Mail.ru.

Hva annet å lese om emnet:

  1. Tre nivåer av autoskalering i Kubernetes og hvordan du bruker dem effektivt
  2. Kubernetes i piratkopieringens ånd med en mal for implementering.
  3. Vår Telegram-kanal om digital transformasjon.

Kilde: www.habr.com

Legg til en kommentar