
Tere, Habr! Mina olen Artyom Karamyshev, süsteemi haldamise meeskonna juht. . Viimase aasta jooksul on meil olnud palju uusi tooteid. Soovisime tagada, et API teenused oleksid kergesti skaleeritavad, talitluspüsivad ja valmis kiiresti kasvavale kasutajakoormusele. Meie platvorm põhineb OpenStackil ja soovin rääkida nendest talitluspüsivuse probleemidest, millega oleme silmitsi seisnud, et luua talitluspüsiv süsteem. Arvan, et see võiks huvi pakkuda ka neile, kes arendavad tooteid OpenStackil.
Platvormi üldine talitluspüsivus tuleneb selle komponentide talitluspüsivusest. Niisiis, liigume järk-järgult läbi kõikidest tasemetest, kus oleme avastanud riske ja need kõrvaldada.
Selle loo videoversioon, mille algallikaks on ettekande Uptime day 4 konverentsil, mille korraldas , on saadaval .
Füüsilise arhitektuuri talitluspüsivus
MCSi avalik osa asub praegu kahel Tier III andmekeskusel, nende vahel on pimedat kiudkaablit, mis on füüsilisel tasemel reserveeritud erinevate marsruutide jaoks, mille läbilaskevõime on 200 Gbit/s. Tier III tase tagab vajaliku füüsilise infrastruktuuri talitluspüsivuse taseme.
Pime kiud on reserveeritud nii füüsilisel kui ka loogilisel tasemel. Kanali reserveerimise protsess oli iteratiivne, tekkisid probleemid ja pidevalt täiustame sideühendust andmekeskuste vahel.
Näiteks hiljuti, kui töötati ühe andmekeskuse lähedal kaevanduses, lõhkus ekskavaator toru, milles olid nii põhikaabel kui ka reserveeritud optiline kaabel. Meie talitluspüsiv sidekanal andmekeskusega osutus ühes punktis, kaevanduses, haavatavaks. Vastavalt kaotasime osa infrastruktuurist. Tõime välja järeldused, tegime mitmeid samme, sealhulgas paigaldasime kõrvalasuvasse kaevandusse täiendava optika.
Andmekeskustes on sidepakkujate kohalolekupunktid, kellele edastame oma eeliseid BGP kaudu. Iga võrgu suuna jaoks valitakse parim mõõdik, mis võimaldab pakkuda erinevatele klientidele parima kvaliteediga ühendust. Kui ühe pakkuja kaudu ühendus katkeb, kohandame oma marsruutimist saadaval olevate pakkujate kaudu.
Pakkuja töö tõrke korral lülitame automaatselt järgmisele. Kui üks andmekeskus ebaõnnestub, on meil teises andmekeskuses peegelkohalik koopia meie teenustest, mis võtavad kogu koormuse enda peale.

