Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud

Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud
Përshëndetje, unë jam Sergey Elantsev, jam zhvilluar balancues i ngarkesës në rrjet në Yandex.Cloud. Më parë, unë drejtova zhvillimin e balancuesit L7 për portalin Yandex - kolegët bëjnë shaka se pavarësisht se çfarë bëj, rezulton të jetë një balancues. Unë do t'u tregoj lexuesve të Habr se si të menaxhojnë ngarkesën në një platformë cloud, atë që ne e shohim si mjetin ideal për arritjen e këtij qëllimi dhe si po ecim drejt ndërtimit të këtij mjeti.

Së pari, le të prezantojmë disa terma:

  • VIP (IP Virtual) - adresa IP e balancuesit
  • Server, backend, shembull - një makinë virtuale që ekzekuton një aplikacion
  • RIP (IP e vërtetë) - adresa IP e serverit
  • Kontrolli shëndetësor - kontrolli i gatishmërisë së serverit
  • Zona e disponueshmërisë, AZ - infrastrukturë e izoluar në një qendër të dhënash
  • Rajoni - një bashkim i AZ-ve të ndryshme

Balancuesit e ngarkesës zgjidhin tre detyra kryesore: ata kryejnë vetë balancimin, përmirësojnë tolerancën e gabimeve të shërbimit dhe thjeshtojnë shkallëzimin e tij. Toleranca ndaj gabimeve sigurohet përmes menaxhimit automatik të trafikut: balancuesi monitoron gjendjen e aplikacionit dhe përjashton nga balancimi rastet që nuk e kalojnë kontrollin e gjallërisë. Shkallëzimi sigurohet duke shpërndarë në mënyrë të barabartë ngarkesën nëpër instanca, si dhe duke përditësuar listën e rasteve në fluturim. Nëse balancimi nuk është mjaftueshëm uniform, disa nga rastet do të marrin një ngarkesë që tejkalon kufirin e kapacitetit të tyre dhe shërbimi do të bëhet më pak i besueshëm.

Një balancues i ngarkesës shpesh klasifikohet nga shtresa e protokollit nga modeli OSI në të cilin funksionon. Cloud Balancer operon në nivelin TCP, i cili korrespondon me shtresën e katërt, L4.

Le të kalojmë në një përmbledhje të arkitekturës së balancuesit të resë kompjuterike. Do të rrisim gradualisht nivelin e detajeve. Ne i ndajmë komponentët e balancuesit në tre klasa. Klasa e planit të konfigurimit është përgjegjëse për ndërveprimin e përdoruesit dhe ruan gjendjen e synuar të sistemit. Plani i kontrollit ruan gjendjen aktuale të sistemit dhe menaxhon sistemet nga klasa e planit të të dhënave, të cilat janë drejtpërdrejt përgjegjëse për dërgimin e trafikut nga klientët në rastet tuaja.

Plani i të dhënave

Trafiku përfundon në pajisje të shtrenjta të quajtura ruterë kufitarë. Për të rritur tolerancën ndaj gabimeve, disa pajisje të tilla funksionojnë njëkohësisht në një qendër të dhënash. Më pas, trafiku shkon te balancuesit, të cilët shpallin adresat IP të çdo transmetimi për të gjitha AZ-të përmes BGP për klientët. 

Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud

Trafiku transmetohet përmes ECMP - kjo është një strategji rrugëtimi sipas së cilës mund të ketë disa rrugë po aq të mira drejt objektivit (në rastin tonë, objektivi do të jetë adresa IP e destinacionit) dhe paketat mund të dërgohen përgjatë secilës prej tyre. Ne gjithashtu mbështesim punën në disa zona disponueshmërie sipas skemës së mëposhtme: ne reklamojmë një adresë në secilën zonë, trafiku shkon në atë më të afërt dhe nuk shkon përtej kufijve të tij. Më vonë në postim do të shohim më në detaje se çfarë ndodh me trafikun.

Plani i konfigurimit

 
Komponenti kryesor i planit të konfigurimit është API, përmes të cilit kryhen operacionet bazë me balancuesit: krijimi, fshirja, ndryshimi i përbërjes së instancave, marrja e rezultateve të kontrollit shëndetësor, etj. Nga njëra anë, ky është një API REST, dhe nga ana tjetër tjetër, ne në Cloud përdorim shumë shpesh kornizën gRPC, kështu që ne "përkthejmë" REST në gRPC dhe më pas përdorim vetëm gRPC. Çdo kërkesë çon në krijimin e një sërë detyrash idempotente asinkrone që ekzekutohen në një grup të përbashkët punonjësish Yandex.Cloud. Detyrat janë shkruar në atë mënyrë që ato të mund të pezullohen në çdo kohë dhe më pas të rifillojnë. Kjo siguron shkallëzueshmërinë, përsëritshmërinë dhe regjistrimin e operacioneve.

Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud

