Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud

Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud
Dobrý deň, som Sergey Elantsev a vyvíjam sa vyrovnávač zaťaženia siete v Yandex.Cloud. Predtým som viedol vývoj vyvažovača L7 pre portál Yandex – kolegovia žartujú, že nech robím čokoľvek, ukáže sa, že je to vyvažovačka. Čitateľom Habr prezradím, ako zvládať záťaž v cloudovej platforme, čo vidíme ako ideálny nástroj na dosiahnutie tohto cieľa a ako smerujeme k budovaniu tohto nástroja.

Najprv si predstavme niektoré pojmy:

  • VIP (Virtual IP) - IP adresa balancéra
  • Server, backend, inštancia – virtuálny stroj, na ktorom beží aplikácia
  • RIP (Real IP) - IP adresa servera
  • Healthcheck - kontrola pripravenosti servera
  • Zóna dostupnosti, AZ - izolovaná infraštruktúra v dátovom centre
  • Región - zväzok rôznych AZ

Load balancery riešia tri hlavné úlohy: vykonávajú samotné vyvažovanie, zlepšujú odolnosť služby voči poruchám a zjednodušujú jej škálovanie. Odolnosť voči chybám je zabezpečená prostredníctvom automatického riadenia prevádzky: balancer monitoruje stav aplikácie a vylučuje z vyvažovania prípady, ktoré neprejdú kontrolou životnosti. Škálovanie je zabezpečené rovnomerným rozložením záťaže medzi inštancie, ako aj priebežnou aktualizáciou zoznamu inštancií. Ak vyváženie nie je dostatočne jednotné, niektoré z inštancií dostanú záťaž, ktorá presahuje ich kapacitný limit, a služba bude menej spoľahlivá.

Vyrovnávač zaťaženia je často klasifikovaný podľa protokolovej vrstvy podľa modelu OSI, na ktorom beží. Cloud Balancer funguje na úrovni TCP, čo zodpovedá štvrtej vrstve, L4.

Prejdime k prehľadu architektúry Cloud balanceru. Postupne budeme zvyšovať úroveň detailov. Komponenty balancéra delíme do troch tried. Trieda konfiguračnej roviny je zodpovedná za interakciu používateľa a ukladá cieľový stav systému. Riadiaca rovina ukladá aktuálny stav systému a riadi systémy z triedy dátovej roviny, ktoré sú priamo zodpovedné za doručovanie prevádzky od klientov do vašich inštancií.

Dátová rovina

Prevádzka končí na drahých zariadeniach nazývaných hraničné smerovače. Na zvýšenie odolnosti voči chybám funguje niekoľko takýchto zariadení súčasne v jednom dátovom centre. Ďalej prevádzka smeruje k balancerom, ktoré oznamujú všetkým AZ adresy anycast IP cez BGP pre klientov. 

Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud

Prevádzka je prenášaná cez ECMP - ide o smerovaciu stratégiu, podľa ktorej môže existovať niekoľko rovnako dobrých ciest k cieľu (v našom prípade bude cieľom cieľová IP adresa) a po ktorejkoľvek z nich je možné posielať pakety. Podporujeme aj prácu vo viacerých zónach dostupnosti podľa nasledujúcej schémy: v každej zóne inzerujeme adresu, návštevnosť smeruje do najbližšej a neprekračuje jej hranice. Neskôr sa v príspevku pozrieme podrobnejšie na to, čo sa stane s premávkou.

Konfiguračná rovina

 
Kľúčovým komponentom konfiguračnej roviny je API, cez ktoré sa vykonávajú základné operácie s balancermi: vytváranie, mazanie, zmena zloženia inštancií, získavanie výsledkov healthchecks atď. Na jednej strane ide o REST API a na druhej strane iné, my v Cloude veľmi často používame framework gRPC, takže REST „prekladáme“ na gRPC a potom používame iba gRPC. Akákoľvek požiadavka vedie k vytvoreniu série asynchrónnych idempotentných úloh, ktoré sa vykonávajú na spoločnej skupine pracovníkov Yandex.Cloud. Úlohy sú napísané tak, že ich možno kedykoľvek pozastaviť a potom znova spustiť. To zaisťuje škálovateľnosť, opakovateľnosť a protokolovanie operácií.

Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud

Výsledkom je, že úloha z API odošle požiadavku na ovládač služby balancer, ktorý je napísaný v Go. Dokáže pridávať a odstraňovať balancery, meniť zloženie backendov a nastavení. 

Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud

Služba ukladá svoj stav v databáze Yandex, distribuovanej spravovanej databáze, ktorú budete môcť čoskoro používať. V Yandex.Cloud, ako sme už povedal, platí koncept krmiva pre psov: ak my sami využívame naše služby, tak ich radi využijú aj naši klienti. Databáza Yandex je príkladom implementácie takéhoto konceptu. Všetky naše údaje ukladáme v YDB a nemusíme myslieť na údržbu a škálovanie databázy: tieto problémy sú vyriešené za nás, databázu používame ako službu.