Füüsilise infrastruktuuri tõrketaluvus
Mida me kasutame tõrketaluvuse tagamiseks rakendustasandil
Meie teenus on üles ehitatud mitmete avatud lähtekoodiga komponentide baasil.
ExaBGP — teenus, mis realiseerib mitmeid funktsioone dünaamilise marsruutimise protokolli kaudu BGP baasil. Me kasutame seda aktiivselt, et kuulutada välja meie valged IP-aadressid, mille kaudu kasutajad pääsevad API-le.
Edasi läks kliendi päring HAProxy-sse, mis lahendas järgmised ülesanded: — kõrgkoormusega tasakaalustaja, mis lubab seadistada väga paindlikke liikluse tasakaalustamise reegleid erinevatel OSI mudeli tasanditel. Me kasutame seda tasakaalustamiseks kõigi teenuste ees: andmebaasid, sõnumibrokereid, API-teenuseid, veebiteenuseid, meie sisemisi projekte — kõik on HAProxy taga.
API rakendus — veebirakendus, kirjutatud pythonis, mille abil kasutaja haldab oma infrastruktuuri, oma teenust.
Töötaja rakendus (edaspidi lihtsalt töötaja) — OpenStack teenustes on see infrastruktuuri demon, mis võimaldab edastada API-käsku infrastruktuurile. Näiteks toimub ketta loomine just töötajas, samas kui loomise päring läheb API rakendusse.
Standardne OpenStack rakenduste arhitektuur
Enamik OpenStacki alla arendatavaid teenuseid püüab järgida ühtset paradigmat. Teenus koosneb tavaliselt kahest osast: API-st ja töötajatest (taustarakenditest). Reeglina on API WSGI rakendus Pythonis, mis töötab kas iseseisva protsessina (daemon) või olemasoleva veebiserveri Nginx, Apache kaudu. API töötleb kasutaja päringut ja edastab edasised juhised töötaja rakendusele. Edastamine toimub sõnumivahendi kaudu, tavaliselt RabbitMQ, kuid teisi toetatakse halvasti. Kui sõnumid jõuavad vahendisse, töötavad need töötajad ja vajadusel tagastavad vastuse.
See paradigma eeldab isoleeritud üldisi rikkepunkte: RabbitMQ ja andmebaasi. RabbitMQ on aga isoleeritud ühes teenuses ja võib idee kohaselt olla iga teenuse jaoks individuaalne. Seega, meie MCS-is jagame maksimaalselt neid teenuseid; iga projekti jaoks loome eraldi andmebaasi ja eraldi RabbitMQ. See lähenemine on hea, kuna rikke korral mõnes haavatavas kohas ei lõhku kogu teenust, vaid ainult selle osa.
Töötajate rakenduste arv ei ole millegagi piiratud, mistõttu API saab hõlpsasti horisontaalselt skaleerida koormustasakaalustajate abil jõudluse ja tõrkeotsingu suurendamiseks.
Mõnedes teenustes on vajalik koordineerimine teenuse sees - kui toimub keeruline järjestikune tegevus API ja töötajate vahel. Sellisel juhul kasutatakse ühtset koordineerimiskeskust, klastrisüsteemi, nagu Redis, Memcache, etcd, mis võimaldab ühel töötajal öelda teisele, et see ülesanne on tema peale määratud ("palun ära võta seda"). Meie kasutame etcd-d. Reeglina suhtlevad töötajad andmebaasiga aktiivselt, kirjutavad ja loevad sealt teavet. Andmebaasina kasutame MariaDB-d, mis asub meil mitme masteriga klastris.
Klassikaline ühekordne teenus on korraldatud OpenStackile omase viisi kohaselt. Seda võib pidada isoleeritud süsteemiks, mille jaoks on skaleerimise ja tõrkeotsingu viisid piisavalt selged. Näiteks, et tagada API tõrkeotsing, piisab, kui paigaldada nende ette koormustasakaalustaja. Töötajate skaleerimine saavutatakse nende arvu suurendamisega.
Kogu süsteemi nõrk koht on RabbitMQ ja MariaDB. Nende arhitektuur väärib eraldi artiklit. Selles artiklis soovin keskenduda API talitlushäirete vältimisele.

Openstacki rakenduse arhitektuur. Pilveplatvormi koormuse ja talitlushäirete vältimine.
Teeme HAProxy koormuse tasandaja talitlushäirete vältimiseks ExaBGP abil.
Kuna meie API-d peavad olema skaleeritavad, kiired ja talitlushäirete vältimisega, panime nende ette koormuse tasandaja. Valisime HAProxy. Minu arvates omab see kõiki vajalikke omadusi meie ülesande jaoks: tasandamine mitmel OSI tasemel, haldustüüp, paindlikkus ja skaleeritavus, suur hulk tasandamismeetodeid, toeks sessioonitabelid.
Esimene probleem, mille lahendamine on vajalik, on koormuse tasandaja talitlushäirete vältimine. Lihtsalt koormuse tasandaja paigaldamine loob samuti rikke punkti: koormuse tasandaja ebaõnnestub - teenus kukub välja. Selle vältimiseks kasutasime HAProxy koos ExaBGP-ga.
ExaBGP võimaldab rakendada teenuse seisundi kontrollimise mehhanismi. Me kasutasime seda mehhanismi HAProxy töökindluse kontrollimiseks ning probleemide ilmnemisel lülitasime HAProxy teenuse BGP-st välja.
ExaBGP+HAProxy skeem.
- Installime kolmele serverile vajaliku tarkvara, ExaBGP ja HAProxy.
- Loome igas serveris loopback-liidese.
- Kasutame kõigil kolmel serveril selle liidese jaoks sama avalikku IP-aadressi.
- Avalik IP-aadress kuulutab välja ExaBGP kaudu internetis.
Talitlushäirete vältimine saavutatakse sama IP-aadressi kuulutamisega kõigilt kolmest serverist. Võrgu vaates on tegemist sama aadressiga, mis on saadaval kolme erineva järgmise hüppena. Ruuter näeb kolme identset marsruuti, valib oma meetri alusel kõige prioriteetsemalt (tavaliselt on see sama valik) ning liiklus suunatakse ainult ühele serverile.
Kui HAProxy tööga tekivad probleemid või server ebaõnnestub, lõpetab ExaBGP marsruudi kuulutamise ning liiklus sujuvalt lülitub teisele serverile.
Nii saavutasime koormuse tasandaja talitlushäirete vältimise.