Si rezultat, detyra nga API do t'i bëjë një kërkesë kontrolluesit të shërbimit të balancuesit, e cila është shkruar në Go. Mund të shtojë dhe heqë balancuesit, të ndryshojë përbërjen e backend-eve dhe cilësimeve. 

Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud

Shërbimi ruan gjendjen e tij në Yandex Database, një bazë të dhënash e menaxhuar e shpërndarë që së shpejti do të mund ta përdorni. Në Yandex.Cloud, si ne tashmë tha, aplikohet koncepti i ushqimit të qenve: nëse ne vetë përdorim shërbimet tona, atëherë edhe klientët tanë do të jenë të lumtur t'i përdorin ato. Baza e të dhënave Yandex është një shembull i zbatimit të një koncepti të tillë. Ne ruajmë të gjitha të dhënat tona në YDB dhe nuk duhet të mendojmë për mirëmbajtjen dhe shkallëzimin e bazës së të dhënave: këto probleme janë zgjidhur për ne, ne përdorim bazën e të dhënave si shërbim.

Le të kthehemi te kontrolluesi i balancuesit. Detyra e tij është të ruajë informacionin në lidhje me balancuesin dhe të dërgojë një detyrë për të kontrolluar gatishmërinë e makinës virtuale te kontrolluesi i kontrollit shëndetësor.

Kontrolluesi i kontrollit shëndetësor

Ai merr kërkesa për të ndryshuar rregullat e kontrollit, i ruan ato në YDB, shpërndan detyra midis nyjeve të kontrollit të shëndetit dhe grumbullon rezultatet, të cilat më pas ruhen në bazën e të dhënave dhe dërgohen te kontrolluesi i balancuesit të ngarkesës. Ai, nga ana tjetër, dërgon një kërkesë për të ndryshuar përbërjen e grupit në planin e të dhënave në nyjen loadbalancer, të cilën do ta diskutoj më poshtë.

Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud

Le të flasim më shumë për kontrollet shëndetësore. Ato mund të ndahen në disa klasa. Auditimet kanë kritere të ndryshme suksesi. Kontrollet TCP duhet të krijojnë me sukses një lidhje brenda një kohe të caktuar. Kontrollet HTTP kërkojnë një lidhje të suksesshme dhe një përgjigje me një kod statusi 200.

Gjithashtu, kontrollet ndryshojnë në klasën e veprimit - ato janë aktive dhe pasive. Kontrollet pasive thjesht monitorojnë atë që po ndodh me trafikun pa ndërmarrë ndonjë veprim të veçantë. Kjo nuk funksionon shumë mirë në L4 sepse varet nga logjika e protokolleve të nivelit më të lartë: në L4 nuk ka asnjë informacion se sa kohë zgjati operacioni ose nëse përfundimi i lidhjes ishte i mirë apo i keq. Kontrollet aktive kërkojnë që balancuesi të dërgojë kërkesa në çdo shembull të serverit.

Shumica e balancuesve të ngarkesës kryejnë vetë kontrolle të gjallërisë. Në Cloud, ne vendosëm të veçonim këto pjesë të sistemit për të rritur shkallëzueshmërinë. Kjo qasje do të na lejojë të rrisim numrin e balancuesve duke ruajtur numrin e kërkesave të kontrollit shëndetësor në shërbim. Kontrollet kryhen nga nyje të veçanta të kontrollit shëndetësor, nëpër të cilat objektivat e kontrollit janë copëtuar dhe përsëritur. Ju nuk mund të kryeni kontrolle nga një host, pasi mund të dështojë. Atëherë nuk do të marrim gjendjen e rasteve që ai kontrolloi. Ne kryejmë kontrolle në cilindo nga rastet nga të paktën tre nyje kontrolli shëndetësor. Ne ndajmë qëllimet e kontrolleve midis nyjeve duke përdorur algoritme të qëndrueshme hashing.

Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud

Ndarja e balancimit dhe kontrollit shëndetësor mund të çojë në probleme. Nëse nyja e kontrollit shëndetësor i bën kërkesa instancës, duke anashkaluar balancuesin (i cili aktualisht nuk po shërben trafikun), atëherë lind një situatë e çuditshme: burimi duket se është i gjallë, por trafiku nuk do ta arrijë atë. Ne e zgjidhim këtë problem në këtë mënyrë: ne jemi të garantuar të inicojmë trafikun e kontrollit shëndetësor përmes balancuesve. Me fjalë të tjera, skema e lëvizjes së paketave me trafik nga klientët dhe nga kontrollet shëndetësore ndryshon minimalisht: në të dyja rastet, paketat do të arrijnë te balancuesit, të cilët do t'i dorëzojnë ato në burimet e synuara.

Dallimi është se klientët bëjnë kërkesa për VIP, ndërsa kontrollet shëndetësore bëjnë kërkesa për çdo RIP individual. Këtu lind një problem interesant: ne u japim përdoruesve tanë mundësinë për të krijuar burime në rrjetet IP gri. Le të imagjinojmë se ka dy pronarë të ndryshëm cloud që kanë fshehur shërbimet e tyre pas balancuesve. Secili prej tyre ka burime në nënrrjetin 10.0.0.1/24, me të njëjtat adresa. Ju duhet të jeni në gjendje t'i dalloni disi ato, dhe këtu duhet të zhyteni në strukturën e rrjetit virtual Yandex.Cloud. Është më mirë të zbuloni më shumë detaje në video nga about:cloud event, është e rëndësishme për ne tani që rrjeti është me shumë shtresa dhe ka tunele që mund të dallohen nga id i nënrrjetit.

Nyjet e kontrollit shëndetësor kontaktojnë balancuesit duke përdorur të ashtuquajturat adresa kuazi-IPv6. Një kuazi-adresë është një adresë IPv6 me një adresë IPv4 dhe id të nënrrjetit të përdoruesit të ngulitur brenda saj. Trafiku arrin balancuesin, i cili nxjerr adresën e burimit IPv4 prej tij, zëvendëson IPv6 me IPv4 dhe e dërgon paketën në rrjetin e përdoruesit.

Trafiku i kundërt shkon në të njëjtën mënyrë: balancuesi sheh që destinacioni është një rrjet gri nga kontrollorët shëndetësorë dhe konverton IPv4 në IPv6.

VPP - zemra e planit të të dhënave

Balancuesi zbatohet duke përdorur teknologjinë Vector Packet Processing (VPP), një kornizë nga Cisco për përpunimin e grupeve të trafikut të rrjetit. Në rastin tonë, korniza funksionon në krye të bibliotekës së menaxhimit të pajisjes së rrjetit të hapësirës së përdoruesit - Data Plane Development Kit (DPDK). Kjo siguron performancë të lartë të përpunimit të paketave: shumë më pak ndërprerje ndodhin në kernel dhe nuk ka ndërrime të kontekstit midis hapësirës së kernelit dhe hapësirës së përdoruesit. 

VPP shkon edhe më tej dhe shtrydh edhe më shumë performancë nga sistemi duke kombinuar paketat në tufa. Fitimet e performancës vijnë nga përdorimi agresiv i cache-ve në procesorët modernë. Përdoren të dy memoriet e të dhënave (paketat përpunohen në "vektorë", të dhënat janë afër njëra-tjetrës) dhe memoriet e instruksioneve: në VPP, përpunimi i paketave ndjek një grafik, nyjet e të cilit përmbajnë funksione që kryejnë të njëjtën detyrë.

Për shembull, përpunimi i paketave IP në VPP ndodh në rendin e mëposhtëm: së pari, titujt e paketave analizohen në nyjen analizuese, dhe më pas ato dërgohen në nyje, e cila i përcjell paketat më tej sipas tabelave të rrugëzimit.

Pak hardcore. Autorët e VPP nuk tolerojnë kompromise në përdorimin e cache-ve të procesorit, kështu që kodi tipik për përpunimin e një vektori paketash përmban vektorizim manual: ekziston një lak përpunimi në të cilin përpunohet një situatë si "kemi katër pako në radhë". pastaj e njëjta gjë për dy, pastaj - për një. Udhëzimet e marrjes paraprake shpesh përdoren për të ngarkuar të dhënat në cache për të shpejtuar aksesin në to në përsëritjet e mëvonshme.

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

Pra, Healthchecks flasin mbi IPv6 me VPP, i cili i kthen ato në IPv4. Kjo bëhet nga një nyje në grafik, të cilën e quajmë NAT algoritmike. Për trafikun e kundërt (dhe konvertimin nga IPv6 në IPv4) ekziston e njëjta nyje algoritmike NAT.

Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud

Trafiku i drejtpërdrejtë nga klientët balancues kalon nëpër nyjet grafike, të cilat kryejnë vetë balancimin. 

Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud

Nyja e parë është seancat ngjitëse. Ajo ruan hash-in e 5-tup për seancat e vendosura. 5-tuple përfshin adresën dhe portin e klientit nga i cili transmetohet informacioni, adresën dhe portat e burimeve të disponueshme për marrjen e trafikut, si dhe protokollin e rrjetit. 

Hash-i me 5 dyfish na ndihmon të kryejmë më pak llogaritje në nyjen pasuese të hashimit të qëndrueshëm, si dhe të trajtojmë më mirë ndryshimet e listës së burimeve pas balancuesit. Kur një paketë për të cilën nuk ka sesion arrin në balancuesin, ajo dërgohet në nyjen e hashimit të qëndrueshëm. Këtu ndodh balancimi duke përdorur hash të vazhdueshëm: ne zgjedhim një burim nga lista e burimeve të disponueshme "të drejtpërdrejta". Më pas, paketat dërgohen në nyjen NAT, e cila në fakt zëvendëson adresën e destinacionit dhe rillogarit shumat e kontrollit. Siç mund ta shihni, ne ndjekim rregullat e VPP - si të pëlqejmë, duke grupuar llogaritjet e ngjashme për të rritur efikasitetin e cache-ve të procesorit.

Hashimi i qëndrueshëm

Pse e zgjodhëm dhe çfarë është madje? Së pari, le të shqyrtojmë detyrën e mëparshme - zgjedhjen e një burimi nga lista. 

Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud

Me hash jokonsistent, llogaritet hash-i i paketës hyrëse dhe një burim zgjidhet nga lista me pjesën e mbetur të pjesëtimit të këtij hash me numrin e burimeve. Për sa kohë që lista mbetet e pandryshuar, kjo skemë funksionon mirë: ne gjithmonë dërgojmë pako me të njëjtin 5-tuple në të njëjtin shembull. Nëse, për shembull, disa burime nuk iu përgjigjën kontrolleve shëndetësore, atëherë zgjedhja do të ndryshojë për një pjesë të konsiderueshme të hasheve. Lidhjet TCP të klientit do të prishen: një paketë që ka arritur më parë në instancën A mund të fillojë të arrijë në instancën B, e cila nuk është e njohur me sesionin për këtë paketë.

Hashimi i vazhdueshëm zgjidh problemin e përshkruar. Mënyra më e lehtë për të shpjeguar këtë koncept është kjo: imagjinoni që keni një unazë të cilës i shpërndani burimet me hash (për shembull, me IP:port). Zgjedhja e një burimi është rrotullimi i timonit nga një kënd, i cili përcaktohet nga hash-i i paketës.

Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud

Kjo minimizon rishpërndarjen e trafikut kur ndryshon përbërja e burimeve. Fshirja e një burimi do të ndikojë vetëm në pjesën e unazës së qëndrueshme hash në të cilën ndodhej burimi. Shtimi i një burimi gjithashtu ndryshon shpërndarjen, por ne kemi një nyje sesionesh ngjitëse, e cila na lejon të mos kalojmë sesionet e krijuara tashmë në burime të reja.

Ne shikuam se çfarë ndodh me trafikun e drejtpërdrejtë midis balancuesit dhe burimeve. Tani le të shohim trafikun e kthimit. Ai ndjek të njëjtin model si trafiku i kontrollit - përmes NAT algoritmik, domethënë përmes NAT 44 të kundërt për trafikun e klientit dhe përmes NAT 46 për trafikun e kontrolleve shëndetësore. Ne i përmbahemi skemës sonë: ne bashkojmë trafikun e kontrolleve shëndetësore dhe trafikun e vërtetë të përdoruesve.

Loadbalancer-nyja dhe komponentët e montuar

