Hur Äterfinns en robust webbarkitektur pÄ plattformen Mail.ru Cloud Solutions.

Hur Äterfinns en robust webbarkitektur pÄ plattformen Mail.ru Cloud Solutions.

Hej, Habr! Jag heter Artyom Karamyshev och Àr chef för systemadministrationsteamet Mail.Ru Cloud Solutions (MCS). Under det senaste Äret har vi haft mÄnga lanseringar av nya produkter. Vi vill att API-tjÀnsterna ska vara lÀtta att skala, vara motstÄndskraftiga och redo för snabb ökning av anvÀndarbelastningen. VÄr plattform Àr baserad pÄ OpenStack, och jag vill berÀtta om vilka problem med komponenternas motstÄndskraft vi behövde lösa för att fÄ en robust system. Jag tror att detta kommer att vara intressant för dem som ocksÄ utvecklar produkter pÄ OpenStack.

Den totala motstÄndskraften hos plattformen bestÄr av motstÄndskraften hos dess komponenter. SÄ vi kommer gradvis att gÄ igenom alla nivÄer dÀr vi har upptÀckt risker och ÄtgÀrdat dem.

Videoversionen av denna historia, som har sitt ursprung i en presentation pÄ konferensen Uptime Day 4, organiserad av ITSumma, kan ses pÄ YouTube-kanalen Uptime Community.

MotstÄndskraft hos den fysiska arkitekturen

Den offentliga delen av MCS-molnet Àr för nÀrvarande baserad i tvÄ Tier III-datacenter, mellan dem finns egen mörk fiber som Àr reserverad pÄ fysisk nivÄ med olika spÄr, med en kapacitet pÄ 200 Gbit/s. Tier III-nivÄn ger den nödvÀndiga motstÄndskraften för den fysiska infrastrukturen.

Den mörka fibern Àr reserverad bÄde pÄ fysisk och logisk nivÄ. Processen för reservkanaler var iterativ, vi stötte pÄ problem och vi förbÀttrar stÀndigt kommunikationen mellan datacentren.

Till exempel, för inte sÄ lÀnge sedan, under arbete i en brunn nÀra ett av datacentren, trÀffade en grÀvmaskin röret, och bÄde huvud- och reserv optisk fiberkabel hamnade i denna rörledning. VÄr motstÄndskraftiga kommunikationskanal med datacenter var sÄrbar vid en punkt, i brunnen. DÀrför förlorade vi en del av infrastrukturen. Vi drog slutsatser, vidtog en rad ÄtgÀrder, inklusive att vi lade en extra optisk fiber genom den nÀrliggande brunnen.

I datacentren finns det punkter dÀr leverantörer av kommunikation har nÀrvaro, till vilka vi sÀnder vÄra prefix via BGP. För varje nÀtverksriktning vÀljs den bÀsta metrik, vilket gör att vi kan erbjuda olika kunder den bÀsta kvalitén pÄ anslutningen. Om en förbindelse via en leverantör bryts, omkonfigurerar vi vÄr routing via tillgÀngliga leverantörer.

Vid ett leverantörsfel vÀxlar vi automatiskt till nÀsta. Om ett av datacentren slutar fungera har vi en spegelkopi av vÄra tjÀnster i det andra datacentret, som tar hela belastningen.

Hur Äterfinns en robust webbarkitektur pÄ plattformen Mail.ru Cloud Solutions.
FelutstÀllning av fysisk infrastruktur

Vad vi anvÀnder för felutstÀllning pÄ applikationsnivÄ

VÄr tjÀnst Àr byggd pÄ en rad öppna komponenter.

ExaBGP — en tjĂ€nst som implementerar en rad funktioner med hjĂ€lp av protokollet för dynamisk routing baserat pĂ„ BGP. Vi anvĂ€nder den aktivt för att annonsera vĂ„ra vita IP-adresser, genom vilka anvĂ€ndare fĂ„r tillgĂ„ng till API:t.

HAProxy — en högbelastad balans- re Ă€r, som möjliggör mycket flexibla regler för trafikbalansering pĂ„ olika nivĂ„er av OSI-modellen. Vi anvĂ€nder den för att balansera framför alla tjĂ€nster: databaser, meddelandehanterare, API-tjĂ€nster, webb-tjĂ€nster, vĂ„ra interna projekt — allt stĂ„r bakom HAProxy.