HAProxy koormuse tasandajate talitlushäirete vältimine.
Skeem on osutunud mitteideaalseks: me õppisime HAProxy varukoopiaid tegema, kuid ei õppinud koormust teenuste sees jagama. Seetõttu laiendasime skeemi veidi: läksime üle koormuse tasandamisele mitme avaliku IP-aadressi vahel.
DNS ja BGP-põhine koormuse tasakaalustamine
Koormuse tasakaalustamine meie HAProxy ees on endiselt lahendamata küsimus. Siiski, seda on võimalik lahendada üsna lihtsalt, nagu me ka ise tegime.
Kolme serveri tasakaalustamiseks on vajalik 3 avalikku IP-aadressi ja vana hea DNS. Igaüks neist aadressidest määratakse iga HAProxy loopback-liidesele ja kuulutatakse internetis.
OpenStackis kasutatakse ressursside haldamiseks teenuste katalooge, kus määratakse mingisuguse teenuse API lõpp-punkt. Selles kataloogis kirjutame domeeninime — public.infra.mail.ru, mis lahendatakse DNS kaudu kolme erineva IP-aadressiga. Tulemuseks on koormuse jaotamine kolme aadressi vahel DNS-i kaudu.
Kuna me ei halda avalike IP-aadresside kuulutamisel serveri valimise prioriteete, ei ole see veel tasakaalustamine. Üldiselt valitakse ainult üks server IP-aadresside vanuse alusel, samas kui kaks teist jäävad käest, kuna BGP-s ei ole mingeid meetrikaid määratud.
Oleme hakanud kuulutama marsruute ExaBGP kaudu erineva meetriku järgi. Iga koormuse tasakaalustaja kuulutab kõik kolm avalikku IP-aadressi, kuid üks neist, antud koormuse tasakaalustaja jaoks peamine, kuulutatakse minimaalse meetrikaga. Seetõttu, kuni kõik kolm tasakaalustajat on töös, saavad esimest IP-aadressi ringlusse esimesest tasakaalustajast, teise aadressi teiselt ja kolmanda aadressi kolmandalt.
Mis juhtub, kui üks koormuse tasakaalustajatest tõrjub? Kui mõni koormuse tasakaalustaja ebaõnnestub, kuulutatakse tema põhjaadress ikka veel kahe teise kaudu, liiklus nende vahel jaotatakse ümber. Seeläbi saame kasutajale DNS kaudu kohe mitu IP-aadressi. DNS-i ja erineva meetrikaga tasakaalustamisega saavutame koormuse ühtlase jaotuse kõigi kolme tasakaalustaja vahel. Ja samal ajal ei kaota me rikke taluvust.

HAProxy tasakaalustamine DNS + BGP-põhiselt
ExaBGP ja HAProxy vaheline suhtlus
Seega oleme rakendanud rikke taluvuse serveri väljalangemise korral, tuginedes marsruutide kuulutamise lõpetamisele. Kuid HAProxy võib sulguda ka muudel põhjustel, kui serveri rike: haldusvead, teenuse sisevead. Soovime sellistel juhtudel kahjustatud koormuse tasakaalustaja koormuse alt eemaldada ja seetõttu on vajalik teine mehhanism.
Seetõttu, laiendades eelmist skeemi, rakendasime heartbeat'i ExaBGP ja HAProxy vahel. See on tarkvara rakendus ExaBGP ja HAProxy vahelise suhtluse jaoks, kus ExaBGP kasutab rakenduste staatuse kontrollimiseks kohandatud skripte.
Selleks tuleb ExaBGP konfiguratsioonis seadistada tervise kontrollimise tööriist, mis suudab HAProxy staatust jälgida. Meie puhul seadistasime HAProxy-s tervise taustteenuse ja ExaBGP kontrollime seda lihtsa GET päringuga. Kui kuulutus lõpetab toimumise, ei tohiks HAProxy tõenäoliselt töötada ja kuulutamine ei ole vajalik.

