
Hallo, Habr! Ek is Artem Karamyshev, hoof van die stelseladministrasiespan . Ons het die afgelope jaar baie nuwe produkbekendstellings gehad. Ons wou verseker dat API-dienste maklik skaalbaar, foutverdraagsaam en gereed is vir vinnige groei in gebruikerslading. Ons platform is op OpenStack geïmplementeer, en ek wil jou vertel watter komponent fouttoleransie probleme ons moes oplos om 'n foutverdraagsame stelsel te kry. Ek dink dit sal interessant wees vir diegene wat ook produkte op OpenStack ontwikkel.
Die algehele fouttoleransie van 'n platform bestaan uit die veerkragtigheid van sy komponente. Ons sal dus geleidelik deur al die vlakke gaan waar ons risiko's geïdentifiseer en gesluit het.
Videoweergawe van hierdie storie, waarvan die primêre bron 'n verslag was by die Uptime day 4-konferensie, georganiseer deur , jy kan sien .
Veerkragtigheid van die fisiese argitektuur
Die publieke deel van die MCS-wolk is nou gebaseer in twee Tier III-datasentrums, tussen hulle is daar sy eie donker vesel, gereserveer op die fisiese vlak deur verskillende roetes, met 'n deurset van 200 Gbit/s. Vlak III verskaf die nodige vlak van fouttoleransie vir die fisiese infrastruktuur.
Donker vesel word op beide die fisiese en logiese vlak gereserveer. Die kanaalbesprekingsproses was iteratief, probleme het ontstaan, en ons verbeter voortdurend kommunikasie tussen datasentrums.
Byvoorbeeld, nie lank gelede nie, terwyl hy in 'n put naby een van die datasentrums gewerk het, het 'n graafmasjien 'n pyp gebreek, en binne-in hierdie pyp was daar beide 'n hoof- en 'n rugsteun optiese kabel. Ons foutverdraagsame kommunikasiekanaal met die datasentrum het op 'n stadium, in die put, kwesbaar geblyk te wees. Gevolglik het ons 'n deel van die infrastruktuur verloor. Ons het gevolgtrekkings gemaak en 'n aantal aksies geneem, insluitend die installering van bykomende optika in die aangrensende put.
In datasentrums is daar punte van teenwoordigheid van kommunikasieverskaffers na wie ons ons voorvoegsels via BGP uitsaai. Vir elke netwerkrigting word die beste maatstaf gekies, wat dit moontlik maak om verskillende kliënte van die beste verbindingskwaliteit te voorsien. As kommunikasie deur een verskaffer afbreek, herbou ons ons roetering deur die beskikbare verskaffers.
As 'n verskaffer misluk, skakel ons outomaties oor na die volgende een. In die geval van 'n mislukking van een van die datasentrums, het ons 'n spieëlkopie van ons dienste in die tweede datasentrum, wat die hele vrag opneem.