API-applikation — en webbapplikation skriven i Python, med vilken anvĂ€ndaren hanterar sin infrastruktur och sina tjĂ€nster.

Arbetarapplikation (hĂ€refter bara worker) — i OpenStack-tjĂ€nster Ă€r det en infrastrukturdemon som gör det möjligt att överföra API-kommandon till infrastrukturen. Till exempel sker skapandet av disk just i worker, medan begĂ€ran om skapande görs i API-applikationen.

Standardarkitektur för OpenStack-applikationer

De flesta tjĂ€nster som utvecklas för OpenStack försöker följa en enhetlig paradigm. TjĂ€nsten bestĂ„r vanligtvis av tvĂ„ delar: API och arbetare (backend-executors). I allmĂ€nhet Ă€r API:et en WSGI-applikation i Python som antingen körs som en fristĂ„ende process (demon) eller via en redan etablerad webbserver som Nginx eller Apache. API:et behandlar anvĂ€ndarens förfrĂ„gan och vidarebefordrar instruktionerna till arbetarapplikationen för utförande. Överföringen sker med hjĂ€lp av en meddelandebroker, vanligtvis RabbitMQ, medan andra stöds dĂ„ligt. NĂ€r meddelandena nĂ„r brokern behandlas de av arbetarna och, om det behövs, returnerar de ett svar.

Denna paradiigm innebÀr isolerade gemensamma felpunkter: RabbitMQ och databasen. Men RabbitMQ Àr isolerat inom ramen för en tjÀnst och kan i teorin vara individuellt för varje tjÀnst. SÄ vi pÄ MCS separerar dessa tjÀnster sÄ mycket som möjligt, för varje separat projekt skapar vi en separat databas och en separat RabbitMQ. Denna metod har den fördelen att om det uppstÄr en nödsituation pÄ nÄgot sÄrbart stÀlle, sÄ bryts inte hela tjÀnsten utan bara en del av den.

Antalet arbetarapplikationer Àr oÀndligt, sÄ API:et kan enkelt skalas horisontellt bakom belastningsbalanserare för att öka prestandan och redundansen.

I vissa tjĂ€nster krĂ€vs koordinering inom tjĂ€nsten — nĂ€r komplexa sekventiella operationer uppstĂ„r mellan API och arbetare. I dessa fall anvĂ€nds ett centralt koordinationssystem, en klusterlösning som Redis, Memcache, etcd, som tillĂ„ter en arbetare att informera en annan om att en uppgift Ă€r tilldelad honom ("Tack, ta inte den hĂ€r"). Vi anvĂ€nder etcd. Arbetarna kommunicerar vanligtvis aktivt med databasen, skriver och lĂ€ser information dĂ€rifrĂ„n. Som databas anvĂ€nder vi MariaDB, som vi har i en multimaster-kluster.

SÄdan en klassisk ensam tjÀnst Àr organiserad pÄ ett allmÀnt accepterat sÀtt för OpenStack. Den kan betraktas som ett slutet system, för vilket skalnings- och redundantlösningarna Àr ganska uppenbara. Till exempel, för redundans Àr det tillrÀckligt att sÀtta en belastningsbalanserare framför API:et. Skalningen av arbetarna uppnÄs genom att öka deras antal.

En svaghet i hela systemet Àr RabbitMQ och MariaDB. Deras arkitektur förtjÀnar en egen artikel. I den hÀr artikeln vill jag fokusera pÄ API:ns felbestÀndighet.

Hur Äterfinns en robust webbarkitektur pÄ plattformen Mail.ru Cloud Solutions.
Arkitektur för OpenStack-applikationen. Belastningsbalansering och felbestÀndighet för molnplattformen.

Vi gör HAProxy-belastningsbalanseraren felbestÀndig med hjÀlp av ExaBGP.

