La transició de Tinder a Kubernetes

Nota. transl.: Els empleats del famós servei Tinder van compartir recentment alguns detalls tècnics de la migració de la seva infraestructura a Kubernetes. El procés va durar gairebé dos anys i va donar lloc al llançament d'una plataforma a gran escala en K8, que consta de 200 serveis allotjats en 48 mil contenidors. Quines dificultats interessants van trobar els enginyers de Tinder i a quins resultats van arribar? Llegeix aquesta traducció.

La transició de Tinder a Kubernetes

Per què?

Fa gairebé dos anys, Tinder va decidir traslladar la seva plataforma a Kubernetes. Kubernetes permetria a l'equip de Tinder contenidoritzar i passar a la producció amb un esforç mínim mitjançant un desplegament immutable (desplegament immutable). En aquest cas, el conjunt d'aplicacions, el seu desplegament i la pròpia infraestructura es definirien de manera única pel codi.

També estàvem buscant una solució al problema d'escalabilitat i estabilitat. Quan l'escala esdevenia crítica, sovint havíem d'esperar uns quants minuts perquè les noves instàncies EC2 s'ampliessin. La idea de llançar contenidors i començar a donar servei al trànsit en segons en lloc de minuts ens va resultar molt atractiva.

El procés va resultar difícil. Durant la nostra migració a principis del 2019, el clúster de Kubernetes va assolir una massa crítica i vam començar a trobar diversos problemes a causa del volum de trànsit, la mida del clúster i el DNS. Al llarg del camí, hem resolt molts problemes interessants relacionats amb la migració de 200 serveis i el manteniment d'un clúster de Kubernetes format per 1000 nodes, 15000 pods i 48000 contenidors en execució.

Com?

Des del gener de 2018, hem passat per diverses etapes migratòries. Vam començar contenint tots els nostres serveis i implementant-los als entorns de núvol de proves de Kubernetes. A partir de l'octubre, vam començar a migrar metòdicament tots els serveis existents a Kubernetes. Al març de l'any següent, vam completar la migració i ara la plataforma Tinder funciona exclusivament a Kubernetes.

Creació d'imatges per a Kubernetes

Tenim més de 30 repositoris de codi font per a microserveis que s'executen en un clúster de Kubernetes. El codi d'aquests repositoris està escrit en diferents idiomes (per exemple, Node.js, Java, Scala, Go) amb diversos entorns d'execució per al mateix llenguatge.

El sistema de compilació està dissenyat per proporcionar un "context de creació" totalment personalitzable per a cada microservei. Normalment consta d'un Dockerfile i una llista d'ordres de shell. El seu contingut és totalment personalitzable i, al mateix temps, tots aquests contextos de construcció estan escrits segons un format estandarditzat. L'estandardització dels contextos de compilació permet que un únic sistema de compilació gestioni tots els microserveis.

La transició de Tinder a Kubernetes
Figura 1-1. Procés de construcció estandarditzat mitjançant contenidor Builder

Per aconseguir la màxima coherència entre els temps d'execució (entorns d'execució) el mateix procés de creació s'utilitza durant el desenvolupament i les proves. Ens hem enfrontat a un repte molt interessant: havíem de desenvolupar una manera de garantir la coherència de l'entorn de construcció a tota la plataforma. Per aconseguir-ho, tots els processos de muntatge es realitzen dins d'un contenidor especial. Constructor.

