
Përshëndetje, Habr! Unë jam Artem Karamyshev, kreu i ekipit të administrimit të sistemit . Kemi pasur shumë lansime të produkteve të reja gjatë vitit të kaluar. Ne donim të siguronim që shërbimet API të ishin lehtësisht të shkallëzueshme, tolerante ndaj gabimeve dhe të gatshme për rritje të shpejtë të ngarkesës së përdoruesit. Platforma jonë është implementuar në OpenStack dhe unë dua t'ju tregoj se cilat probleme të tolerancës së gabimeve të komponentëve duhet të zgjidhnim për të marrë një sistem tolerant ndaj gabimeve. Unë mendoj se kjo do të jetë interesante për ata që gjithashtu zhvillojnë produkte në OpenStack.
Toleranca e përgjithshme e gabimeve të një platforme konsiston në elasticitetin e përbërësve të saj. Pra gradualisht do të kalojmë nëpër të gjitha nivelet ku kemi identifikuar rreziqet dhe i kemi mbyllur ato.
Versioni video i kësaj historie, burimi kryesor i së cilës ishte një raport në konferencën e ditës së katërt të Uptime, organizuar nga , ti mund te shohesh .
Rezistenca e arkitekturës fizike
Pjesa publike e resë MCS bazohet tani në dy qendra të të dhënave të nivelit III, midis tyre ekziston fibra e saj e errët, e rezervuar në nivel fizik nga rrugë të ndryshme, me një xhiros prej 200 Gbit/s. Niveli III siguron nivelin e nevojshëm të tolerancës ndaj gabimeve për infrastrukturën fizike.
Fibra e errët është e rezervuar si në nivelin fizik ashtu edhe në atë logjik. Procesi i rezervimit të kanalit ishte i përsëritur, u shfaqën probleme dhe ne po përmirësojmë vazhdimisht komunikimin midis qendrave të të dhënave.
Për shembull, jo shumë kohë më parë, ndërsa punonte në një pus pranë njërës prej qendrave të të dhënave, një ekskavator theu një tub, dhe brenda këtij tubi kishte një kabllo optike kryesore dhe një rezervë. Kanali ynë i komunikimit tolerant ndaj gabimeve me qendrën e të dhënave doli të ishte i prekshëm në një moment, në pus. Prandaj, ne kemi humbur një pjesë të infrastrukturës. Ne nxorëm përfundime dhe ndërmorëm një sërë veprimesh, duke përfshirë instalimin e optikës shtesë në pusin ngjitur.
Në qendrat e të dhënave ka pika të pranisë së ofruesve të komunikimit të cilëve u transmetojmë prefikset tona nëpërmjet BGP. Për çdo drejtim rrjeti, zgjidhet metrika më e mirë, e cila lejon klientë të ndryshëm të pajisen me cilësinë më të mirë të lidhjes. Nëse komunikimi përmes një ofruesi prishet, ne rindërtojmë rrugën tonë përmes ofruesve të disponueshëm.
Nëse një ofrues dështon, ne kalojmë automatikisht te tjetri. Në rast të dështimit të një prej qendrave të të dhënave, ne kemi një kopje pasqyre të shërbimeve tona në qendrën e dytë të të dhënave, të cilat marrin të gjithë ngarkesën.