För att vÄra API:er ska vara skalbara, snabba och felbestÀndiga installerade vi en belastningsbalanserare. Vi valde HAProxy. Enligt min Äsikt har den alla nödvÀndiga egenskaper för vÄr uppgift: belastningsbalansering pÄ flera OSI-nivÄer, hanteringsgrÀnssnitt, flexibilitet och skalbarhet, mÄnga belastningsbalanseringsmetoder och stöd för sessions-tabeller.

Det första problemet som mÄste lösas Àr felbestÀndigheten hos sjÀlva belastningsbalanseraren. Att bara installera en belastningsbalanserare skapar ocksÄ en punktsvaghet: om belastningsbalanseraren gÄr sönder, faller tjÀnsten. För att undvika detta anvÀnde vi HAProxy tillsammans med ExaBGP.

ExaBGP möjliggör implementering av en tjÀnstehÀlsokontroll. Vi anvÀnde denna mekanism för att kontrollera HAProxy:s funktionalitet och stÀnga av HAProxy-tjÀnsten frÄn BGP vid problem.

Diagram över ExaBGP+HAProxy

  1. Vi installerar den nödvÀndiga programvaran, ExaBGP och HAProxy, pÄ tre servrar.
  2. PÄ varje server skapar vi en loopback-grÀnssnitt.
  3. Vi konfigurerar samma offentliga IP-adress pÄ detta grÀnssnitt pÄ alla tre servrar.
  4. Den offentliga IP-adressen annonseras pÄ internet via ExaBGP.

FelbestÀndighet uppnÄs genom att annonsera samma IP-adress frÄn alla tre servrar. FrÄn nÀtverkets synvinkel Àr samma adress tillgÀnglig frÄn tre olika nÀsta hopp. Routern ser tre identiska rutter, vÀljer den mest prioriterade baserat pÄ sin egen metric (detta Àr oftast samma alternativ), och trafiken gÄr endast till en av servrarna.

Vid problem med HAProxy eller en server som gÄr ner slutar ExaBGP annonsera rutten, och trafiken vÀxlar smidigt till en annan server.

PÄ sÄ sÀtt har vi uppnÄtt felbestÀndighet för belastningsbalanseraren.

Hur Äterfinns en robust webbarkitektur pÄ plattformen Mail.ru Cloud Solutions.
FelbestÀndighet för HAProxy-belastningsbalanserare.

Schemat Àr inte perfekt: vi har lÀrt oss att reservera HAProxy, men vi har inte lÀrt oss att fördela belastningen inom tjÀnsterna. DÀrför har vi utökat denna plan: vi har övergÄtt till belastningsbalansering mellan flera offentliga IP-adresser.

DNS-baserad balansering plus BGP

FrĂ„gan om lastbalansering med vĂ„ra HAProxy Ă€r fortfarande olöst. ÄndĂ„ kan det lösas ganska enkelt, precis som vi har gjort med oss sjĂ€lva.

För att balansera tre servrar behövs 3 offentliga IP-adresser och den gamla hederliga DNS. Varje av dessa adresser definieras pÄ loopback-grÀnssnittet för varje HAProxy och annonseras pÄ internet.

I OpenStack anvĂ€nds en tjĂ€nstkatalog för att hantera resurser, dĂ€r API-endpointen för olika tjĂ€nster anges. I denna katalog skriver vi in domĂ€nnamnet – public.infra.mail.ru, som resolveras via DNS till tre olika IP-adresser. Resultatet blir att vi fĂ„r lastbalansering mellan tre adresser via DNS.

Men eftersom vi vid annonseringen av offentliga IP-adresser inte hanterar prioriteter för serverval, Àr detta fortfarande inte lastbalansering. Vanligtvis vÀljs endast en server baserat pÄ IP-adressens rang, medan de tvÄ andra blir inaktiva, eftersom inga metrikvÀrden anges i BGP.

Vi började annonsera rutter via ExaBGP med olika metrikvÀrden. Varje lastbalanserare annonserar alla tre offentliga IP-adresser, men en av dem, den primÀra för den aktuella lastbalanseraren, annonseras med lÀgsta metrik. SÄ lÀnge alla tre lastbalanserare Àr aktiva, gÄr begÀrningar till den första IP-adressen till den första lastbalanseraren, begÀrningar till den andra till den andra, och till den tredje till den tredje.

