Hur feltolerant webbarkitektur implementeras i Mail.ru Cloud Solutions-plattformen

Hur feltolerant webbarkitektur implementeras i Mail.ru Cloud Solutions-plattformen

Hej Habr! Jag är Artem Karamyshev och chef för systemadministrationsgruppen. Mail.Ru molnlösningar (MCS)Under det senaste året har vi lanserat många nya produkter. Vi ville se till att API-tjänsterna är lätt skalbara, feltoleranta och redo för snabb tillväxt av användarbelastningen. Vår plattform är implementerad på OpenStack, och jag vill berätta vilka feltoleransproblem med komponenter vi var tvungna att lösa för att få ett feltolerant system. Jag tror att detta kommer att vara intressant för dem som också utvecklar produkter på OpenStack.

Plattformens övergripande motståndskraft består av motståndskraften hos dess komponenter. Så vi kommer gradvis att gå igenom alla nivåer där vi har identifierat risker och åtgärdat dem.

En videoversion av den här artikeln, vars ursprungliga källa var en presentation på Uptime day 4-konferensen, organiserad av ITSumma, du kan ta en titt på Uptime Community YouTube-kanalen.

Feltolerans i den fysiska arkitekturen

Den publika delen av MCS-molnet är för närvarande baserad i två Tier III-datacenter, mellan vilka det finns en privat mörk fiber, reserverad på fysisk nivå via olika rutter, med en datahastighet på 200 Gbps. Tier III-nivån ger den nödvändiga nivån av feltolerans för den fysiska infrastrukturen.

Mörkfiber är redundant på både fysisk och logisk nivå. Processen för redundans av kanaler var iterativ, problem uppstod och vi förbättrar ständigt anslutningen mellan datacenter.

Till exempel, för inte så länge sedan, under arbete i en brunn nära ett av datacentren, trängde en grävmaskin igenom ett rör, och både huvud- och reservoptikkabeln fanns inuti detta rör. Vår feltoleranta kommunikationskanal med datacentret visade sig vara sårbar vid ett tillfälle, i brunnen. Följaktligen förlorade vi en del av infrastrukturen. Vi drog slutsatser, vidtog ett antal åtgärder, inklusive att lägga ytterligare optik genom den angränsande brunnen.

Datacenter har kommunikationsleverantörers närvaropunkter, till vilka vi sänder våra prefix via BGP. För varje nätverksriktning väljs det bästa måttet, vilket gör att vi kan ge bästa möjliga anslutningskvalitet till olika klienter. Om anslutningen via en leverantör avbryts, bygger vi om vår routing genom tillgängliga leverantörer.

Vid fel på leverantören byter vi automatiskt till nästa. Vid fel på ett av datacentren har vi en spegelkopia av våra tjänster i det andra datacentret, som tar över hela belastningen.

Hur feltolerant webbarkitektur implementeras i Mail.ru Cloud Solutions-plattformen
Motståndskraft i fysisk infrastruktur

Vad vi använder för feltolerans på applikationsnivå

Vår tjänst är byggd på ett antal open source-komponenter.

ExaBGP — en tjänst som implementerar ett antal funktioner med hjälp av det dynamiska routingprotokollet baserat på BGP. Vi använder det aktivt för att tillkännage våra vita IP-adresser genom vilka användare får tillgång till API:et.

haproxy — en högbelastningsbalanserare som låter dig konfigurera mycket flexibla trafikbalanseringsregler på olika nivåer av OSI-modellen. Vi använder den för balansering inför alla tjänster: databaser, meddelandehanteringstjänster, API-tjänster, webbtjänster, våra interna projekt — allt ligger bakom HAProxy.

API-applikation — en webbapplikation skriven i Python, med vars hjälp användaren hanterar sin infrastruktur, sin tjänst.

Arbetaransökan (hädanefter helt enkelt worker) — i OpenStack-tjänster är detta en infrastrukturdemon som tillåter API-kommandon att överföras till infrastrukturen. Till exempel sker diskskapande i worker, och begäran om skapande sker i API-applikation.

OpenStack-applikationsstandardarkitektur