Rezistenca e infrastrukturës fizike
Çfarë përdorim për tolerancën e gabimeve në nivelin e aplikacionit
Shërbimi ynë është ndërtuar mbi një numër komponentësh me burim të hapur.
ExaBGP është një shërbim që zbaton një sërë funksionesh duke përdorur protokollin dinamik të rrugëtimit të bazuar në BGP. Ne e përdorim atë në mënyrë aktive për të reklamuar adresat tona IP të listës së bardhë përmes të cilave përdoruesit hyjnë në API.
HAProxy është një balancues me ngarkesë të lartë që ju lejon të konfiguroni rregulla shumë fleksibël të balancimit të trafikut në nivele të ndryshme të modelit OSI. Ne e përdorim atë për të balancuar para të gjitha shërbimeve: bazat e të dhënave, ndërmjetësit e mesazheve, shërbimet API, shërbimet në ueb, projektet tona të brendshme - gjithçka është pas HAProxy.
Aplikacioni API — një aplikacion ueb i shkruar në python, me të cilin përdoruesi menaxhon infrastrukturën dhe shërbimin e tij.
Aplikimi i punëtorit (në tekstin e mëtejmë thjesht punëtor) - në shërbimet OpenStack, ky është një demon infrastrukturor që ju lejon të transmetoni komandat API në infrastrukturë. Për shembull, krijimi i diskut ndodh në punëtorin dhe kërkesa për krijimin ndodh në API të aplikacionit.
Arkitektura standarde e aplikacionit OpenStack
Shumica e shërbimeve që janë zhvilluar për OpenStack përpiqen të ndjekin një paradigmë të vetme. Një shërbim zakonisht përbëhet nga 2 pjesë: API dhe punëtorë (ekzekutues prapavijë). Si rregull, një API është një aplikacion WSGI në python, i cili lëshohet ose si një proces i pavarur (daemon), ose duke përdorur një server të gatshëm Nginx ose Apache. API përpunon kërkesën e përdoruesit dhe i kalon udhëzime të mëtejshme aplikacionit të punonjësit për ekzekutim. Transferimi ndodh duke përdorur një ndërmjetës mesazhesh, zakonisht RabbitMQ, të tjerët mbështeten dobët. Kur mesazhet arrijnë te ndërmjetësi, ato përpunohen nga punëtorët dhe, nëse është e nevojshme, kthejnë një përgjigje.
Kjo paradigmë përfshin pika të përbashkëta të izoluara të dështimit: RabbitMQ dhe bazën e të dhënave. Por RabbitMQ është i izoluar brenda një shërbimi dhe, në teori, mund të jetë individual për çdo shërbim. Pra, në MCS ne i ndajmë këto shërbime sa më shumë që të jetë e mundur; për çdo projekt individual ne krijojmë një bazë të dhënash të veçantë, një RabbitMQ të veçantë. Kjo qasje është e mirë sepse në rast aksidenti në disa pika vulnerabël nuk prishet i gjithë shërbimi, por vetëm një pjesë e tij.
Numri i aplikacioneve të punëtorëve është i pakufizuar, kështu që API mund të shkallëzohet lehtësisht horizontalisht pas balancuesve në mënyrë që të rrisë performancën dhe tolerancën ndaj gabimeve.
Disa shërbime kërkojnë koordinim brenda shërbimit kur ndodhin operacione komplekse të njëpasnjëshme midis API-ve dhe punonjësve. Në këtë rast, përdoret një qendër e vetme koordinimi, një sistem grupimi si Redis, Memcache, etj., i cili lejon një punonjës t'i tregojë një tjetri se kjo detyrë i është caktuar atij ("ju lutem mos e merrni"). Ne përdorim etjd. Si rregull, punëtorët komunikojnë në mënyrë aktive me bazën e të dhënave, shkruajnë dhe lexojnë informacione nga atje. Ne përdorim mariadb si një bazë të dhënash, e cila ndodhet në një grup multimaster.
Ky shërbim klasik i vetëm është i organizuar në një mënyrë të pranuar përgjithësisht për OpenStack. Mund të konsiderohet si një sistem i mbyllur, për të cilin metodat e shkallëzimit dhe tolerancës së gabimeve janë mjaft të dukshme. Për shembull, për tolerancën e gabimeve në API, mjafton të vendosni një balancues përpara tyre. Shkallëzimi i punëtorëve arrihet duke rritur numrin e tyre.
Pika e dobët në të gjithë skemën është RabbitMQ dhe MariaDB. Arkitektura e tyre meriton një artikull më vete.Në këtë artikull dua të fokusohem në tolerancën e gabimeve të API.

