Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud

Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud
Saluton, mi estas Sergey Elantsev, mi evoluas reta ŝarĝobalancilo en Yandex.Cloud. Antaŭe, mi gvidis la disvolviĝon de la ekvilibristo L7 por la portalo Yandex - kolegoj ŝercas, ke negrave kion mi faras, ĝi rezultas esti ekvilibristo. Mi diros al Habr-legantoj kiel administri la ŝarĝon en nuba platformo, kion ni vidas kiel la ideala ilo por atingi ĉi tiun celon, kaj kiel ni iras al konstruado de ĉi tiu ilo.

Unue, ni enkonduku kelkajn terminojn:

  • VIP (Virtuala IP) - ekvilibra IP-adreso
  • Servilo, backend, instance - virtuala maŝino rulanta aplikaĵon
  • RIP (Reala IP) - IP-adreso de servilo
  • Sankontrolo - kontrolanta servilon pretecon
  • Disponebla Zono, AZ - izolita infrastrukturo en datumcentro
  • Regiono - unio de diversaj AZoj

Ŝarĝbalanciloj solvas tri ĉefajn taskojn: ili plenumas la ekvilibron mem, plibonigas la misfunkciadon de la servo kaj simpligas ĝian skaladon. Faŭltoleremo estas certigita per aŭtomata trafikadministrado: la ekvilibristo kontrolas la staton de la aplikaĵo kaj ekskludas kazojn de ekvilibro, kiuj ne preterpasas la vivkontrolon. Skalado estas certigita per egale distribuado de la ŝarĝo tra okazoj, kaj ankaŭ ĝisdatigante la liston de okazoj sur la flugo. Se la ekvilibro ne estas sufiĉe unuforma, iuj el la okazoj ricevos ŝarĝon, kiu superas sian kapacitan limon, kaj la servo fariĝos malpli fidinda.

Ŝarĝbalancilo ofte estas klasifikita per la protokoltavolo de la OSI-modelo sur kiu ĝi funkcias. La Cloud Balancer funkcias ĉe la TCP-nivelo, kiu respondas al la kvara tavolo, L4.

Ni transiru al superrigardo de la arkitekturo de Cloudbalancer. Ni iom post iom pliigos la nivelon de detalo. Ni dividas la ekvilibrajn komponantojn en tri klasojn. La agorda ebena klaso respondecas pri uzantinterago kaj konservas la celstato de la sistemo. La kontrolaviadilo konservas la nunan staton de la sistemo kaj administras sistemojn de la datumaviadilo klaso, kiuj estas rekte respondecaj pri liverado de trafiko de klientoj al viaj petskriboj.

Datuma aviadilo

La trafiko finiĝas per multekostaj aparatoj nomataj landlimaj enkursigiloj. Por pliigi misfunkciadon, pluraj tiaj aparatoj funkcias samtempe en unu datumcentro. Poste, la trafiko iras al ekvilibristoj, kiuj anoncas ajnajn IP-adresojn al ĉiuj AZ-oj per BGP por klientoj. 

Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud

Trafiko estas transdonita per ECMP - ĉi tio estas vojstrategio laŭ kiu povas ekzisti pluraj same bonaj vojoj al la celo (en nia kazo, la celo estos la celo IP-adreso) kaj pakaĵoj povas esti senditaj laŭ iu ajn el ili. Ni ankaŭ subtenas laboron en pluraj haveblecaj zonoj laŭ la sekva skemo: ni reklamas adreson en ĉiu zono, trafiko iras al la plej proksima kaj ne preterpasas ĝiajn limojn. Poste en la afiŝo ni rigardos pli detale kio okazas al trafiko.

Agorda aviadilo

 
La ŝlosila komponanto de la agorda aviadilo estas la API, per kiu bazaj operacioj kun balanciloj estas faritaj: krei, forigi, ŝanĝi la konsiston de petskriboj, akiri sankontrolajn rezultojn, ktp. Unuflanke, ĉi tio estas REST API, kaj sur la aliaj, ni en la Nubo tre ofte uzas la kadron gRPC, do ni "tradukas" REST al gRPC kaj poste uzas nur gRPC. Ajna peto kondukas al la kreado de serio de nesinkronaj idempotentaj taskoj, kiuj estas ekzekutitaj sur komuna aro de Yandex.Cloud-laboristoj. Taskoj estas skribitaj tiel, ke ili povas esti ĉesigitaj iam ajn kaj poste rekomencitaj. Ĉi tio certigas skaleblon, ripeteblon kaj registradon de operacioj.

Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud

Kiel rezulto, la tasko de la API faros peton al la ekvilibra servoregilo, kiu estas skribita en Go. Ĝi povas aldoni kaj forigi ekvilibrilojn, ŝanĝi la komponadon de backends kaj agordojn. 

Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud

La servo konservas sian staton en Yandex Database, distribuita administrita datumbazo, kiun vi baldaŭ povos uzi. En Yandex.Cloud, kiel ni jam rakontis, validas la koncepto de hunda manĝaĵo: se ni mem uzas niajn servojn, tiam niaj klientoj ankaŭ volonte uzos ilin. Yandex Database estas ekzemplo de efektivigo de tia koncepto. Ni konservas ĉiujn niajn datumojn en YDB, kaj ni ne devas pensi pri konservado kaj skalado de la datumbazo: ĉi tiuj problemoj estas solvitaj por ni, ni uzas la datumbazon kiel servon.

Ni revenu al la ekvilibra regilo. Ĝia tasko estas konservi informojn pri la ekvilibristo kaj sendi taskon por kontroli la pretecon de la virtuala maŝino al la sankontrolregilo.

Sankontrola regilo

Ĝi ricevas petojn por ŝanĝi kontrolregulojn, konservas ilin en YDB, distribuas taskojn inter sankontrolaj nodoj kaj agregas la rezultojn, kiuj tiam estas konservitaj al la datumbazo kaj senditaj al la ŝarĝbalancilo-regilo. Ĝi, siavice, sendas peton ŝanĝi la konsiston de la areto en la datumaviadilo al la ŝarĝobalancilo-nodo, kiun mi diskutos sube.

Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud

Ni parolu pli pri sankontroloj. Ili povas esti dividitaj en plurajn klasojn. Revizioj havas malsamajn sukceskriteriojn. TCP-kontroloj devas sukcese establi konekton ene de fiksa tempodaŭro. HTTP-kontroloj postulas kaj sukcesan konekton kaj respondon kun 200 statuskodo.

Ankaŭ ĉekoj malsamas en la klaso de ago - ili estas aktivaj kaj pasivaj. Pasivaj kontroloj simple kontrolas kio okazas kun trafiko sen fari ajnan specialan agon. Ĉi tio ne tre bone funkcias ĉe L4 ĉar ĝi dependas de la logiko de la altnivelaj protokoloj: ĉe L4 mankas informo pri kiom da tempo daŭris la operacio aŭ ĉu la konektkompletiĝo estis bona aŭ malbona. Aktivaj ĉekoj postulas, ke la ekvilibristo sendu petojn al ĉiu servila petskribo.

Plej multaj ŝarĝbalanciloj mem faras vivkontrolojn. Ĉe Cloud, ni decidis apartigi ĉi tiujn partojn de la sistemo por pliigi skaleblon. Ĉi tiu aliro permesos al ni pliigi la nombron da ekvilibristoj konservante la nombron da sankontrolpetoj al la servo. Kontroloj estas faritaj per apartaj sankontrolnodoj, trans kiuj ĉekceloj estas disrompitaj kaj reproduktitaj. Vi ne povas fari kontrolojn de unu gastiganto, ĉar ĝi povas malsukcesi. Tiam ni ne ricevos la staton de la kazoj, kiujn li kontrolis. Ni faras kontrolojn pri iu ajn el la okazoj de almenaŭ tri sankontrolaj nodoj. Ni disigas la celojn de kontroloj inter nodoj uzante konsekvencajn algoritmojn.

Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud

Apartigi ekvilibron kaj sankontrolon povas konduki al problemoj. Se la sankontrola nodo faras petojn al la petskribo, preterirante la ekvilibranton (kiu nuntempe ne servas trafikon), tiam okazas stranga situacio: la rimedo ŝajnas esti viva, sed la trafiko ne atingos ĝin. Ni solvas ĉi tiun problemon tiamaniere: ni estas garantiitaj komenci sankontrolan trafikon per balanciloj. Alivorte, la skemo por movi pakaĵetojn kun trafiko de klientoj kaj de sankontroloj minimume malsamas: en ambaŭ kazoj, la pakoj atingos la ekvilibristojn, kiuj liveros ilin al la celaj rimedoj.

La diferenco estas, ke klientoj faras petojn al VIP, dum sankontroloj faras petojn al ĉiu individua RIP. Interesa problemo ekestas ĉi tie: ni donas al niaj uzantoj la ŝancon krei rimedojn en grizaj IP-retoj. Ni imagu, ke ekzistas du malsamaj nubaj posedantoj, kiuj kaŝis siajn servojn malantaŭ ekvilibristoj. Ĉiu el ili havas rimedojn en la subreto 10.0.0.1/24, kun la samaj adresoj. Vi devas iel distingi ilin, kaj ĉi tie vi devas plonĝi en la strukturon de la virtuala reto Yandex.Cloud. Pli bone estas ekscii pli da detaloj en video de about:nuba evento, gravas por ni nun, ke la reto estas plurtavola kaj havas tunelojn, kiuj povas esti distingitaj per subreta id.

Healthcheck-nodoj kontaktas ekvilibristojn uzante tielnomitajn kvazaŭ-IPv6-adresojn. Kvazaŭadreso estas IPv6-adreso kun IPv4-adreso kaj uzanta subreto-identigilo enigita en ĝi. La trafiko atingas la ekvilibriston, kiu ĉerpas la IPv4-rimedan adreson de ĝi, anstataŭigas IPv6 per IPv4 kaj sendas la pakaĵon al la reto de la uzanto.

La inversa trafiko iras same: la ekvilibristo vidas, ke la celo estas griza reto de sankontrolistoj, kaj konvertas IPv4 al IPv6.

VPP - la koro de la datumaviadilo

La ekvilibristo estas efektivigita uzante Vector Packet Processing (VPP) teknologion, kadron de Cisco por grupa prilaborado de rettrafiko. En nia kazo, la kadro funkcias supre de la uzantspaca reto-aparata administra biblioteko - Data Plane Development Kit (DPDK). Ĉi tio certigas altan pakaĵetan pretigan efikecon: multe malpli da interrompoj okazas en la kerno, kaj ekzistas neniuj kuntekstoŝanĝoj inter kernspaco kaj uzantspaco. 

VPP iras eĉ pli for kaj elpremas eĉ pli da efikeco el la sistemo kombinante pakaĵojn en arojn. La rendimentogajnoj venas de la agresema uzo de kaŝmemoroj sur modernaj procesoroj. Ambaŭ datumkaŝmemoroj estas uzataj (pakaĵetoj estas prilaboritaj en "vektoroj", la datumoj estas proksimaj unu al la alia) kaj instrukciaj kaŝmemoroj: en VPP, pakatraktado sekvas grafeon, kies nodoj enhavas funkciojn kiuj plenumas la saman taskon.

Ekzemple, la prilaborado de IP-pakaĵetoj en VPP okazas en la sekva sinsekvo: unue, la pakaĵettitoloj estas analizitaj en la analiza nodo, kaj tiam ili estas senditaj al la nodo, kiu plusendas la pakaĵetojn plu laŭ vojtabloj.

Iom hardcore. La aŭtoroj de VPP ne toleras kompromisojn en la uzo de procesoraj kaŝmemoroj, do tipa kodo por prilaborado de vektoro de pakaĵoj enhavas manan vektorigon: ekzistas pretiga buklo en kiu situacio kiel "ni havas kvar pakaĵojn en la atendovico" estas procesita, tiam same por du, tiam — por unu. Prefetch-instrukcioj ofte estas uzataj por ŝarĝi datumojn en kaŝmemorojn por akceli aliron al ili en postaj ripetoj.

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

