Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud

Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud
Dobrý den, jsem Sergey Elantsev a vyvíjím se nástroj pro vyrovnávání zatížení sítě v Yandex.Cloud. Dříve jsem vedl vývoj balanceru L7 pro portál Yandex – kolegové vtipkují, že ať dělám, co dělám, ukazuje se, že je to balancer. Čtenářům Habr řeknu, jak zvládat zátěž v cloudové platformě, v čem vidíme ideální nástroj k dosažení tohoto cíle a jak směřujeme k budování tohoto nástroje.

Nejprve si představíme některé pojmy:

  • VIP (Virtual IP) - IP adresa balanceru
  • Server, backend, instance – virtuální stroj, na kterém běží aplikace
  • RIP (Real IP) - IP adresa serveru
  • Healthcheck - kontrola připravenosti serveru
  • Zóna dostupnosti, AZ - izolovaná infrastruktura v datovém centru
  • Region – svazek různých AZ

Load balancery řeší tři hlavní úkoly: provádějí samotné vyvažování, zlepšují odolnost služby proti chybám a zjednodušují její škálování. Odolnost proti chybám je zajištěna prostřednictvím automatického řízení provozu: balancer sleduje stav aplikace a vylučuje z vyvažování instance, které neprojdou kontrolou živosti. Škálování je zajištěno rovnoměrným rozložením zátěže mezi instancemi a také průběžnou aktualizací seznamu instancí. Pokud není vyvážení dostatečně jednotné, některé instance obdrží zátěž, která překračuje limit jejich kapacity, a služba bude méně spolehlivá.

Nástroj pro vyrovnávání zatížení je často klasifikován podle vrstvy protokolu podle modelu OSI, na kterém běží. Cloud Balancer pracuje na úrovni TCP, která odpovídá čtvrté vrstvě, L4.

Přejděme k přehledu architektury Cloud balanceru. Úroveň detailů budeme postupně zvyšovat. Komponenty balanceru dělíme do tří tříd. Třída konfigurační roviny je zodpovědná za interakci uživatele a ukládá cílový stav systému. Řídicí rovina ukládá aktuální stav systému a spravuje systémy z třídy datové roviny, které jsou přímo zodpovědné za doručování provozu od klientů do vašich instancí.

Datová rovina

Provoz končí na drahých zařízeních zvaných hraniční směrovače. Pro zvýšení odolnosti proti chybám pracuje několik takových zařízení současně v jednom datovém centru. Dále provoz jde do balancerů, které oznamují anycast IP adresy všem AZ přes BGP pro klienty. 

Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud

Provoz je přenášen přes ECMP - jedná se o směrovací strategii, podle které může existovat několik stejně dobrých cest k cíli (v našem případě bude cílem cílová IP adresa) a po kterékoli z nich lze posílat pakety. Podporujeme také práci v několika zónách dostupnosti podle následujícího schématu: v každé zóně inzerujeme adresu, provoz směřuje do nejbližší a nepřekračuje její limity. Později v příspěvku se podíváme podrobněji na to, co se děje s dopravou.

Konfigurační rovina

 
Klíčovou součástí konfigurační roviny je API, přes které se provádějí základní operace s balancery: vytváření, mazání, změna složení instancí, získávání výsledků healthchecks atd. Jednak se jedná o REST API, jednak jinak v Cloudu velmi často používáme framework gRPC, takže REST „překládáme“ na gRPC a pak používáme pouze gRPC. Jakýkoli požadavek vede k vytvoření řady asynchronních idempotentních úloh, které jsou prováděny na společném fondu pracovníků Yandex.Cloud. Úkoly jsou napsány tak, že je lze kdykoli pozastavit a poté znovu spustit. To zajišťuje škálovatelnost, opakovatelnost a protokolování operací.

Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud

V důsledku toho úloha z API odešle požadavek na řadič služby balancer, který je napsán v Go. Může přidávat a odebírat balancery, měnit složení backendů a nastavení. 

Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud

Služba ukládá svůj stav do databáze Yandex, distribuované spravované databáze, kterou budete brzy moci používat. V Yandex.Cloud, jak jsme již řekla, platí koncept krmiva pro psy: pokud naše služby využíváme my sami, pak je rádi využijí i naši klienti. Databáze Yandex je příkladem implementace takového konceptu. Všechna naše data ukládáme do YDB a nemusíme myslet na údržbu a škálování databáze: tyto problémy jsou vyřešeny za nás, používáme databázi jako službu.

Vraťme se k ovladači balanceru. Jeho úkolem je uložit informace o balanceru a odeslat úkol ke kontrole připravenosti virtuálního stroje do ovladače healthcheck.