Veerkragtigheid van fisiese infrastruktuur
Wat ons gebruik vir fouttoleransie op toepassingsvlak
Ons diens is gebou op 'n aantal oopbronkomponente.
ExaBGP is 'n diens wat 'n aantal funksies implementeer deur die BGP-gebaseerde dinamiese roeteringprotokol te gebruik. Ons gebruik dit aktief om ons witlys-IP-adresse te adverteer waardeur gebruikers toegang tot die API kry.
HAProxy is 'n hoëlasbalanseerder wat jou toelaat om baie buigsame verkeersbalanseringsreëls op verskillende vlakke van die OSI-model op te stel. Ons gebruik dit om voor alle dienste te balanseer: databasisse, boodskapmakelaars, API-dienste, webdienste, ons interne projekte – alles is agter HAProxy.
API aansoek — 'n webtoepassing wat in python geskryf is, waarmee die gebruiker sy infrastruktuur en sy diens bestuur.
Werker aansoek (hierna eenvoudig werker) - in OpenStack-dienste is dit 'n infrastruktuur daemon wat jou toelaat om API-opdragte na die infrastruktuur uit te saai. Skyfskepping vind byvoorbeeld in die werker plaas, en die skeppingsversoek vind plaas in die toepassing-API.
Standaard OpenStack-toepassingsargitektuur
Die meeste dienste wat vir OpenStack ontwikkel is, probeer om 'n enkele paradigma te volg. 'n Diens bestaan gewoonlik uit 2 dele: API en werkers (backend eksekuteurs). As 'n reël is 'n API 'n WSGI-toepassing in python, wat óf as 'n onafhanklike proses (daemon) geloods word, óf met behulp van 'n klaargemaakte Nginx- of Apache-webbediener. Die API verwerk die gebruikerversoek en gee verdere instruksies aan die werkeraansoek deur vir uitvoering. Die oordrag vind plaas met behulp van 'n boodskapmakelaar, gewoonlik RabbitMQ, die ander word swak ondersteun. Wanneer boodskappe die makelaar bereik, word dit deur werkers verwerk en, indien nodig, gee hulle 'n antwoord terug.
Hierdie paradigma behels geïsoleerde algemene punte van mislukking: RabbitMQ en die databasis. Maar RabbitMQ is geïsoleer binne een diens en kan in teorie individueel vir elke diens wees. So by MCS skei ons hierdie dienste so veel as moontlik; vir elke individuele projek skep ons 'n aparte databasis, 'n aparte RabbitMQ. Hierdie benadering is goed, want in die geval van 'n ongeluk by sommige kwesbare punte, breek nie die hele diens nie, maar slegs 'n deel daarvan.
Die aantal werkerstoepassings is onbeperk, so die API kan maklik horisontaal agter balanseerders skaal om werkverrigting en fouttoleransie te verhoog.
Sommige dienste vereis koördinasie binne die diens wanneer komplekse opeenvolgende bewerkings tussen API's en werkers plaasvind. In hierdie geval word 'n enkele koördinasiesentrum gebruik, 'n groepstelsel soos Redis, Memcache, ens., wat een werker in staat stel om vir 'n ander te sê dat hierdie taak aan hom opgedra is ("moet dit asseblief nie neem nie"). Ons gebruik ens. As 'n reël kommunikeer werkers aktief met die databasis, skryf en lees inligting daarvandaan. Ons gebruik mariadb as 'n databasis, wat in 'n multimeester-kluster geleë is.
Hierdie klassieke enkeldiens is georganiseer op 'n manier wat algemeen aanvaar word vir OpenStack. Dit kan beskou word as 'n geslote stelsel, waarvoor die metodes van skalering en fouttoleransie redelik voor die hand liggend is. Byvoorbeeld, vir API-fouttoleransie is dit genoeg om 'n balanseerder voor hulle te plaas. Om werkers te skaal word bereik deur hul getal te vermeerder.
Die swak punt in die hele skema is RabbitMQ en MariaDB. Hul argitektuur verdien 'n aparte artikel. In hierdie artikel wil ek fokus op API-fouttoleransie.

Openstack-toepassingsargitektuur. Balansering en fouttoleransie van die wolkplatform
Maak die HAProxy balancer foutverdraagsaam deur ExaBGP te gebruik
Om ons API's skaalbaar, vinnig en foutverdraagsaam te maak, sit ons 'n lasbalanseerder voor hulle. Ons het HAProxy gekies. Na my mening het dit al die nodige eienskappe vir ons taak: balansering op verskeie OSI-vlakke, 'n bestuurskoppelvlak, buigsaamheid en skaalbaarheid, 'n groot aantal balanseringsmetodes, ondersteuning vir sessietabelle.
Die eerste probleem wat opgelos moes word, was die fouttoleransie van die balanseerder self. Die installering van 'n balanseerder skep ook 'n punt van mislukking: die balanseerder breek en die diens stort ineen. Om te voorkom dat dit gebeur, het ons HAProxy in samewerking met ExaBGP gebruik.
Met ExaBGP kan u 'n meganisme implementeer om die toestand van 'n diens na te gaan. Ons het hierdie meganisme gebruik om die funksionaliteit van HAProxy na te gaan en, in geval van probleme, die HAProxy-diens van BGP te deaktiveer.
ExaBGP+HAProxy-skema
- Ons installeer die nodige sagteware, ExaBGP en HAProxy, op drie bedieners.
- Ons skep 'n terugloop-koppelvlak op elke bediener.
- Op al drie bedieners ken ons dieselfde wit IP-adres aan hierdie koppelvlak toe.
- 'n Wit IP-adres word via ExaBGP op die internet geadverteer.
Foutverdraagsaamheid word bereik deur dieselfde IP-adres vanaf al drie bedieners te adverteer. Vanuit 'n netwerkoogpunt is dieselfde adres toeganklik vanaf drie verskillende volgende hops. Die router sien drie identiese roetes, kies die hoogste prioriteit daarvan op grond van sy eie metrieke (dit is gewoonlik dieselfde opsie), en die verkeer gaan net na een van die bedieners.
In die geval van probleme met die werking van HAProxy of 'n bedienerfout, hou ExaBGP op om die roete aan te kondig, en die verkeer skakel glad oor na 'n ander bediener.
Sodoende het ons fouttoleransie van die balanseerder bereik.