Do, Healthchecks parolas per IPv6 al la VPP, kiu igas ilin IPv4. Ĉi tio estas farita per nodo en la grafeo, kiun ni nomas algoritma NAT. Por inversa trafiko (kaj konvertiĝo de IPv6 al IPv4) ekzistas la sama algoritma NAT-nodo.

Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud

Rekta trafiko de la ekvilibraj klientoj trairas la grafeajn nodojn, kiuj plenumas la ekvilibron mem. 

Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud

La unua nodo estas gluiĝemaj sesioj. Ĝi stokas la haŝiŝon de 5-opo por establitaj kunsidoj. 5-opo inkluzivas la adreson kaj havenon de la kliento de kiu informoj estas transdonitaj, la adreson kaj havenojn de rimedoj disponeblaj por ricevi trafikon, same kiel la retan protokolon. 

La 5-opa hash helpas nin fari malpli da komputado en la posta konsekvenca hashnodo, kaj ankaŭ pli bone pritrakti rimedlistŝanĝojn malantaŭ la ekvilibristo. Kiam pakaĵeto por kiu ne ekzistas sesio alvenas al la ekvilibristo, ĝi estas sendita al la konsekvenca haŝnodo. Ĉi tie okazas ekvilibro uzante konsekvencan haŝadon: ni elektas rimedon el la listo de disponeblaj "vivaj" rimedoj. Poste, la pakaĵetoj estas senditaj al la NAT-nodo, kiu fakte anstataŭigas la cel-adreson kaj rekalkulas la ĉeksumojn. Kiel vi povas vidi, ni sekvas la regulojn de VPP - ŝatas ŝati, grupigante similajn kalkulojn por pliigi la efikecon de procesoraj kaŝmemoroj.

Konsekvenca hakado

Kial ni elektis ĝin kaj kio ĝi estas eĉ? Unue, ni konsideru la antaŭan taskon - elektante rimedon el la listo. 

Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud

Kun malkonsekvenca hash, la hash de la envenanta pako estas kalkulita, kaj rimedo estas elektita el la listo per la resto de dividado de tiu hash per la nombro da resursoj. Dum la listo restas senŝanĝa, ĉi tiu skemo funkcias bone: ni ĉiam sendas pakaĵetojn kun la sama 5-opo al la sama petskribo. Se, ekzemple, iu rimedo ĉesis respondi al sankontroloj, tiam por signifa parto de la haŝoj la elekto ŝanĝiĝos. La TCP-konektoj de la kliento estos rompitaj: pakaĵeto kiu antaŭe atingis kazon A povas komenci atingi ekzemplon B, kiu ne konas la seancon por ĉi tiu pako.

Konsekvenca hashing solvas la priskribitan problemon. La plej facila maniero por klarigi ĉi tiun koncepton estas jena: imagu, ke vi havas ringon, al kiu vi disdonas rimedojn per haŝo (ekzemple per IP:porto). Elekti rimedon estas turni la radon per angulo, kiu estas determinita de la hash de la pako.

Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud

Ĉi tio minimumigas trafikan redistribuon kiam la konsisto de resursoj ŝanĝiĝas. Forigo de rimedo nur influos la parton de la konsekvenca hashing ringo en kiu la rimedo troviĝis. Aldonado de rimedo ankaŭ ŝanĝas la distribuon, sed ni havas gluecan sesian nodon, kiu permesas al ni ne ŝanĝi jam establitajn sesiojn al novaj rimedoj.

Ni rigardis kio okazas al rekta trafiko inter la ekvilibristo kaj rimedoj. Nun ni rigardu revenan trafikon. Ĝi sekvas la saman ŝablonon kiel ĉektrafiko - per algoritma NAT, tio estas, tra inversa NAT 44 por klienttrafiko kaj tra NAT 46 por sankontrolaj trafiko. Ni aliĝas al nia propra skemo: ni unuigas sankontrolan trafikon kaj realan uzanttrafikon.

Loadbalancer-nodo kaj kunvenitaj komponantoj