HAProxy Tervise Kontroll
HAProxy Peers: sessioonide sünkroniseerimine
Järgmine, mida tuli teha, oli sessioonide sünkroniseerimine. Jaotatud tasakaalustajate kaudu töötades on keeruline korraldada kliendi sessioonide teabe säilitamist. Kuid HAProxy on üks väheseid tasakaalustajaid, mis suudab seda teha Peers funktsionaalsuse kaudu, mis võimaldab edastada HAProxy erinevate protsesside vahel sessioonide tabelid.
On olemas erinevaid tasakaalustamise meetodeid: lihtsad, nagu , ja täiustatud, kui kliendi sessioon salvestatakse ja ta jõuab igal korral samale serverile, nagu varem. Soovisime rakendada teist varianti.
HAProxy-s kasutatakse kliendi sessioonide säilitamiseks selle mehhanismi stick-tables. Need salvestavad kliendi algse IP-aadressi, valitud sihtaadressi (taustteenus) ja teatud teenuseteavet. Üldiselt kasutatakse stick-tabeleid source-IP ja destination-IP paari säilitamiseks, mis on eriti kasulik rakendustes, mis ei suuda edastada kasutaja sessiooni konteksti teisele tasakaalustajale, näiteks RoundRobin tasakaalustamise režiimis.
Kui stick-tabelit õpetada liikuma erinevate HAProxy protsesside vahel (kui toimub tasakaalustamine), saavad meie tasakaalustajad töötada ühe stick-tabelite kogumiga. See võimaldab sujuvat kliendi võrgu üleminekut, kui üks tasakaalustaja kokku kukub, ja kliendi sessioonidega töötamine jätkub valitud taustteenustes, mis olid valitud varem.
Õige toimimise tagamiseks peab olema lahendatud tasakaalustaja source IP-aadressi probleem, millelt sessioon on loodud. Meie puhul on see dünaamiline aadress loopback-interfääsil.
Peers'i õige töö saavutatakse ainult teatud tingimustes. See tähendab, et TCP aegumised peavad olema piisavalt suured või üleminek peab olema piisavalt kiire, et TCP seanss ei katkeks. Sellegipoolest võimaldab see sujuvat üleminekut.
Meie IaaS-is on teenus, mis on ehitatud sama tehnoloogia alusel. See on , mille nimi on Octavia. See põhineb kahe HAProxy protsessi baasil ning selles on algselt olemas peer'ide tugi. Selles teenuses on nad end hästi tõestanud.
Pildil on diagrammil kujutatud peers-tabelite liikumist kolme HAProxy instantsi vahel, on pakutud konfiguratsioon, kuidas seda seadistada:

