Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud

Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud
Pozdravljeni, sem Sergey Elantsev, razvijam se izravnalnik obremenitve omrežja v Yandex.Cloud. Prej sem vodil razvoj balanserja L7 za portal Yandex - kolegi se šalijo, da ne glede na to, kaj naredim, se izkaže, da je balanser. Bralcem Habra bom povedal, kako obvladovati obremenitev v platformi v oblaku, kaj vidimo kot idealno orodje za dosego tega cilja in kako napredujemo k izgradnji tega orodja.

Najprej predstavimo nekaj izrazov:

  • VIP (Virtual IP) - IP naslov izravnalnika
  • Strežnik, zaledje, instanca – virtualni stroj, ki izvaja aplikacijo
  • RIP (Real IP) - naslov IP strežnika
  • Healthcheck - preverjanje pripravljenosti strežnika
  • Območje razpoložljivosti, AZ - izolirana infrastruktura v podatkovnem centru
  • Regija - zveza različnih AZ

Izravnalniki obremenitve rešujejo tri glavne naloge: izvajajo samo uravnoteženje, izboljšajo toleranco napak storitve in poenostavijo njeno skaliranje. Odpornost na napake je zagotovljena s samodejnim upravljanjem prometa: izravnalnik spremlja stanje aplikacije in izloči primerke iz uravnoteženja, ki ne prestanejo preverjanja živosti. Skaliranje je zagotovljeno z enakomerno porazdelitvijo obremenitve po instancah, kot tudi s sprotnim posodabljanjem seznama instanc. Če uravnoteženje ni dovolj enotno, bodo nekateri primerki prejeli obremenitev, ki presega njihovo omejitev zmogljivosti, storitev pa bo postala manj zanesljiva.

Izravnalnik obremenitve je pogosto razvrščen glede na sloj protokola iz modela OSI, na katerem deluje. Cloud Balancer deluje na ravni TCP, ki ustreza četrti plasti, L4.

Preidimo na pregled arhitekture Cloud balancerja. Postopoma bomo povečevali stopnjo podrobnosti. Komponente balansirja delimo v tri razrede. Razred konfiguracijske ravnine je odgovoren za interakcijo uporabnika in shranjuje ciljno stanje sistema. Nadzorna ravnina shranjuje trenutno stanje sistema in upravlja sisteme iz razreda podatkovne ravnine, ki so neposredno odgovorni za dostavo prometa od strank do vaših instanc.

Podatkovna ravnina

Promet se konča na dragih napravah, imenovanih mejni usmerjevalniki. Za večjo toleranco na napake deluje več takih naprav hkrati v enem podatkovnem centru. Nato gre promet k izravnavam, ki vsem AZ prek BGP za odjemalce objavijo naslove IP za poljubno oddajanje. 

Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud

Promet se prenaša prek ECMP - to je strategija usmerjanja, po kateri je lahko več enako dobrih poti do cilja (v našem primeru bo cilj ciljni naslov IP) in paketi se lahko pošiljajo po kateri koli od njih. Podpiramo tudi delo v več conah razpoložljivosti po naslednji shemi: v vsaki coni oglašujemo naslov, promet gre do najbližje in ne presega njenih meja. Kasneje v objavi si bomo podrobneje ogledali, kaj se dogaja s prometom.

Konfiguracijska ravnina

 
Ključna komponenta konfiguracijske ravnine je API, prek katerega se izvajajo osnovne operacije z izravnalniki: ustvarjanje, brisanje, spreminjanje sestave primerkov, pridobivanje rezultatov preverjanja stanja itd. Na eni strani je to REST API, na drugi pa drugo, mi v oblaku zelo pogosto uporabljamo ogrodje gRPC, zato REST »prevedemo« v gRPC in nato uporabimo samo gRPC. Vsaka zahteva vodi do ustvarjanja niza asinhronih idempotentnih nalog, ki se izvajajo v skupni skupini delavcev Yandex.Cloud. Naloge so napisane tako, da jih je mogoče kadarkoli prekiniti in nato ponovno zagnati. To zagotavlja razširljivost, ponovljivost in beleženje operacij.

Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud

Posledično bo naloga iz API-ja poslala zahtevo krmilniku storitve uravnoteženja, ki je napisan v Go. Doda in odstrani lahko izravnalce, spremeni sestavo ozadij in nastavitev. 

Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud

Storitev shranjuje svoje stanje v zbirko podatkov Yandex, porazdeljeno upravljano zbirko podatkov, ki jo boste kmalu lahko uporabljali. V Yandex.Cloud, kot že mi povedal, velja koncept pasje hrane: če sami uporabljamo naše storitve, jih bodo z veseljem uporabljale tudi naše stranke. Zbirka podatkov Yandex je primer implementacije takšnega koncepta. Vse naše podatke hranimo v YDB in nam ni treba razmišljati o vzdrževanju in povečanju baze podatkov: te težave so za nas rešene, bazo uporabljamo kot storitev.