Vráťme sa k ovládaču balanceru. Jeho úlohou je uložiť informácie o balanceri a odoslať úlohu na kontrolu pripravenosti virtuálneho stroja do radiča healthcheck.

Kontrolér zdravotného stavu

Prijíma požiadavky na zmenu kontrolných pravidiel, ukladá ich do YDB, rozdeľuje úlohy medzi healtcheck uzly a agreguje výsledky, ktoré sa potom ukladajú do databázy a odosielajú do regulátora loadbalanceru. Ten zase odošle požiadavku na zmenu zloženia klastra v dátovej rovine do loadbalancer-node, o ktorom budem diskutovať nižšie.

Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud

Povedzme si viac o zdravotných kontrolách. Môžu byť rozdelené do niekoľkých tried. Audity majú rôzne kritériá úspešnosti. Kontroly TCP musia úspešne nadviazať spojenie v rámci pevne stanoveného času. Kontroly HTTP vyžadujú úspešné pripojenie a odpoveď so stavovým kódom 200.

Kontroly sa tiež líšia triedou akcií - sú aktívne a pasívne. Pasívne kontroly jednoducho sledujú, čo sa deje s premávkou, bez toho, aby podnikli nejaké špeciálne opatrenia. Toto nefunguje veľmi dobre na L4, pretože to závisí od logiky protokolov vyššej úrovne: na L4 nie sú žiadne informácie o tom, ako dlho operácia trvala alebo či bolo dokončenie pripojenia dobré alebo zlé. Aktívne kontroly vyžadujú, aby balancer posielal požiadavky každej inštancii servera.

Väčšina zariadení na vyvažovanie záťaže sama vykonáva kontrolu životnosti. V Cloude sme sa rozhodli tieto časti systému oddeliť, aby sme zvýšili škálovateľnosť. Tento prístup nám umožní zvýšiť počet balancerov pri zachovaní počtu žiadostí o zdravotné kontroly do služby. Kontroly sa vykonávajú samostatnými uzlami kontroly stavu, cez ktoré sú ciele kontroly rozdelené a replikované. Nemôžete vykonávať kontroly z jedného hostiteľa, pretože môže zlyhať. Potom nezískame stav inštancií, ktoré skontroloval. Vykonávame kontroly na ktorejkoľvek z inštancií z najmenej troch uzlov zdravotnej kontroly. Pomocou konzistentných hašovacích algoritmov sme rozdelili účely kontrol medzi uzlami.

Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud

Oddelenie vyváženia a zdravotnej kontroly môže viesť k problémom. Ak uzol healthcheck odošle požiadavky na inštanciu, pričom obíde balancer (ktorý momentálne neobsluhuje premávku), potom nastane zvláštna situácia: zdroj sa zdá byť živý, ale prevádzka ho nedosiahne. Tento problém riešime takto: zaručene iniciujeme návštevnosť Healthcheck cez balancery. Inými slovami, schéma presunu paketov s prevádzkou od klientov a zo zdravotných kontrol sa líši minimálne: v oboch prípadoch sa pakety dostanú k balancérom, ktoré ich doručia do cieľových zdrojov.

Rozdiel je v tom, že klienti zadávajú požiadavky na VIP, zatiaľ čo zdravotné kontroly zadávajú požiadavky na každý jednotlivý RIP. Tu vzniká zaujímavý problém: našim používateľom dávame možnosť vytvárať zdroje v šedých IP sieťach. Predstavme si, že existujú dvaja rôzni vlastníci cloudu, ktorí svoje služby schovali za balancery. Každý z nich má zdroje v podsieti 10.0.0.1/24 s rovnakými adresami. Musíte ich nejako rozlíšiť a tu sa musíte ponoriť do štruktúry virtuálnej siete Yandex.Cloud. Viac podrobností je lepšie zistiť v video z udalosti about:cloud, teraz je pre nás dôležité, že sieť je viacvrstvová a má tunely, ktoré sa dajú rozlíšiť podľa ID podsiete.

Uzly Healthcheck kontaktujú balancery pomocou takzvaných kvázi IPv6 adries. Kvázi adresa je adresa IPv6 s adresou IPv4 a ID podsiete používateľa. Prevádzka sa dostane do balancéra, ktorý z nej extrahuje adresu zdroja IPv4, nahradí IPv6 IPv4 a pošle paket do siete používateľa.

Reverzná prevádzka prebieha rovnakým spôsobom: balancer vidí, že cieľom je sivá sieť od Healthcheckers, a skonvertuje IPv4 na IPv6.

VPP – srdce dátovej roviny

