Tinder-overgang til Kubernetes

Merk. overs.: Ansatte i den verdensberømte Tinder-tjenesten delte nylig noen tekniske detaljer om å migrere infrastrukturen deres til Kubernetes. Prosessen tok nesten to år og resulterte i lanseringen av en meget storskala plattform på K8s, bestående av 200 tjenester hostet på 48 tusen containere. Hvilke interessante vanskeligheter møtte Tinder-ingeniørene og hvilke resultater kom de til? Les denne oversettelsen.

Tinder-overgang til Kubernetes

Hvorfor?

For snart to år siden bestemte Tinder seg for å flytte plattformen sin til Kubernetes. Kubernetes ville tillate Tinder-teamet å containerisere og flytte til produksjon med minimal innsats gjennom uforanderlig distribusjon (uforanderlig distribusjon). I dette tilfellet vil sammenstillingen av applikasjoner, deres distribusjon og selve infrastrukturen være unikt definert av kode.

Vi var også på utkikk etter en løsning på problemet med skalerbarhet og stabilitet. Når skalering ble kritisk, måtte vi ofte vente flere minutter på at nye EC2-forekomster skulle spinne opp. Ideen om å lansere containere og begynne å betjene trafikk på sekunder i stedet for minutter ble veldig attraktiv for oss.

Prosessen viste seg å være vanskelig. Under migreringen vår tidlig i 2019 nådde Kubernetes-klyngen kritisk masse og vi begynte å støte på ulike problemer på grunn av trafikkvolum, klyngestørrelse og DNS. Underveis løste vi mange interessante problemer knyttet til migrering av 200 tjenester og vedlikehold av en Kubernetes-klynge bestående av 1000 noder, 15000 pods og 48000 kjørende containere.

Hvordan?

Siden januar 2018 har vi gått gjennom ulike stadier av migrasjon. Vi startet med å beholde alle tjenestene våre og distribuere dem til Kubernetes testskymiljøer. Fra oktober begynte vi metodisk å migrere alle eksisterende tjenester til Kubernetes. I mars året etter fullførte vi migreringen, og nå kjører Tinder-plattformen utelukkende på Kubernetes.

Bygge bilder for Kubernetes

Vi har over 30 kildekodelagre for mikrotjenester som kjører på en Kubernetes-klynge. Koden i disse depotene er skrevet på forskjellige språk (for eksempel Node.js, Java, Scala, Go) med flere kjøretidsmiljøer for samme språk.

Byggesystemet er designet for å gi en fullt tilpassbar "byggkontekst" for hver mikrotjeneste. Den består vanligvis av en Dockerfile og en liste over skallkommandoer. Innholdet deres er fullstendig tilpassbart, og samtidig er alle disse byggekontekstene skrevet i et standardisert format. Standardisering av byggekontekster lar ett enkelt byggesystem håndtere alle mikrotjenester.

Tinder-overgang til Kubernetes
Figur 1-1. Standardisert byggeprosess via Builder-beholder

For å oppnå maksimal konsistens mellom kjøretider (kjøretidsmiljøer) den samme byggeprosessen brukes under utvikling og testing. Vi sto overfor en veldig interessant utfordring: vi måtte utvikle en måte å sikre konsistens i byggemiljøet på tvers av hele plattformen. For å oppnå dette utføres alle monteringsprosesser i en spesiell beholder. Builder.

Containerimplementeringen hans krevde avanserte Docker-teknikker. Builder arver den lokale bruker-IDen og hemmelighetene (som SSH-nøkkel, AWS-legitimasjon osv.) som kreves for å få tilgang til private Tinder-depoter. Den monterer lokale kataloger som inneholder kilder for naturlig å lagre byggeartefakter. Denne tilnærmingen forbedrer ytelsen fordi den eliminerer behovet for å kopiere byggeartefakter mellom Builder-beholderen og verten. Lagrede byggeartefakter kan gjenbrukes uten ytterligere konfigurasjon.

For noen tjenester måtte vi lage en annen container for å kartlegge kompileringsmiljøet til kjøretidsmiljøet (for eksempel genererer Node.js bcrypt-biblioteket plattformspesifikke binære artefakter under installasjonen). Under kompileringsprosessen kan kravene variere mellom tjenestene, og den endelige Dockerfilen kompileres umiddelbart.

Kubernetes klyngearkitektur og migrering

Klyngestørrelsesstyring