Arkitektura e Aplikimit Openstack. Balancimi dhe toleranca e gabimeve të platformës cloud
Bërja e balancuesit HAProxy tolerant ndaj gabimeve duke përdorur ExaBGP
Për t'i bërë API-të tona të shkallëzueshme, të shpejta dhe tolerante ndaj gabimeve, ne vendosim një balancues ngarkese përpara tyre. Ne zgjodhëm HAProxy. Sipas mendimit tim, ai ka të gjitha karakteristikat e nevojshme për detyrën tonë: balancim në disa nivele OSI, një ndërfaqe menaxhimi, fleksibilitet dhe shkallëzueshmëri, një numër i madh metodash balancimi, mbështetje për tabelat e sesioneve.
Problemi i parë që duhej zgjidhur ishte toleranca e gabimeve të vetë balancuesit. Thjesht instalimi i një balancuesi krijon gjithashtu një pikë dështimi: balancuesi prishet dhe shërbimi prishet. Për të parandaluar që kjo të ndodhë, ne përdorëm HAProxy në lidhje me ExaBGP.
ExaBGP ju lejon të zbatoni një mekanizëm për të kontrolluar gjendjen e një shërbimi. Ne përdorëm këtë mekanizëm për të kontrolluar funksionalitetin e HAProxy dhe, në rast problemesh, çaktivizonim shërbimin HAProxy nga BGP.
Skema ExaBGP+HAProxy
- Ne instalojmë softuerin e nevojshëm, ExaBGP dhe HAProxy, në tre serverë.
- Ne krijojmë një ndërfaqe loopback në çdo server.
- Në të tre serverët ne caktojmë të njëjtën adresë IP të bardhë në këtë ndërfaqe.
- Një adresë IP e bardhë reklamohet në internet përmes ExaBGP.
Toleranca ndaj gabimeve arrihet duke reklamuar të njëjtën adresë IP nga të tre serverët. Nga pikëpamja e rrjetit, e njëjta adresë është e aksesueshme nga tre kërcime të ndryshme të ardhshme. Ruteri sheh tre rrugë identike, zgjedh prioritetin më të lartë prej tyre bazuar në metrikën e tij (ky është zakonisht i njëjti opsion) dhe trafiku shkon vetëm në një nga serverët.
Në rast të problemeve me funksionimin e HAProxy ose një dështimi të serverit, ExaBGP ndalon shpalljen e rrugës dhe trafiku kalon pa probleme në një server tjetër.
Kështu, kemi arritur tolerancën e gabimeve të balancuesit.

Toleranca e gabimeve të balancuesve HAProxy
Skema rezultoi e papërsosur: ne mësuam se si të rezervonim HAProxy, por nuk mësuam se si ta shpërndajmë ngarkesën brenda shërbimeve. Prandaj, ne e zgjeruam pak këtë skemë: kaluam në balancimin midis disa adresave IP të bardha.
Balancimi i bazuar në DNS plus BGP
Çështja e balancimit të ngarkesës për HAProxy tonë mbetet e pazgjidhur. Sidoqoftë, mund të zgjidhet mjaft thjesht, siç bëmë këtu.
Për të balancuar tre serverë do t'ju nevojiten 3 adresa IP të bardha dhe DNS të mira të vjetra. Secila prej këtyre adresave përcaktohet në ndërfaqen loopback të çdo HAProxy dhe reklamohet në internet.
Në OpenStack, për të menaxhuar burimet, përdoret një direktori shërbimi, i cili specifikon API-në e pikës fundore të një shërbimi të caktuar. Në këtë direktori ne regjistrojmë një emër domain - public.infra.mail.ru, i cili zgjidhet përmes DNS nga tre adresa IP të ndryshme. Si rezultat, ne marrim shpërndarjen e ngarkesës midis tre adresave përmes DNS.
Por meqenëse kur shpallim adresat IP të bardha, ne nuk kontrollojmë përparësitë e përzgjedhjes së serverit, kjo nuk është ende e balancuar. Në mënyrë tipike, vetëm një server do të zgjidhet në bazë të vjetërsisë së adresës IP dhe dy të tjerët do të jenë të papunë sepse nuk specifikohen metrikë në BGP.
Ne filluam të dërgojmë rrugë përmes ExaBGP me metrika të ndryshme. Çdo balancues reklamon të tre adresat IP të bardha, por njëra prej tyre, ajo kryesore për këtë balancues, reklamohet me metrikën minimale. Pra, ndërsa të tre balancuesit janë në funksion, thirrjet në adresën IP të parë shkojnë te balancuesi i parë, thirrjet te i dyti te i dyti dhe thirrjet te i treti te i treti.
Çfarë ndodh kur një nga balancuesit bie? Nëse ndonjë balancues dështon, adresa e tij kryesore ende reklamohet nga dy të tjerët dhe trafiku rishpërndahet mes tyre. Kështu, ne i japim përdoruesit disa adresa IP menjëherë përmes DNS. Duke balancuar me DNS dhe metrika të ndryshme, marrim një shpërndarje të barabartë të ngarkesës në të tre balancuesit. Dhe në të njëjtën kohë ne nuk e humbim tolerancën ndaj fajit.

