Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud

Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud
Zdravo, ja sam Sergey Elantsev, ja se razvijam balansiranje mrežnog opterećenja u Yandex.Cloud. Ranije sam vodio razvoj L7 balansera za portal Yandex - kolege se šale da šta god da radim, ispada da je balans. Čitaocima Habra ću reći kako da upravljaju opterećenjem na platformi u oblaku, šta vidimo kao idealan alat za postizanje ovog cilja i kako idemo ka izgradnji ovog alata.

Prvo, uvedemo neke pojmove:

  • VIP (Virtualni IP) - IP adresa balansera
  • Server, backend, instanca - virtuelna mašina koja pokreće aplikaciju
  • RIP (Real IP) - IP adresa servera
  • Healthcheck - provjera spremnosti servera
  • Zona dostupnosti, AZ - izolovana infrastruktura u data centru
  • Region - unija različitih AZ

Balanseri opterećenja rješavaju tri glavna zadatka: izvode samo balansiranje, poboljšavaju toleranciju na greške servisa i pojednostavljuju njegovo skaliranje. Tolerancija grešaka je osigurana putem automatskog upravljanja prometom: balanser prati stanje aplikacije i isključuje slučajeve iz balansiranja koji ne prođu provjeru živosti. Skaliranje je osigurano ravnomjernom distribucijom opterećenja na instance, kao i ažuriranjem liste instanci u hodu. Ako balansiranje nije dovoljno ujednačeno, neke od instanci će primiti opterećenje koje premašuje ograničenje njihovog kapaciteta, a usluga će postati manje pouzdana.

Balansator opterećenja se često klasifikuje prema sloju protokola iz OSI modela na kojem radi. Cloud Balancer radi na TCP nivou, koji odgovara četvrtom sloju, L4.

Pređimo na pregled arhitekture Cloud balansera. Postepeno ćemo povećavati nivo detalja. Komponente balansera dijelimo u tri klase. Klasa config plane je odgovorna za interakciju korisnika i pohranjuje ciljno stanje sistema. Kontrolna ravan pohranjuje trenutno stanje sistema i upravlja sistemima iz klase ravni podataka, koji su direktno odgovorni za isporuku prometa od klijenata do vaših instanci.

Ravan podataka

Saobraćaj završava na skupim uređajima zvanim granični ruteri. Kako bi se povećala tolerancija grešaka, nekoliko takvih uređaja radi istovremeno u jednom podatkovnom centru. Zatim, promet ide balanserima, koji objavljuju bilo kakve IP adrese svim AZ-ovima preko BGP-a za klijente. 

Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud

Saobraćaj se prenosi preko ECMP-a - ovo je strategija rutiranja prema kojoj može postojati nekoliko jednako dobrih ruta do cilja (u našem slučaju cilj će biti odredišna IP adresa) i paketi se mogu slati duž bilo koje od njih. Podržavamo i rad u nekoliko zona dostupnosti prema sljedećoj šemi: u svakoj zoni oglašavamo adresu, promet ide do najbliže i ne prelazi njene granice. Kasnije u postu ćemo detaljnije pogledati šta se dešava sa saobraćajem.

Config plane

 
Ključna komponenta config plana je API, preko kojeg se izvode osnovne operacije sa balanserima: kreiranje, brisanje, promjena sastava instanci, dobijanje rezultata provjere zdravlja, itd. S jedne strane, ovo je REST API, a sa drugo, mi u Cloud-u vrlo često koristimo okvir gRPC, tako da „prevodimo“ REST u gRPC i onda koristimo samo gRPC. Svaki zahtjev dovodi do stvaranja niza asinhronih idempotentnih zadataka koji se izvršavaju na zajedničkom skupu Yandex.Cloud radnika. Zadaci su napisani na način da se u bilo kom trenutku mogu obustaviti, a zatim ponovo pokrenuti. Ovo osigurava skalabilnost, ponovljivost i evidentiranje operacija.

Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud

Kao rezultat toga, zadatak iz API-ja će uputiti zahtjev kontroloru usluge balansiranja, koji je napisan u Go. Može dodavati i uklanjati balansere, mijenjati sastav backenda i postavke. 

Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud

Usluga pohranjuje svoje stanje u Yandex Database, distribuiranu upravljanu bazu podataka koju ćete uskoro moći koristiti. U Yandex.Cloud, kao što smo već rekao je, primjenjuje se koncept hrane za pse: ako mi sami koristimo naše usluge, onda će ih i naši klijenti rado koristiti. Yandex Database je primjer implementacije takvog koncepta. Sve svoje podatke pohranjujemo u YDB, i ne moramo razmišljati o održavanju i skaliranju baze podataka: ovi problemi su riješeni za nas, koristimo bazu podataka kao uslugu.