Foutverdraagsaamheid van HAProxy-balanseerders
Die skema blyk onvolmaak te wees: ons het geleer hoe om HAProxy te reserveer, maar het nie geleer hoe om die vrag binne die dienste te versprei nie. Daarom het ons hierdie skema 'n bietjie uitgebrei: ons het voortgegaan om te balanseer tussen verskeie wit IP-adresse.
Balansering gebaseer op DNS plus BGP
Die kwessie van lasbalansering vir ons HAProxy bly onopgelos. Dit kan egter eenvoudig opgelos word, soos ons hier gedoen het.
Om drie bedieners te balanseer, benodig u 3 wit IP-adresse en goeie ou DNS. Elkeen van hierdie adresse word op die teruglus-koppelvlak van elke HAProxy bepaal en op die internet geadverteer.
In OpenStack, om hulpbronne te bestuur, word 'n diensgids gebruik, wat die eindpunt-API van 'n spesifieke diens spesifiseer. In hierdie gids registreer ons 'n domeinnaam - public.infra.mail.ru, wat via DNS deur drie verskillende IP-adresse opgelos word. As gevolg hiervan kry ons vragverspreiding tussen drie adresse via DNS.
Maar aangesien ons nie die bedienerkeuseprioriteite beheer wanneer ons wit IP-adresse aankondig nie, balanseer dit nog nie. Tipies sal slegs een bediener gekies word op grond van IP-adres senioriteit, en die ander twee sal ledig wees omdat geen maatstawwe in BGP gespesifiseer word nie.
Ons het roetes via ExaBGP met verskillende maatstawwe begin stuur. Elke balanseerder adverteer al drie wit IP-adresse, maar een van hulle, die hoof een vir hierdie balanseerder, word met die minimum maatstaf geadverteer. So terwyl al drie balanseerders in werking is, gaan oproepe na die eerste IP-adres na die eerste balanseerder, oproepe na die tweede na die tweede, en oproepe na die derde na die derde.
Wat gebeur wanneer een van die balanseerders val? As enige balanseerder misluk, word sy hoofadres steeds vanaf die ander twee geadverteer, en verkeer word tussen hulle herverdeel. Dus gee ons die gebruiker verskeie IP-adresse op een slag via DNS. Deur te balanseer volgens DNS en verskillende maatstawwe, kry ons 'n eweredige verspreiding van die las oor al drie balanseerders. En terselfdertyd verloor ons nie foutverdraagsaamheid nie.

Balansering van HAProxy gebaseer op DNS + BGP
Interaksie tussen ExaBGP en HAProxy
Dus, ons het foutverdraagsaamheid geïmplementeer ingeval die bediener verlaat, gebaseer op die stop van die aankondiging van roetes. Maar HAProxy kan om ander redes as bedienerfoute sluit: administrasiefoute, mislukkings binne die diens. Ons wil ook in hierdie gevalle die stukkende balanseerder onder die vrag verwyder, en ons het 'n ander meganisme nodig.
Daarom, deur die vorige skema uit te brei, het ons hartklop tussen ExaBGP en HAProxy geïmplementeer. Dit is 'n sagteware-implementering van die interaksie tussen ExaBGP en HAProxy, wanneer ExaBGP pasgemaakte skrifte gebruik om die status van toepassings na te gaan.
Om dit te doen, moet u 'n gesondheidskontroleerder in die ExaBGP-konfigurasie opstel, wat die status van HAProxy kan nagaan. In ons geval het ons die gesondheidsagtergrond in HAProxy gekonfigureer, en vanaf die ExaBGP-kant kyk ons met 'n eenvoudige GET-versoek. As die aankondiging ophou om te gebeur, werk HAProxy heel waarskynlik nie en is dit nie nodig om dit te adverteer nie.