Vrnimo se k regulatorju uravnoteženja. Njegova naloga je, da shrani informacije o uravnoteženju in pošlje nalogo za preverjanje pripravljenosti virtualnega stroja krmilniku Healthcheck.

Krmilnik Healthcheck

Prejema zahteve za spremembo pravil preverjanja, jih shrani v YDB, razdeli naloge med vozlišča healtcheck in združi rezultate, ki se nato shranijo v bazo podatkov in pošljejo krmilniku loadbalancerja. Po drugi strani pa pošlje zahtevo za spremembo sestave gruče v podatkovni ravnini vozlišču loadbalancer, o čemer bom razpravljal spodaj.

Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud

Pogovorimo se več o zdravstvenih pregledih. Razdelimo jih lahko v več razredov. Revizije imajo drugačna merila uspešnosti. Preverjanja TCP morajo uspešno vzpostaviti povezavo v določenem času. Preverjanja HTTP zahtevajo uspešno povezavo in odgovor s statusno kodo 200.

Čeki se razlikujejo tudi po razredu dejanj - so aktivni in pasivni. Pasivni pregledi zgolj spremljajo dogajanje s prometom brez kakršnih koli posebnih ukrepov. To ne deluje dobro na L4, ker je odvisno od logike protokolov na višji ravni: na L4 ni podatkov o tem, kako dolgo je trajala operacija ali ali je bila vzpostavitev povezave dobra ali slaba. Aktivna preverjanja zahtevajo, da izravnalnik pošlje zahteve vsakemu primerku strežnika.

Večina naprav za izravnavo obremenitve sama izvaja preverjanje vzdržljivosti. Pri Cloudu smo se odločili, da te dele sistema ločimo, da bi povečali razširljivost. Ta pristop nam bo omogočil povečati število izravnalcev, hkrati pa ohraniti število zahtev za preverjanje stanja za storitev. Preverjanja izvajajo ločena vozlišča za preverjanje stanja, prek katerih so cilji preverjanja razdeljeni in podvojeni. Ne morete izvajati preverjanj z enega gostitelja, ker lahko ne uspe. Potem ne bomo dobili stanja primerkov, ki jih je preveril. Izvajamo preglede katere koli instance iz vsaj treh vozlišč za preverjanje stanja. Namene preverjanj med vozlišči delimo z doslednimi algoritmi zgoščevanja.

Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud

Ločevanje uravnoteženja in zdravstvenega pregleda lahko povzroči težave. Če vozlišče za preverjanje stanja pošlje zahteve primerku, mimo izravnalnika (ki trenutno ne služi prometu), se pojavi nenavadna situacija: zdi se, da je vir živ, vendar ga promet ne doseže. To težavo rešujemo na ta način: zagotovljeno nam je, da sprožimo promet preverjanja stanja prek izravnalcev. Z drugimi besedami, shema za premikanje paketov s prometom od odjemalcev in iz zdravstvenih pregledov se minimalno razlikuje: v obeh primerih bodo paketi dosegli uravnoteženje, ki jih bo dostavilo do ciljnih virov.

Razlika je v tem, da odjemalci pošiljajo zahteve VIP-u, medtem ko zdravstveni pregledi zahtevajo posamezne RIP-e. Tu se pojavi zanimiva težava: našim uporabnikom dajemo možnost ustvarjanja virov v sivih omrežjih IP. Predstavljajmo si, da obstajata dva različna lastnika oblaka, ki sta svoje storitve skrila za balanserji. Vsak od njih ima vire v podomrežju 10.0.0.1/24 z enakimi naslovi. Morate jih znati nekako razlikovati in tukaj se morate potopiti v strukturo virtualnega omrežja Yandex.Cloud. Bolje je, da izveste več podrobnosti v video iz dogodka about:cloud, je za nas zdaj pomembno, da je omrežje večplastno in ima tunele, ki jih je mogoče razlikovati po ID-ju podomrežja.

Vozlišča Healthcheck vzpostavijo stik z izravnalniki z uporabo tako imenovanih naslovov kvazi-IPv6. Kvazinaslov je naslov IPv6 z vdelanim naslovom IPv4 in ID-jem podomrežja uporabnika. Promet doseže izravnalnik, ki iz njega izvleče naslov vira IPv4, zamenja IPv6 z IPv4 in pošlje paket v uporabnikovo omrežje.

Povratni promet poteka na enak način: izravnalnik vidi, da je cilj sivo omrežje iz preverjalcev stanja, in pretvori IPv4 v IPv6.

VPP - srce podatkovne ravnine

