
Përshëndetje, Habr! Unë jam Artem Karamyshev, udhëheqësi i ekipit të administratës së sistemeve . Në vitin e kaluar kemi pasur shumë nisje të produkteve të reja. Ne deshëm që shërbimet API të ishin lehtësisht të shkallëzueshme, të ishin të besueshme dhe të gatshme për rritjen e shpejtë të ngarkesës së përdoruesve. Platforma jonë është realizuar në OpenStack, dhe dua të flas për problemet e qëndrueshmërisë së komponentëve që ne duhej të zgjidhnim për të arritur një sistem të qëndrueshëm. Mendoj se kjo do të jetë interesante për ata që gjithashtu zhvillojnë produkte në OpenStack.
Qëndrueshmëria totale e platformës përbëhet nga qëndrueshmëria e komponenteve të saj. Kështu që ne do të kalojmë gradualisht nëpër të gjitha nivelet, ku kemi zbuluar rreziqe dhe i kemi zgjidhur ato.
Versionin në video të kësaj historie, e cila është burimi i raportit në konferencën Uptime day 4, të organizuar nga , mund ta shikoni .
Qëndrueshmëria e arkitekturës fizike
Pjesa publike e cloud-it MCS tani bazohet në dy qendra të të dhënave nivel Tier III, ndërmjet të cilave ka fibra të errët të rezervuar, të rezervuar në nivelin fizik me rrugë të ndryshme, me kapacitet prej 200 Gbit/s. Niveli Tier III ofron nivelin e kërkuar të qëndrueshmërisë së infrastrukturës fizike.
Fibra e errët është rezervuar si në nivelin fizik ashtu edhe në atë logjik. Procesi i rezervimit të kanaleve ka qenë iterativ, kemi hasur në probleme dhe ne vazhdimisht po përmirësojmë lidhjen ndërmjet qendrave të të dhënave.
Për shembull, jo shumë kohë më parë, gjatë punimeve në një gropë pranë një prej qendrave të të dhënave, ekskavatori e goditi një tub, brenda të cilit ishin si kablloja optike kryesore ashtu edhe ajo rezervë. Kanali ynë i qëndrueshmërisë me qendrën e të dhënave doli të ishte vulnerabël në një pikë, në gropë. Në përputhje, humbëm pjesë të infrastrukturës. Ne nxorrëm përfundime, u ndërmorrëm disa veprime, duke përfshirë vendosjen e optikës shtesë në gropën përballë.
Në qendrat e të dhënave ka pika pranisë të ofruesve të komunikimit, të cilëve ne u transmetojmë prefiksat tona nëpërmjet BGP. Për çdo drejtim rrjeti zgjidhet metrika më e mirë, çka lejon ofrimin e cilësisë më të mirë të lidhjes për klientët tanë të ndryshëm. Nëse lidhja përmes një ofruesi ndalon, ne ristrukturojmë rrugëtimin tonë përmes ofruesve të disponueshëm.
Në rast të dështimit të ofruesit, ne kalojmë automatikisht në të ardhshmin. Në rast të dështimit të njërit prej qendrave të të dhënave, ne kemi një kopje pasqyrë të shërbimeve tona në qendrën e dytë të të dhënave, të cilat mbajnë të gjithë ngarkesën.