Vratimo se na kontroler balansera. Njegov zadatak je da sačuva informacije o balanseru i pošalje zadatak za proveru spremnosti virtuelne mašine na Healthcheck kontroleru.

Kontrolor zdravstvene provere

Prima zahtjeve za promjenom pravila provjere, sprema ih u YDB, distribuira zadatke među healtcheck čvorovima i agregira rezultate, koji se zatim spremaju u bazu podataka i šalju kontroloru loadbalancera. On, zauzvrat, šalje zahtjev za promjenu sastava klastera u podatkovnoj ravni čvoru loadbalancer-a, o čemu ću govoriti u nastavku.

Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud

Razgovarajmo više o zdravstvenim pregledima. Mogu se podijeliti u nekoliko klasa. Revizije imaju različite kriterije uspjeha. TCP provjere moraju uspješno uspostaviti vezu unutar fiksnog vremenskog perioda. HTTP provjere zahtijevaju i uspješnu vezu i odgovor sa statusnim kodom 200.

Također, provjere se razlikuju po klasi radnje - one su aktivne i pasivne. Pasivne provere jednostavno prate šta se dešava sa saobraćajem bez preduzimanja posebnih radnji. Ovo ne radi dobro na L4 jer zavisi od logike protokola višeg nivoa: na L4 nema informacija o tome koliko je operacija trajala ili da li je završetak veze bio dobar ili loš. Aktivne provjere zahtijevaju od balansera da pošalje zahtjeve svakoj instanci servera.

Većina balansera opterećenja sama obavlja provjere životnosti. U Cloud-u smo odlučili da odvojimo ove dijelove sistema kako bismo povećali skalabilnost. Ovaj pristup će nam omogućiti da povećamo broj balansera uz zadržavanje broja zahtjeva za provjeru zdravlja servisu. Provjere se izvode od strane zasebnih čvorova za provjeru zdravlja, preko kojih su ciljevi provjere podijeljeni i replicirani. Ne možete izvršiti provjere sa jednog hosta, jer može propasti. Tada nećemo dobiti stanje instanci koje je provjerio. Vršimo provjere na bilo kojoj od instanci iz najmanje tri Healthcheck čvora. Mi dijelimo svrhe provjera između čvorova koristeći konzistentne algoritame heširanja.

Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud

Razdvajanje balansiranja i zdravstvene kontrole može dovesti do problema. Ako čvor za provjeru zdravlja šalje zahtjeve instanci, zaobilazeći balanser (koji trenutno ne opslužuje promet), tada se javlja čudna situacija: čini se da je resurs živ, ali promet neće doći do njega. Ovaj problem rješavamo na ovaj način: garantirano ćemo pokrenuti zdravstveni promet putem balansera. Drugim riječima, shema za premještanje paketa sa prometom od klijenata i sa zdravstvenih provjera razlikuje se minimalno: u oba slučaja, paketi će doći do balansera, koji će ih isporučiti ciljnim resursima.

Razlika je u tome što klijenti upućuju zahtjeve VIP-u, dok zdravstveni pregledi postavljaju zahtjeve svakom pojedinačnom RIP-u. Ovdje se javlja zanimljiv problem: dajemo našim korisnicima priliku da kreiraju resurse u sivim IP mrežama. Zamislimo da postoje dva različita vlasnika oblaka koji su sakrili svoje usluge iza balansera. Svaki od njih ima resurse u podmreži 10.0.0.1/24, sa istim adresama. Morate ih nekako razlikovati, a ovdje morate zaroniti u strukturu virtualne mreže Yandex.Cloud. Bolje je saznati više detalja u video sa događaja about:cloud, za nas je sada važno da je mreža višeslojna i da ima tunele koji se mogu razlikovati po ID-u podmreže.

Healthcheck čvorovi kontaktiraju balansere koristeći takozvane kvazi-IPv6 adrese. Kvazi-adresa je IPv6 adresa sa IPv4 adresom i ID-om korisničke podmreže ugrađenim u nju. Promet dolazi do balansera, koji iz njega izdvaja IPv4 adresu resursa, zamjenjuje IPv6 sa IPv4 i šalje paket u mrežu korisnika.

Obrnuti saobraćaj ide na isti način: balanser vidi da je odredište siva mreža od zdravstvenih provera i konvertuje IPv4 u IPv6.

VPP - srce ravni podataka

