Tinder-overgang naar Kubernetes

Opmerking. vert.: Medewerkers van de wereldberoemde Tinder-service deelden onlangs enkele technische details over het migreren van hun infrastructuur naar Kubernetes. Het proces duurde bijna twee jaar en resulteerde in de lancering van een zeer grootschalig platform op K8s, bestaande uit 200 diensten gehost op 48 containers. Welke interessante moeilijkheden kwamen de Tinder-ingenieurs tegen en tot welke resultaten kwamen ze?Lees deze vertaling.

Tinder-overgang naar Kubernetes

Waarom?

Bijna twee jaar geleden besloot Tinder zijn platform naar Kubernetes te verhuizen. Kubernetes zou het Tinder-team in staat stellen om met minimale inspanning te containeriseren en naar productie te gaan via een onveranderlijke implementatie (onveranderlijke implementatie). In dit geval zouden de verzameling van applicaties, hun implementatie en de infrastructuur zelf op unieke wijze worden gedefinieerd door code.

We waren ook op zoek naar een oplossing voor het probleem van schaalbaarheid en stabiliteit. Toen schaalvergroting cruciaal werd, moesten we vaak enkele minuten wachten voordat nieuwe EC2-instanties op gang kwamen. Het idee om containers te lanceren en verkeer binnen enkele seconden in plaats van minuten te bedienen, werd voor ons erg aantrekkelijk.

Het proces bleek moeilijk. Tijdens onze migratie begin 2019 bereikte het Kubernetes-cluster een kritische massa en kwamen we verschillende problemen tegen vanwege het verkeersvolume, de clustergrootte en DNS. Gaandeweg hebben we veel interessante problemen opgelost met betrekking tot het migreren van 200 services en het onderhouden van een Kubernetes-cluster bestaande uit 1000 knooppunten, 15000 pods en 48000 actieve containers.

Hoe?

Sinds januari 2018 hebben we verschillende fasen van de migratie doorlopen. We zijn begonnen met het containeriseren van al onze services en het implementeren ervan in Kubernetes-testcloudomgevingen. Vanaf oktober zijn we begonnen met het methodisch migreren van alle bestaande services naar Kubernetes. In maart van het daaropvolgende jaar voltooiden we de migratie en nu draait het Tinder-platform exclusief op Kubernetes.

Images maken voor Kubernetes

We hebben meer dan 30 broncodeopslagplaatsen voor microservices die op een Kubernetes-cluster draaien. De code in deze repositories is geschreven in verschillende talen (bijvoorbeeld Node.js, Java, Scala, Go) met meerdere runtime-omgevingen voor dezelfde taal.

Het bouwsysteem is ontworpen om voor elke microservice een volledig aanpasbare “bouwcontext” te bieden. Het bestaat meestal uit een Docker-bestand en een lijst met shell-opdrachten. Hun inhoud is volledig aanpasbaar en tegelijkertijd zijn al deze bouwcontexten geschreven volgens een gestandaardiseerd formaat. Door de bouwcontexten te standaardiseren, kan één enkel bouwsysteem alle microservices afhandelen.

Tinder-overgang naar Kubernetes
Figuur 1-1. Gestandaardiseerd bouwproces via Builder-container

Om maximale consistentie tussen runtimes te bereiken (runtime-omgevingen) hetzelfde bouwproces wordt gebruikt tijdens het ontwikkelen en testen. We stonden voor een zeer interessante uitdaging: we moesten een manier ontwikkelen om de consistentie van de bouwomgeving over het hele platform te garanderen. Om dit te bereiken worden alle assemblageprocessen uitgevoerd in een speciale container. Bouwer.

Zijn containerimplementatie vereiste geavanceerde Docker-technieken. Builder neemt de lokale gebruikers-ID en geheimen (zoals SSH-sleutel, AWS-inloggegevens, enz.) over die nodig zijn om toegang te krijgen tot privé-Tinder-opslagplaatsen. Het koppelt lokale mappen met bronnen aan om build-artefacten op natuurlijke wijze op te slaan. Deze aanpak verbetert de prestaties omdat het de noodzaak elimineert om build-artefacten tussen de Builder-container en de host te kopiëren. Opgeslagen build-artefacten kunnen zonder aanvullende configuratie opnieuw worden gebruikt.

Voor sommige services moesten we een andere container maken om de compilatieomgeving toe te wijzen aan de runtime-omgeving (de bcrypt-bibliotheek Node.js genereert bijvoorbeeld platformspecifieke binaire artefacten tijdens de installatie). Tijdens het compilatieproces kunnen de vereisten per service variëren, en het uiteindelijke Docker-bestand wordt direct gecompileerd.

Kubernetes-clusterarchitectuur en -migratie

Beheer van clustergrootte

