Tinderov prelazak na Kubernetes

Bilješka. transl.: Zaposlenici svjetski poznatog Tinder servisa nedavno su podijelili neke tehničke detalje migracije svoje infrastrukture na Kubernetes. Proces je trajao skoro dvije godine i rezultirao je lansiranjem vrlo velike platforme na K8s, koja se sastoji od 200 servisa smještenih na 48 hiljada kontejnera. Na koje su zanimljive poteškoće naišli Tinder inženjeri i do kojih su rezultata došli? Pročitajte ovaj prijevod.

Tinderov prelazak na Kubernetes

Zašto?

Prije skoro dvije godine, Tinder je odlučio da premjesti svoju platformu na Kubernetes. Kubernetes bi omogućio Tinder timu da se kontejnerizira i pređe na proizvodnju uz minimalan napor kroz nepromjenjivu implementaciju (nepromjenjivo postavljanje). U ovom slučaju, sastavljanje aplikacija, njihova implementacija i sama infrastruktura bili bi jedinstveno definirani kodom.

Također smo tražili rješenje za problem skalabilnosti i stabilnosti. Kada je skaliranje postalo kritično, često smo morali čekati nekoliko minuta da se pokrenu nove EC2 instance. Ideja o pokretanju kontejnera i pokretanju prometa u sekundama umjesto u minuti postala nam je vrlo privlačna.

Ispostavilo se da je proces težak. Tokom naše migracije početkom 2019. godine, Kubernetes klaster je dostigao kritičnu masu i počeli smo da se susrećemo sa raznim problemima zbog obima saobraćaja, veličine klastera i DNS-a. Usput smo riješili mnogo zanimljivih problema vezanih za migraciju 200 servisa i održavanje Kubernetes klastera koji se sastoji od 1000 čvorova, 15000 48000 podova i XNUMX XNUMX aktivnih kontejnera.

Kako?

Od januara 2018. godine prošli smo kroz različite faze migracije. Počeli smo tako što smo spremili sve naše usluge u kontejnere i postavili ih u Kubernetes testna cloud okruženja. Počevši od oktobra, počeli smo metodično migrirati sve postojeće usluge na Kubernetes. Do marta naredne godine završili smo migraciju i sada Tinder platforma radi isključivo na Kubernetesu.

Izrada slika za Kubernetes

Imamo preko 30 spremišta izvornog koda za mikroservise koji rade na Kubernetes klasteru. Kôd u ovim repozitorijumima je napisan na različitim jezicima (na primjer, Node.js, Java, Scala, Go) sa više okruženja za izvršavanje za isti jezik.

Sistem izgradnje je dizajniran da obezbedi potpuno prilagodljiv „kontekst izgradnje“ za svaku mikroservis. Obično se sastoji od Dockerfile-a i liste komandi ljuske. Njihov sadržaj je potpuno prilagodljiv, a istovremeno su svi ovi konteksti izgradnje napisani prema standardiziranom formatu. Standardiziranje konteksta izgradnje omogućava jednom sistemu izgradnje da rukuje svim mikroservisima.

Tinderov prelazak na Kubernetes
Slika 1-1. Standardizirani proces izgradnje putem Builder kontejnera

Za postizanje maksimalne konzistentnosti između vremena izvođenja (okruženja za vrijeme rada) isti proces izgradnje se koristi tokom razvoja i testiranja. Suočili smo se s vrlo zanimljivim izazovom: morali smo razviti način da osiguramo konzistentnost okruženja za izgradnju na cijeloj platformi. Da bi se to postiglo, svi procesi montaže se izvode unutar posebnog kontejnera. graditelj.

Njegova implementacija kontejnera zahtijevala je napredne Docker tehnike. Builder nasljeđuje lokalni korisnički ID i tajne (kao što su SSH ključ, AWS vjerodajnice, itd.) potrebne za pristup privatnim Tinder spremištima. Montira lokalne direktorije koji sadrže izvore za prirodno pohranjivanje artefakata izgradnje. Ovaj pristup poboljšava performanse jer eliminira potrebu za kopiranjem artefakata građenja između Builder kontejnera i hosta. Pohranjeni artefakti izrade mogu se ponovo koristiti bez dodatne konfiguracije.

Za neke usluge, morali smo da kreiramo drugi kontejner za mapiranje okruženja kompilacije u okruženje za izvršavanje (na primer, biblioteka Node.js bcrypt generiše binarne artefakte specifične za platformu tokom instalacije). Tokom procesa kompilacije, zahtjevi mogu varirati između usluga, a konačni Dockerfile se kompajlira u hodu.

Arhitektura Kubernetes klastera i migracija

Upravljanje veličinom klastera