Vi bestemte oss for å bruke kube-aws for automatisert klyngedistribusjon på Amazon EC2-forekomster. Helt i begynnelsen fungerte alt i en felles pool av noder. Vi innså raskt behovet for å skille arbeidsbelastninger etter størrelse og forekomsttype for å gjøre mer effektiv bruk av ressursene. Logikken var at det å kjøre flere innlastede flertrådede pods viste seg å være mer forutsigbart med tanke på ytelse enn deres sameksistens med et stort antall enkelt-trådede pods.

Til slutt bestemte vi oss for:

  • m5.4xstor — for overvåking (Prometheus);
  • c5.4xlarge - for Node.js arbeidsbelastning (en-tråds arbeidsbelastning);
  • c5.2xlarge - for Java og Go (flertrådsarbeidsmengde);
  • c5.4xlarge — for kontrollpanelet (3 noder).

migrasjon

Et av de forberedende trinnene for migrering fra den gamle infrastrukturen til Kubernetes var å omdirigere den eksisterende direkte kommunikasjonen mellom tjenester til de nye lastbalanserne (Elastic Load Balancers (ELB). De ble opprettet på et spesifikt undernett av en virtuell privat sky (VPC). Dette undernettet ble koblet til en Kubernetes VPC. Dette tillot oss å migrere moduler gradvis, uten å ta hensyn til den spesifikke rekkefølgen av tjenesteavhengigheter.

Disse endepunktene ble opprettet ved å bruke vektede sett med DNS-poster som hadde CNAME-er som pekte til hver nye ELB. For å bytte over la vi til en ny oppføring som peker til den nye ELB-en til Kubernetes-tjenesten med en vekt på 0. Vi satte deretter Time To Live (TTL) for oppføringen til 0. Etter dette ble den gamle og nye vekten sakte justert, og til slutt ble 100% av lasten sendt til en ny server. Etter at byttet var fullført, gikk TTL-verdien tilbake til et mer adekvat nivå.

Java-modulene vi hadde kunne takle lav TTL DNS, men Node-applikasjonene kunne ikke. En av ingeniørene skrev om deler av tilkoblingspoolkoden og pakket den inn i en manager som oppdaterte bassengene hvert 60. sekund. Den valgte tilnærmingen fungerte veldig bra og uten noen merkbar ytelsesforringelse.

Leksjoner

Nettverksstoffets grenser

Tidlig på morgenen 8. januar 2019 krasjet Tinder-plattformen uventet. Som svar på en urelatert økning i plattformlatens tidligere den morgenen, økte antallet pods og noder i klyngen. Dette førte til at ARP-cachen ble oppbrukt på alle nodene våre.

Det er tre Linux-alternativer relatert til ARP-cachen:

Tinder-overgang til Kubernetes
(kilde)

gc_thresh3 – Dette er en hard grense. Utseendet til "nabotabelloverløp"-oppføringer i loggen betydde at selv etter synkron søppelinnsamling (GC), var det ikke nok plass i ARP-cachen til å lagre nabooppføringen. I dette tilfellet forkastet kjernen ganske enkelt pakken fullstendig.

Vi bruker Flanell som et nettverksstoff i Kubernetes. Pakker sendes over VXLAN. VXLAN er en L2-tunnel hevet på toppen av et L3-nettverk. Teknologien bruker MAC-in-UDP (MAC Address-in-User Datagram Protocol) innkapsling og tillater utvidelse av lag 2 nettverkssegmenter. Transportprotokollen på det fysiske datasenternettverket er IP pluss UDP.

Tinder-overgang til Kubernetes
Figur 2–1. Flanelldiagram (kilde)

Tinder-overgang til Kubernetes
Figur 2-2. VXLAN-pakke (kilde)

Hver Kubernetes-arbeidernode tildeler et virtuelt adresseområde med en /24-maske fra en større /9-blokk. For hver node er dette betyr én oppføring i rutetabellen, én oppføring i ARP-tabellen (på flannel.1-grensesnittet), og én oppføring i byttetabellen (FDB). De legges til første gang en arbeidernode startes eller hver gang en ny node oppdages.

I tillegg går node-pod (eller pod-pod) kommunikasjon til slutt gjennom grensesnittet eth0 (som vist i flanelldiagrammet ovenfor). Dette resulterer i en ekstra oppføring i ARP-tabellen for hver tilsvarende kilde og destinasjonsvert.

I vårt miljø er denne typen kommunikasjon svært vanlig. For tjenesteobjekter i Kubernetes opprettes en ELB og Kubernetes registrerer hver node med ELB. ELB vet ingenting om pods og den valgte noden er kanskje ikke den endelige destinasjonen for pakken. Poenget er at når en node mottar en pakke fra ELB, vurderer den at den tar hensyn til reglene iptables for en spesifikk tjeneste og velger tilfeldig en pod på en annen node.

På tidspunktet for feilen var det 605 noder i klyngen. Av grunnene nevnt ovenfor var dette tilstrekkelig til å overvinne betydningen gc_thresh3, som er standard. Når dette skjer, begynner ikke bare pakker å slippes, men hele den virtuelle Flannel-adresseplassen med en /24-maske forsvinner fra ARP-tabellen. Node-pod-kommunikasjon og DNS-spørringer blir avbrutt (DNS er vert i en klynge; les senere i denne artikkelen for detaljer).

For å løse dette problemet må du øke verdiene gc_thresh1, gc_thresh2 и gc_thresh3 og start Flannel på nytt for å registrere de manglende nettverkene på nytt.

Uventet DNS-skalering

Under migreringsprosessen brukte vi DNS aktivt for å administrere trafikk og gradvis overføre tjenester fra den gamle infrastrukturen til Kubernetes. Vi setter relativt lave TTL-verdier for tilhørende RecordSets i Route53. Da den gamle infrastrukturen kjørte på EC2-forekomster, pekte oppløsningskonfigurasjonen vår til Amazon DNS. Vi tok dette for gitt, og virkningen av den lave TTL på tjenestene våre og Amazon-tjenester (som DynamoDB) gikk stort sett ubemerket hen.

Da vi migrerte tjenester til Kubernetes, fant vi ut at DNS behandlet 250 tusen forespørsler per sekund. Som et resultat begynte applikasjoner å oppleve konstante og alvorlige tidsavbrudd for DNS-spørringer. Dette skjedde til tross for utrolig innsats for å optimalisere og bytte DNS-leverandøren til CoreDNS (som ved toppbelastning nådde 1000 pods som kjørte på 120 kjerner).

Mens vi undersøkte andre mulige årsaker og løsninger, oppdaget vi artikkel, som beskriver raseforhold som påvirker rammeverket for pakkefiltrering nettfilter i Linux. Tidsavbruddene vi observerte, kombinert med en økende teller insert_failed i Flanell-grensesnittet var i samsvar med funnene i artikkelen.

Problemet oppstår på stadiet av kilde- og målnettverksadresseoversettelse (SNAT og DNAT) og påfølgende oppføring i tabellen kontekst. En av løsningene som ble diskutert internt og foreslått av fellesskapet var å flytte DNS til selve arbeidernoden. I dette tilfellet:

  • SNAT er ikke nødvendig fordi trafikken forblir inne i noden. Det trenger ikke å bli rutet gjennom grensesnittet eth0.
  • DNAT er ikke nødvendig siden destinasjons-IP er lokal for noden, og ikke en tilfeldig valgt pod i henhold til reglene iptables.

Vi bestemte oss for å holde fast ved denne tilnærmingen. CoreDNS ble distribuert som et DaemonSet i Kubernetes og vi implementerte en lokal node DNS-server i resolve.conf hver pod ved å sette et flagg --cluster-dns kommandoer kubelet . Denne løsningen viste seg å være effektiv for DNS-tidsavbrudd.

Imidlertid så vi fortsatt pakketap og en økning i telleren insert_failed i Flanell-grensesnittet. Dette fortsatte etter at løsningen ble implementert fordi vi var i stand til å eliminere SNAT og/eller DNAT kun for DNS-trafikk. Løpsforholdene ble bevart for andre typer trafikk. Heldigvis er de fleste av pakkene våre TCP, og hvis det oppstår et problem blir de ganske enkelt sendt på nytt. Vi prøver fortsatt å finne en passende løsning for alle typer trafikk.

Bruke Envoy for bedre lastbalansering

Da vi migrerte backend-tjenester til Kubernetes, begynte vi å lide av ubalansert belastning mellom pods. Vi fant ut at HTTP Keepalive fikk ELB-tilkoblinger til å henge på de første klare podene for hver distribusjon som ble rullet ut. Dermed gikk hoveddelen av trafikken gjennom en liten prosentandel av tilgjengelige pods. Den første løsningen vi testet var å sette MaxSurge til 100 % på nye distribusjoner for verste fall. Effekten viste seg å være ubetydelig og lite lovende med tanke på større utplasseringer.

En annen løsning vi brukte var å kunstig øke ressursforespørsler for kritiske tjenester. I dette tilfellet vil pods plassert i nærheten ha mer plass å manøvrere sammenlignet med andre tunge pods. Det ville heller ikke fungert i lengden fordi det ville være sløsing med ressurser. I tillegg var våre Node-applikasjoner entrådede og kunne følgelig bare bruke én kjerne. Den eneste reelle løsningen var å bruke bedre lastbalansering.

Vi har lenge ønsket å sette full pris på Envoy. Den nåværende situasjonen tillot oss å distribuere den på en svært begrenset måte og få umiddelbare resultater. Envoy er en høyytelses, åpen kildekode, lag-XNUMX proxy designet for store SOA-applikasjoner. Den kan implementere avanserte lastbalanseringsteknikker, inkludert automatiske forsøk, kretsbrytere og global hastighetsbegrensning. (Merk. overs.: Du kan lese mer om dette i denne artikkelen om Istio, som er basert på Envoy.)

Vi kom opp med følgende konfigurasjon: ha en Envoy-sidevogn for hver pod og en enkelt rute, og koble klyngen til containeren lokalt via havn. For å minimere potensiell overlapping og opprettholde en liten treffradius, brukte vi en flåte av Envoy front-proxy pods, én per tilgjengelighetssone (AZ) for hver tjeneste. De stolte på en enkel tjenesteoppdagelsesmotor skrevet av en av våre ingeniører som ganske enkelt returnerte en liste over pods i hver AZ for en gitt tjeneste.

Tjenestefrontutsendinger brukte deretter denne tjenesteoppdagelsesmekanismen med én oppstrømsklynge og rute. Vi satte tilstrekkelige tidsavbrudd, økte alle strømbryterinnstillinger og la til minimalt med konfigurasjon av forsøk på nytt for å hjelpe med enkeltfeil og sikre jevn utrulling. Vi plasserte en TCP ELB foran hver av disse tjenestefrontutsendingene. Selv om keepalive fra hovedproxylaget vårt ble sittende fast på noen Envoy-pods, var de fortsatt i stand til å håndtere belastningen mye bedre og ble konfigurert til å balansere gjennom minste_request i backend.

For utplassering brukte vi preStop-kroken på både applikasjonsputer og sidevognsputer. Kroken utløste en feil under kontroll av statusen til admin-endepunktet på sidevognsbeholderen og gikk i dvale en stund for å la aktive tilkoblinger avsluttes.

En av grunnene til at vi var i stand til å bevege oss så raskt er på grunn av de detaljerte beregningene som vi enkelt kunne integrere i en typisk Prometheus-installasjon. Dette gjorde at vi kunne se nøyaktig hva som skjedde mens vi justerte konfigurasjonsparametere og omfordelte trafikk.

Resultatene var umiddelbare og åpenbare. Vi startet med de mest ubalanserte tjenestene, og for øyeblikket opererer den foran de 12 viktigste tjenestene i klyngen. I år planlegger vi en overgang til et fullservicenettverk med mer avansert tjenesteoppdagelse, kretsbrudd, avvikdeteksjon, hastighetsbegrensning og sporing.

Tinder-overgang til Kubernetes
Figur 3–1. CPU-konvergens av én tjeneste under overgangen til Envoy

Tinder-overgang til Kubernetes

Tinder-overgang til Kubernetes

Endelig resultat

Gjennom denne erfaringen og ytterligere forskning har vi bygget et sterkt infrastrukturteam med sterke ferdigheter i å designe, distribuere og drifte store Kubernetes-klynger. Alle Tinder-ingeniører har nå kunnskap og erfaring til å pakke containere og distribuere applikasjoner til Kubernetes.

Da behovet for ekstra kapasitet oppsto på den gamle infrastrukturen, måtte vi vente flere minutter på at nye EC2-instanser skulle lanseres. Nå begynner containere å kjøre og begynner å behandle trafikk i løpet av sekunder i stedet for minutter. Planlegging av flere beholdere på en enkelt EC2-forekomst gir også forbedret horisontal konsentrasjon. Som et resultat anslår vi en betydelig reduksjon i EC2019-kostnadene i 2 sammenlignet med fjoråret.

Migreringen tok nesten to år, men vi fullførte den i mars 2019. Foreløpig kjører Tinder-plattformen utelukkende på en Kubernetes-klynge som består av 200 tjenester, 1000 15 noder, 000 48 pods og 000 XNUMX kjørende containere. Infrastruktur er ikke lenger det eneste domenet til driftsteam. Alle våre ingeniører deler dette ansvaret og kontrollerer prosessen med å bygge og distribuere applikasjonene sine kun ved å bruke kode.

PS fra oversetter

Les også en serie artikler på bloggen vår:

Kilde: www.habr.com

Legg til en kommentar