Vad hÀnder i det ögonblicket nÀr en av lastbalanserarna faller? Vid fel pÄ nÄgon lastbalanserare annonseras dess grundlÀggande adress fortfarande av de tvÄ andra, och trafiken omfördelas mellan dem. SÄledes ger vi anvÀndaren flera IP-adresser via DNS. Genom DNS-balansering och olika metrikvÀrden fÄr vi en jÀmn fördelning av belastningen pÄ alla tre lastbalanserarna. Och vi förlorar inte heller redundansen.

Hur Äterfinns en robust webbarkitektur pÄ plattformen Mail.ru Cloud Solutions.
HAProxy-lastbalansering baserad pÄ DNS + BGP

Interaktion mellan ExaBGP och HAProxy

SÄ, vi har implementerat redundans för serveravbrott baserat pÄ att annonseringen av rutter upphör. Men HAProxy kan ocksÄ stÀngas av av andra skÀl Àn serverns fel: administrationsfel, inre tjÀnstfel. Vi vill undanröja en trasig lastbalanserare frÄn belastningen Àven i dessa fall, och dÀrför behövs en annan mekanism.

DÀrför implementerade vi heartbeat mellan ExaBGP och HAProxy genom att bygga pÄ den tidigare schemat. Det hÀr Àr en mjukvaruimplementation av interaktionen mellan ExaBGP och HAProxy, dÀr ExaBGP anvÀnder skrÀddarsydda skript för att kontrollera applikationernas status.

För detta behöver man konfigurera en hÀlsokontrollare i ExaBGP som kan kontrollera HAProxy:s status. I vÄrt fall konfigurerade vi en health backend i HAProxy, och frÄn ExaBGP gör vi en enkel GET-förfrÄgan. Om annonseringen slutar ske, Àr det troligt att HAProxy inte fungerar, och vi behöver inte annonsera det.

Hur Äterfinns en robust webbarkitektur pÄ plattformen Mail.ru Cloud Solutions.
HAProxy HĂ€lsokontroll

HAProxy Peers: session synkronisering

NÀsta steg var att synkronisera sessionerna. Vid arbete genom distribuerade lastbalanserare Àr det svÄrt att bevara information om kundsessioner. Men HAProxy Àr en av de fÄ lastbalanserarna som kan göra detta tack vare funktionaliteten Peers - möjligheten att överföra sessionstabeller mellan olika HAProxy-processer.

Det finns olika metoder för lastbalansering: enkla, som round-robin, och avancerade, dÀr kundens session sparas, och han alltid hamnar pÄ samma server som tidigare. Vi ville implementera det andra alternativet.

I HAProxy anvÀnds stick-tables för att spara kundsessioner. De lagrar den ursprungliga IP-adressen för kunden, den valda mÄladressen (backend) och viss metadata. Vanligtvis anvÀnds stick-tabeller för att spara paret source-IP + destination-IP, vilket Àr sÀrskilt anvÀndbart för applikationer som inte kan överföra anvÀndarsessionens kontext nÀr de vÀxlar till en annan lastbalanserare, till exempel i RoundRobin-lÀget.

Om stick-tabellen lÀr sig att flytta mellan olika HAProxy-processer (mellan vilka lastbalansering sker) kan vÄra lastbalanserare arbeta med en gemensam uppsÀttning stick-tabeller. Detta kommer att möjliggöra sömlös nÀtverksvÀxling för kunden vid nedgÄng av en av lastbalanserarna, och arbetet med kundsessionerna kan fortsÀtta pÄ de samma backends som valdes tidigare.

För att det ska fungera korrekt mÄste problemet med source IP-adressen för lastbalanseraren, frÄn vilken sessionen skapades, lösas. I vÄrt fall Àr detta en dynamisk adress pÄ loopback-grÀnssnittet.

Korrekt arbete med peers uppnĂ„s endast under vissa förutsĂ€ttningar. Det vill sĂ€ga att TCP-tidsgrĂ€nserna mĂ„ste vara tillrĂ€ckligt stora eller att omkopplingen mĂ„ste ske tillrĂ€ckligt snabbt för att TCP-sessionen inte ska avbrytas. ÄndĂ„ möjliggör detta sömlös omkoppling.