Balancimi i HAProxy bazuar në DNS + BGP
Ndërveprimi midis ExaBGP dhe HAProxy
Pra, ne kemi zbatuar tolerancën e gabimeve në rast se serveri largohet, bazuar në ndalimin e njoftimit të itinerareve. Por HAProxy mund të mbyllet për arsye të tjera përveç dështimit të serverit: gabime administrimi, dështime brenda shërbimit. Ne duam të heqim balancuesin e prishur nga nën ngarkesë edhe në këto raste dhe na duhet një mekanizëm tjetër.
Prandaj, duke zgjeruar skemën e mëparshme, ne implementuam rrahjet e zemrës midis ExaBGP dhe HAProxy. Ky është një zbatim softuerik i ndërveprimit midis ExaBGP dhe HAProxy, kur ExaBGP përdor skriptet e personalizuara për të kontrolluar statusin e aplikacioneve.
Për ta bërë këtë, duhet të konfiguroni një kontrollues shëndetësor në konfigurimin ExaBGP, i cili mund të kontrollojë statusin e HAProxy. Në rastin tonë, ne konfiguruam backend-in shëndetësor në HAProxy, dhe nga ana ExaBGP ne kontrollojmë me një kërkesë të thjeshtë GET. Nëse njoftimi ndalon së ndodhuri, atëherë HAProxy ka shumë të ngjarë të mos funksionojë dhe nuk ka nevojë ta reklamoni atë.

Kontrolli shëndetësor HAProxy
HAProxy Peers: sinkronizimi i sesioneve
Gjëja tjetër për të bërë ishte sinkronizimi i seancave. Kur punoni përmes balancuesve të shpërndarë, është e vështirë të organizoni ruajtjen e informacionit për seancat e klientit. Por HAProxy është një nga balancuesit e paktë që mund ta bëjë këtë për shkak të funksionalitetit Peers - aftësia për të transferuar tabelat e sesioneve midis proceseve të ndryshme HAProxy.
Ka metoda të ndryshme balancimi: të thjeshta si p.sh , dhe zgjatet, kur kujtohet sesioni i klientit dhe çdo herë ai përfundon në të njëjtin server si më parë. Ne donim të zbatonim opsionin e dytë.
HAProxy përdor tabela ngjitëse për të ruajtur seancat e klientit të këtij mekanizmi. Ata ruajnë adresën IP origjinale të klientit, adresën e zgjedhur të synuar (backend) dhe disa informacione shërbimi. Në mënyrë tipike, tabelat ngjitëse përdoren për të ruajtur një çift burim-IP + destinacion-IP, i cili është veçanërisht i dobishëm për aplikacionet që nuk mund të transferojnë kontekstin e sesionit të përdoruesit kur kalojnë në një balancues tjetër, për shembull, në modalitetin e balancimit RoundRobin.
Nëse një tavolinë ngjitëse mësohet të lëvizë ndërmjet proceseve të ndryshme HAProxy (ndërmjet të cilave ndodh balancimi), balancuesit tanë do të jenë në gjendje të punojnë me një grup tabelash ngjitëse. Kjo do të bëjë të mundur ndërrimin pa probleme të rrjetit të klientit nëse një nga balancuesit dështon; puna me seancat e klientit do të vazhdojë në të njëjtat backend që janë zgjedhur më parë.
Për funksionimin e duhur, duhet të zgjidhet problemi i adresës IP të burimit të balancuesit nga i cili është krijuar seanca. Në rastin tonë, kjo është një adresë dinamike në ndërfaqen loopback.
Puna korrekte e bashkëmoshatarëve arrihet vetëm në kushte të caktuara. Kjo do të thotë, afatet e TCP duhet të jenë mjaft të mëdha ose kalimi duhet të jetë mjaft i shpejtë në mënyrë që sesioni TCP të mos ketë kohë për t'u përfunduar. Megjithatë, lejon ndërrimin pa probleme.
Në IaaS ne kemi një shërbim të ndërtuar duke përdorur të njëjtën teknologji. Kjo , e cila quhet Octavia. Ai bazohet në dy procese HAProxy dhe fillimisht përfshin mbështetje për kolegët. Ata janë treguar të shkëlqyer në këtë shërbim.
Fotografia tregon në mënyrë skematike lëvizjen e tabelave të kolegëve midis tre instancave HAProxy, propozohet një konfigurim se si mund të konfigurohet kjo:

HAProxy Peers (sinkronizimi i sesionit)
Nëse zbatoni të njëjtën skemë, funksionimi i saj duhet të testohet me kujdes. Nuk është fakt që do të funksionojë në të njëjtën formë 100% të rasteve. Por të paktën nuk do t'i humbisni tabelat ngjitëse kur duhet të mbani mend IP-në e burimit të klientit.
Kufizimi i numrit të kërkesave të njëkohshme nga i njëjti klient
Çdo shërbim që është i disponueshëm publikisht, duke përfshirë API-të tona, mund t'i nënshtrohet ortekëve të kërkesave. Arsyet për to mund të jenë krejtësisht të ndryshme, nga gabimet e përdoruesve deri te sulmet e synuara. Ne jemi DDoSed periodikisht nga adresat IP. Klientët shpesh bëjnë gabime në skriptet e tyre dhe na japin mini-DDoS.
Në një mënyrë apo tjetër, duhet të sigurohet mbrojtje shtesë. Zgjidhja e qartë është të kufizoni numrin e kërkesave API dhe të mos humbni kohën e CPU-së për të përpunuar kërkesat me qëllim të keq.
Për të zbatuar kufizime të tilla, ne përdorim kufijtë e tarifave, të organizuara në bazë të HAProxy, duke përdorur të njëjtat tabela ngjitëse. Vendosja e kufijve është mjaft e thjeshtë dhe ju lejon të kufizoni përdoruesin me numrin e kërkesave në API. Algoritmi kujton IP-në e burimit nga i cili bëhen kërkesat dhe kufizon numrin e kërkesave të njëkohshme nga një përdorues. Sigurisht, ne kemi llogaritur profilin mesatar të ngarkesës API për çdo shërbim dhe kemi vendosur një kufi prej ≈ 10 herë më shumë se kjo vlerë. Ne vazhdojmë të monitorojmë nga afër situatën dhe mbajmë gishtin në pulsin.
Si duket kjo në praktikë? Ne kemi klientë që përdorin API-të tona të shkallëzimit automatik gjatë gjithë kohës. Ata krijojnë afërsisht dy deri në treqind makina virtuale në mëngjes dhe i fshijnë ato në mbrëmje. Për OpenStack, krijimi i një makinerie virtuale, gjithashtu me shërbime PaaS, kërkon të paktën 1000 kërkesa API, pasi ndërveprimi ndërmjet shërbimeve ndodh edhe përmes API-së.
Një transferim i tillë i detyrave shkakton një ngarkesë mjaft të madhe. Ne e vlerësuam këtë ngarkesë, mblodhëm majat ditore, i dhjetëfishuam dhe ky u bë kufiri ynë i tarifës. Ne e mbajmë gishtin tek pulsi. Ne shpesh shohim robotë dhe skanerë që përpiqen të na shikojnë për të parë nëse kemi ndonjë skript CGA që mund të ekzekutohet, ne po i presim ato në mënyrë aktive.
Si të përditësoni bazën tuaj të kodit pa e vënë re përdoruesit
Ne gjithashtu zbatojmë tolerancën e gabimeve në nivelin e proceseve të vendosjes së kodit. Mund të ketë defekte gjatë prezantimit, por ndikimi i tyre në disponueshmërinë e shërbimit mund të minimizohet.
Ne përditësojmë vazhdimisht shërbimet tona dhe duhet të sigurojmë që baza e kodeve të përditësohet pa ndikuar tek përdoruesit. Ne arritëm ta zgjidhim këtë problem duke përdorur aftësitë e menaxhimit të HAProxy dhe zbatimin e Graceful Shutdown në shërbimet tona.
Për të zgjidhur këtë problem, ishte e nevojshme të sigurohet kontrolli i balancuesit dhe mbyllja "korrekte" e shërbimeve:
- Në rastin e HAProxy, kontrolli kryhet përmes një skedari statistikor, i cili në thelb është një fole dhe përcaktohet në konfigurimin HAProxy. Ju mund t'i dërgoni komanda nëpërmjet stdio. Por mjeti ynë kryesor i kontrollit të konfigurimit është i mundshëm, kështu që ka një modul të integruar për menaxhimin e HAProxy. Të cilat ne i përdorim në mënyrë aktive.
- Shumica e shërbimeve tona API dhe Engine mbështesin teknologjitë e këndshme të mbylljes: kur mbyllen, ata presin që detyra aktuale të përfundojë, qoftë një kërkesë http ose ndonjë detyrë shërbimi. E njëjta gjë ndodh me punëtorin. Ai i di të gjitha detyrat që po bën dhe përfundon kur të ketë përfunduar me sukses gjithçka.
Falë këtyre dy pikave, algoritmi i sigurt për vendosjen tonë duket kështu.
- Zhvilluesi mbledh një paketë të re kodi (për ne kjo është RPM), e teston atë në mjedisin e dev, e teston në fazë dhe e lë në depon e fazës.
- Zhvilluesi vendos detyrën për vendosjen me përshkrimin më të detajuar të "artefakteve": versionin e paketës së re, një përshkrim të funksionalitetit të ri dhe detaje të tjera në lidhje me vendosjen nëse është e nevojshme.
- Administratori i sistemit fillon përditësimin. Nis librin e lojërave Ansible, i cili nga ana tjetër bën sa vijon:
- Merr një paketë nga depoja e fazës dhe e përdor atë për të përditësuar versionin e paketës në depon e produktit.
- Përpilon një listë të backend-eve të shërbimit të përditësuar.
- Fik shërbimin e parë që do të përditësohet në HAProxy dhe pret që proceset e tij të përfundojnë ekzekutimin. Falë mbylljes së këndshme, ne kemi besim se të gjitha kërkesat aktuale të klientëve do të përfundojnë me sukses.
- Pasi API dhe punëtorët janë ndalur plotësisht dhe HAProxy është fikur, kodi përditësohet.
- Shërbimet e ekzekutimit të Ansible.
- Për çdo shërbim, tërhiqen "doreza" të caktuara, të cilat kryejnë testimin e njësisë në një numër testesh të paracaktuara të çelësave. Bëhet një kontroll bazë i kodit të ri.
- Nëse nuk u gjetën gabime në hapin e mëparshëm, sistemi mbështetës aktivizohet.
- Le të kalojmë në fundin tjetër.
- Pasi të gjitha backend-et të përditësohen, nisin testet funksionale. Nëse ato mungojnë, atëherë zhvilluesi shikon çdo funksionalitet të ri që krijoi.
Kjo përfundon vendosjen.

Cikli i përditësimit të shërbimit
Kjo skemë nuk do të funksiononte nëse nuk do të kishim një rregull. Ne mbështesim si versionin e vjetër ashtu edhe atë të ri në betejë. Paraprakisht, në fazën e zhvillimit të softuerit, parashikohet që edhe nëse ka ndryshime në bazën e të dhënave të shërbimit, ato nuk do të thyejnë kodin e mëparshëm. Si rezultat, baza e kodit përditësohet gradualisht.
Përfundim
Duke ndarë mendimet e mia për një arkitekturë WEB tolerante ndaj gabimeve, do të doja të shënoja edhe një herë pikat kryesore të saj:
- toleranca fizike ndaj defekteve;
- toleranca e gabimeve në rrjet (balancues, BGP);
- toleranca ndaj gabimeve të softuerit të përdorur dhe zhvilluar.
Koha e qëndrueshme për të gjithë!
Burimi: www.habr.com