We besloten te gebruiken kubus-aws voor geautomatiseerde clusterimplementatie op Amazon EC2-instanties. Helemaal aan het begin werkte alles in één gemeenschappelijke pool van knooppunten. We beseften al snel dat het nodig was om workloads te scheiden op grootte en exemplaartype om efficiënter gebruik te kunnen maken van resources. De logica was dat het draaien van meerdere geladen multi-threaded pods qua prestaties voorspelbaarder bleek te zijn dan het naast elkaar bestaan ​​ervan met een groot aantal single-threaded pods.

Uiteindelijk zijn we uitgekomen op:

  • m5.4xgroot — voor monitoring (Prometheus);
  • c5.4xgroot - voor Node.js-werkbelasting (werkbelasting met één thread);
  • c5.2xgroot - voor Java en Go (multithreaded werklast);
  • c5.4xgroot — voor het bedieningspaneel (3 knooppunten).

migratie

Eén van de voorbereidende stappen voor de migratie van de oude infrastructuur naar Kubernetes was het omleiden van de bestaande directe communicatie tussen services naar de nieuwe load balancers (Elastic Load Balancers (ELB). Ze zijn gemaakt op een specifiek subnet van een virtuele privécloud (VPC). Dit subnet was verbonden met een Kubernetes VPC. Hierdoor konden we modules geleidelijk migreren, zonder rekening te houden met de specifieke volgorde van serviceafhankelijkheden.

Deze eindpunten zijn gemaakt met behulp van gewogen sets DNS-records met CNAME's die naar elke nieuwe ELB verwijzen. Om over te schakelen hebben we een nieuwe entry toegevoegd die verwijst naar de nieuwe ELB van de Kubernetes-service met een gewicht van 0. Vervolgens hebben we de Time To Live (TTL) van de entry ingesteld op 0. Hierna werden de oude en nieuwe gewichten langzaam aangepast en uiteindelijk werd 100% van de belasting naar een nieuwe server gestuurd. Nadat het schakelen was voltooid, keerde de TTL-waarde terug naar een adequater niveau.

De Java-modules die we hadden konden omgaan met lage TTL DNS, maar de Node-applicaties niet. Een van de technici herschreef een deel van de verbindingspoolcode en verpakte dit in een manager die de pools elke 60 seconden bijwerkte. De gekozen aanpak werkte zeer goed en zonder merkbare prestatievermindering.

ervaring

De grenzen van het netwerkweefsel

In de vroege ochtend van 8 januari 2019 crashte het Tinder-platform onverwachts. Als reactie op een niet-gerelateerde toename van de platformlatentie eerder die ochtend, nam het aantal peulen en knooppunten in het cluster toe. Dit zorgde ervoor dat de ARP-cache op al onze knooppunten uitgeput was.

Er zijn drie Linux-opties gerelateerd aan de ARP-cache:

Tinder-overgang naar Kubernetes
(bron)

gc_thresh3 - dit is een harde limiet. Het verschijnen van “nighbor table overflow”-items in het logboek betekende dat er zelfs na synchrone garbage collection (GC) niet genoeg ruimte in de ARP-cache was om het aangrenzende item op te slaan. In dit geval heeft de kernel het pakket eenvoudigweg volledig weggegooid.

We gebruiken Flanel als netwerkweefsel in Kubernetes. Pakketten worden verzonden via VXLAN. VXLAN is een L2-tunnel die bovenop een L3-netwerk is geplaatst. De technologie maakt gebruik van MAC-in-UDP (MAC Address-in-User Datagram Protocol)-inkapseling en maakt uitbreiding van Layer 2-netwerksegmenten mogelijk. Het transportprotocol op het fysieke datacenternetwerk is IP plus UDP.

Tinder-overgang naar Kubernetes
Figuur 2–1. Flaneldiagram (bron)

Tinder-overgang naar Kubernetes
Figuur 2–2. VXLAN-pakket (bron)

Elk Kubernetes-werkknooppunt wijst een virtuele adresruimte toe met een /24-masker uit een groter /9-blok. Voor elk knooppunt is dit het geval middelen één vermelding in de routeringstabel, één vermelding in de ARP-tabel (op de flanel.1-interface) en één vermelding in de schakeltabel (FDB). Ze worden toegevoegd wanneer een werkknooppunt voor de eerste keer wordt gestart of telkens wanneer een nieuw knooppunt wordt ontdekt.

Bovendien verloopt de communicatie tussen knooppunten (of pod-pods) uiteindelijk via de interface eth0 (zoals weergegeven in het flaneldiagram hierboven). Dit resulteert in een extra vermelding in de ARP-tabel voor elke corresponderende bron- en bestemmingshost.

In onze omgeving is dit soort communicatie heel gebruikelijk. Voor serviceobjecten in Kubernetes wordt een ELB aangemaakt en Kubernetes registreert elk knooppunt bij de ELB. De ELB weet niets over pods en het geselecteerde knooppunt is mogelijk niet de eindbestemming van het pakket. Het punt is dat wanneer een knooppunt een pakket ontvangt van de ELB, het dit beschouwt als rekening houdend met de regels iptables voor een specifieke service en selecteert willekeurig een pod op een ander knooppunt.

Op het moment van de storing waren er 605 knooppunten in het cluster. Om bovengenoemde redenen was dit voldoende om de betekenis te ondervangen gc_thresh3, wat de standaard is. Wanneer dit gebeurt, beginnen niet alleen pakketten te worden verwijderd, maar verdwijnt de volledige virtuele Flannel-adresruimte met een /24-masker uit de ARP-tabel. Node-pod-communicatie en DNS-query's worden onderbroken (DNS wordt gehost in een cluster; lees verderop in dit artikel voor meer informatie).

Om dit probleem op te lossen, moet u de waarden verhogen gc_thresh1, gc_thresh2 и gc_thresh3 en start Flanel opnieuw om de ontbrekende netwerken opnieuw te registreren.

Onverwachte DNS-schaling

Tijdens het migratieproces hebben we DNS actief gebruikt om het verkeer te beheren en geleidelijk diensten over te zetten van de oude infrastructuur naar Kubernetes. We stellen relatief lage TTL-waarden in voor bijbehorende RecordSets in Route53. Toen de oude infrastructuur op EC2-instances draaide, wees onze solverconfiguratie naar Amazon DNS. We vonden dit vanzelfsprekend en de impact van de lage TTL op onze diensten en Amazon-diensten (zoals DynamoDB) bleef grotendeels onopgemerkt.

Toen we services naar Kubernetes migreerden, ontdekten we dat DNS 250 verzoeken per seconde verwerkte. Als gevolg hiervan begonnen applicaties constante en ernstige time-outs te ervaren voor DNS-query's. Dit gebeurde ondanks ongelooflijke inspanningen om de DNS-provider te optimaliseren en over te schakelen naar CoreDNS (die bij piekbelasting 1000 pods bereikte die op 120 cores draaiden).

Terwijl we andere mogelijke oorzaken en oplossingen onderzochten, kwamen we erachter статью, waarin raceomstandigheden worden beschreven die van invloed zijn op het pakketfilterframework netfilter onder Linux. De time-outs die we waarnamen, gekoppeld aan een oplopende teller invoegen_mislukt in de Flanel-interface kwamen overeen met de bevindingen van het artikel.

Het probleem doet zich voor in de fase van de bron- en bestemmingsnetwerkadresvertaling (SNAT en DNAT) en de daaropvolgende invoer in de tabel conntrack. Een van de oplossingen die intern werd besproken en door de community werd voorgesteld, was het verplaatsen van de DNS naar het werkknooppunt zelf. In dit geval:

  • SNAT is niet nodig omdat het verkeer binnen het knooppunt blijft. Het hoeft niet via de interface te worden gerouteerd eth0.
  • DNAT is niet nodig omdat het bestemmings-IP lokaal is voor het knooppunt en niet een willekeurig geselecteerde pod volgens de regels iptables.

Wij hebben besloten deze aanpak te blijven volgen. CoreDNS werd ingezet als DaemonSet in Kubernetes en we implementeerden er een lokale node DNS-server in oplossen.conf elke pod door een vlag in te stellen --cluster-dns commando's kubus . Deze oplossing bleek effectief voor DNS-time-outs.

Wel zagen we nog steeds pakketverlies en een stijging van de teller invoegen_mislukt in de Flanel-interface. Dit ging door nadat de tijdelijke oplossing was geïmplementeerd, omdat we SNAT en/of DNAT alleen voor DNS-verkeer konden elimineren. De raceomstandigheden bleven behouden voor andere vormen van verkeer. Gelukkig zijn de meeste van onze pakketten TCP en als er zich een probleem voordoet, worden ze eenvoudigweg opnieuw verzonden. Voor alle soorten verkeer zijn wij nog steeds bezig een passende oplossing te vinden.

Gebruik Envoy voor een betere taakverdeling

Toen we de backend-services naar Kubernetes migreerden, kregen we last van een onevenwichtige belasting tussen de pods. We ontdekten dat HTTP Keepalive ervoor zorgde dat ELB-verbindingen vastliepen op de eerste gereedstaande pods van elke implementatie die werd uitgerold. Het grootste deel van het verkeer ging dus via een klein percentage van de beschikbare pods. De eerste oplossing die we testten was het instellen van MaxSurge op 100% bij nieuwe implementaties voor worstcasescenario's. Het effect bleek onbeduidend en weinig belovend in termen van grotere implementaties.

Een andere oplossing die we gebruikten was het kunstmatig verhogen van de resourceaanvragen voor kritieke services. In dit geval zouden nabijgelegen pods meer manoeuvreerruimte hebben vergeleken met andere zware pods. Het zou op de lange termijn ook niet werken, omdat het een verspilling van middelen zou zijn. Bovendien waren onze Node-applicaties single-threaded en konden daarom slechts één kern gebruiken. De enige echte oplossing was het gebruik van een betere taakverdeling.

We wilden het al lang ten volle waarderen Gezant. Door de huidige situatie konden we het op een zeer beperkte manier inzetten en onmiddellijke resultaten boeken. Envoy is een krachtige, open-source, Layer-XNUMX-proxy die is ontworpen voor grote SOA-applicaties. Het kan geavanceerde technieken voor taakverdeling implementeren, waaronder automatische nieuwe pogingen, stroomonderbrekers en globale snelheidsbeperking. (Opmerking. vert.: Meer hierover leest u in dit artikel over Istio, dat is gebaseerd op Envoy.)

We hebben de volgende configuratie bedacht: zorg voor een Envoy-zijspan voor elke pod en één route, en verbind het cluster lokaal via een poort met de container. Om potentiële cascadering te minimaliseren en een kleine trefradius te behouden, hebben we een vloot Envoy front-proxy-pods gebruikt, één per Availability Zone (AZ) voor elke service. Ze vertrouwden op een eenvoudige service-ontdekkingsengine, geschreven door een van onze technici, die eenvoudigweg een lijst met pods in elke AZ voor een bepaalde service terugstuurde.

Service front-Envoys gebruikten vervolgens dit servicedetectiemechanisme met één upstreamcluster en route. We hebben adequate time-outs ingesteld, alle instellingen voor stroomonderbrekers verhoogd en een minimale configuratie voor nieuwe pogingen toegevoegd om eenmalige fouten te helpen oplossen en een soepele implementatie te garanderen. We plaatsten een TCP ELB voor elk van deze front-gezanten van de dienst. Zelfs als de keepalive van onze hoofdproxylaag op sommige Envoy-pods vastzat, konden ze de belasting nog steeds veel beter aan en waren ze geconfigureerd om te balanceren via least_request in de backend.

Voor de implementatie hebben we de preStop-haak op zowel applicatiepods als zijspanpods gebruikt. De hook veroorzaakte een fout bij het controleren van de status van het beheerderseindpunt in de zijspancontainer en ging een tijdje in de sluimerstand zodat actieve verbindingen konden worden beëindigd.

Een van de redenen waarom we zo snel konden handelen, is te danken aan de gedetailleerde statistieken die we eenvoudig konden integreren in een typische Prometheus-installatie. Hierdoor konden we precies zien wat er gebeurde terwijl we de configuratieparameters aanpasten en het verkeer herverdeelden.

De resultaten waren onmiddellijk en duidelijk. We zijn begonnen met de meest onevenwichtige services en opereren momenteel vóór de twaalf belangrijkste services in het cluster. Dit jaar plannen we een transitie naar een full-service mesh met meer geavanceerde servicedetectie, circuitonderbreking, detectie van uitschieters, snelheidsbeperking en tracering.

Tinder-overgang naar Kubernetes
Figuur 3–1. CPU-convergentie van één service tijdens de overgang naar Envoy

Tinder-overgang naar Kubernetes

Tinder-overgang naar Kubernetes

Eindresultaat

Door deze ervaring en aanvullend onderzoek hebben we een sterk infrastructuurteam opgebouwd met sterke vaardigheden in het ontwerpen, implementeren en exploiteren van grote Kubernetes-clusters. Alle Tinder-engineers hebben nu de kennis en ervaring om containers te verpakken en applicaties op Kubernetes te implementeren.

Toen er behoefte ontstond aan extra capaciteit op de oude infrastructuur, moesten we enkele minuten wachten voordat nieuwe EC2-instances werden gelanceerd. Nu beginnen containers te draaien en beginnen ze binnen enkele seconden in plaats van minuten verkeer te verwerken. Het plannen van meerdere containers op één EC2-instantie zorgt ook voor een verbeterde horizontale concentratie. Als gevolg hiervan voorspellen we een aanzienlijke verlaging van de EC2019-kosten in 2 vergeleken met vorig jaar.

De migratie duurde bijna twee jaar, maar we hebben deze in maart 2019 afgerond. Momenteel draait het Tinder-platform exclusief op een Kubernetes-cluster bestaande uit 200 services, 1000 knooppunten, 15 pods en 000 actieve containers. Infrastructuur is niet langer het enige domein van operationele teams. Al onze engineers delen deze verantwoordelijkheid en controleren het proces van het bouwen en implementeren van hun applicaties met alleen code.

PS van vertaler

Lees ook een reeks artikelen op onze blog:

Bron: www.habr.com

Voeg een reactie