HAProxy Peers (seansside sünkroonimine)
Kui te rakendate sarnast skeemi, tuleb selle tööd hoolikalt testida. Pole kindel, et see töötab samamoodi 100% juhtudel. Kuid vähemalt ei kaota te stick-tabeleid, kui on vaja meeles pidada kliendi source IP-d.
Piirang samaaegsete päringute arvu peale ühelt ja samalt kliendilt
Kõik avalikud teenused, sealhulgas meie API-d, võivad olla kalduvad päringute laviinidele. Nende põhjused võivad olla täiesti erinevad, alates kasutajate vigadest kuni sihitud rünnakuteni. Meid rünnatakse aeg-ajalt DDoS-iga IP-aadresside kaudu. Kliendid teevad sageli vigu oma skriptides, tehes meile mini-DDoS-e.
Nii või teisiti tuleb ette näha lisakaitse. Ilmselge lahendus on piirata päringute arvu API-le ja mitte raisata protsessoriaega pahatahtlike päringute töötlemiseks.
Selliste piirangute rakendamiseks kasutame rate limits, mis on organiseeritud HAProxy baasil, samade stick-tabelite abil. Piirangute seadistamine on piisavalt lihtne ja võimaldab piirata kasutajat API päringute arvu osas. Algoritm salvestab source IP, kust päringud tehakse, ja piirab sama kasutaja samaaegsete päringute arvu. Loomulikult oleme arvutanud igas teenuses API koormuse keskmise profiili ja seadnud piiri, mis on umbes 10 korda suurem kui see väärtus. Jätkame siiani tähelepanelikku olukorra jälgimist, hoides käe pulsil.
Kuidas see praktikas välja näeb? Meil on kliente, kes kasutavad pidevalt meie API-sid automaatselt skaleerimiseks. Nad loovad umbes kakssada-kolmsada virtuaalmasinat varahommikul ja kustutavad need õhtul. OpenStackis virtuaalmasina loomine, koos PaaS-teenustega, tähendab vähemalt 1000 API-päringut, kuna teenuste vaheline suhtlus toimub samuti läbi API.
Sellised ülesannete ülekanded tekitavad üsna suurt koormust. Me hindasime seda koormust, kogusime päevased tipud, suurendasime neid kümme korda ja see sai meie rate-limiidiks. Hoidke kät pulsil. Näeme tihti roboteid, skaneerijaid, kes proovivad meie peale vaadata, kas meil on mingeid CGA-skripte, mida käivitada, ja me lõikame neid aktiivselt.
Kuidas värskendada koodibaasi kasutajatele märkamatult
Kasutame ka koodi juurutamise protsesside tasemel tõrketaluvust. Juurutamise ajal võivad esineda tõrked, kuid nende mõju teenuste kättesaadavusele saab minimeerida.
Uuendame pidevalt oma teenuseid ja peame tagama koodibaasi uuendamise protsessi ilma kasutajatele mõju avaldamata. Selle ülesande lahendamine õnnestus meil, kasutades HAProxy haldusvõimalusi ja meie teenustes Graceful Shutdowni rakendamist.
Selle ülesande lahendamiseks oli vajalik tagada koormuse ja „õige” teenuste väljalülitamine:
- HAProxy puhul toimub haldus läbi stats-faili, mis on põhimõtteliselt soket ja määratakse HAProxy konfigurasioonis. Sellele saab komande edastada läbi stdio. Kuid meie peamine tööriist konfiguratsioonide kontrollimiseks on ansible, millel on sisse ehitatud moodul HAProxy haldamiseks. Seda kasutame aktiivselt.
- Enamik meie API ja Engine teenustest toetavad graceful shutdown tehnoloogiaid: välja lülitamisel ootavad nad praeguse ülesande, olgu see http-päring või mõni teenindusülesanne, täielikku lõpetamist. Sama juhtub ka töötlusega. Ta teab kõik ülesanded, mida ta teeb, ja lõpetab, kui kõik on edukalt tehtud.
Nende kahe asja tõttu näeb meie ohutu juurutamisprotsess välja järgmiselt.
- Arendaja kogub uue koodipaketi (meie puhul RPM), testib arenduskeskkonnas, testib etapis ja jätab etapi-repositooriumisse.
- Arendaja esitab juurutamiseks ülesande koos võimalikult üksikasjaliku kirjeldusega "artefaktidest": uue paketi versioon, uue funktsionaalsuse kirjeldus ja muud üksikasjad juurutamiseks vajadusel.
- Süsteemihaldur alustab värskendamist. Käivitab Ansible'i mängu, mis omakorda teeb järgmist:
- Võtab paki stage-repositooriumist ja värskendab paki versiooni tootmisrepositooriumis.
- Koostab uuendatava teenuse taustsüsteemide nimekirja.
- Lülitab välja esimese uuendatava teenuse HAProxy's ja ootab, kuni selle protsessid lõpetavad töö. Aitäh sujuvale sulgemisele, oleme kindlad, et kõik praegused kliendi päringud lõpetatakse edukalt.
- Pärast API, töötajate ja HAProxy täielikku sulgemist toimub koodi värskendamine.
- Ansible käivitab teenused.
- Iga teenuse jaoks tõmbab ta kindlaid "nuppe", mis viivad läbiüksustestis, põhinedes eelnevalt määratud võtmetestidele. Toimub uue koodi põhialuste kontroll.
- Kui eelmisel sammul ei leitud vigu, aktiveeritakse taustsüsteem.
- Liigume järgmise taustsüsteemi juurde.
- Pärast kõigi taustsüsteemide värskendamist käivitatakse funktsionaalsed testid. Kui neid on puudu, vaatab arendaja igasugust uut funktsionaalsust, mida ta on loonud.
Sellega on juurutamine lõppenud.

Teenuse värskendamise tsükkel
See skeem ei toimiks, kui meil ei oleks üht reeglit. Me toetame samal ajal tootmises nii vana kui ka uut versiooni. Varakult, tarkvara arendamise etapis, on paika pandud, et isegi kui teenuse andmebaasis toimuvad muudatused, ei riku need eelmist koodi. Tulemuseks on järkjärguline koodibaasi värskendamine.
Kokkuvõte
Jagades oma mõtteid talitluspüsiva WEB-arhitektuuri kohta, tahan veel kord rõhutada selle võtmeelemente:
- füüsiline talitluspüsivus;
- võrgu talitluspüsivus (tasakaalustajad, BGP);
- kasutatud ja arendatava tarkvara talitluspüsivus.
Kõigile stabiilset tööaega!
Allikas: habr.com