Vi har en tjÀnst inom IaaS som Àr byggd med samma teknologi. Det Àr Load Balancer som tjÀnst för OpenStack, som heter Octavia. Den Àr baserad pÄ tvÄ processer av HAProxy, och stöd för peers inbyggs frÄn början. I den hÀr tjÀnsten har de visat sig fungera mycket bra.

PÄ bilden visas schematiskt hur peers-tabeller flyttas mellan tre instanser av HAProxy, med en konfiguration som visar hur detta kan stÀllas in:

Hur Äterfinns en robust webbarkitektur pÄ plattformen Mail.ru Cloud Solutions.
HAProxy Peers (session synkronisering)

Om du ska implementera en liknande lösning, bör dess funktion testas noggrant. Det Àr inte sÀkert att det fungerar pÄ samma sÀtt i 100 % av fallen. Men Ätminstone kommer du inte att förlora stick-tabeller nÀr du behöver komma ihÄg kÀll-IP för klienten.

BegrÀnsning av antalet samtidiga förfrÄgningar frÄn samma klient

Alla tjÀnster som Àr offentligt tillgÀngliga, inklusive vÄra API:er, kan utsÀttas för stormar av förfrÄgningar. Orsakerna till detta kan vara helt olika, frÄn anvÀndarfel till avsiktliga attacker. Vi blir periodvis DDoSat via IP-adresser. Kunder gör ofta misstag i sina skript och orsakar mini-DDoS attacker.

PÄ ett eller annat sÀtt Àr det nödvÀndigt att förutse ytterligare skydd. En uppenbar lösning Àr att begrÀnsa antalet förfrÄgningar till API:et och inte slösa processortid pÄ att hantera skadliga förfrÄgningar.

För att implementera sÄdana begrÀnsningar anvÀnder vi rate limits, organiserade pÄ basen av HAProxy, med hjÀlp av samma stick-tabeller. GrÀnserna stÀlls in ganska enkelt och gör det möjligt att begrÀnsa anvÀndaren vad gÀller antalet förfrÄgningar till API:et. Algoritmen kommer ihÄg kÀll-IP:n som gör förfrÄgningarna och begrÀnsar antalet samtidiga förfrÄgningar frÄn en anvÀndare. Naturligtvis har vi berÀknat det genomsnittliga belastningsprofilen för API:et hos varje tjÀnst och satt grÀnsen till cirka 10 gÄnger detta vÀrde. Vi fortsÀtter att noggrant övervaka situationen och hÄller ett öga pÄ lÀget.

Hur ser det ut i praktiken? Vi har kunder som stÀndigt anvÀnder vÄra API:er för automatisk skalning. De skapar ungefÀr tvÄ till tre hundra virtuella maskiner pÄ morgonen och tar bort dem pÄ kvÀllen. För att skapa en virtuell maskin i OpenStack, Àven med PaaS-tjÀnster, krÀvs minst 1000 API-anrop, eftersom interaktionen mellan tjÀnsterna ocksÄ sker via API.

Dessa överflyttningar av uppgifter medför en ganska stor belastning. Vi har uppskattat denna belastning, samlat in dagliga toppar, ökat dem tio gÄnger, och det blev vÄr rate-limit. Vi hÄller ett öga pÄ situationen. Vi ser ofta botar och skannrar som försöker ta reda pÄ om vi har nÄgra CGA-skript som kan köras, och vi tar aktivt bort dem.

Hur man uppdaterar kodbasen utan att anvÀndarna mÀrker nÄgot

Vi implementerar ocksÄ redundans pÄ nivÄn för kodutplaceringens processer. Vid utplaceringar sker det ibland fel, men deras pÄverkan pÄ tjÀnsternas tillgÀnglighet kan minimeras.

Vi uppdaterar stÀndigt vÄra tjÀnster och mÄste sÀkerstÀlla att processen för att uppdatera kodbasen sker utan att pÄverka anvÀndarna. Vi lyckades lösa denna uppgift genom att anvÀnda möjligheterna för HAProxy-hantering och implementering av Graceful Shutdown i vÄra tjÀnster.

