Tinder se přestěhoval do Kubernetes

Autor: Chris O'Brien, inženýrský manažer | Chris Thomas, inženýrský manažer | Jinyong Lee, vedoucí softwarový inženýr | Editoval: Cooper Jackson, softwarový inženýr

Proč

Téměř před dvěma lety se Tinder rozhodl přesunout svou platformu na Kubernetes. Kubernetes nám poskytl příležitost řídit Tinder Engineering směrem k kontejnerizaci a nízkoteplotnímu provozu díky neměnnému nasazení. Budování, rozmístění a infrastruktura aplikací by byly definovány jako kód.

Také jsme se snažili řešit výzvy z rozsahu a stability. Když se škálování stalo kritickým, často jsme trpěli několika minutami čekání na nové případy EC2, které se objeví online. Myšlenka, že se kontejnery plánují a obsluhují provoz během několika sekund, na rozdíl od minut, byla pro nás lákavá.

Nebylo to snadné. Během naší migrace na začátku roku 2019 jsme dosáhli kritického množství v našem klastru Kubernetes a začali jsme čelit různým výzvám kvůli objemu provozu, velikosti clusteru a DNS. Vyřešili jsme zajímavé výzvy k migraci 200 služeb a spuštění clusteru Kubernetes v měřítku v celkovém počtu 1 000 uzlů, 15 000 lusků a 48 000 běžících kontejnerů.

Jak

Od ledna 2018 jsme prošli různými fázemi migračního úsilí. Začali jsme kontejnerováním všech našich služeb a jejich nasazením do řady pracovních prostředí hostovaných Kubernetes. Od října jsme začali metodicky přesouvat všechny naše starší služby do Kubernetes. Do března následujícího roku jsme dokončili naši migraci a platforma Tinder Platform nyní běží výhradně na Kubernetes.

Vytváření obrázků pro Kubernetes

Existuje více než 30 úložišť zdrojového kódu pro mikroprocesy, které běží v klastru Kubernetes. Kód v těchto úložištích je psán v různých jazycích (např. Node.js, Java, Scala, Go) s více runtime prostředími pro stejný jazyk.

Sestavovací systém je navržen tak, aby fungoval v plně přizpůsobitelném „sestavovacím kontextu“ pro každou mikroservis, který obvykle sestává z Dockerfile a řady příkazů shellu. I když je jejich obsah plně přizpůsobitelný, všechny tyto kontexty sestavení jsou psány podle standardizovaného formátu. Standardizace sestavovacích kontextů umožňuje, aby jediný systém sestavování zpracovával všechny mikroservisy.

Obrázek 1-1 Standardizovaný proces sestavování prostřednictvím kontejneru Builder

Aby se dosáhlo maximální konzistence mezi runtime prostředími, používá se během fáze vývoje a testování stejný proces vytváření. To vyžadovalo jedinečnou výzvu, když jsme potřebovali vymyslet způsob, jak zaručit konzistentní prostředí pro sestavení napříč platformou. Výsledkem je, že všechny procesy sestavení jsou prováděny uvnitř speciálního kontejneru „Builder“.

Implementace kontejneru Builder vyžadovala řadu pokročilých technik Docker. Tento kontejner Builder zdědí místní ID uživatele a tajemství (např. Klíč SSH, pověření AWS atd.), Jak je požadováno pro přístup k soukromým úložištím Tinder. Připojuje místní adresáře obsahující zdrojový kód, aby bylo možné přirozeně ukládat artefakty sestavení. Tento přístup zvyšuje výkon, protože eliminuje kopírování vytvořených artefaktů mezi kontejnerem Builder a hostitelským počítačem. Uložené artefakty sestavení se příště znovu použijí bez další konfigurace.

U některých služeb jsme potřebovali vytvořit další kontejner v rámci Tvůrce, aby odpovídal prostředí kompilace s prostředím run-time (např. Instalace knihovny bcrypt Node.js generuje binární artefakty specifické pro platformu). Požadavky na dobu kompilace se mohou mezi službami lišit a finální Dockerfile se skládá za běhu.

Kubernetes Cluster Architecture and Migration

Cluster Sizing