De flesta tjänster som utvecklats för OpenStack försöker följa ett enda paradigm. Tjänsten består vanligtvis av två delar: API och workers (backend-executors). Som regel är API:et en WSGI-applikation i Python, som startas antingen som en oberoende process (daemon) eller med hjälp av en färdig webbserver, Nginx eller Apache. API:et bearbetar användarens begäran och skickar vidare instruktioner för exekvering till worker-applikationen. Överföringen sker med hjälp av en meddelandebroker, vanligtvis RabbitMQ, resten har dåligt stöd. När meddelanden når brokern bearbetas de av workers och returnerar vid behov ett svar.

Detta paradigm innebär isolerade gemensamma felpunkter: RabbitMQ och databasen. Men RabbitMQ är isolerat inom en tjänst och kan i teorin vara individuellt för varje tjänst. Så i MCS separerar vi dessa tjänster så mycket som möjligt, för varje separat projekt skapar vi en separat databas, en separat RabbitMQ. Denna metod är bra eftersom i händelse av en olycka på vissa sårbara punkter går inte hela tjänsten sönder, utan bara en del av den.

Det finns ingen gräns för antalet arbetsapplikationer, så API:et kan enkelt skalas horisontellt bakom balanserare för att öka prestanda och feltolerans.

Vissa tjänster kräver samordning inom tjänsten – när komplexa sekventiella operationer sker mellan API och arbetare. I det här fallet används ett enda koordinationscenter, ett klustersystem som Redis, Memcache, etcd, vilket gör att en arbetare kan berätta för en annan att den här uppgiften är tilldelad honom ("snälla, ta den inte"). Vi använder etcd. Som regel kommunicerar arbetare aktivt med databasen, skriver och läser information från den. Som databas använder vi mariadb, som finns i vårt multimasterkluster.

En sådan klassisk enskild tjänst är organiserad på ett sätt som allmänt accepteras för OpenStack. Den kan betraktas som ett slutet system, för vilket metoderna för skalning och feltolerans är ganska uppenbara. Till exempel, för feltolerans för API:er räcker det att placera en balanserare framför dem. Skalning av arbetare uppnås genom att öka deras antal.

Den svaga punkten i hela schemat är RabbitMQ och MariaDB. Deras arkitektur förtjänar en separat artikel. I den här artikeln vill jag fokusera på API-feltolerans.

Hur feltolerant webbarkitektur implementeras i Mail.ru Cloud Solutions-plattformen
Openstack-applikationsarkitektur. Balansering och feltolerans för molnplattformen.

Göra HAProxy Load Balancer feltolerant med ExaBGP

För att göra våra API:er skalbara, snabba och feltoleranta har vi installerat en balanserare framför dem. Vi valde HAProxy. Enligt min mening har den alla nödvändiga egenskaper för vår uppgift: balansering på flera OSI-nivåer, hanteringsgränssnitt, flexibilitet och skalbarhet, ett stort antal balanseringsmetoder, stöd för sessionstabeller.

Det första problemet som behövde lösas var själva balanserns feltolerans. Att bara installera balansern skapar också en felpunkt: balansern går sönder – tjänsten kraschar. För att förhindra att detta händer använde vi HAProxy tillsammans med ExaBGP.

ExaBGP möjliggör implementering av en mekanism för tjänstehälsokontroll. Vi använde denna mekanism för att kontrollera HAProxys hälsotillstånd och, vid problem, för att inaktivera HAProxy-tjänsten från BGP.

ExaBGP+HAProxy-schema

  1. Vi installerar nödvändig programvara, ExaBGP och HAProxy, på tre servrar.
  2. På var och en av servrarna skapar vi ett loopback-gränssnitt.
  3. På alla tre servrarna registrerar vi samma vita IP-adress på detta gränssnitt.
  4. Den vita IP-adressen meddelas till internet via ExaBGP.

Feltolerans uppnås genom att samma IP-adress tillkännages från alla tre servrar. Ur nätverkets synvinkel är samma adress tillgänglig från tre olika nästa hopp. Routern ser tre identiska rutter, väljer den med högst prioritet baserat på sin egen mätvärde (vanligtvis samma alternativ), och trafiken går endast till en av servrarna.