HAProxy-gesondheidsondersoek
HAProxy Eweknieë: sessiesinchronisasie
Die volgende ding om te doen was om die sessies te sinchroniseer. Wanneer deur verspreide balanseerders gewerk word, is dit moeilik om die berging van inligting oor kliëntsessies te organiseer. Maar HAProxy is een van die min balanseerders wat dit kan doen as gevolg van die Peers-funksionaliteit - die vermoë om sessietabelle tussen verskillende HAProxy-prosesse oor te dra.
Daar is verskillende balanseringsmetodes: eenvoudiges soos , en verleng, wanneer die kliënt se sessie onthou word, en elke keer beland hy op dieselfde bediener as voorheen. Ons wou die tweede opsie implementeer.
HAProxy gebruik stok-tabelle om kliëntsessies van hierdie meganisme te stoor. Hulle stoor die kliënt se oorspronklike IP-adres, die geselekteerde teikenadres (agterkant) en sekere diensinligting. Tipies word stoktabelle gebruik om 'n bron-IP + bestemming-IP-paar te stoor, wat veral nuttig is vir toepassings wat nie gebruikerssessiekonteks kan oordra wanneer na 'n ander balanseerder oorgeskakel word nie, byvoorbeeld in RoundRobin-balanseringsmodus.
As 'n stoktafel geleer word om tussen verskillende HAProxy-prosesse te beweeg (tussen watter balansering plaasvind), sal ons balanseerders met een poel stoktafels kan werk. Dit sal dit moontlik maak om die kliënt se netwerk naatloos te verander as een van die balanseerders misluk; werk met kliëntsessies sal voortgaan op dieselfde backends wat vroeër gekies is.
Vir behoorlike werking moet die probleem van die bron-IP-adres van die balanseerder waaruit die sessie gestig is, opgelos word. In ons geval is dit 'n dinamiese adres op die terugloop-koppelvlak.
Korrekte werk van eweknieë word slegs onder sekere omstandighede bereik. Dit wil sê, TCP-timeouts moet groot genoeg wees of omskakeling moet vinnig genoeg wees sodat die TCP-sessie nie tyd het om te beëindig nie. Dit maak egter voorsiening vir naatlose skakeling.
In IaaS het ons 'n diens wat met dieselfde tegnologie gebou is. Hierdie , wat Octavia genoem word. Dit is gebaseer op twee HAProxy-prosesse en sluit aanvanklik ondersteuning vir eweknieë in. Hulle het hulself uitstekend bewys in hierdie diens.
Die prent toon skematies die beweging van eweknie-tabelle tussen drie HAProxy-gevalle, 'n konfigurasie word voorgestel oor hoe dit gekonfigureer kan word:

HAProxy Eweknieë (sessiesinchronisasie)
As jy dieselfde skema implementeer, moet die werking daarvan noukeurig getoets word. Dit is nie 'n feit dat dit 100% van die tyd op dieselfde manier sal werk nie. Maar jy sal ten minste nie stoktafels verloor as jy die kliënt se bron-IP moet onthou nie.
Beperk die aantal gelyktydige versoeke van dieselfde kliënt
Enige dienste wat publiek beskikbaar is, insluitend ons API's, kan onderhewig wees aan stortvloede van versoeke. Die redes daarvoor kan heeltemal anders wees, van gebruikersfoute tot geteikende aanvalle. Ons word periodiek DDoSed deur IP-adresse. Kliënte maak dikwels foute in hul skrifte en gee vir ons mini-DDoS'e.
Op een of ander manier moet bykomende beskerming verskaf word. Die voor die hand liggende oplossing is om die aantal API-versoeke te beperk en nie SVE-tyd te mors met die verwerking van kwaadwillige versoeke nie.
Om sulke beperkings te implementeer, gebruik ons tarieflimiete, georganiseer op grond van HAProxy, met dieselfde stoktabelle. Die opstel van limiete is redelik eenvoudig en laat jou toe om die gebruiker te beperk deur die aantal versoeke na die API. Die algoritme onthou die bron-IP waaruit versoeke gemaak word en beperk die aantal gelyktydige versoeke van een gebruiker. Natuurlik het ons die gemiddelde API-ladingsprofiel vir elke diens bereken en 'n limiet van ≈ 10 keer hierdie waarde gestel. Ons gaan voort om die situasie fyn dop te hou en hou ons vinger op die pols.
Hoe lyk dit in die praktyk? Ons het kliënte wat ons outoskaal-API's heeltyd gebruik. Hulle skep soggens ongeveer twee tot driehonderd virtuele masjiene en vee dit saans uit. Vir OpenStack vereis die skep van 'n virtuele masjien, ook met PaaS-dienste, ten minste 1000 API-versoeke, aangesien interaksie tussen dienste ook deur die API plaasvind.
Sulke oordrag van take veroorsaak 'n redelike groot las. Ons het hierdie vrag beoordeel, daaglikse pieke ingesamel, dit tienvoudig verhoog, en dit het ons tariefperk geword. Ons hou ons vinger op die pols. Ons sien dikwels bots en skandeerders wat na ons probeer kyk om te sien of ons enige CGA-skripte het wat uitgevoer kan word, ons sny dit aktief.
Hoe om jou kodebasis op te dateer sonder dat gebruikers dit agterkom
Ons implementeer ook foutverdraagsaamheid op die vlak van kode-ontplooiingsprosesse. Daar kan foute wees tydens ontplooiing, maar die impak daarvan op diensbeskikbaarheid kan tot die minimum beperk word.
Ons werk voortdurend ons dienste op en moet verseker dat die kodebasis opgedateer word sonder om gebruikers te beïnvloed. Ons het daarin geslaag om hierdie probleem op te los deur die bestuursvermoëns van HAProxy en die implementering van Graceful Shutdown in ons dienste te gebruik.
Om hierdie probleem op te los, was dit nodig om beheer van die balanseerder en die "korrekte" sluiting van dienste te verseker:
- In die geval van HAProxy word beheer uitgevoer deur 'n statistieklêer, wat in wese 'n sok is en in die HAProxy-konfigurasie gedefinieer word. Jy kan opdragte na dit stuur via stdio. Maar ons hoofkonfigurasiebeheerinstrument is moontlik, so dit het 'n ingeboude module om HAProxy te bestuur. Wat ons aktief gebruik.
- Die meeste van ons API- en Engine-dienste ondersteun grasieuse afsluittegnologieë: wanneer hulle afskakel, wag hulle vir die huidige taak om te voltooi, of dit nou 'n http-versoek of een of ander dienstaak is. Dieselfde ding gebeur met die werker. Dit ken al die take wat dit doen en eindig wanneer dit alles suksesvol voltooi het.
Danksy hierdie twee punte lyk die veilige algoritme vir ons ontplooiing so.
- Die ontwikkelaar stel 'n nuwe pakket kode saam (vir ons is dit RPM), toets dit in die dev-omgewing, toets dit in die stadium en laat dit in die stadiumbewaarplek.
- Die ontwikkelaar stel die taak vir ontplooiing met die mees gedetailleerde beskrywing van die "artefakte": die weergawe van die nuwe pakket, 'n beskrywing van die nuwe funksionaliteit en ander besonderhede oor die ontplooiing indien nodig.
- Die stelseladministrateur begin die opdatering. Begin die Ansible-speelboek, wat op sy beurt die volgende doen:
- Neem 'n pakket uit die verhoogbewaarplek en gebruik dit om die weergawe van die pakket in die produkbewaarplek op te dateer.
- Stel 'n lys van backends van die opgedateerde diens saam.
- Skakel die eerste diens af wat in HAProxy opgedateer word en wag vir die prosesse om klaar te loop. Danksy grasieuse afsluiting is ons vol vertroue dat alle huidige kliëntversoeke suksesvol sal voltooi.
- Nadat die API en werkers heeltemal gestop is, en HAProxy afgeskakel is, word die kode opgedateer.
- Ansible bedryf dienste.
- Vir elke diens word sekere "handvatsels" getrek, wat eenheidstoetse op 'n aantal vooraf gedefinieerde sleuteltoetse uitvoer. 'n Basiese kontrolering van die nuwe kode vind plaas.
- As geen foute in die vorige stap gevind is nie, word die agterkant geaktiveer.
- Kom ons gaan aan na die volgende agterkant.
- Nadat alle backends opgedateer is, word funksionele toetse van stapel gestuur. As hulle ontbreek, kyk die ontwikkelaar na enige nuwe funksionaliteit wat hy geskep het.
Dit voltooi die ontplooiing.

Diensopdateringsiklus
Hierdie skema sou nie werk as ons nie een reël het nie. Ons ondersteun beide die ou en nuwe weergawes in die stryd. Vooraf, in die stadium van sagteware-ontwikkeling, word bepaal dat selfs al is daar veranderinge in die diensdatabasis, dit nie die vorige kode sal breek nie. As gevolg hiervan word die kodebasis geleidelik opgedateer.
Gevolgtrekking
Deur my eie gedagtes oor 'n foutverdraagsame WEB-argitektuur te deel, wil ek weereens kennis neem van die belangrikste punte daarvan:
- fisiese foutverdraagsaamheid;
- netwerkfouttoleransie (balanseerders, BGP);
- fouttoleransie van die sagteware wat gebruik en ontwikkel is.
Stabiele uptyd almal!
Bron: will.com
