Pozdravljeni, sem Sergey Elantsev, razvijam se
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.
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.
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.
Storitev shranjuje svoje stanje v zbirko podatkov Yandex, porazdeljeno upravljano zbirko podatkov, ki jo boste kmalu lahko uporabljali. V Yandex.Cloud, kot že mi
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.
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.
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
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.
Neposredni promet iz odjemalcev za izravnavo poteka skozi vozlišča grafa, ki izvajajo samo uravnoteženje.
Prvo vozlišče so lepljive seje. Shranjuje zgoščeno vrednost
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.
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.
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.
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
Pred kratkim je bila javna izdaja Yandex Load Balancer. Raziščite
Vir: www.habr.com