Vid problem med HAProxy-drift eller serverfel slutar ExaBGP att annonsera rutten och trafiken växlar smidigt till en annan server.

På detta sätt uppnådde vi feltolerans hos balanseraren.

Hur feltolerant webbarkitektur implementeras i Mail.ru Cloud Solutions-plattformen
HAProxy Load Balancer Feltolerans

Schemat visade sig vara ofullkomligt: ​​vi lärde oss att reservera HAProxy, men inte att fördela belastningen inom tjänsterna. Därför utökade vi detta schema lite: vi bytte till att balansera mellan flera vita IP-adresser.

DNS-baserad balansering plus BGP

Problemet med lastbalansering framför vår HAProxy är fortfarande olöst. Det kan dock lösas ganska enkelt, precis som vi gjorde i vår.

För att balansera tre servrar behöver du tre vita IP-adresser och den gamla goda DNS-servern. Var och en av dessa adresser definieras på loopback-gränssnittet för varje HAProxy och meddelas på internet.

I OpenStack används en tjänstekatalog för att hantera resurser, där API-slutpunkten för en viss tjänst anges. I denna katalog anger vi domännamnet — public.infra.mail.ru, vilket via DNS löses upp av tre olika IP-adresser. Som ett resultat får vi lastfördelning mellan tre adresser via DNS.

Men eftersom vi inte kontrollerar prioriteringarna för serverval när vi tillkännager vitlistade IP-adresser, är detta inte balanserat ännu. Vanligtvis kommer endast en server att väljas baserat på IP-adressens senioritet, och de andra två kommer att vara inaktiva eftersom inga mätvärden anges i BGP.

Vi började utfärda rutter via ExaBGP med olika mätvärden. Varje balanserare meddelar alla tre vita IP-adresser, men en av dem, den huvudsakliga för denna balanserare, meddelas med det lägsta mätvärdet. Så medan alla tre balanserare är i drift går förfrågningar till den första IP-adressen till den första balanseraren, förfrågningar till den andra till den andra, till den tredje till den tredje.

Vad händer när en av balanserarna går sönder? När en balanserare går sönder, annonseras dess huvudadress fortfarande från de andra två, och trafiken omfördelas mellan dem. Således ger vi användaren flera IP-adresser samtidigt via DNS. Genom att balansera med DNS och olika mätvärden får vi en jämn fördelning av belastningen på alla tre balanserarna. Och samtidigt förlorar vi inte feltolerans.

Hur feltolerant webbarkitektur implementeras i Mail.ru Cloud Solutions-plattformen
HAProxy-belastningsbalansering baserat på DNS ​​+ BGP

Interaktion mellan ExaBGP och HAProxy

Så vi har implementerat feltolerans vid serverfel, baserat på att ruttmeddelanden avslutas. Men HAProxy kan också kopplas bort av andra orsaker än serverfel: administrationsfel, fel inom tjänsten. Vi vill även i dessa fall ta bort den trasiga balanseraren från belastningen, och vi behöver en annan mekanism.

Därför utökade vi det tidigare schemat och implementerade ett pulsslag mellan ExaBGP och HAProxy. Detta är en programvaruimplementering av interaktionen mellan ExaBGP och HAProxy, där ExaBGP använder anpassade skript för att kontrollera statusen för applikationer.

För att göra detta behöver du konfigurera en hälsokontroll i ExaBGP-konfigurationen som kan kontrollera HAProxy-statusen. I vårt fall konfigurerade vi en hälsobackend i HAProxy, och från ExaBGP-sidan kontrollerar vi med en enkel GET-förfrågan. Om tillkännagivandet slutar hända fungerar HAProxy troligtvis inte, och det finns ingen anledning att tillkännage det.

Hur feltolerant webbarkitektur implementeras i Mail.ru Cloud Solutions-plattformen
HAProxy-hälsokontroll

HAProxy-peers: Sessionssynkronisering

Nästa sak som behövde göras var att synkronisera sessioner. När man arbetar via distribuerade balanserare är det svårt att organisera lagringen av information om klientsessioner. Men HAProxy är en av få balanserare som kan göra detta tack vare Peers-funktionen - möjligheten att överföra en sessionstabell mellan olika HAProxy-processer.