Balanser je implementiran pomoću tehnologije Vector Packet Processing (VPP), okvira iz Cisco za grupnu obradu mrežnog saobraćaja. U našem slučaju, okvir radi na vrhu biblioteke za upravljanje mrežnim uređajima u korisničkom prostoru - Data Plane Development Kit (DPDK). Ovo osigurava visoke performanse obrade paketa: mnogo manje prekida se javlja u kernelu i nema promjene konteksta između prostora kernela i korisničkog prostora. 

VPP ide još dalje i istiskuje još više performansi iz sistema kombinovanjem paketa u grupe. Porast performansi dolazi od agresivne upotrebe keš memorije na modernim procesorima. Koriste se oba predmemorija podataka (paketi se obrađuju u „vektorima“, podaci su blizu jedan drugom) i keš instrukcija: u VPP-u, obrada paketa prati graf, čiji čvorovi sadrže funkcije koje obavljaju isti zadatak.

Na primjer, obrada IP paketa u VPP-u se odvija sljedećim redoslijedom: prvo se zaglavlja paketa raščlanjuju u čvoru za raščlanjivanje, a zatim se šalju čvoru, koji dalje prosljeđuje pakete prema tabelama rutiranja.

Malo hardkora. Autori VPP-a ne tolerišu kompromise u korišćenju keš memorije procesora, pa tipičan kod za obradu vektora paketa sadrži ručnu vektorizaciju: postoji petlja obrade u kojoj se obrađuje situacija poput „imamo četiri paketa u redu“, zatim isto za dvoje, pa - za jednog. Instrukcije prethodnog preuzimanja se često koriste za učitavanje podataka u keš memorije kako bi se ubrzao pristup njima u narednim iteracijama.

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

Dakle, Healthchecks razgovaraju preko IPv6 s VPP-om, što ih pretvara u IPv4. To radi čvor u grafu, koji zovemo algoritamski NAT. Za obrnuti promet (i konverziju iz IPv6 u IPv4) postoji isti algoritamski NAT čvor.

Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud

Direktan promet od klijenata balansera prolazi kroz čvorove grafa, koji obavljaju samo balansiranje. 

Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud

Prvi čvor su ljepljive sesije. Pohranjuje hash od 5-torka za ustanovljene sjednice. 5-torka uključuje adresu i port klijenta sa kojeg se prenose informacije, adresu i portove resursa dostupnih za prijem saobraćaja, kao i mrežni protokol. 

Heš od 5 torki pomaže nam da izvršimo manje izračunavanja u naknadnom konzistentnom heš čvoru, kao i da bolje upravljamo promjenama liste resursa iza balansera. Kada paket za koji ne postoji sesija stigne u balanser, on se šalje konzistentnom čvoru za heširanje. Ovdje dolazi do balansiranja korištenjem dosljednog heširanja: odabiremo resurs sa liste dostupnih „živih“ resursa. Zatim se paketi šalju u NAT čvor, koji zapravo zamjenjuje odredišnu adresu i ponovo izračunava kontrolne sume. Kao što možete vidjeti, slijedimo pravila VPP-a - volimo lajkovati, grupisati slične proračune kako bismo povećali efikasnost procesorskih keša.

Konzistentno heširanje

Zašto smo ga izabrali i šta je uopšte? Prvo, razmotrimo prethodni zadatak - odabir resursa sa liste. 

Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud

Kod nekonzistentnog heširanja izračunava se heš dolaznog paketa, a resurs se bira sa liste ostatkom dijeljenja ovog heša sa brojem resursa. Sve dok lista ostaje nepromenjena, ova šema dobro funkcioniše: uvek šaljemo pakete sa istim 5-torkom u istu instancu. Ako, na primjer, neki resurs prestane da odgovara na zdravstvene provjere, tada će se za značajan dio hashova izbor promijeniti. TCP veze klijenta će biti prekinute: paket koji je prethodno stigao do instance A može početi da stiže do instance B, koja nije upoznata sa sesijom za ovaj paket.

Dosljedno heširanje rješava opisani problem. Najlakši način da se objasni ovaj koncept je sljedeći: zamislite da imate prsten na koji distribuirate resurse po hash-u (na primjer, putem IP:porta). Odabir resursa je okretanje kotača za ugao, koji je određen hešom paketa.

Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud

Ovo minimizira preraspodjelu prometa kada se sastav resursa promijeni. Brisanje resursa će utjecati samo na dio konzistentnog hashing prstena u kojem se nalazi resurs. Dodavanje resursa također mijenja distribuciju, ali imamo čvor za ljepljive sesije, koji nam omogućava da ne prebacimo već uspostavljene sesije na nove resurse.