La seva implementació de contenidors requeria tècniques avançades de Docker. Builder hereta l'ID d'usuari local i els secrets (com ara la clau SSH, les credencials d'AWS, etc.) necessaris per accedir als repositoris privats de Tinder. Munta directoris locals que contenen fonts per emmagatzemar de manera natural artefactes de construcció. Aquest enfocament millora el rendiment perquè elimina la necessitat de copiar artefactes de compilació entre el contenidor del Builder i l'amfitrió. Els artefactes de construcció emmagatzemats es poden reutilitzar sense configuració addicional.

Per a alguns serveis, vam haver de crear un altre contenidor per assignar l'entorn de compilació a l'entorn d'execució (per exemple, la biblioteca bcrypt de Node.js genera artefactes binaris específics de la plataforma durant la instal·lació). Durant el procés de compilació, els requisits poden variar entre els serveis i el Dockerfile final es compila sobre la marxa.

Arquitectura i migració de clúster de Kubernetes

Gestió de la mida del clúster

Vam decidir utilitzar kube-aws per al desplegament de clúster automatitzat a instàncies d'Amazon EC2. Al principi, tot funcionava en un conjunt comú de nodes. Ràpidament ens vam adonar de la necessitat de separar les càrregues de treball per mida i tipus d'instància per fer un ús més eficient dels recursos. La lògica era que l'execució de diversos pods multifils carregats resultava ser més previsible en termes de rendiment que la seva coexistència amb un gran nombre de pods d'un sol fil.

Al final ens vam decidir per:

  • m5.4xgran — per al seguiment (Prometeu);
  • c5.4xgran - per a la càrrega de treball de Node.js (càrrega de treball d'un sol fil);
  • c5.2xgran - per a Java i Go (càrrega de treball multiprocés);
  • c5.4xgran — per al panell de control (3 nodes).

La migració

Un dels passos preparatoris per migrar de l'antiga infraestructura a Kubernetes va ser redirigir la comunicació directa existent entre serveis als nous equilibradors de càrrega (Elastic Load Balancers (ELB). Es van crear en una subxarxa específica d'un núvol privat virtual (VPC). Aquesta subxarxa estava connectada a un VPC de Kubernetes. Això ens va permetre migrar els mòduls de manera gradual, sense tenir en compte l'ordre específic de les dependències del servei.

Aquests punts finals es van crear mitjançant conjunts ponderats de registres DNS que tenien CNAME que apuntaven a cada ELB nou. Per canviar, vam afegir una nova entrada que apuntava al nou ELB del servei Kubernetes amb un pes de 0. A continuació, vam establir el temps de vida (TTL) de l'entrada establert a 0. Després d'això, els pesos antic i nou es van s'ha ajustat lentament i, finalment, el 100% de la càrrega es va enviar a un nou servidor. Un cop finalitzat el canvi, el valor TTL va tornar a un nivell més adequat.

Els mòduls Java que teníem podien fer front a un DNS TTL baix, però les aplicacions Node no ho podien fer. Un dels enginyers va reescriure part del codi del grup de connexió i el va embolicar en un gestor que actualitzava els grups cada 60 segons. L'enfocament escollit va funcionar molt bé i sense cap degradació notable del rendiment.

Lliçons

Els límits del teixit de xarxa

A primera hora del matí del 8 de gener de 2019, la plataforma Tinder es va estavellar inesperadament. Com a resposta a un augment no relacionat de la latència de la plataforma a primera hora del matí, el nombre de pods i nodes del clúster va augmentar. Això va fer que la memòria cau ARP s'esgotés a tots els nostres nodes.

Hi ha tres opcions de Linux relacionades amb la memòria cau ARP:

La transició de Tinder a Kubernetes
(font)

gc_thresh3 - Aquest és un límit dur. L'aparició d'entrades de "desbordament de la taula veïna" al registre va significar que fins i tot després de la recollida sincrònica d'escombraries (GC), no hi havia prou espai a la memòria cau ARP per emmagatzemar l'entrada veïna. En aquest cas, el nucli simplement va descartar el paquet completament.

Fem servir Flanel com a teixit de xarxa a Kubernetes. Els paquets es transmeten a través de VXLAN. VXLAN és un túnel L2 aixecat sobre una xarxa L3. La tecnologia utilitza l'encapsulació MAC-in-UDP (MAC Address-in-User Datagram Protocol) i permet l'expansió dels segments de xarxa de capa 2. El protocol de transport a la xarxa física del centre de dades és IP més UDP.

La transició de Tinder a Kubernetes
Figura 2-1. Diagrama de franel·la (font)

La transició de Tinder a Kubernetes
Figura 2-2. paquet VXLAN (font)

Cada node de treball de Kubernetes assigna un espai d'adreces virtuals amb una màscara /24 d'un bloc /9 més gran. Per a cada node això és 1/2 una entrada a la taula d'encaminament, una entrada a la taula ARP (a la interfície flannel.1) i una entrada a la taula de commutació (FDB). S'afegeixen la primera vegada que s'inicia un node de treball o cada vegada que es descobreix un nou node.

A més, la comunicació node-pod (o pod-pod) finalment passa per la interfície eth0 (com es mostra al diagrama de franela anterior). Això resulta en una entrada addicional a la taula ARP per a cada amfitrió d'origen i de destinació corresponent.

Al nostre entorn, aquest tipus de comunicació és molt habitual. Per als objectes de servei a Kubernetes, es crea un ELB i Kubernetes registra cada node amb l'ELB. L'ELB no sap res dels pods i és possible que el node seleccionat no sigui la destinació final del paquet. La qüestió és que quan un node rep un paquet de l'ELB, ho considera tenint en compte les regles iptables per a un servei específic i selecciona aleatòriament un pod en un altre node.

En el moment de la fallada, hi havia 605 nodes al clúster. Per les raons exposades anteriorment, això va ser suficient per superar la importància gc_thresh3, que és el valor predeterminat. Quan això passa, no només comencen a deixar-se caure paquets, sinó que tot l'espai d'adreces virtuals de Flannel amb una màscara /24 desapareix de la taula ARP. La comunicació node-pod i les consultes DNS s'interrompen (el DNS està allotjat en un clúster; llegiu més endavant en aquest article per obtenir-ne més informació).

Per resoldre aquest problema, cal augmentar els valors gc_thresh1, gc_thresh2 и gc_thresh3 i reinicieu Flannel per tornar a registrar les xarxes que falten.

Escalat de DNS inesperat

Durant el procés de migració, vam utilitzar activament DNS per gestionar el trànsit i transferir gradualment els serveis de l'antiga infraestructura a Kubernetes. Establem valors TTL relativament baixos per a RecordSets associats a Route53. Quan l'antiga infraestructura s'executava a instàncies EC2, la nostra configuració de resolució apuntava a Amazon DNS. Ho vam donar per fet i l'impacte del baix TTL en els nostres serveis i serveis d'Amazon (com DynamoDB) va passar en gran mesura desapercebut.

Quan migràvem els serveis a Kubernetes, vam trobar que DNS processava 250 mil sol·licituds per segon. Com a resultat, les aplicacions van començar a experimentar temps d'espera constants i greus per a les consultes de DNS. Això va passar malgrat els esforços increïbles per optimitzar i canviar el proveïdor de DNS a CoreDNS (que a la càrrega màxima va arribar als 1000 pods amb 120 nuclis).

Mentre investigàvem altres possibles causes i solucions, vam descobrir un article, descrivint les condicions de carrera que afecten el marc de filtratge de paquets netfilter en Linux. Els temps morts que vam observar, juntament amb un comptador creixent insert_failed a la interfície Flannel eren coherents amb les conclusions de l'article.

El problema es produeix en l'etapa de traducció d'adreces de xarxa d'origen i de destinació (SNAT i DNAT) i la posterior entrada a la taula contratrack. Una de les solucions alternatives discutides internament i suggerides per la comunitat va ser traslladar el DNS al propi node de treball. En aquest cas:

  • SNAT no és necessari perquè el trànsit es manté dins del node. No cal encaminar-lo a través de la interfície eth0.
  • DNAT no és necessari, ja que la IP de destinació és local al node i no és un pod seleccionat aleatòriament segons les regles. iptables.

Vam decidir seguir amb aquest enfocament. CoreDNS es va implementar com a DaemonSet a Kubernetes i vam implementar un servidor DNS de nodes locals a resolv.conf cada beina posant una bandera --cluster-dns ordres kubelet . Aquesta solució va resultar eficaç per als temps d'espera de DNS.

No obstant això, encara vam veure la pèrdua de paquets i un augment del comptador insert_failed a la interfície Flannel. Això va continuar després que es va implementar la solució perquè vam poder eliminar SNAT i/o DNAT només per al trànsit DNS. Es van preservar les condicions de la cursa per a altres tipus de trànsit. Afortunadament, la majoria dels nostres paquets són TCP, i si es produeix un problema, simplement es retransmeten. Encara estem intentant trobar una solució adequada per a tot tipus de trànsit.

Utilitzant Envoy per a un millor equilibri de càrrega

A mesura que migràvem els serveis de backend a Kubernetes, vam començar a patir una càrrega desequilibrada entre pods. Hem trobat que HTTP Keepalive va provocar que les connexions ELB es penguessin als primers pods preparats de cada desplegament. Així, la major part del trànsit passava per un petit percentatge de pods disponibles. La primera solució que vam provar va ser establir MaxSurge al 100% en nous desplegaments per als pitjors escenaris. L'efecte va resultar ser insignificant i poc prometedor pel que fa a desplegaments més grans.

Una altra solució que vam utilitzar va ser augmentar artificialment les sol·licituds de recursos per a serveis crítics. En aquest cas, les beines col·locades a prop tindrien més marge de maniobra en comparació amb altres beines pesades. A la llarga tampoc funcionaria perquè seria un malbaratament de recursos. A més, les nostres aplicacions Node eren d'un sol fil i, en conseqüència, només podien utilitzar un nucli. L'única solució real era utilitzar un millor equilibri de càrrega.

Fa temps que volíem apreciar-ho plenament Enviat. La situació actual ens va permetre desplegar-lo de manera molt limitada i obtenir resultats immediats. Envoy és un servidor intermediari de capa 7 de codi obert d'alt rendiment dissenyat per a grans aplicacions SOA. Pot implementar tècniques avançades d'equilibri de càrrega, com ara reintents automàtics, interruptors automàtics i limitació de velocitat global. (Nota. transl.: Podeu llegir més sobre això a aquest article sobre Istio, que es basa en Envoy.)

Vam tenir la configuració següent: tenir un sidecar Envoy per a cada pod i una única ruta, i connectar el clúster al contenidor localment a través del port. Per minimitzar la possible cascada i mantenir un petit radi d'impacte, hem utilitzat una flota de pods de proxy frontal Envoy, un per zona de disponibilitat (AZ) per a cada servei. Es van basar en un motor de descoberta de serveis senzill escrit per un dels nostres enginyers que simplement va tornar una llista de pods a cada AZ per a un servei determinat.

Aleshores, els enviats frontals del servei van utilitzar aquest mecanisme de descoberta de serveis amb un clúster i una ruta amunt. Hem establert temps d'espera adequats, hem augmentat tots els paràmetres de l'interruptor i hem afegit una configuració mínima de reintent per ajudar amb errors únics i garantir un desplegament fluid. Vam col·locar un TCP ELB davant de cadascun d'aquests enviats frontals de servei. Fins i tot si el keepalive de la nostra capa de servidor intermediari principal estava enganxat en alguns pods d'Envoy, encara podien gestionar la càrrega molt millor i estaven configurats per equilibrar-se mitjançant minimum_request al backend.

Per al desplegament, hem utilitzat el ganxo preStop tant als pods d'aplicacions com als sidecars. El ganxo va provocar un error en comprovar l'estat del punt final de l'administració situat al contenidor del sidecar i es va anar a dormir una estona per permetre que les connexions actives s'acabessin.

Una de les raons per les quals vam poder moure'ns tan ràpidament és a causa de les mètriques detallades que vam poder integrar fàcilment en una instal·lació típica de Prometheus. Això ens va permetre veure exactament què passava mentre ajustem els paràmetres de configuració i redistribuïm el trànsit.

Els resultats van ser immediats i evidents. Vam començar amb els serveis més desequilibrats, i actualment opera davant dels 12 serveis més importants del clúster. Aquest any estem planejant una transició a una malla de servei complet amb descobriment de serveis més avançats, interrupció de circuits, detecció de valors atípics, limitació de velocitat i seguiment.

La transició de Tinder a Kubernetes
Figura 3-1. Convergència de la CPU d'un servei durant la transició a Envoy

La transició de Tinder a Kubernetes

La transició de Tinder a Kubernetes

Resultat final

A través d'aquesta experiència i investigacions addicionals, hem creat un equip d'infraestructura sòlid amb fortes habilitats per dissenyar, desplegar i operar grans clústers de Kubernetes. Tots els enginyers de Tinder tenen ara el coneixement i l'experiència per empaquetar contenidors i desplegar aplicacions a Kubernetes.

Quan va sorgir la necessitat de capacitat addicional a la infraestructura antiga, vam haver d'esperar uns quants minuts perquè s'iniciessin noves instàncies EC2. Ara els contenidors comencen a funcionar i comencen a processar el trànsit en qüestió de segons en lloc de minuts. La programació de diversos contenidors en una sola instància EC2 també proporciona una concentració horitzontal millorada. Com a resultat, preveiem una reducció significativa dels costos d'EC2019 el 2 en comparació amb l'any passat.

La migració va durar gairebé dos anys, però la vam completar el març del 2019. Actualment, la plataforma Tinder funciona exclusivament en un clúster Kubernetes format per 200 serveis, 1000 nodes, 15 pods i 000 contenidors en funcionament. La infraestructura ja no és l'únic domini dels equips d'operacions. Tots els nostres enginyers comparteixen aquesta responsabilitat i controlen el procés de creació i implementació de les seves aplicacions només amb codi.

PS del traductor

Llegiu també una sèrie d'articles al nostre blog:

Font: www.habr.com

Afegeix comentari