La konsisto de balanciloj kaj rimedoj en VPP estas raportita de la loka servo - loadbalancer-node. Ĝi abonas la fluon de eventoj de ŝarĝobalancilo-regilo kaj kapablas desegni la diferencon inter la nuna VPP-ŝtato kaj la celstato ricevita de la regilo. Ni ricevas fermitan sistemon: eventoj de la API venas al la ekvilibra regilo, kiu asignas taskojn al la sankontrola regilo por kontroli la "vivecon" de rimedoj. Tio, siavice, asignas taskojn al la sankontrolo-nodo kaj agregas la rezultojn, post kio ĝi resendas ilin al la ekvilibra regilo. Loadbalancer-nodo abonas eventojn de la regilo kaj ŝanĝas la staton de la VPP. En tia sistemo, ĉiu servo scias nur kio estas necesa pri najbaraj servoj. La nombro da konektoj estas limigita kaj ni havas la kapablon funkciigi kaj grimpi malsamajn segmentojn sendepende.

Arkitekturo de ekvilibra ŝarĝo de reto en Yandex.Cloud

Kiuj aferoj estis evititaj?

Ĉiuj niaj servoj en la kontrolaviadilo estas skribitaj en Go kaj havas bonajn skalajn kaj fidindecajn trajtojn. Go havas multajn malfermkodajn bibliotekojn por konstrui distribuitajn sistemojn. Ni aktive uzas GRPC, ĉiuj komponantoj enhavas malfermfontan efektivigon de servo-malkovro - niaj servoj kontrolas la rendimenton de unu la alian, povas ŝanĝi sian komponadon dinamike, kaj ni ligis tion kun GRPC-ekvilibro. Por metrikoj, ni ankaŭ uzas liberkodan solvon. En la datuma ebeno, ni akiris decan rendimenton kaj grandan rezervon de rimedoj: montriĝis tre malfacile kunmeti standon, sur kiu ni povus fidi la agadon de VPP, prefere ol fera retkarto.

Problemoj kaj Solvoj

Kio ne tiel bone funkciis? Go havas aŭtomatan memoradministradon, sed memorfuĝoj ankoraŭ okazas. La plej facila maniero trakti ilin estas ruli goroutines kaj memori ĉesigi ilin. Konsumo: Rigardu la memorkonsumon de viaj Go-programoj. Ofte bona indikilo estas la nombro da gorutinoj. Estas pluso en ĉi tiu rakonto: en Go estas facile akiri rultempajn datumojn - memorkonsumon, la nombron da kurantaj gorutinoj kaj multaj aliaj parametroj.

Ankaŭ, Go eble ne estas la plej bona elekto por funkciaj testoj. Ili estas sufiĉe multvortaj, kaj la norma aliro "fari ĉion en CI en aro" ne tre taŭgas por ili. La fakto estas, ke funkciaj testoj estas pli postulataj de rimedoj kaj kaŭzas realajn forpasojn. Pro tio, testoj povas malsukcesi ĉar la CPU estas okupata de unuotestoj. Konkludo: Se eble, faru "pezajn" provojn aparte de unuo testoj. 

Mikroserva arkitekturo de eventoj estas pli kompleksa ol monolito: kolekti protokolojn sur dekoj da malsamaj maŝinoj ne estas tre oportuna. Konkludo: se vi faras mikroservojn, tuj pensu pri spurado.

Niaj planoj

Ni lanĉos internan balancilon, IPv6-balancilon, aldonos subtenon por Kubernetes-skriptoj, daŭre disrompos niajn servojn (nuntempe nur healthcheck-node kaj healthcheck-ctrl estas sharded), aldonos novajn sankontrolojn kaj ankaŭ efektivigos inteligentan agregadon de ĉekoj. Ni pripensas la eblecon fari niajn servojn eĉ pli sendependaj - por ke ili komunikiĝu ne rekte unu kun la alia, sed uzante mesaĝvicon. Lastatempe aperis SQS-kongrua servo en la Nubo Yandex Mesaĝa Vico.

Lastatempe okazis la publika liberigo de Yandex Load Balancer. Esploru dokumentado al la servo, administru ekvilibrilojn en maniero oportuna por vi kaj pliigu la misfunkciadon de viaj projektoj!

fonto: www.habr.com

Aldoni komenton