Healthcheck ovladač

Přijímá požadavky na změnu kontrolních pravidel, ukládá je do YDB, rozděluje úkoly mezi healtcheck uzly a agreguje výsledky, které jsou následně ukládány do databáze a odesílány do řadiče loadbalanceru. Ten zase odešle požadavek na změnu složení clusteru v datové rovině do loadbalancer-node, o kterém pojednám níže.

Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud

Promluvme si více o zdravotních kontrolách. Lze je rozdělit do několika tříd. Audity mají různá kritéria úspěšnosti. Kontroly TCP musí úspěšně navázat spojení v pevně stanoveném čase. Kontroly HTTP vyžadují úspěšné připojení a odpověď se stavovým kódem 200.

Kontroly se také liší třídou akcí – jsou aktivní a pasivní. Pasivní kontroly jednoduše sledují, co se děje s provozem, aniž by podnikly nějaké speciální akce. To na L4 nefunguje příliš dobře, protože to závisí na logice protokolů vyšší úrovně: na L4 nejsou žádné informace o tom, jak dlouho operace trvala nebo zda bylo dokončení připojení dobré nebo špatné. Aktivní kontroly vyžadují, aby balancer zasílal požadavky na každou instanci serveru.

Většina vyvažovačů zátěže provádí kontroly životnosti sama. V Cloudu jsme se rozhodli tyto části systému oddělit, abychom zvýšili škálovatelnost. Tento přístup nám umožní zvýšit počet balancerů při zachování počtu požadavků na zdravotní kontrolu do služby. Kontroly se provádějí pomocí samostatných uzlů kontroly stavu, přes které jsou cíle kontroly rozděleny a replikovány. Nemůžete provádět kontroly z jednoho hostitele, protože může selhat. Pak nezískáme stav instancí, které zkontroloval. Provádíme kontroly na kterékoli z instancí z alespoň tří uzlů healthcheck. Pomocí konzistentních hashovacích algoritmů jsme rozbili účely kontrol mezi uzly.

Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud

Oddělení vyvažování a kontroly stavu může vést k problémům. Pokud uzel healthcheck zadává požadavky na instanci a obchází balancer (který aktuálně neobsluhuje provoz), pak nastává podivná situace: zdroj se zdá být živý, ale provoz k němu nedosáhne. Tento problém řešíme tímto způsobem: garantujeme zahájení provozu Healthcheck prostřednictvím balancerů. Jinými slovy, schéma přesunu paketů s provozem od klientů a ze zdravotních kontrol se liší minimálně: v obou případech se pakety dostanou k balancérům, které je doručí do cílových zdrojů.

Rozdíl je v tom, že klienti zadávají požadavky na VIP, zatímco Healthchecks zasílají požadavky na každý jednotlivý RIP. Zde vzniká zajímavý problém: dáváme našim uživatelům možnost vytvářet zdroje v šedých IP sítích. Představme si, že existují dva různí vlastníci cloudu, kteří své služby schovali za balancery. Každý z nich má prostředky v podsíti 10.0.0.1/24 se stejnými adresami. Musíte je umět nějak rozlišit a zde se musíte ponořit do struktury virtuální sítě Yandex.Cloud. Více podrobností je lepší zjistit v video z události about:cloud, je pro nás nyní důležité, že síť je vícevrstvá a má tunely, které lze rozlišit podle id podsítě.

Uzly Healthcheck kontaktují balancery pomocí tzv. kvazi-IPv6 adres. Kvazi adresa je adresa IPv6 s adresou IPv4 a ID podsítě uživatele, která je v ní vložena. Provoz se dostane do balanceru, který z něj extrahuje adresu zdroje IPv4, nahradí IPv6 IPv4 a odešle paket do sítě uživatele.

Reverzní provoz probíhá stejným způsobem: balancer vidí, že cílem je šedá síť od Healthcheckers, a převede IPv4 na IPv6.

VPP – srdce datové roviny

Balancér je implementován pomocí technologie Vector Packet Processing (VPP), framework od společnosti Cisco pro dávkové zpracování síťového provozu. V našem případě framework funguje nad knihovnou pro správu síťových zařízení v uživatelském prostoru – Data Plane Development Kit (DPDK). To zajišťuje vysoký výkon zpracování paketů: v jádře se vyskytuje mnohem méně přerušení a neexistují žádné kontextové přepínače mezi prostorem jádra a uživatelským prostorem. 