Det finns olika metoder för balansering: enkla sådana, som round robin, och utökade, när klientens session är ihågkommen, och varje gång hen kommer till samma server som tidigare. Vi ville implementera det andra alternativet.

HAProxy använder stick-tabeller för att lagra klientsessioner för denna mekanism. De lagrar klientens käll-IP-adress, den valda måladressen (backend) och viss tjänstinformation. Stick-tabeller används vanligtvis för att lagra ett käll-IP + destination-IP-par, vilket är särskilt användbart för applikationer som inte kan överföra användarsessionskontexten när de byter till en annan balanserare, till exempel i RoundRobin-balanseringsläge.

Om vi ​​lär stick-tabellen att röra sig mellan olika HAProxy-processer (mellan vilka balansering sker), kommer våra balanserare att kunna arbeta med en pool av stick-tabeller. Detta möjliggör sömlös växling av klientnätverket när en av balanserarna fallerar, arbetet med klientsessioner kommer att fortsätta på samma backends som valdes tidigare.

För korrekt drift måste problemet med käll-IP-adressen för balanseraren från vilken sessionen upprättas lösas. I vårt fall är detta en dynamisk adress på loopback-gränssnittet.

Peers fungerar endast korrekt under vissa förhållanden. Det vill säga att TCP-timeouts måste vara tillräckligt långa eller att växlingen måste vara tillräckligt snabb så att TCP-sessionen inte hinner brytas. Detta möjliggör dock sömlös växling.

Vi har en tjänst i IaaS byggd på samma teknik. Detta är Load Balancer som en tjänst för OpenStack, som heter Octavia. Den är baserad på två HAProxy-processer och har inbyggt stöd från andra parter från början. De har visat sig vara utmärkta inom den här tjänsten.

Bilden visar schematiskt förflyttningen av peer-tabeller mellan tre HAProxy-instanser, och en konfigurationsguide för hur detta kan konfigureras erbjuds:

Hur feltolerant webbarkitektur implementeras i Mail.ru Cloud Solutions-plattformen
HAProxy-peers (sessionssynkronisering)

Om du implementerar samma schema måste du testa det noggrant. Det är inte ett faktum att det kommer att fungera i samma form i 100 % av fallen. Men åtminstone kommer du inte att förlora minnestabeller när du behöver komma ihåg klientens käll-IP.

Begränsa antalet samtidiga förfrågningar från samma klient

Alla offentligt tillgängliga tjänster, inklusive våra API:er, kan utsättas för laviner av förfrågningar. Orsakerna till dem kan vara helt olika, från användarfel till riktade attacker. Vi utsätts regelbundet för DDoS-attacker baserat på IP-adresser. Klienter gör ofta misstag i sina skript, vilket orsakar mini-DDoS.

På ett eller annat sätt måste ytterligare skydd tillhandahållas. Den uppenbara lösningen är att begränsa antalet förfrågningar till API:et och inte slösa processortid på att bearbeta skadliga förfrågningar.

För att implementera sådana begränsningar använder vi hastighetsgränser, organiserade baserat på HAProxy, med samma sticktabeller. Gränserna konfigureras ganska enkelt och låter dig begränsa användaren med antalet förfrågningar till API:et. Algoritmen kommer ihåg käll-IP:n från vilken förfrågningarna görs och begränsar antalet samtidiga förfrågningar från en användare. Naturligtvis beräknade vi den genomsnittliga belastningsprofilen på API:et för varje tjänst och satte en gräns ≈ 10 gånger större än detta värde. Vi fortsätter att noggrant övervaka situationen och håller fingret på pulsen.

Hur ser detta ut i praktiken? Vi har kunder som ständigt använder våra API:er för autoskalning. De skapar ungefär två eller trehundra virtuella maskiner närmare morgonen och tar bort dem närmare kvällen. För OpenStack krävs det minst 1000 API-förfrågningar för att skapa en virtuell maskin, särskilt med PaaS-tjänster, eftersom interaktion mellan tjänster också sker via API:er.