Përbërja e balancuesve dhe burimeve në VPP raportohet nga shërbimi lokal - loadbalancer-node. Ai pajtohet në rrjedhën e ngjarjeve nga loadbalancer-kontroller dhe është në gjendje të grafikojë ndryshimin midis gjendjes aktuale VPP dhe gjendjes së synuar të marrë nga kontrolluesi. Ne marrim një sistem të mbyllur: ngjarjet nga API vijnë te kontrolluesi i balancuesit, i cili i cakton detyra kontrolluesit të kontrollit shëndetësor për të kontrolluar "gjallërinë" e burimeve. Kjo, nga ana tjetër, i cakton detyrat nyjes së kontrollit shëndetësor dhe i grumbullon rezultatet, pas së cilës i dërgon ato përsëri te kontrolluesi i balancuesit. Loadbalancer-node pajtohet me ngjarjet nga kontrolluesi dhe ndryshon gjendjen e VPP. Në një sistem të tillë, çdo shërbim di vetëm atë që është e nevojshme për shërbimet fqinje. Numri i lidhjeve është i kufizuar dhe ne kemi mundësinë për të operuar dhe shkallëzuar segmente të ndryshme në mënyrë të pavarur.

Arkitektura e një balancuesi të ngarkesës së rrjetit në Yandex.Cloud

Cilat çështje u shmangën?

Të gjitha shërbimet tona në planin e kontrollit janë shkruar në Go dhe kanë karakteristika të mira shkallëzimi dhe besueshmërie. Go ka shumë biblioteka me burim të hapur për ndërtimin e sistemeve të shpërndara. Ne përdorim në mënyrë aktive GRPC, të gjithë komponentët përmbajnë një zbatim me burim të hapur të zbulimit të shërbimit - shërbimet tona monitorojnë performancën e njëri-tjetrit, mund të ndryshojnë përbërjen e tyre në mënyrë dinamike dhe ne e lidhëm këtë me balancimin e GRPC. Për metrikë, ne përdorim gjithashtu një zgjidhje me burim të hapur. Në planin e të dhënave, ne morëm performancë të mirë dhe një rezervë të madhe burimesh: doli të ishte shumë e vështirë të montonim një stendë në të cilën mund të mbështeteshim në performancën e një VPP, në vend të një karte rrjeti hekuri.

Problemet dhe Zgjidhjet

Çfarë nuk funksionoi aq mirë? Go ka menaxhim automatik të kujtesës, por rrjedhjet e kujtesës ende ndodhin. Mënyra më e lehtë për t'u marrë me to është të ekzekutoni gorutina dhe mos harroni t'i përfundoni ato. Takeaway: Shikoni konsumin e memories së programeve tuaja Go. Shpesh një tregues i mirë është numri i gorutinave. Ka një plus në këtë histori: në Go është e lehtë të marrësh të dhëna për kohën e ekzekutimit - konsumin e kujtesës, numrin e gorutinave të ekzekutimit dhe shumë parametra të tjerë.

Gjithashtu, Go mund të mos jetë zgjidhja më e mirë për testet funksionale. Ata janë mjaft të përfolur dhe qasja standarde e "drejtimit të gjithçkaje në CI në një grup" nuk është shumë e përshtatshme për ta. Fakti është se testet funksionale kërkojnë më shumë burime dhe shkaktojnë afate reale. Për shkak të kësaj, testet mund të dështojnë sepse CPU është i zënë me testet e njësisë. Përfundim: Nëse është e mundur, kryeni teste "të rënda" veçmas nga testet e njësisë. 

Arkitektura e ngjarjeve të mikroshërbimit është më komplekse se një monolit: mbledhja e shkrimeve në dhjetëra makina të ndryshme nuk është shumë e përshtatshme. Përfundim: nëse bëni mikroshërbime, mendoni menjëherë për gjurmimin.

Planet tona

Ne do të lançojmë një balancues të brendshëm, një balancues IPv6, do të shtojmë mbështetje për skriptet Kubernetes, do të vazhdojmë të ndajmë shërbimet tona (aktualisht vetëm Healthcheck-node dhe Healthcheck-ctrl janë të copëtuara), shtojmë kontrolle të reja shëndetësore dhe gjithashtu do të zbatojmë një grumbullim inteligjent të kontrolleve. Ne po shqyrtojmë mundësinë që shërbimet tona t'i bëjmë edhe më të pavarura - në mënyrë që ato të mos komunikojnë drejtpërdrejt me njëri-tjetrin, por duke përdorur një radhë mesazhesh. Një shërbim i pajtueshëm me SQS është shfaqur së fundi në Cloud Radha e mesazheve në Yandex.

Kohët e fundit, u bë publikimi i Yandex Load Balancer. Eksploroni dokumentacionin ndaj shërbimit, menaxhoni balancuesit në një mënyrë të përshtatshme për ju dhe rrisni tolerancën ndaj gabimeve të projekteve tuaja!

Burimi: www.habr.com

Shto një koment