Pogledali smo šta se dešava sa direktnim saobraćajem između balansera i resursa. Pogledajmo sada povratni saobraćaj. Slijedi isti obrazac kao i promet provjere - preko algoritamskog NAT-a, odnosno preko obrnutog NAT-a 44 za klijentski promet i kroz NAT 46 za promet provjere zdravlja. Pridržavamo se vlastite sheme: objedinjujemo promet zdravstvenih provjera i stvarni korisnički promet.

Loadbalancer-čvor i sastavljene komponente

Sastav balansera i resursa u VPP-u izvještava lokalna usluga - loadbalancer-čvor. Pretplaćuje se na tok događaja iz kontrolera za balansiranje opterećenja i može iscrtati razliku između trenutnog VPP stanja i ciljnog stanja primljenog od kontrolera. Dobijamo zatvoreni sistem: događaji iz API-ja dolaze do kontrolora balansiranja, koji dodeljuje zadatke kontroleru zdravstvene provere da proveri „živost“ resursa. To, zauzvrat, dodjeljuje zadatke Healthcheck-čvoru i agregira rezultate, nakon čega ih šalje nazad u balansni kontroler. Loadbalancer-čvor se pretplaćuje na događaje iz kontrolera i mijenja stanje VPP-a. U takvom sistemu, svaka služba zna samo ono što je potrebno o susjednim uslugama. Broj priključaka je ograničen i imamo mogućnost samostalnog rada i skaliranja različitih segmenata.

Arhitektura balansiranja mrežnog opterećenja u Yandex.Cloud

Koji problemi su izbjegnuti?

Sve naše usluge u upravljačkoj ravni su napisane u Go i imaju dobre karakteristike skaliranja i pouzdanosti. Go ima mnogo biblioteka otvorenog koda za izgradnju distribuiranih sistema. Aktivno koristimo GRPC, sve komponente sadrže open source implementaciju otkrivanja servisa - naše usluge međusobno prate performanse, mogu dinamički mijenjati svoj sastav, a mi smo to povezali sa GRPC balansiranjem. Za metriku koristimo i rješenje otvorenog koda. U podatkovnoj ravni dobili smo pristojne performanse i veliku rezervu resursa: pokazalo se da je vrlo teško sastaviti postolje na koje bismo se mogli osloniti na performanse VPP-a, a ne na željenu mrežnu karticu.

Problemi i rešenja

Šta nije funkcionisalo tako dobro? Go ima automatsko upravljanje memorijom, ali se i dalje dešavaju curenja memorije. Najlakši način da se nosite s njima je da pokrenete gorrutine i zapamtite da ih prekinete. Za poneti: pratite potrošnju memorije vaših Go programa. Često je dobar pokazatelj broj gorrutina. U ovoj priči postoji plus: u Go-u je lako dobiti podatke o vremenu izvršavanja - potrošnju memorije, broj pokrenutih gorutina i mnoge druge parametre.

Takođe, Go možda nije najbolji izbor za funkcionalne testove. Oni su prilično opsežni, a standardni pristup „pokretanja svega u CI u paketu“ nije baš prikladan za njih. Činjenica je da su funkcionalni testovi zahtjevniji za resurse i uzrokuju stvarne tajm-aute. Zbog toga, testovi mogu biti neuspješni jer je CPU zauzet jediničnim testovima. Zaključak: Ako je moguće, izvršite “teške” testove odvojeno od jediničnih testova. 

Arhitektura događaja mikroservisa je složenija od monolita: prikupljanje dnevnika na desetinama različitih mašina nije baš zgodno. Zaključak: ako napravite mikroservise, odmah razmislite o praćenju.

Naši planovi

Pokrenut ćemo interni balanser, IPv6 balanser, dodati podršku za Kubernetes skripte, nastaviti s razdjelom naših usluga (trenutno su podijeljeni samo healthcheck-node i healthcheck-ctrl), dodati nove zdravstvene provjere, a također ćemo implementirati pametno agregiranje provjera. Razmatramo mogućnost da naše servise učinimo još nezavisnijim – tako da ne komuniciraju direktno jedni s drugima, već koristeći red poruka. U Cloud-u se nedavno pojavila usluga kompatibilna sa SQS-om Yandex red poruka.

Nedavno je javno objavljen Yandex Load Balancer. Istražiti dokumentaciju servisu, upravljajte balanserima na način koji vam odgovara i povećajte toleranciju na greške svojih projekata!

izvor: www.habr.com

Dodajte komentar