Sådana uppgiftsöverföringar orsakar en ganska stor belastning. Vi utvärderade denna belastning, samlade in dagliga toppar, ökade dem tiofaldigt, och detta blev vår hastighetsgräns. Vi håller fingret på pulsen. Vi ser ofta bottar, skannrar, som försöker se om vi har några CGA-skript som kan startas, vi klipper aktivt bort dem.

Hur man uppdaterar sin kodbas utan att användarna märker det

Vi implementerar även feltolerans på nivån av koddistributionsprocesser. Det förekommer fel under utrullningar, men deras inverkan på tjänstens tillgänglighet kan minimeras.

Vi uppdaterar ständigt våra tjänster och behöver se till att kodbasen uppdateras utan att det påverkar användarna. Vi har uppnått detta genom att utnyttja HAProxys hanteringsfunktioner och implementera Graceful Shutdown i våra tjänster.

För att lösa detta problem var det nödvändigt att säkerställa balanshantering och "korrekt" avstängning av tjänster:

  • När det gäller HAProxy sker kontrollen via en statistikfil, som i huvudsak är en socket och definieras i HAProxy-konfigurationen. Du kan skicka kommandon till den via stdio. Men vårt huvudsakliga konfigurationsverktyg är ansible, så det har en inbyggd modul för att hantera HAProxy. Vilken vi aktivt använder.
  • De flesta av våra API- och motortjänster stöder tekniker för smidig avstängning: vid avstängning väntar de på att den aktuella uppgiften ska slutföras, oavsett om det är en http-förfrågan eller någon tjänstuppgift. Samma sak händer med en arbetare. Den känner till alla uppgifter den utför och avslutas när den har slutfört allting.

Tack vare dessa två punkter ser vår säkra distributionsalgoritm ut så här.

  1. Utvecklaren bygger ett nytt kodpaket (i vårt fall är det RPM), testar det i utvecklingsmiljön, testar det i scenen och lämnar det i scenens repository.
  2. Utvecklaren anger en uppgift för distribution med den mest detaljerade beskrivningen av "artefakterna": versionen av det nya paketet, en beskrivning av den nya funktionaliteten och andra detaljer om distributionen om det behövs.
  3. Systemadministratören startar uppdateringen. Kör Ansible-handboken, som i sin tur gör följande:
    • Hämtar ett paket från stage-arkivet och uppdaterar paketversionen i produktarkivet baserat på det.
    • Sammanställer en lista över backend-tjänster för den tjänst som uppdateras.
    • Stänger av den första tjänsten i HAProxy som ska uppdateras och väntar på att dess processer ska slutföras. En smidig avstängning säkerställer att alla aktuella klientförfrågningar slutförs utan problem.
    • Efter ett fullständigt stopp av API:et, workers och avstängning av HAProxy uppdateras koden.
    • Ansible startar tjänster.
    • För varje tjänst hämtas vissa "handtag" som utför enhetstestning för ett antal fördefinierade nyckeltester. Grundläggande kontroll av ny kod sker.
    • Om inga fel hittades i föregående steg aktiveras backend-systemet.
    • Låt oss gå vidare till nästa backend.
  4. Efter att alla backend-system har uppdaterats körs funktionstester. Om de saknas tittar utvecklaren på eventuella nya funktioner som han har gjort.

Detta slutför distributionen.

Hur feltolerant webbarkitektur implementeras i Mail.ru Cloud Solutions-plattformen
Serviceuppdateringscykel

Det här systemet skulle inte fungera om vi inte hade en regel. Vi stöder både gamla och nya versioner samtidigt i strid. I förväg, i mjukvaruutvecklingsstadiet, är det fastställt att även om det sker ändringar i tjänstedatabasen, kommer de inte att förstöra den tidigare koden. Som ett resultat sker en gradvis uppdatering av kodbasen.

Slutsats

Jag delar med mig av mina egna tankar om feltolerant webbarkitektur och vill återigen lyfta fram dess viktigaste punkter:

  • fysisk feltolerans;
  • nätverksfeltolerans (balanserare, BGP);
  • feltolerans hos den programvara som används och utvecklas.

Ha en stabil drifttid allihopa!

Källa: will.com

Lägg en kommentar