Balancer je implementiran s tehnologijo Vector Packet Processing (VPP), ogrodja podjetja Cisco za paketno obdelavo omrežnega prometa. V našem primeru ogrodje deluje na vrhu knjižnice za upravljanje omrežnih naprav v uporabniškem prostoru - Data Plane Development Kit (DPDK). To zagotavlja visoko zmogljivost obdelave paketov: veliko manj prekinitev se pojavi v jedru in ni preklopov konteksta med prostorom jedra in uporabniškim prostorom. 

VPP gre še dlje in iz sistema iztisne še večjo zmogljivost z združevanjem paketov v pakete. Povečanje zmogljivosti izhaja iz agresivne uporabe predpomnilnikov na sodobnih procesorjih. Uporabljajo se tako predpomnilniki podatkov (paketi se obdelujejo v »vektorjih«, podatki so blizu drug drugemu) kot predpomnilniki navodil: v VPP obdelava paketov sledi grafu, katerega vozlišča vsebujejo funkcije, ki opravljajo isto nalogo.

Na primer, obdelava paketov IP v VPP poteka v naslednjem vrstnem redu: najprej se glave paketov razčlenijo v vozlišču za razčlenjevanje, nato pa se pošljejo vozlišču, ki posreduje pakete naprej v skladu z usmerjevalnimi tabelami.

Malo hardcore. Avtorji VPP ne tolerirajo kompromisov pri uporabi procesorskih predpomnilnikov, zato tipična koda za obdelavo vektorja paketov vsebuje ročno vektorizacijo: obstaja procesna zanka, v kateri se obdela situacija, kot je "v čakalni vrsti imamo štiri pakete", potem enako za dva, nato - za enega. Navodila za vnaprejšnje pridobivanje se pogosto uporabljajo za nalaganje podatkov v predpomnilnike, da se pospeši dostop do njih v naslednjih iteracijah.

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);
}

Torej, Healthchecks komunicirajo prek IPv6 z VPP, ki jih spremeni v IPv4. To naredi vozlišče v grafu, ki ga imenujemo algoritemski NAT. Za povratni promet (in pretvorbo iz IPv6 v IPv4) obstaja isto algoritemsko vozlišče NAT.

Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud

Neposredni promet iz odjemalcev za izravnavo poteka skozi vozlišča grafa, ki izvajajo samo uravnoteženje. 

Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud

Prvo vozlišče so lepljive seje. Shranjuje zgoščeno vrednost 5-tuple za ustaljene seje. 5-tuple vključuje naslov in vrata odjemalca, iz katerega se prenašajo informacije, naslov in vrata virov, ki so na voljo za sprejem prometa, kot tudi omrežni protokol. 

5-toplotno zgoščevanje nam pomaga izvesti manj računanja v naslednjem doslednem vozlišču zgoščevanja, pa tudi bolje obravnavati spremembe seznama virov za izravnalnikom. Ko paket, za katerega ni seje, prispe v izravnalnik, se pošlje doslednemu vozlišču zgoščevanja. Tu pride do uravnoteženja z doslednim zgoščevanjem: izberemo vir s seznama razpoložljivih virov v živo. Nato se paketi pošljejo vozlišču NAT, ki dejansko nadomesti ciljni naslov in ponovno izračuna kontrolne vsote. Kot lahko vidite, sledimo pravilom VPP - like to like, združevanje podobnih izračunov za povečanje učinkovitosti procesorskih predpomnilnikov.

Dosledno zgoščevanje

Zakaj smo ga izbrali in kaj sploh je? Najprej razmislimo o prejšnji nalogi - izbiri vira s seznama. 

Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud

Pri nedoslednem zgoščevanju se izračuna zgoščevanje dohodnega paketa in vir se izbere s seznama z ostankom deljenja tega zgoščevanja s številom virov. Dokler je seznam nespremenjen, ta shema deluje dobro: vedno pošljemo pakete z isto 5-torko na isto instanco. Če se je na primer nek vir prenehal odzivati ​​na preglede stanja, se bo izbira za velik del zgoščenih vrednosti spremenila. Odjemalčeve povezave TCP bodo prekinjene: paket, ki je prej dosegel primerek A, lahko začne dosegati primerek B, ki ne pozna seje za ta paket.

Dosledno zgoščevanje rešuje opisani problem. Najlažji način za razlago tega koncepta je naslednji: predstavljajte si, da imate obroč, ki mu razdelite sredstva po zgoščeni vrednosti (na primer po IP:port). Izbira vira je vrtenje kolesca za kot, ki je določen z zgoščenostjo paketa.

Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud

To minimizira prerazporeditev prometa, ko se sestava virov spremeni. Brisanje vira bo vplivalo samo na del konsistentnega zgoščevalnega obroča, v katerem je bil vir. Z dodajanjem vira se spremeni tudi distribucija, vendar imamo vozlišče sticky sessions, ki nam omogoča, da že vzpostavljenih sej ne preklopimo na nove vire.

Pogledali smo, kaj se zgodi z usmerjanjem prometa med izravnalnikom in viri. Zdaj pa poglejmo povratni promet. Sledi istemu vzorcu kot promet preverjanja – prek algoritemskega NAT, to je prek povratnega NAT 44 za promet odjemalcev in prek NAT 46 za promet preverjanja stanja. Držimo se lastne sheme: poenotimo promet Healthchecks in promet realnih uporabnikov.

Vozlišče izravnalnika obremenitve in sestavljene komponente

Sestavo izravnalcev in virov v VPP poroča lokalna storitev - vozlišče loadbalancer-node. Naročen je na tok dogodkov iz loadbalancer-controllerja in lahko nariše razliko med trenutnim stanjem VPP in ciljnim stanjem, prejetim od krmilnika. Dobimo zaprt sistem: dogodki iz API-ja pridejo do krmilnika balancerja, ki krmilniku Healthcheck dodeli naloge za preverjanje »živosti« virov. To nato vozlišču za preverjanje stanja dodeli naloge in združi rezultate, nato pa jih pošlje nazaj v krmilnik izravnalnika. Vozlišče Loadbalancer-node se naroči na dogodke iz krmilnika in spremeni stanje VPP. V takem sistemu vsaka služba ve le tisto, kar je potrebno o sosednjih storitvah. Število povezav je omejeno in imamo možnost samostojnega upravljanja in skaliranja različnih segmentov.

Arhitektura izravnalnika obremenitve omrežja v Yandex.Cloud

Katerim težavam smo se izognili?

Vse naše storitve v nadzorni ravnini so napisane v Go in imajo dobre lastnosti skaliranja in zanesljivosti. Go ima veliko odprtokodnih knjižnic za gradnjo porazdeljenih sistemov. Aktivno uporabljamo GRPC, vse komponente vsebujejo odprtokodno implementacijo odkrivanja storitev - naše storitve spremljajo delovanje druga druge, lahko dinamično spreminjajo svojo sestavo, to pa smo povezali z GRPC uravnoteženjem. Za meritve uporabljamo tudi odprtokodno rešitev. V podatkovni ravnini smo dobili dostojno zmogljivost in veliko rezervo virov: izkazalo se je, da je zelo težko sestaviti stojalo, na katerega bi se lahko zanesli na zmogljivost VPP, ne pa na železno omrežno kartico.

Problemi in rešitve

Kaj ni delovalo tako dobro? Go ima samodejno upravljanje pomnilnika, vendar še vedno prihaja do uhajanja pomnilnika. Najlažji način, da se z njimi spopadete, je, da zaženete goroutine in jih ne pozabite prekiniti. Zaključek: opazujte porabo pomnilnika programov Go. Pogosto je dober pokazatelj število goroutin. V tej zgodbi je plus: v Go je enostavno dobiti podatke o času izvajanja - porabo pomnilnika, število zagnanih goroutin in številne druge parametre.

Poleg tega Go morda ni najboljša izbira za funkcionalne teste. So precej podrobni in standardni pristop »izvajanja vsega v CI v paketu« zanje ni preveč primeren. Dejstvo je, da so funkcionalni testi bolj zahtevni po virih in povzročajo prave časovne omejitve. Zaradi tega lahko testi ne uspejo, ker je CPE zaseden s testi enot. Zaključek: Če je mogoče, izvedite "težke" teste ločeno od testov enot. 

Arhitektura dogodkov mikrostoritev je bolj zapletena kot monolit: zbiranje dnevnikov na desetinah različnih strojev ni zelo priročno. Zaključek: če izdelujete mikrostoritve, takoj pomislite na sledenje.

Naši načrti

Zagnali bomo notranji uravnoteževalec, uravnoteževalec IPv6, dodali podporo za skripte Kubernetes, nadaljevali z razdelitvijo naših storitev (trenutno sta razdrobljena le healthcheck-node in healthcheck-ctrl), dodali nove preglede stanja in uvedli tudi pametno združevanje preverjanj. Razmišljamo o možnostih, da bi naše storitve naredili še bolj neodvisne – tako da ne bi komunicirale neposredno med seboj, temveč prek čakalne vrste sporočil. Storitev, združljiva s SQS, se je nedavno pojavila v oblaku Yandexova čakalna vrsta sporočil.

Pred kratkim je bila javna izdaja Yandex Load Balancer. Raziščite dokumentacijo do storitve, upravljajte balanserje na način, ki vam ustreza, in povečajte toleranco napak vaših projektov!

Vir: www.habr.com

Dodaj komentar