Qëndrueshmëria e infrastrukturës fizike
Çfarë përdorim për qëndrueshmërinë në nivelin e aplikacioneve
Shërbimi ynë është ndërtuar mbi një sërë komponentësh opensource.
ExaBGP — shërbimi që implementon një sërë funksionesh duke përdorur protokollin e rrugëzimit dinamik mbi bazën e BGP. Ne e përdorim aktivisht këtë për të shpallur adresat tona të bardha IP, përmes të cilave përdoruesit kanë qasje në API.
HAProxy — një balancues i ngarkesës me performancë të lartë, i cili mundëson konfigurimin e rregullave shumë fleksibël të balancimit të trafikut në nivele të ndryshme të modelit OSI. Ne e përdorim atë për balancimin para të gjitha shërbimeve: bazat e të dhënave, brokerët e mesazheve, shërbimet API, shërbimet web, projektet tona të brendshme — gjithçka është prapa HAProxy.
API application — një aplikacion web, i shkruar në python, me të cilin përdoruesi menaxhon infrastrukturën e tij, shërbimin e tij.
Worker application (tani thjesht worker) — në shërbimet OpenStack, kjo është një demon infrastrukture, i cili mundëson transmetimin e komandave API në infrastrukturë. Për shembull, krijimi i një disku ndodh vetëm në worker, ndërsa kërkesa për krijim ndodh në API application.
Arkitektura standarde e OpenStack Application
Shumica e shërbimeve që zhvillohen për OpenStack përpiqen të ndjekin një paradigëm të përbashkët. Shërbimi zakonisht përbëhet nga 2 pjesë: API dhe worker-at (ekzekutorët e backend). Rregullisht, API është një aplikacion WSGI në python, i cili ekzekutohet ose si një proces i pavarur (daemon), ose me ndihmën e një serveri web të gatshëm si Nginx ose Apache. API përpunon kërkesën e përdoruesit dhe i dërgon udhëzime të mëtejshme aplikacionit worker. Dërgesa ndodh përmes një brokera mesazhe, zakonisht RabbitMQ, të tjerët mbështeten dobët. Kur mesazhet arrijnë në broker, ato përpunohen nga worker-at dhe në rast nevoje kthejnë përgjigje.
Kjo paradigëm nënkupton pika të izoluara të përbashkëta të dështimit: RabbitMQ dhe baza e të dhënave. Por RabbitMQ është i izoluara brenda një shërbimi dhe në ide mund të jetë individual për secilin shërbim. Prandaj, ne në MCS e ndajmë sa më shumë që të jetë e mundur këto shërbime, krijojmë një bazë të veçantë për çdo projekt të veçantë, një RabbitMQ të veçantë. Ky qasje është e mira sepse në rast avarie në disa pika të prekshme, nuk dështojë krejt shërbimi, por vetëm një pjesë e tij.
Numri i aplikacioneve worker nuk është kufizuar, kështu që API mund të shkallëzohet lehtësisht horizontalisht pas balancuesve për të rritur performancën dhe qëndrueshmërinë ndaj dështimeve.
Në disa shërbime kërkohet koordinimi brenda shërbimit — kur ndodhin operacione komplekse sekondarë midis API dhe worker-ave. Në këtë rast përdoret një qendër koordinimi e vetme, një sistem klasteri si Redis, Memcache, etcd, i cili lejon që një worker të informojë tjetrin se kjo detyrë është e caktuar për të (“ti, të lutem, mos e merr”). Ne përdorim etcd. Zakonisht, worker-at komunikojnë aktivisht me bazën e të dhënave, shkruajnë dhe lexojnë informacione prej aty. Si bazë të dhënash ne përdorim mariadb, e cila ndodhet në klasterin tonë multi-master.
Ky shërbim klasik i vetëm është organizuar në një mënyrë të zakonshme për OpenStack. Ai mund të konsiderohet si një sistem i mbyllur, për të cilin mënyrat e shkallëzimit dhe qëndrueshmërisë janë mjaft të qarta. Për shembull, për qëndrueshmërinë, API mjafton të ketë një balancues para tyre. Shkallëzimi i worker-ave 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 të veçantë. Në këtë artikull do të fokusohem te qëndrueshmëria e API-së.