Odlučili smo da koristimo kube-aws za automatsku implementaciju klastera na Amazon EC2 instancama. Na samom početku, sve je funkcionisalo u jednom zajedničkom skupu čvorova. Brzo smo shvatili potrebu za odvajanjem radnih opterećenja prema veličini i tipu instance kako bismo efikasnije koristili resurse. Logika je bila da se ispostavilo da je pokretanje nekoliko učitanih višenitnih podova predvidljivije u smislu performansi od njihovog suživota s velikim brojem jednonitnih podova.

Na kraju smo se odlučili na:

  • m5.4xlarge — za praćenje (Prometej);
  • c5.4xlarge - za Node.js radno opterećenje (single-threaded workload);
  • c5.2xlarge - za Java and Go (višenitno radno opterećenje);
  • c5.4xlarge — za kontrolni panel (3 čvora).

Migracija

Jedan od pripremnih koraka za migraciju sa stare infrastrukture na Kubernetes bio je preusmjeravanje postojeće direktne komunikacije između usluga na nove balansere opterećenja (Elastic Load Balancers (ELB). Kreirani su na određenoj podmreži virtuelnog privatnog oblaka (VPC). Ova podmreža je bila povezana na Kubernetes VPC. To nam je omogućilo da postepeno migriramo module, bez razmatranja specifičnog redoslijeda ovisnosti usluge.

Ove krajnje tačke su kreirane korišćenjem ponderisanih skupova DNS zapisa koji su imali CNAME-ove koji ukazuju na svaki novi ELB. Da bismo se prebacili, dodali smo novi unos koji ukazuje na novi ELB Kubernetes usluge sa težinom od 0. Zatim smo postavili Time To Live (TTL) unosa postavljenog na 0. Nakon toga, stari i novi ponderi su bili polako se prilagođavao, i na kraju je 100% opterećenja poslano na novi server. Nakon što je prebacivanje završeno, TTL vrijednost se vratila na adekvatniji nivo.

Java moduli koje smo imali mogli su se nositi sa niskim TTL DNS-om, ali Node aplikacije nisu mogle. Jedan od inženjera je prepisao dio koda spremišta veza i umotao ga u upravitelja koji je ažurirao skupove svakih 60 sekundi. Odabrani pristup je funkcionirao vrlo dobro i bez primjetne degradacije performansi.

Predavanja

Granice mrežnog tkanja

U rano jutro 8. januara 2019. godine platforma Tinder se neočekivano srušila. Kao odgovor na nepovezano povećanje latencije platforme ranije tog jutra, povećao se broj podova i čvorova u klasteru. To je dovelo do iscrpljivanja ARP keša na svim našim čvorovima.

Postoje tri Linux opcije vezane za ARP keš memoriju:

Tinderov prelazak na Kubernetes
(izvor)

gc_thresh3 - ovo je tvrda granica. Pojava unosa „preklapanja susedne tabele“ u dnevniku značila je da čak i nakon sinhronog sakupljanja smeća (GC), nije bilo dovoljno prostora u ARP kešu za skladištenje susednog unosa. U ovom slučaju, kernel je jednostavno potpuno odbacio paket.

Koristimo Flannel kao mrežno tkivo u Kubernetesu. Paketi se prenose preko VXLAN-a. VXLAN je L2 tunel podignut na vrhu L3 mreže. Tehnologija koristi MAC-in-UDP (MAC Address-in-User Datagram Protocol) enkapsulaciju i omogućava proširenje segmenata mreže Layer 2. Protokol za transport na mreži fizičkog podatkovnog centra je IP plus UDP.

Tinderov prelazak na Kubernetes
Slika 2–1. flanelski dijagram (izvor)

Tinderov prelazak na Kubernetes
Slika 2–2. VXLAN paket (izvor)

Svaki Kubernetes radni čvor dodeljuje virtuelni adresni prostor sa /24 maskom iz većeg /9 bloka. Za svaki čvor ovo je znači jedan unos u tabeli rutiranja, jedan unos u ARP tabeli (na sučelju flanel.1) i jedan unos u tabeli prebacivanja (FDB). Oni se dodaju prvi put kada se pokrene radni čvor ili svaki put kada se otkrije novi čvor.

Dodatno, komunikacija čvor-pod (ili pod-pod) na kraju ide kroz interfejs eth0 (kao što je prikazano na dijagramu flanela iznad). Ovo rezultira dodatnim unosom u ARP tablici za svaki odgovarajući izvorni i odredišni host.

U našem okruženju ova vrsta komunikacije je vrlo česta. Za objekte usluge u Kubernetesu, kreira se ELB i Kubernetes registruje svaki čvor sa ELB-om. ELB ne zna ništa o podovima i odabrani čvor možda nije konačno odredište paketa. Poenta je da kada čvor primi paket od ELB-a, on to smatra uzimajući u obzir pravila iptables za određenu uslugu i nasumično bira pod na drugom čvoru.

U trenutku kvara, u klasteru je bilo 605 čvorova. Iz gore navedenih razloga, ovo je bilo dovoljno da se prevaziđe značaj gc_thresh3, što je podrazumevano. Kada se to dogodi, ne samo da paketi počinju da se ispuštaju, već i čitav Flannel virtuelni adresni prostor sa /24 maskom nestaje iz ARP tabele. Komunikacija node-pod i DNS upiti su prekinuti (DNS se nalazi u klasteru; pročitajte kasnije u ovom članku za detalje).

Da biste riješili ovaj problem, potrebno je povećati vrijednosti gc_thresh1, gc_thresh2 и gc_thresh3 i ponovo pokrenite Flannel da ponovo registrujete nedostajuće mreže.

Neočekivano DNS skaliranje

Tokom procesa migracije, aktivno smo koristili DNS za upravljanje prometom i postupni prijenos usluga sa stare infrastrukture na Kubernetes. Postavili smo relativno niske TTL vrijednosti za povezane skupove zapisa u Route53. Kada je stara infrastruktura radila na EC2 instancama, naša konfiguracija rezolvera je ukazivala na Amazon DNS. Uzeli smo ovo zdravo za gotovo i utjecaj niskog TTL-a na naše usluge i Amazonove usluge (kao što je DynamoDB) ostao je uglavnom neprimijećen.

Kako smo migrirali usluge na Kubernetes, otkrili smo da DNS obrađuje 250 hiljada zahtjeva u sekundi. Kao rezultat toga, aplikacije su počele da doživljavaju stalna i ozbiljna vremenska ograničenja za DNS upite. Ovo se dogodilo uprkos nevjerovatnim naporima da se optimizira i prebaci DNS provajder na CoreDNS (koji je pri vrhunskom opterećenju dostigao 1000 podova koji rade na 120 jezgara).

Istražujući druge moguće uzroke i rješenja, otkrili smo članak, koji opisuje uslove trke koji utiču na okvir za filtriranje paketa net filter u Linuxu. Tajm-auti koje smo uočili, zajedno sa sve većim brojačem insert_failed u interfejsu Flannel bili su u skladu sa nalazima članka.

Problem se javlja u fazi prevođenja izvorne i odredišne ​​mrežne adrese (SNAT i DNAT) i naknadnog unosa u tabelu conntrack. Jedno od rješenja o kojima se interno raspravljalo i koje je zajednica predložila bilo je premještanje DNS-a na sam radni čvor. U ovom slučaju:

  • SNAT nije potreban jer promet ostaje unutar čvora. Ne mora se usmjeravati kroz sučelje eth0.
  • DNAT nije potreban jer je odredišna IP adresa lokalna za čvor, a ne nasumično odabran pod prema pravilima iptables.

Odlučili smo da se držimo ovog pristupa. CoreDNS je raspoređen kao DaemonSet u Kubernetesu i implementirali smo DNS server lokalnog čvora u resolve.conf svaki pod postavljanjem zastavice --cluster-dns naredbe kubelet . Ovo rješenje se pokazalo efikasnim za DNS timeouts.

Međutim, i dalje smo vidjeli gubitak paketa i povećanje brojača insert_failed u Flannel interfejsu. Ovo se nastavilo nakon što je zaobilazno rješenje implementirano jer smo uspjeli eliminirati SNAT i/ili DNAT samo za DNS promet. Uslovi trke su očuvani i za ostale vidove saobraćaja. Srećom, većina naših paketa je TCP, i ako se pojavi problem oni se jednostavno ponovo prenose. Još uvijek pokušavamo pronaći odgovarajuće rješenje za sve vrste saobraćaja.

Korištenje Envoy-a za bolje balansiranje opterećenja

Kako smo migrirali pozadinske usluge na Kubernetes, počeli smo da patimo od neuravnoteženog opterećenja između podova. Otkrili smo da je HTTP Keepalive uzrokovao da ELB veze visi na prvim spremnim podovima svake implementacije. Dakle, najveći dio prometa prošao je kroz mali postotak dostupnih mahuna. Prvo rješenje koje smo testirali bilo je postavljanje MaxSurge-a na 100% na novim implementacijama za najgore scenarije. Ispostavilo se da je učinak beznačajan i neobećavajući u smislu većih primjena.

Drugo rješenje koje smo koristili bilo je umjetno povećanje zahtjeva za resursima za kritične usluge. U ovom slučaju, mahune postavljene u blizini imale bi više prostora za manevar u odnosu na druge teške mahune. Ni to ne bi funkcionisalo na duge staze jer bi bilo gubljenje resursa. Osim toga, naše Node aplikacije su bile jednonitne i, prema tome, mogle su koristiti samo jednu jezgru. Jedino pravo rješenje bilo je korištenje boljeg balansiranja opterećenja.

Dugo smo željeli da u potpunosti cijenimo izaslanik. Trenutna situacija nam je omogućila da ga implementiramo na vrlo ograničen način i dobijemo trenutne rezultate. Envoy je high-performance, open-source, layer-XNUMX proxy dizajniran za velike SOA aplikacije. Može implementirati napredne tehnike balansiranja opterećenja, uključujući automatske ponovne pokušaje, prekidače i globalno ograničavanje brzine. (Bilješka. transl.: Više o ovome možete pročitati u ovaj članak o Istiu, koji je zasnovan na Envoyu.)

Došli smo do sljedeće konfiguracije: imati Envoy sidecar za svaki pod i jednu rutu, i povezati klaster s kontejnerom lokalno preko porta. Da bismo smanjili potencijalno kaskadno slanje i održali mali radijus pogotka, koristili smo flotu Envoy front-proxy modula, jedan po zoni dostupnosti (AZ) za svaku uslugu. Oslonili su se na jednostavan mehanizam za otkrivanje usluga koji je napisao jedan od naših inženjera koji je jednostavno vratio listu podova u svakom AZ za datu uslugu.

Prednji izaslanici usluge su zatim koristili ovaj mehanizam otkrivanja usluge s jednim uzvodnim klasterom i rutom. Postavili smo odgovarajuće vremensko ograničenje, povećali sve postavke prekidača i dodali minimalnu konfiguraciju ponovnih pokušaja kako bismo pomogli kod pojedinačnih kvarova i osigurali nesmetano korištenje. Postavili smo TCP ELB ispred svakog od ovih prednjih izaslanika usluge. Čak i ako je održavanje aktivacije sa našeg glavnog proxy sloja zaglavljeno na nekim Envoy podovima, oni su i dalje mogli mnogo bolje da podnose opterećenje i bili su konfigurisani da balansiraju kroz minimum_request u pozadini.

Za implementaciju smo koristili preStop kuku i na modulima aplikacija i na bočnim kolicima. Hook je pokrenuo grešku u provjeravanju statusa krajnje točke administratora koji se nalazi na bočnom kontejneru i otišao u stanje mirovanja na neko vrijeme kako bi omogućio prekid aktivnih veza.

Jedan od razloga zašto smo bili u mogućnosti da se krećemo tako brzo je zbog detaljnih metričkih podataka koje smo mogli lako integrirati u tipičnu Prometheusovu instalaciju. To nam je omogućilo da vidimo šta se tačno dešava dok smo prilagođavali konfiguracione parametre i preraspodelili saobraćaj.

Rezultati su bili trenutni i očigledni. Počeli smo sa najneuravnoteženijim servisima, a trenutno posluje ispred 12 najvažnijih servisa u klasteru. Ove godine planiramo prelazak na mrežu pune usluge sa naprednijim otkrivanjem usluga, prekidom strujnog kola, detekcijom odstupanja, ograničavanjem brzine i praćenjem.

Tinderov prelazak na Kubernetes
Slika 3–1. CPU konvergencija jedne usluge tokom tranzicije na Envoy

Tinderov prelazak na Kubernetes

Tinderov prelazak na Kubernetes

Konačni rezultat

Kroz ovo iskustvo i dodatna istraživanja, izgradili smo snažan infrastrukturni tim sa jakim vještinama u dizajniranju, implementaciji i upravljanju velikim Kubernetes klasterima. Svi Tinder inženjeri sada imaju znanje i iskustvo za pakiranje kontejnera i implementaciju aplikacija u Kubernetes.

Kada se pojavila potreba za dodatnim kapacitetom na staroj infrastrukturi, morali smo čekati nekoliko minuta da se pokrenu nove EC2 instance. Sada kontejneri počinju da rade i obrađuju promet u roku od nekoliko sekundi umjesto minuta. Zakazivanje više kontejnera na jednoj EC2 instanci također osigurava poboljšanu horizontalnu koncentraciju. Kao rezultat toga, predviđamo značajno smanjenje troškova EC2019 u 2. u odnosu na prošlu godinu.

Migracija je trajala skoro dvije godine, ali smo je završili u martu 2019. Trenutno, Tinder platforma radi isključivo na Kubernetes klasteru koji se sastoji od 200 usluga, 1000 čvorova, 15 podova i 000 aktivnih kontejnera. Infrastruktura više nije isključiva domena operativnih timova. Svi naši inženjeri dijele ovu odgovornost i kontroliraju proces izgradnje i implementacije svojih aplikacija samo koristeći kod.

PS od prevodioca

Pročitajte i niz članaka na našem blogu:

izvor: www.habr.com

Dodajte komentar