Balancér je implementovaný pomocou technológie Vector Packet Processing (VPP), rámca od spoločnosti Cisco pre dávkové spracovanie sieťovej prevádzky. V našom prípade rámec funguje nad knižnicou na správu sieťových zariadení v používateľskom priestore – Data Plane Development Kit (DPDK). To zaisťuje vysoký výkon spracovania paketov: v jadre sa vyskytuje oveľa menej prerušení a neexistujú žiadne kontextové prepínače medzi priestorom jadra a používateľským priestorom. 

VPP ide ešte ďalej a vyžmýka zo systému ešte viac výkonu kombinovaním balíkov do dávok. Zvýšenie výkonu pochádza z agresívneho používania vyrovnávacej pamäte na moderných procesoroch. Používajú sa obe dátové cache (pakety sa spracovávajú vo „vektoroch“, dáta sú blízko seba) a inštrukčné cache: vo VPP prebieha spracovanie paketov podľa grafu, ktorého uzly obsahujú funkcie, ktoré vykonávajú rovnakú úlohu.

Napríklad spracovanie IP paketov vo VPP prebieha v nasledujúcom poradí: najprv sa hlavičky paketov analyzujú v uzle na analýzu a potom sa pošlú do uzla, ktorý pakety posiela ďalej podľa smerovacích tabuliek.

Trochu hardcore. Autori VPP netolerujú kompromisy pri používaní vyrovnávacej pamäte procesora, preto typický kód na spracovanie vektora paketov obsahuje manuálnu vektorizáciu: existuje procesná slučka, v ktorej sa spracováva situácia typu „máme štyri pakety vo fronte“, potom to isté pre dvoch, potom - pre jedného. Inštrukcie predbežného načítania sa často používajú na načítanie údajov do vyrovnávacej pamäte, aby sa urýchlil prístup k nim v nasledujúcich iteráciá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 teda hovorí cez IPv6 s VPP, čo ich zmení na IPv4. Robí to uzol v grafe, ktorý nazývame algoritmický NAT. Pre spätnú prevádzku (a konverziu z IPv6 na IPv4) existuje rovnaký algoritmický uzol NAT.

Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud

Priama prevádzka z klientov balancéra prechádza cez uzly grafu, ktoré samotné vyvažovanie vykonávajú. 

Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud

Prvým uzlom sú lepivé relácie. Ukladá hash z 5-násobok pre zavedené relácie. 5-násobok obsahuje adresu a port klienta, z ktorého sa prenášajú informácie, adresu a porty zdrojov dostupných pre príjem prevádzky, ako aj sieťový protokol. 

5-násobný hash nám pomáha vykonávať menej výpočtov v následnom konzistentnom hašovacom uzle, ako aj lepšie zvládnuť zmeny zoznamu zdrojov za balancérom. Keď paket, pre ktorý neexistuje žiadna relácia, dorazí do balancéra, odošle sa do konzistentného hašovacieho uzla. Tu dochádza k vyvažovaniu pomocou konzistentného hashovania: vyberáme zdroj zo zoznamu dostupných „živých“ zdrojov. Potom sú pakety odoslané do uzla NAT, ktorý v skutočnosti nahradí cieľovú adresu a prepočíta kontrolné súčty. Ako vidíte, riadime sa pravidlami VPP – páči sa mi, zoskupením podobných výpočtov na zvýšenie efektivity vyrovnávacích pamätí procesora.

Konzistentné hašovanie

Prečo sme si ho vybrali a čo to vlastne je? Najprv zvážime predchádzajúcu úlohu - výber zdroja zo zoznamu. 

Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud

Pri nekonzistentnom hashovaní sa vypočíta hash prichádzajúceho paketu a zdroj sa vyberie zo zoznamu zvyškom vydelenia tohto hashu počtom zdrojov. Pokiaľ zostáva zoznam nezmenený, táto schéma funguje dobre: ​​vždy posielame pakety s rovnakou 5-ničkou do rovnakej inštancie. Ak napríklad niektorý zdroj prestane reagovať na zdravotné kontroly, potom sa pre značnú časť hashov výber zmení. TCP spojenia klienta budú prerušené: paket, ktorý predtým dosiahol inštanciu A, môže začať dosahovať inštanciu B, ktorá nepozná reláciu pre tento paket.

Dôsledné hashovanie rieši opísaný problém. Najjednoduchší spôsob, ako vysvetliť tento koncept, je tento: predstavte si, že máte kruh, do ktorého distribuujete zdroje podľa hash (napríklad podľa IP:port). Výber zdroja je otočenie kolesa o uhol, ktorý je určený hashom paketu.

Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud

Tým sa minimalizuje prerozdelenie návštevnosti pri zmene zloženia zdrojov. Odstránenie zdroja ovplyvní iba časť konzistentného hašovacieho kruhu, v ktorom sa zdroj nachádzal. Pridaním zdroja sa zmení aj distribúcia, no máme uzol s fixnými reláciami, ktorý nám umožňuje neprepínať už vytvorené relácie na nové zdroje.

Pozreli sme sa na to, čo sa stane s priamou premávkou medzi balancérom a zdrojmi. Teraz sa pozrime na spiatočnú dopravu. Postupuje podľa rovnakého vzoru ako kontrolná prevádzka - cez algoritmický NAT, to znamená cez reverzný NAT 44 pre klientsku prevádzku a cez NAT 46 pre prevádzku zdravotných kontrol. Držíme sa našej vlastnej schémy: zjednocujeme návštevnosť zdravotných kontrol a reálnu návštevnosť používateľov.

Loadbalancer-uzol a zostavené komponenty

Zloženie balancerov a zdrojov vo VPP hlási lokálna služba - loadbalancer-node. Odoberá tok udalostí z loadbalancer-controllera a je schopný vykresliť rozdiel medzi aktuálnym stavom VPP a cieľovým stavom prijatým z radiča. Získame uzavretý systém: udalosti z API prichádzajú do ovládača balancera, ktorý priraďuje úlohy ovládaču healthcheck na kontrolu „živosti“ zdrojov. To zase priradí úlohy uzlu healthcheck a agreguje výsledky, po ktorých ich odošle späť do ovládača balanceru. Loadbalancer-node sa prihlási k udalostiam z kontroléra a zmení stav VPP. V takomto systéme každá služba vie o susedných službách len to, čo je nevyhnutné. Počet pripojení je obmedzený a máme možnosť nezávisle prevádzkovať a škálovať rôzne segmenty.

Architektúra vyrovnávača zaťaženia siete v Yandex.Cloud

Akým problémom sa predišlo?

Všetky naše služby v riadiacej rovine sú napísané v Go a majú dobré škálovacie a spoľahlivé charakteristiky. Go má veľa knižníc s otvoreným zdrojom na vytváranie distribuovaných systémov. Aktívne využívame GRPC, všetky komponenty obsahujú open source implementáciu zisťovania služieb – naše služby si navzájom monitorujú výkon, dokážu dynamicky meniť svoje zloženie a to sme prepojili s vyvažovaním GRPC. Pre metriky používame aj open source riešenie. V dátovej rovine sme dostali slušný výkon a veľkú rezervu zdrojov: ukázalo sa, že je veľmi ťažké zostaviť stojan, na ktorý by sme sa mohli spoľahnúť skôr na výkon VPP ako na železnú sieťovú kartu.

Problémy a riešenia

Čo sa tak nepodarilo? Go má automatickú správu pamäte, ale stále dochádza k úniku pamäte. Najjednoduchší spôsob, ako sa s nimi vysporiadať, je spustiť goroutiny a nezabudnúť ich ukončiť. Takeway: Sledujte spotrebu pamäte programov Go. Často je dobrým ukazovateľom počet gorutínov. V tomto príbehu je plus: v Go je ľahké získať údaje za behu – spotreba pamäte, počet spustených gorutínov a mnoho ďalších parametrov.

Go tiež nemusí byť najlepšou voľbou pre funkčné testy. Sú dosť podrobné a štandardný prístup „spustiť všetko v CI naraz“ nie je pre nich príliš vhodný. Faktom je, že funkčné testy sú náročnejšie na zdroje a spôsobujú skutočné časové limity. Z tohto dôvodu môžu testy zlyhať, pretože CPU je zaneprázdnený testami jednotiek. Záver: Ak je to možné, vykonajte „ťažké“ testy oddelene od jednotkových testov. 

Architektúra udalostí mikroslužieb je zložitejšia ako monolit: zhromažďovanie protokolov na desiatkach rôznych strojov nie je príliš pohodlné. Záver: ak robíte mikroslužby, okamžite premýšľajte o sledovaní.

Naše plány

Spustíme interný balancer, IPv6 balancer, pridáme podporu pre skripty Kubernetes, budeme pokračovať v sharde našich služieb (v súčasnosti sú shardované iba healthcheck-node a healthcheck-ctrl), pridáme nové zdravotné kontroly a tiež implementujeme inteligentnú agregáciu kontrol. Zvažujeme možnosť ešte viac osamostatniť naše služby – aby komunikovali nie priamo medzi sebou, ale pomocou frontu správ. Nedávno sa v Cloude objavila služba kompatibilná s SQS Front správ Yandex.

Nedávno sa uskutočnilo verejné vydanie nástroja Yandex Load Balancer. Preskúmajte dokumentácia do služby, spravujte balancery spôsobom, ktorý vám vyhovuje, a zvýšte odolnosť voči chybám vašich projektov!

Zdroj: hab.com

Pridať komentár