Rozhodli jsme se použít kube-aws pro automatizované zajišťování clusterů v instancích Amazon EC2. Brzy jsme provozovali vše v jednom obecném fondu uzlů. Rychle jsme identifikovali potřebu rozdělit pracovní zatížení do různých velikostí a typů instancí, abychom lépe využívali zdroje. Důvodem bylo to, že provozování méně silně seříznutých lusků dohromady nám přineslo předvídatelnější výsledky, než nechat je koexistovat s větším počtem jednozávitových lusků.

Usadili jsme se:

  • m5,4xlarge pro monitorování (Prometheus)
  • c5.4xlarge pro pracovní vytížení Node.js (jednozávitové pracovní vytížení)
  • c5.2xlarge pro Java a Go (vícevláknové pracovní vytížení)
  • c5.4xlarge pro řídicí rovinu (3 uzly)

Migrace

Jedním z přípravných kroků pro migraci z naší starší infrastruktury na Kubernetes bylo změnit stávající komunikaci mezi službami a poukazovat na nové pružné vyrovnávače zatížení (ELB), které byly vytvořeny ve specifické podsíti Virtual Private Cloud (VPC). Tato podsíť byla nahlédnuta do VPC Kubernetes. To nám umožnilo podrobně migrovat moduly bez ohledu na konkrétní pořadí závislostí na službách.

Tyto koncové body byly vytvořeny pomocí vážených sad záznamů DNS, které měly CNAME směřující na každou novou ELB. K překročení jsme přidali nový záznam, ukazující na novou službu Kubernetes ELB, s váhou 0. Potom jsme nastavili Time To Live (TTL) v záznamu nastaveném na 0. Staré a nové závaží se pak pomalu upravily na nakonec skončí se 100% na novém serveru. Po dokončení přestávky byl TTL nastaven na něco rozumnějšího.

Naše Java moduly ocenily nízké DNS TTL, ale naše Node aplikace ne. Jeden z našich inženýrů přepsal část kódu fondu připojení tak, aby byl zabalen do správce, který obnoví fondy každých 60 s. To pro nás fungovalo velmi dobře bez výrazného zásahu do výkonu.

Poučení

Limity síťových tkanin

V časných ranních hodinách 8. ledna 2019 utrpěla Tinderova platforma trvalý výpadek. V reakci na nesouvisející zvýšení latence platformy dříve ráno byly počty pod a uzlů na clusteru upraveny. Výsledkem bylo vyčerpání mezipaměti ARP na všech našich uzlech.

Pro mezipaměť ARP existují tři hodnoty Linuxu:

Kredit

gc_thresh3 je tvrdá čepice. Pokud získáváte položky protokolu „přetečení tabulky sousedů“, znamená to, že i po synchronizované sbírce odpadků (GC) mezipaměti ARP nebylo dostatek místa pro uložení sousedního záznamu. V tomto případě jádro paket pouze zcela vypustí.

Flannel používáme jako síťovou strukturu v Kubernetes. Pakety jsou předávány přes VXLAN. VXLAN je schéma překrytí vrstvy 2 v síti vrstvy 3. Používá zapouzdření protokolu MAC Address-in-User Datagram Protocol (MAC-in-UDP) jako prostředek k rozšíření síťových segmentů vrstvy 2. Transportním protokolem po síti fyzických datových center je IP plus UDP.

Obrázek 2–1 Flanelův diagram (kredit)

Obrázek 2–2 VXLAN paket (kredit)

Každý pracovní uzel Kubernetes přiděluje svůj vlastní / 24 virtuálního adresového prostoru z většího / 9 bloku. Výsledkem je pro každý uzel 1 záznam tabulky tras, 1 záznam tabulky ARP (na rozhraní flannel.1) a 1 záznam databáze pro předávání (FDB). Přidají se při prvním spuštění pracovního uzlu nebo při objevení každého nového uzlu.

Komunikace mezi uzly a pody (nebo pody-pod) nakonec nakonec protéká přes rozhraní eth0 (znázorněno na výše uvedeném Flanellově diagramu). Výsledkem bude další položka v tabulce ARP pro každý odpovídající zdroj uzlu a cíl uzlu.

V našem prostředí je tento typ komunikace velmi běžný. Pro naše servisní objekty Kubernetes se vytvoří ELB a Kubernetes registruje každý uzel u ELB. ELB si není vědoma a vybraný uzel nemusí být konečným cílem paketu. Je tomu tak proto, že když uzel přijme paket z ELB, vyhodnotí svá pravidla iptables pro službu a náhodně vybere modul na jiném uzlu.