Arkitektura e Openstack Application. Balancimi dhe qëndrueshmëria e platformës cloud.
E bëjmë balancuesin HAProxy të qëndrueshëm me ndihmën e ExaBGP.
Për të siguruar që API-të tona ishin të shkallëzuara, të shpejta dhe të qëndrueshme, vendosëm një balancues para tyre. Zgjedhja jonë ishte HAProxy. Sipas mendimit tim, ai ka të gjitha karakteristikat e nevojshme për detyrën tonë: balancimi në disa nivele OSI, ndërfaqe menaxhimi, fleksibilitet dhe shkallëzim, një numër i madh metodash balancimi, mbështetje për tabelat e sesioneve.
Problemi i parë që duhej zgjidhur ishte qëndrueshmëria e vetë balancuesit. Thjesht instalimi i balancuesit gjithashtu krijon një pikë dështimi: nëse balancuesi dështoi, shërbimi bie. Për të mos ndodhur kjo, ne përdorëm HAProxy së bashku me ExaBGP.
ExaBGP lejon realizimin e mekanizmit të kontrollit të gjendjes së shërbimit. Ne e përdorëm këtë mekanizëm për të kontrolluar funksionimin e HAProxy dhe, në rast problemesh, për të çaktivizuar shërbimin HAProxy nga BGP.
Skema ExaBGP+HAProxy
- Instalojmë softin e nevojshëm, ExaBGP dhe HAProxy, në tre servera.
- Në secilin prej serverëve krijojmë një ndërfaqe loopback.
- Në të tre serverat vendosim të njëjtin adresë IP të bardhë në këtë ndërfaqe.
- Adresa IP e bardhë shpallet në internet përmes ExaBGP.
Qëndrueshmëria arrihet duke shpallur të njëjtën adresë IP nga të tre serverat. Nga pikëpamja e rrjetit, e njëjta adresë është e aksesueshme nga tre hop-e të ndryshme. Routeri sheh tre rrugë të njëjta, zgjedh sipas metrike të tij më prioritare (zakonisht kjo është e njëjta mundësi), dhe trafiku kalon vetëm në një nga serverat.
Në rast problemesh me punën e HAProxy ose dështimit të serverit, ExaBGP pushton shpalljen e rrugës, dhe trafiku kalon në mënyrë të qetë në serverin tjetër.
Kështu kemi arritur qëndrueshmërinë e balancuesit.

Qëndrueshmëria e balancuesve HAProxy
Skema doli e paideale: ne mësuam të rezervojmë HAProxy, por nuk mësuam të shpërndajmë ngarkesën brenda shërbimeve. Prandaj, e zgjeruam pak këtë skemë: kaluam në balancimin midis disa adresave IP të bardha.
Balancimi mbi bazën e DNS plus BGP
Pyetja për balancimin e ngarkesës përpara HAProxy tonë mbetet e pazgjidhur. Megjithatë, zgjidhja e saj është mjaft e thjeshtë, siç bëmë dhe ne.
Për balancimin e tre serverëve nevojiten 3 adresa IP të bardha dhe DNS i vjetër. Çdo një nga këto adresa përcaktohet në interfacin loopback të çdo HAProxy dhe publikohet në internet.
Në OpenStack, për menaxhimin e burimeve, përdoret një katalog shërbimesh në të cilin përcaktohet endpoint API i shërbimeve të ndryshme. Në këtë katalog ne shkruajmë emrin e domenit — public.infra.mail.ru, i cili rezolvohet përmes DNS me tre adresa IP të ndryshme. Si rezultat, ne marrim shpërndarjen e ngarkesës ndërmjet tre adresave përmes DNS.
Por pasi në publikimin e adresave IP të bardha ne nuk menaxhojmë prioritetet e zgjedhjes së serverit, tani kjo nuk është balancim. Në rregull, zakonisht do të zgjidhet vetëm një server sipas rendit të adresës IP, ndërsa dy të tjerat do të ngelin të papërdorura, pasi nuk janë përcaktuar metrika në BGP.
Kemi filluar të ndajmë rrugët përmes ExaBGP me metrika të ndryshme. Secili balancues publikon të tre adresat IP të bardha, por njëri prej tyre, kryesor për këtë balancues, publikohet me metrikën më të ulët. Kështu që për sa kohë që të tre balancuesit janë në funksion, kërkesat për adresën IP të parë shkojnë te balancuesi i parë, kërkesat për adresën e dytë shkojnë te i dyti, dhe për të tretin te i treti.
Çfarë ndodh në momentin kur njëri nga balancuesit dështon? Në rast të ndalimit të ndonjë balancuesi, adresa e tij kryesore akoma publikohet nga dy të tjerët, trafiku midis tyre ri-shpërndahet. Kështu, ne i ofrojmë përdoruesit përmes DNS disa adresa IP menjëherë. Duke përdorur balancimin me DNS dhe metrika të ndryshme, ne arrijmë një shpërndarje të barabartë të ngarkesës mbi të tre balancuesit. Dhe në këtë mënyrë nuk humbasim qëndrueshmërinë.