VPP jde ještě dále a vymačkává ze systému ještě více výkonu díky kombinování balíčků do dávek. Zvýšení výkonu pochází z agresivního používání mezipaměti na moderních procesorech. Používají se jak datové cache (pakety jsou zpracovávány ve „vektorech“, data jsou blízko sebe), tak instrukční cache: ve VPP probíhá zpracování paketů podle grafu, jehož uzly obsahují funkce, které provádějí stejnou úlohu.

Například zpracování IP paketů ve VPP probíhá v následujícím pořadí: nejprve jsou hlavičky paketů analyzovány v uzlu pro analýzu a poté jsou odeslány do uzlu, který pakety předává dále podle směrovacích tabulek.

Trochu hardcore. Autoři VPP netolerují kompromisy ve využití procesorových cache, takže typický kód pro zpracování vektoru paketů obsahuje manuální vektorizaci: existuje procesní smyčka, ve které se zpracovává situace typu „máme čtyři pakety ve frontě“, pak totéž pro dva, pak - pro jednoho. Instrukce předběžného načtení se často používají k načtení dat do mezipaměti, aby se urychlil přístup k nim v následujících iteracích.

n_left_from = frame->n_vectors;
while (n_left_from > 0)
{
    vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
    // ...
    while (n_left_from >= 4 && n_left_to_next >= 2)
    {
        // processing multiple packets at once
        u32 next0 = SAMPLE_NEXT_INTERFACE_OUTPUT;
        u32 next1 = SAMPLE_NEXT_INTERFACE_OUTPUT;
        // ...
        /* Prefetch next iteration. */
        {
            vlib_buffer_t *p2, *p3;

            p2 = vlib_get_buffer (vm, from[2]);
            p3 = vlib_get_buffer (vm, from[3]);

            vlib_prefetch_buffer_header (p2, LOAD);
            vlib_prefetch_buffer_header (p3, LOAD);

            CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
            CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
        }
        // actually process data
        /* verify speculative enqueues, maybe switch current next frame */
        vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
                to_next, n_left_to_next,
                bi0, bi1, next0, next1);
    }

    while (n_left_from > 0 && n_left_to_next > 0)
    {
        // processing packets by one
    }

    // processed batch
    vlib_put_next_frame (vm, node, next_index, n_left_to_next);
}

Healthchecks tedy mluví přes IPv6 s VPP, což je změní na IPv4. K tomu slouží uzel v grafu, který nazýváme algoritmický NAT. Pro reverzní provoz (a konverzi z IPv6 na IPv4) existuje stejný algoritmický uzel NAT.

Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud

Přímý provoz z klientů balanceru prochází přes uzly grafu, které samy provádějí vyrovnávání. 

Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud

Prvním uzlem jsou lepivé relace. Ukládá hash of 5-násobek pro zavedená relace. 5-tice zahrnuje adresu a port klienta, ze kterého jsou přenášeny informace, adresu a porty zdrojů dostupných pro příjem provozu a také síťový protokol. 

5násobný hash nám pomáhá provádět méně výpočtů v následném konzistentním hašovacím uzlu a také lépe zvládat změny seznamu zdrojů za balancerem. Když paket, pro který neexistuje žádná relace, dorazí do balanceru, je odeslán do konzistentního hashovacího uzlu. Zde dochází k vyvažování pomocí konzistentního hashování: vybereme zdroj ze seznamu dostupných „živých“ zdrojů. Dále jsou pakety odeslány do uzlu NAT, který ve skutečnosti nahradí cílovou adresu a přepočítá kontrolní součty. Jak vidíte, dodržujeme pravidla VPP – like to like, seskupování podobných výpočtů pro zvýšení efektivity procesorových cache.

Konzistentní hašování

Proč jsme si ho vybrali a co to vlastně je? Nejprve se podívejme na předchozí úkol – výběr zdroje ze seznamu. 

Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud

Při nekonzistentním hashování se vypočítá hash příchozího paketu a zdroj je vybrán ze seznamu zbývajícím podílem tohoto hashování počtem zdrojů. Dokud zůstane seznam nezměněn, toto schéma funguje dobře: vždy posíláme pakety se stejnou 5-ti n-tice do stejné instance. Pokud například některý zdroj přestal reagovat na kontroly stavu, pak se u významné části hashů volba změní. TCP spojení klienta bude přerušeno: paket, který dříve dosáhl instance A, může začít dosahovat instance B, která nezná relaci pro tento paket.

Důsledné hashování popsaný problém řeší. Nejjednodušší způsob, jak vysvětlit tento koncept, je tento: představte si, že máte kruh, do kterého distribuujete zdroje pomocí hash (například podle IP:port). Výběr zdroje je otočením kola o úhel, který je určen hashem paketu.

Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud

To minimalizuje redistribuci provozu při změně složení zdrojů. Odstranění zdroje ovlivní pouze tu část konzistentního hashovacího kruhu, ve kterém byl zdroj umístěn. Přidáním zdroje se také změní distribuce, ale máme uzel ukotvení relací, který nám umožňuje nepřepínat již vytvořené relace na nové zdroje.

Podívali jsme se na to, co se stane s přímým provozem mezi balancerem a zdroji. Nyní se podíváme na zpáteční dopravu. Postupuje se stejným vzorem jako kontrolní provoz – přes algoritmický NAT, tedy přes reverzní NAT 44 pro klientský provoz a přes NAT 46 pro provoz healthchecks. Držíme se vlastního schématu: sjednocujeme návštěvnost zdravotních kontrol a reálnou návštěvnost uživatelů.

Loadbalancer-uzel a sestavené komponenty

Složení balancerů a zdrojů ve VPP hlásí místní služba - loadbalancer-node. Přihlašuje se k proudu událostí z loadbalancer-controlleru a je schopen vykreslit rozdíl mezi aktuálním stavem VPP a cílovým stavem přijatým z kontroleru. Získáme uzavřený systém: události z API přicházejí do ovladače balanceru, který přiděluje úkoly ovladači healthcheck, aby zkontroloval „živost“ zdrojů. To zase přiřadí úkoly uzlu healthcheck a agreguje výsledky, načež je odešle zpět do ovladače balanceru. Loadbalancer-node se přihlásí k odběru událostí z řadiče a změní stav VPP. V takovém systému každá služba ví o sousedních službách pouze to, co je nezbytné. Počet připojení je omezený a máme schopnost provozovat a škálovat různé segmenty nezávisle.

Architektura nástroje pro vyrovnávání zatížení sítě v Yandex.Cloud

Jakým problémům se předešlo?

Všechny naše služby v řídicí rovině jsou napsány v Go a mají dobré škálování a spolehlivost. Go má mnoho open source knihoven pro vytváření distribuovaných systémů. Aktivně využíváme GRPC, všechny komponenty obsahují open source implementaci zjišťování služeb – naše služby si navzájem monitorují výkon, mohou dynamicky měnit svou skladbu a to jsme propojili s vyvažováním GRPC. Pro metriky také používáme řešení s otevřeným zdrojovým kódem. V datové rovině jsme dostali slušný výkon a velkou rezervu zdrojů: ukázalo se, že je velmi obtížné sestavit stojan, na který bychom se mohli spolehnout na výkon VPP, spíše než na železnou síťovou kartu.

Problémy a řešení

Co se tak nepovedlo? Go má automatickou správu paměti, ale stále dochází k únikům paměti. Nejjednodušší způsob, jak se s nimi vypořádat, je spustit goroutiny a nezapomenout je ukončit. S sebou: Sledujte spotřebu paměti programů Go. Často je dobrým ukazatelem počet goroutin. V tomto příběhu je plus: v Go je snadné získat data za běhu – spotřeba paměti, počet spuštěných goroutin a mnoho dalších parametrů.

Go také nemusí být nejlepší volbou pro funkční testy. Jsou dost upovídaní a standardní přístup „spouštění všeho v CI v dávce“ pro ně není příliš vhodný. Faktem je, že funkční testy jsou náročnější na zdroje a způsobují skutečné časové limity. Z tohoto důvodu mohou testy selhat, protože CPU je zaneprázdněn testy jednotek. Závěr: Pokud je to možné, provádějte „těžké“ testy odděleně od jednotkových testů. 

Architektura událostí mikroslužeb je složitější než monolit: shromažďování protokolů na desítkách různých strojů není příliš pohodlné. Závěr: pokud děláte mikroslužby, okamžitě přemýšlejte o sledování.

Naše plány

Spustíme interní balancer, IPv6 balancer, přidáme podporu pro skripty Kubernetes, budeme pokračovat ve shardování našich služeb (aktuálně jsou shardovány pouze healthcheck-node a healthcheck-ctrl), přidáme nové healthchecky a také implementujeme chytrou agregaci kontrol. Zvažujeme možnost ještě více osamostatnit naše služby – aby komunikovaly nikoli přímo mezi sebou, ale pomocí fronty zpráv. Nedávno se v Cloudu objevila služba kompatibilní s SQS Fronta zpráv Yandex.

Nedávno došlo k veřejnému vydání Yandex Load Balancer. Prozkoumat dokumentace do služby, spravujte balancery způsobem, který vám vyhovuje, a zvyšte odolnost svých projektů proti chybám!

Zdroj: www.habr.com

Přidat komentář