V době výpadku bylo v klastru celkem 605 uzlů. Z výše uvedených důvodů stačilo zatměnit výchozí hodnotu gc_thresh3. Jakmile k tomu dojde, nejen se vyhodí pakety, ale v tabulce ARP chybí celé Flannel / 24s virtuálního adresového prostoru. Selhání komunikace mezi uzly a podky a vyhledávání DNS. (DNS je hostována v klastru, jak bude podrobněji vysvětleno dále v tomto článku.)

K vyřešení jsou hodnoty gc_thresh1, gc_thresh2 a gc_thresh3 zvýšeny a Flannel musí být restartován, aby se znovu zaregistrovaly chybějící sítě.

Neočekávaně spuštěné DNS na stupnici

Abychom vyhověli naší migraci, využívali jsme služby DNS silně k tomu, abychom našim službám usnadnili tvarování provozu a postupné přepínání z původního na Kubernetes. Na přidružené sady záznamů Route53 jsme nastavili relativně nízké hodnoty TTL. Když jsme provozovali naši starší infrastrukturu v případech EC2, naše konfigurace překladače ukázala na Amazonův DNS. Berli jsme to jako samozřejmost a náklady na relativně nízké TTL za naše služby a služby Amazonu (např. DynamoDB) zůstaly bez povšimnutí.

Když jsme na palubě stále více služeb pro Kubernetes, zjistili jsme, že provozujeme službu DNS, která odpovídala 250 000 požadavků za sekundu. V našich aplikacích jsme narazili na přerušované a působivé časové limity vyhledávání DNS. K tomu došlo navzdory vyčerpávajícímu úsilí o ladění a poskytovatel DNS přešel na nasazení CoreDNS, které najednou dosáhlo vrcholu 1 000 tobolek, které spotřebovaly 120 jader.

Při zkoumání dalších možných příčin a řešení jsme našli článek popisující stav rasy ovlivňující netfilter framework filtrování paketů v systému Linux. Časové limity DNS, které jsme viděli, spolu s inkrementujícím čítačem insert_failed na rozhraní Flannel, byly zarovnány s nálezy článku.

K problému dochází během překladu zdrojové a cílové síťové adresy (SNAT a DNAT) a následného vložení do tabulky conntrack. Jedním řešením interně diskutovaným a navrženým komunitou bylo přesunout DNS do samotného pracovního uzlu. V tomto případě:

  • SNAT není nutný, protože provoz zůstává lokálně v uzlu. Není nutné jej přenášet přes rozhraní eth0.
  • DNAT není nutný, protože cílová IP je lokální pro uzel a ne náhodně vybraná pravidla pro iptables.

S tímto přístupem jsme se rozhodli postupovat kupředu. CoreDNS byl nasazen jako DaemonSet v Kubernetes a my jsme injikovali místní DNS server uzlu do resolv.conf každého podu konfigurací příznaku kubelet - cluster-dns. Řešení bylo účinné pro časové limity DNS.

Stále však vidíme vyřazené pakety a přírůstek čítače insert_failed rozhraní Flannel. To bude přetrvávat i po výše uvedeném řešení, protože jsme se vyhnuli pouze SNAT a / nebo DNAT pro provoz DNS. Závodní podmínka bude nadále platit pro ostatní typy provozu. Naštěstí většina našich paketů je TCP a když nastane podmínka, pakety budou úspěšně znovu vyslány. Dlouhodobá oprava pro všechny typy provozu je něco, o čem stále diskutujeme.

Použití vyslance k dosažení lepšího vyvážení zatížení

Když jsme migrovali naše backendové služby do Kubernetes, začali jsme trpět nevyváženým zatížením přes lusky. Zjistili jsme, že díky HTTP Keepalive se připojení ELB přilepila k prvním připraveným modulům každého postupného nasazení, takže většina provozu tekla malým procentem dostupných modulů. Jedním z prvních zmírnění, které jsme se pokusili, bylo použití 100% MaxSurge na nová nasazení pro nejhorší pachatele. To bylo okrajově efektivní a dlouhodobě neudržitelné u některých větších rozmístění.

Další zmírnění, které jsme použili, bylo umělé nafouknutí požadavků na zdroje u kritických služeb tak, aby obarvené lusky měly větší prostor vedle jiných těžkých lusků. Také to nebude dlouhodobě udržitelné kvůli plýtvání zdroji a naše aplikace Node byly jednozávitové, a tedy účinně omezené na 1 jádro. Jediným jasným řešením bylo využít lepší vyvážení zátěže.

Interně jsme se snažili hodnotit vyslance. To nám dalo šanci jej nasadit velmi omezeným způsobem a získat okamžité výhody. Envoy je open source, vysoce výkonný proxy server vrstvy 7 určený pro velké architektury orientované na služby. Je schopen implementovat pokročilé techniky vyrovnávání zátěže, včetně automatických opakování, přerušení obvodu a omezení globální rychlosti.

Konfigurace, kterou jsme přišli, měla mít vedlejší vůz Envoy vedle každého podu, který měl jednu trasu a klastr, aby zasáhl místní kontejnerový port. Abychom minimalizovali potenciální kaskádování a udrželi malý poloměr výbuchu, použili jsme pro každou službu flotilu modulů Envoy front-proxy, jedno nasazení v každé zóně dostupnosti (AZ). Tito zasáhli malý mechanismus objevování služeb, který sestavil jeden z našich inženýrů a který jednoduše vrátil seznam lusků v každé AZ pro danou službu.

Front-Envoys služeb pak využil tento mechanismus pro vyhledávání služeb s jedním klastrem a cestou proti proudu. Nakonfigurovali jsme přiměřené časové limity, posílili jsme všechna nastavení jističe a poté jsme nastavili minimální opakování konfigurace, abychom pomohli s přechodnými poruchami a hladkým nasazením. Každou z těchto předních služeb Envoy jsme postavili před TCP ELB. I když se udržovací modul z naší hlavní přední proxy vrstvy připnul na určitých podvětech Envoy, byli mnohem lépe schopni zvládnout zátěž a byli nakonfigurováni tak, aby vyvažovali prostřednictvím minim_request na backend.

Pro nasazení jsme použili háček preStop na aplikaci i na postranním vozíku. Tento háček zvaný kontrola zdravotního stavu postranního vozíku selhal v správcovském koncovém bodu, spolu s malým spánkem, aby poskytl nějaký čas, aby se umožnilo dokončit a vypustit připojení k letu.

Jedním z důvodů, proč jsme se mohli pohybovat tak rychle, bylo díky bohatým metrikám, které jsme dokázali snadno integrovat do našeho normálního nastavení Prometheus. To nám umožnilo přesně vidět, co se děje, když jsme opakovali nastavení konfigurace a omezili přenos.

Výsledky byly okamžité a zřejmé. Začali jsme s nejvíce nevyváženými službami a v tuto chvíli je provozujeme před dvanácti nejdůležitějšími službami v našem klastru. V letošním roce plánujeme přechod na síť s úplnými službami s pokročilejším vyhledáváním služeb, přerušením obvodu, detekcí odlehlých hodnot, omezením rychlosti a trasováním.

Obrázek 3–1 Konvergence CPU jedné služby během přechodu na vyslance

Konečný výsledek

Na základě těchto poznatků a dalšího výzkumu jsme vyvinuli silný tým interní infrastruktury s velkou znalostí toho, jak navrhovat, zavádět a provozovat velké klastry Kubernetes. Celá inženýrská organizace společnosti Tinder má nyní znalosti a zkušenosti o tom, jak kontejnerizovat a nasadit své aplikace na Kubernetes.

Na naší staré infrastruktuře, když bylo potřeba další měřítko, jsme často utrpěli několik minut čekání na nové instance EC2, které se objeví online. Kontejnery nyní plánují a obsluhují provoz během několika sekund na rozdíl od minut. Plánování více kontejnerů na jednu instanci EC2 také poskytuje vylepšenou horizontální hustotu. Výsledkem je, že v roce 2019 ve srovnání s předchozím rokem očekáváme značné úspory nákladů na EC2.

Trvalo to téměř dva roky, ale naši migraci jsme dokončili v březnu 2019. Platforma Tinder běží výhradně na klastru Kubernetes, který se skládá z 200 služeb, 1 000 uzlů, 15 000 lusků a 48 000 běžících kontejnerů. Infrastruktura již není úkolem vyhrazeným pro naše operační týmy. Místo toho se inženýři v celé organizaci podílejí na této odpovědnosti a mají kontrolu nad tím, jak jsou jejich aplikace vytvářeny a rozmístěny se vším, co je kód.