Balancimi i HAProxy mbi bazën e DNS + BGP
Ndërveprimi midis ExaBGP dhe HAProxy
Pra, ne kemi implementuar qëndrueshmëri në rast se serveri largohet, mbi bazën e ndalimit të publikimit të rrugëve. Por HAProxy mund të ndalet për arsye të tjera, përveç se dështimi i serverit: gabime administratoriale, dështime brenda shërbimit. Ne duam të largojmë balancuesin e prishur nga ngarkesa edhe në këto raste, dhe na nevojitet një mekanizëm tjetër.
Prandaj, duke zgjeruar skemën e mëparshme, ne kemi realizuar heartbeat midis ExaBGP dhe HAProxy. Kjo është një realizim softuerik i ndërveprimit midis ExaBGP dhe HAProxy, kur ExaBGP përdor skripte të personalizuara për të kontrolluar statusin e aplikacioneve.
Për këtë, në konfigurimin e ExaBGP, është e nevojshme të konfigurohet një kontrollues shëndeti, i cili mund të kontrollojë statusin e HAProxy. Në rastin tonë, ne konfigurim një shëndet të prapavijës në HAProxy, dhe nga ana e ExaBGP kontrollojmë me një kërkesë të thjeshtë GET. Nëse njoftimi ndalet, atëherë HAProxy, shumë mundësisht, nuk funksionon, dhe nuk duhet ta njoftojmë.

Kontrolli i Shëndetit në HAProxy
HAProxy Peers: sinkronizimi i seancave
E ardhmja, që duhej të bëhej, ishte sinkronizimi i seancave. Kur punojmë përmes balancuesve të shpërndarë, është e vështirë të organizohet ruajtja e informacionit për seancat e klientëve. Por HAProxy është një nga pak balancuesit që e bën këtë përmes funksionalitetit Peers — mundësia e kalimit të tabelave të seancave midis proceseve të ndryshme të HAProxy.
Ekzistojnë metoda të ndryshme balancimi: të thjeshta, si , dhe të avancuara, kur ruhet seanca e klientit dhe ai gjithmonë shkon në serverin e njëjtë, ashtu siç ishte më parë. Ne deshëm të realizojmë variantin e dytë.
Në HAProxy, për ruajtjen e seancave të klientëve, ky mekanizëm përdor stick-tables. Ato ruajnë adresën IP origjinale të klientit, adresën e përzgjedhur të targetit (bëhen) dhe disa informacione të shërbimeve. Zakonisht, stick-tabelat përdoren për ruajtjen e çiftit source-IP + destination-IP, që është veçanërisht e dobishme për aplikacionet që nuk mund të kalojnë kontekstin e seancës së përdoruesit kur kalojnë në një balancues tjetër, për shembull — në modin e balancimit RoundRobin.
Nëse stick-tabela mësohet të lëvizë midis proceseve të ndryshme të HAProxy (midis të cilëve ndodh balancimi), balancuesit tanë do të jenë në gjendje të punojnë me një grup stick-tabelash. Kjo do të ofrojë mundësinë e kalimeve pa ndërprerje të rrjetit të klientëve në rastin e rënies së njërit nga balancuesit, dhe puna me seancat e klientëve do të vazhdojë në të njëjtat bëri që ishin përzgjedhur më parë.
Për funksionimin e duhur, duhet të zgjidhet problemi i adresës IP burimore të balancuesit, nga i cili është vendosur seanca. Në rastin tonë, kjo është një adresë dinamike në ndërfaqen loopback.
Puna funksionimi i peers arrin vetëm nën kushte të caktuara. Kjo do të thotë se, kohët e pritjes TCP duhet të jenë mjaft të mëdha ose kalimi duhet të jetë mjaft i shpejtë, në mënyrë që seanca TCP të mos ndërpritet. Megjithatë, kjo lejon kalimin pa probleme.
Ne kemi një shërbim në IaaS të ndërtuar me të njëjtën teknologji. Ky është , i quajtur Octavia. Ai mbështetet në dy procese HAProxy dhe ka një mbështetje të integruar për peers. Në këtë shërbim ata kanë treguar rezultate të shkëlqyera.
Në ilustrim është paraqitur lëvizja e tabelave të peers midis tre instancave të HAProxy, me një konfigurim të sugjeruar se si mund të organizohet kjo:

HAProxy Peers (sinkronizimi i seancave)
Nëse do të zbatoni një skemë të tillë, duhet të testoni kujdesshëm funksionimin e saj. Nuk ka garanci se kjo do të funksionojë në të njëjtën mënyrë në 100% të rasteve. Por, së paku, nuk do të humbni tabelat stick kur duhet të mbani mend IP-në burimore të klientit.
Kufizimi i numrit të kërkesave të njëkohshme nga një klient i vetëm
Çdo shërbim që është në akses të hapur, përfshirë API-të tona, mund të jetë subjekt i përmbytjeve të kërkesave. Shkaqet e tyre mund të jenë krejtësisht të ndryshme, nga gabimet e përdoruesve deri te sulmet e qëlluara. Neherë DDoS-ojmë përmes adresave IP. Klientët shpesh gabojnë në skriptet e tyre, duke na bërë mini-DDoS.
Kështu ose ndryshe, duhet të parashikojmë mbrojtje shtesë. Një zgjidhje e qartë është të kufizojmë numrin e kërkesave në API dhe të mos e harxhojmë kohën e procesorit për të trajtuar kërkesat keqdashëse.
Për zbatimin e kufizimeve të tilla, ne përdorim kufizime të normës, të organizuara mbi bazën e HAProxy, me ndihmën e tabelave stick. Kufijtë konfigurohen mjaft lehtë dhe lejojnë kufizimin e përdoruesit në numrin e kërkesave në API. Algoritmi mban mend IP-në burimore nga e cila bëhen kërkesat dhe kufizon numrin e kërkesave të njëkohshme nga një përdorues i vetëm. Natyrisht, kemi llogaritur profilin mesatar të ngarkesës në API për çdo shërbim dhe kemi vendosur një limit ≈ 10 herë më të lartë se ky vlerë. Ne vazhdojmë të monitorojmë situatën me kujdes, duke mbajtur dorën në pulsin e saj.
Si e duket kjo në praktikë? Ne kemi klientë që përdorin vazhdimisht API-të tona për automatikimin e shkallëzimit. Ata krijojnë rreth dyqind deri në treqind makina virtuale në mëngjes dhe i fshijnë ato në mbrëmje. Për OpenStack, krijimi i një makine virtuale, së bashku me shërbimet PaaS, kërkon të paktën 1000 kërkesa API, për shkak se ndërveprimi midis shërbimeve ndodh gjithashtu përmes API-së.
Këto kalime detyrash shkaktojnë një ngarkesë mjaft të madhe. Ne e kemi vlerësuar këtë ngarkesë, kemi mbledhur pikat ditore, i kemi rritur ato dhjetë herë, dhe kjo u bë kufiri ynë i normës. Ne e mbajmë dorën mbi pulsin. Shpesh shohim bota, skanerë që përpiqen të shohin nëse kemi ndonjë skripte CGA që mund të lancohen, dhe ne i eliminojmë ato në mënyrë aktive.
Si të përditësoni bazën e kodit pa u vënë re nga përdoruesit
Ne zbatojmë qëndrueshmërinë gjithashtu në nivelin e proceseve të shpërndarjes së kodit. Gjatë publikimeve ka dështime, por ndikimi i tyre në disponueshmërinë e shërbimeve mund të minimizohet.
Ne vazhdojmë të përditësojmë shërbimet tona dhe duhet të sigurojmë procesin e përditësimit të bazës së kodit pa ndikim për përdoruesit. Kjo detyrë u zgjidh duke përdorur mundësitë e menaxhimit të HAProxy dhe realizimin e Graceful Shutdown në shërbimet tona.
Për zgjidhjen e kësaj detyre nevojitej të sigurojmë menaxhimin e balancuesit dhe "nderrimin e duhur" të shërbimeve:
- Në rastin e HAProxy, menaxhimi bëhet përmes skedarit stats, i cili në thelb është një soket dhe përcaktohet në konfigurimin e HAProxy. Komandat mund të kalohen përmes stdio. Por instrumenti ynë kryesor për kontrollin e konfiguracioneve është ansible, prandaj kemi një modul të ndërtuar për menaxhimin e HAProxy. Të cilin e përdorim aktivisht.
- Shumica e shërbimeve tona API dhe Engine mbështesin teknologjitë e graceful shutdown: kur janë duke u fikur ata presin që të përfundojnë plotësisht detyrën aktuale, qoftë kjo një kërkesë http ose ndonjë detyrë ndihmëse. E njëjta gjë ndodh me punëtorin. Ai di të gjitha detyrat që bën dhe përfundon kur i ka përfunduar me sukses të gjitha.
Falë këtyre dy momenteve, algoritmi ynë të sigurt të shpërndarjes duket si në vazhdim.
- Zhvilluesi përgatit një paketë të re kod (për ne është RPM), e teston në mjedisin dev, e teston në stage, dhe e lë në repositorin e stage.
- Zhvilluesi vendos detyrën për desploin me një përshkrim sa më të detajuar të "artefakteve": versioni i paketës së re, përshkrimi i funksionalitetit të ri dhe detaje të tjera mbi desploin në rast nevoje.
- Administrator sistemi fillon azhurnimin. Aktivizon playbook-un Ansible, i cili nga ana e tij bën si më poshtë:
- Merr paketën nga repozitori stage, për të azhurnuar versionin e paketës në repozitorin e produktit.
- Përgatit një listë të backend-ëve të shërbimit në azhurnim.
- Çon në fikjen e shërbimit të parë në HAProxy dhe pret përfundimin e proceseve të tij. Falë ndaljes pa dhunë, ne jemi të sigurt se të gjitha kërkesat aktuale të klientëve do të përfundojnë me sukses.
- Pas ndalimit të plotë të API-t, punëtorëve, dhe fikjes së HAProxy, bëhet azhurnimi i kodit.
- Ansible aktivizon shërbimet.
- Për çdo shërbim aktivizon "tërheqje" specifike, të cilat kryejnë teste jednostike për një seri testesh kyçe të caktuara më parë. Bëhet një kontroll bazik i kodit të ri.
- Nëse në hapin e mëparshëm nuk u gjetën gabime, backend-i aktivizohet.
- Kalohet te backend-i i ardhshëm.
- Pas azhurnimit të të gjithë backend-ëve, aktivizohen testet funksionale. Nëse ato mungojnë, zhvilluesi shqyrton çdo funksionalitet të ri që ka krijuar.
Në këtë pikë, desploi ka përfunduar.

Cikli i azhurnimit të shërbimit
Kjo skemë nuk do të ishte funksionale nëse nuk do të kishim një rregull. Ne mbështesim në prodhim në të njëjtën kohë versionin e vjetër dhe atë të ri. Përpara, në fazën e zhvillimit të softuerit, parashikohet që edhe nëse do të kenë ndryshime në bazën e të dhënave të shërbimit, ato nuk do të prisnin kodin e mëparshëm. Si rezultat, ndodh një azhurnim gradual i bazës së kodit.
Përfundimi
Duke ndarë mendimet e mia për arkitekturën e WEB-it të qëndrueshëm, dua të theksoj përsëri pikat e saj kryesore:
- qëndrueshmëria fizike;
- qëndrueshmëria e rrjetit (balancuesit, BGP);
- qëndrueshmëria e softuerit të përdorur dhe të zhvilluar.
Të gjithë me uptime të qëndrueshëm!
Burimi: habr.com