För att lösa denna uppgift behövs det att sÀkerstÀlla hantering av belastningsbalanseraren och "korrekt" avstÀngning av tjÀnster:

  • I fallet med HAProxy sker hanteringen genom en statsfil, som i huvudsak Ă€r en socket och definieras i HAProxy-konfigurationen. Kommandon kan skickas till den via stdio. Men vĂ„rt huvudsakliga verktyg för konfigurationskontroll Ă€r Ansible, sĂ„ det finns en inbyggd modul för att hantera HAProxy, som vi anvĂ€nder aktivt.
  • Största delen av vĂ„ra API- och Engine-tjĂ€nster stödjer tekniken för graceful shutdown: vid avstĂ€ngning vĂ€ntar de tills den aktuella uppgiften Ă€r helt slutförd, oavsett om det handlar om en http-förfrĂ„gan eller nĂ„gon annan tjĂ€nstrelaterad uppgift. Detsamma gĂ€ller för arbetaren. Han kĂ€nner till alla uppgifter han hanterar och avslutas nĂ€r allt Ă€r avslutat framgĂ„ngsrikt.

Tack vare dessa tvÄ punkter ser vÄr sÀkra algoritm för utplacering ut som följer.

  1. Utvecklaren samlar ihop ett nytt kodpaket (för oss Àr det RPM), testar det i dev-miljön, testar i stage och lÀmnar det i stage-repositoriet.
  2. Utvecklaren sÀtter en uppgift för distribution med en sÄ detaljerad beskrivning av "artefakterna" som möjligt: version av det nya paketet, beskrivning av den nya funktionaliteten och andra detaljer om distributionen vid behov.
  3. Systemadministratören pÄbörjar uppdateringen. Hen startar Ansible-spelboken som i sin tur gör följande:
    • HĂ€mtar paketet frĂ„n stage-arkivet och uppdaterar paketversionen i produktarkivet.
    • Kommer att sammanstĂ€lla en lista över backend-tjĂ€nster som ska uppdateras.
    • StĂ€nger av den första uppdaterade tjĂ€nsten i HAProxy och vĂ€ntar pĂ„ att dess processer ska avslutas. Tack vare en smidig avstĂ€ngning Ă€r vi sĂ€kra pĂ„ att alla pĂ„gĂ„ende kundförfrĂ„gningar slutförs framgĂ„ngsrikt.
    • Efter att API:et, arbetarna och HAProxy har stĂ€ngts av, uppdateras koden.
    • Ansible startar tjĂ€nsterna.
    • För varje tjĂ€nst aktiverar den specifika "knappar" som utför enhetstestning baserat pĂ„ ett antal förutbestĂ€mda nyckeltester. En grundlĂ€ggande kontroll av den nya koden utförs.
    • Om inga fel upptĂ€cktes i det föregĂ„ende steget aktiveras backend-tjĂ€nsten.
    • GĂ„r vidare till nĂ€sta backend.
  4. Efter att alla backend-tjÀnster har uppdaterats startas funktionella tester. Om dessa inte rÀcker, granskar utvecklaren all ny funktionalitet som hen har implementerat.

DĂ€rmed avslutas distributionen.

Hur Äterfinns en robust webbarkitektur pÄ plattformen Mail.ru Cloud Solutions.
Cykeln för uppdatering av tjÀnsten

Detta schema skulle inte fungera om vi inte hade en regler. Vi stödjer samtidigt den gamla och den nya versionen i produktion. Redan i mjukvaruutvecklingsfasen planeras det att Àven om det sker förÀndringar i databasstrukturen för tjÀnsten, sÄ ska de inte bryta den tidigare koden. Resultatet blir en gradvis uppdatering av kodbasen.

Slutsats

NÀr jag delar mina egna tankar om hög tillgÀnglighet i webbarkitektur vill jag Ànnu en gÄng betona dess nyckelpunkter:

  • fysisk redundans;
  • nĂ€tverksredundans (lastbalanserare, BGP);
  • redundans hos den anvĂ€nda och utvecklade mjukvaran.

Önskar alla stabil driftstid!

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster