Hvordan feiltolerant nettarkitektur implementeres i Mail.ru Cloud Solutions-plattformen

Hvordan feiltolerant nettarkitektur implementeres i Mail.ru Cloud Solutions-plattformen

Hei, Habr! Jeg er Artem Karamyshev, leder for systemadministrasjonsteamet Mail.Ru Cloud Solutions (MCS). Vi har hatt mange nye produktlanseringer det siste året. Vi ønsket å sikre at API-tjenester var lett skalerbare, feiltolerante og klare for rask vekst i brukerbelastning. Plattformen vår er implementert på OpenStack, og jeg vil fortelle deg hvilke komponentfeiltoleranseproblemer vi måtte løse for å få et feiltolerant system. Jeg tror dette vil være interessant for de som også utvikler produkter på OpenStack.

Den generelle feiltoleransen til en plattform består av motstandskraften til komponentene. Så vi vil gradvis gå gjennom alle nivåene der vi identifiserte risikoer og lukket dem.

Videoversjon av denne historien, hvor den primære kilden var en rapport på Uptime day 4-konferansen, organisert av ITSumma, du kan se på YouTube-kanalen Uptime Community.

Resiliens av den fysiske arkitekturen

Den offentlige delen av MCS-skyen er nå basert i to Tier III-datasentre, mellom dem er det sin egen mørk fiber, reservert på fysisk nivå av forskjellige ruter, med en gjennomstrømning på 200 Gbit/s. Tier III gir nødvendig grad av feiltoleranse for den fysiske infrastrukturen.

Mørk fiber er reservert både på det fysiske og logiske nivået. Kanalreservasjonsprosessen var iterativ, problemer oppsto, og vi forbedrer stadig kommunikasjonen mellom datasentre.

For eksempel, for ikke lenge siden, mens han jobbet i en brønn i nærheten av et av datasentrene, brøt en gravemaskin et rør, og inne i dette røret var det både en hovedkabel og en reserveoptisk kabel. Vår feiltolerante kommunikasjonskanal med datasenteret viste seg å være sårbar på et tidspunkt, i brønnen. Derfor har vi mistet en del av infrastrukturen. Vi trakk konklusjoner og tok en rekke tiltak, inkludert å installere ekstra optikk i den tilstøtende brønnen.

I datasentre er det punkter for tilstedeværelse av kommunikasjonsleverandører som vi kringkaster prefiksene våre til via BGP. For hver nettverksretning velges den beste metrikken, noe som gjør at forskjellige klienter kan gis den beste tilkoblingskvaliteten. Hvis kommunikasjonen gjennom én leverandør går ned, bygger vi om rutingen vår gjennom de tilgjengelige leverandørene.

Hvis en leverandør mislykkes, bytter vi automatisk til den neste. Ved svikt i et av datasentrene har vi en speilkopi av tjenestene våre i det andre datasenteret, som tar hele belastningen.

Hvordan feiltolerant nettarkitektur implementeres i Mail.ru Cloud Solutions-plattformen
Resiliens av fysisk infrastruktur

Hva vi bruker for feiltoleranse på applikasjonsnivå

Tjenesten vår er bygget på en rekke åpen kildekode-komponenter.

ExaBGP er en tjeneste som implementerer en rekke funksjoner ved hjelp av den BGP-baserte dynamiske rutingprotokollen. Vi bruker den aktivt til å annonsere våre hvitelistede IP-adresser som brukere får tilgang til API-en via.

HAProxy er en high-load balancer som lar deg konfigurere svært fleksible trafikkbalanseringsregler på ulike nivåer av OSI-modellen. Vi bruker den til å balansere foran alle tjenester: databaser, meldingsmeglere, API-tjenester, webtjenester, våre interne prosjekter - alt ligger bak HAProxy.

API-applikasjon — en nettapplikasjon skrevet i python, som brukeren administrerer sin infrastruktur og tjeneste med.

Arbeidersøknad (heretter ganske enkelt arbeider) - i OpenStack-tjenester er dette en infrastrukturdemon som lar deg kringkaste API-kommandoer til infrastrukturen. For eksempel skjer diskoppretting i arbeideren, og opprettelsesforespørselen skjer i applikasjons-API.

Standard OpenStack-applikasjonsarkitektur

De fleste tjenester som er utviklet for OpenStack prøver å følge et enkelt paradigme. En tjeneste består vanligvis av 2 deler: API og arbeidere (backend executors). Som regel er en API en WSGI-applikasjon i python, som lanseres enten som en uavhengig prosess (daemon), eller ved å bruke en ferdiglaget Nginx- eller Apache-webserver. API-en behandler brukerforespørselen og sender ytterligere instruksjoner til arbeiderapplikasjonen for utførelse. Overføringen skjer ved hjelp av en meldingsmegler, vanligvis RabbitMQ, de andre er dårlig støttet. Når meldinger når megleren, behandles de av arbeidere og returnerer om nødvendig et svar.

Dette paradigmet involverer isolerte vanlige feilpunkter: RabbitMQ og databasen. Men RabbitMQ er isolert innenfor én tjeneste og kan i teorien være individuell for hver tjeneste. Så hos MCS skiller vi disse tjenestene så mye som mulig; for hvert enkelt prosjekt lager vi en egen database, en egen RabbitMQ. Denne tilnærmingen er god fordi ved en ulykke på enkelte sårbare punkter, bryter ikke hele tjenesten sammen, men bare deler av den.

Antall arbeiderapplikasjoner er ubegrenset, så API-en kan enkelt skalere horisontalt bak balansere for å øke ytelsen og feiltoleransen.

Noen tjenester krever koordinering i tjenesten når komplekse sekvensielle operasjoner forekommer mellom APIer og arbeidere. I dette tilfellet brukes et enkelt koordineringssenter, et klyngesystem som Redis, Memcache, etcd, som lar en arbeider fortelle en annen at denne oppgaven er tildelt ham ("vær så snill å ikke ta den"). Vi bruker etcd. Som regel kommuniserer arbeidere aktivt med databasen, skriver og leser informasjon derfra. Vi bruker mariadb som en database, som ligger i en multimaster-klynge.

Denne klassiske enkelttjenesten er organisert på en måte som er generelt akseptert for OpenStack. Det kan betraktes som et lukket system, hvor metodene for skalering og feiltoleranse er ganske åpenbare. For eksempel, for API-feiltoleranse, er det nok å sette en balanser foran dem. Skalering av arbeidere oppnås ved å øke antallet.

Det svake punktet i hele opplegget er RabbitMQ og MariaDB. Arkitekturen deres fortjener en egen artikkel. I denne artikkelen ønsker jeg å fokusere på API-feiltoleranse.

Hvordan feiltolerant nettarkitektur implementeres i Mail.ru Cloud Solutions-plattformen
Openstack applikasjonsarkitektur. Balansering og feiltoleranse for skyplattformen

Gjøre HAProxy balancer feiltolerant ved å bruke ExaBGP

For å gjøre API-ene våre skalerbare, raske og feiltolerante, satte vi en lastbalanser foran dem. Vi valgte HAProxy. Etter min mening har den alle nødvendige egenskaper for vår oppgave: balansering på flere OSI-nivåer, et administrasjonsgrensesnitt, fleksibilitet og skalerbarhet, et stort antall balanseringsmetoder, støtte for sesjonstabeller.

Det første problemet som måtte løses var feiltoleransen til selve balanseren. Bare å installere en balanseringsenhet skaper også et feilpunkt: balanseringsenheten går i stykker og tjenesten krasjer. For å forhindre at dette skulle skje, brukte vi HAProxy i forbindelse med ExaBGP.

ExaBGP lar deg implementere en mekanisme for å sjekke tilstanden til en tjeneste. Vi brukte denne mekanismen for å sjekke funksjonaliteten til HAProxy og, i tilfelle problemer, deaktivere HAProxy-tjenesten fra BGP.

ExaBGP+HAProxy-skjema

  1. Vi installerer nødvendig programvare, ExaBGP og HAProxy, på tre servere.
  2. Vi lager et loopback-grensesnitt på hver server.
  3. På alle tre serverne tildeler vi samme hvite IP-adresse til dette grensesnittet.
  4. En hvit IP-adresse annonseres til Internett via ExaBGP.

Feiltoleranse oppnås ved å annonsere den samme IP-adressen fra alle tre serverne. Fra et nettverkssynspunkt er den samme adressen tilgjengelig fra tre forskjellige neste hopp. Ruteren ser tre identiske ruter, velger den høyeste prioritet av dem basert på sin egen beregning (dette er vanligvis det samme alternativet), og trafikken går bare til en av serverne.

Ved problemer med driften av HAProxy eller serverfeil, slutter ExaBGP å annonsere ruten, og trafikken bytter jevnt til en annen server.

Dermed oppnådde vi feiltoleranse for balanseren.

Hvordan feiltolerant nettarkitektur implementeres i Mail.ru Cloud Solutions-plattformen
Feiltoleranse for HAProxy balansere

Ordningen viste seg å være ufullkommen: vi lærte å reservere HAProxy, men lærte ikke å fordele belastningen innenfor tjenestene. Derfor utvidet vi denne ordningen litt: vi gikk over til å balansere mellom flere hvite IP-adresser.

Balansering basert på DNS ​​pluss BGP

Spørsmålet om lastbalansering for HAProxyen vår er fortsatt uløst. Det kan imidlertid løses ganske enkelt, slik vi gjorde her.

For å balansere tre servere trenger du 3 hvite IP-adresser og god gammel DNS. Hver av disse adressene bestemmes på loopback-grensesnittet til hver HAProxy og annonseres til Internett.

I OpenStack, for å administrere ressurser, brukes en tjenestekatalog, som spesifiserer endepunkt-API for en bestemt tjeneste. I denne katalogen registrerer vi et domenenavn - public.infra.mail.ru, som løses via DNS av tre forskjellige IP-adresser. Som et resultat får vi lastfordeling mellom tre adresser via DNS.

Men siden når vi kunngjør hvite IP-adresser, kontrollerer vi ikke prioriteringene for servervalg, er dette ikke balansert ennå. Vanligvis vil bare én server velges basert på IP-adresseansiennitet, og de to andre vil være inaktive fordi ingen beregninger er spesifisert i BGP.

Vi begynte å sende ruter via ExaBGP med forskjellige beregninger. Hver balanserer annonserer alle de tre hvite IP-adressene, men en av dem, den viktigste for denne balanseren, annonseres med minimumsberegningen. Så mens alle tre balansere er i drift, går anrop til den første IP-adressen til den første balanseren, anrop til den andre til den andre og anrop til den tredje til den tredje.

Hva skjer når en av balanserne faller? Hvis en balanseringsenhet mislykkes, blir hovedadressen fortsatt annonsert fra de to andre, og trafikken blir omfordelt mellom dem. Dermed gir vi brukeren flere IP-adresser samtidig via DNS. Ved å balansere etter DNS og ulike metrikker får vi en jevn fordeling av belastningen på alle tre balanserende. Og samtidig mister vi ikke feiltoleransen.

Hvordan feiltolerant nettarkitektur implementeres i Mail.ru Cloud Solutions-plattformen
Balansering av HAProxy basert på DNS ​​+ BGP

Interaksjon mellom ExaBGP og HAProxy

Så vi implementerte feiltoleranse i tilfelle serveren forlater, basert på å stoppe kunngjøringen av ruter. Men HAProxy kan stenge av andre årsaker enn serverfeil: administrasjonsfeil, feil i tjenesten. Vi ønsker å fjerne den ødelagte balansereren fra under lasten også i disse tilfellene, og vi trenger en annen mekanisme.

Derfor, ved å utvide den forrige ordningen, implementerte vi hjerteslag mellom ExaBGP og HAProxy. Dette er en programvareimplementering av interaksjonen mellom ExaBGP og HAProxy, når ExaBGP bruker tilpassede skript for å sjekke statusen til applikasjoner.

For å gjøre dette må du konfigurere en helsesjekker i ExaBGP-konfigurasjonen, som kan sjekke statusen til HAProxy. I vårt tilfelle konfigurerte vi helse-backend i HAProxy, og fra ExaBGP-siden sjekker vi med en enkel GET-forespørsel. Hvis kunngjøringen slutter å skje, fungerer HAProxy mest sannsynlig ikke, og det er ikke nødvendig å annonsere det.

Hvordan feiltolerant nettarkitektur implementeres i Mail.ru Cloud Solutions-plattformen
HAProxy helsesjekk

HAProxy Peers: øktsynkronisering

Den neste tingen å gjøre var å synkronisere øktene. Når du arbeider gjennom distribuerte balansere, er det vanskelig å organisere lagring av informasjon om klientøkter. Men HAProxy er en av få balansere som kan gjøre dette på grunn av Peers-funksjonaliteten – muligheten til å overføre sesjonstabeller mellom ulike HAProxy-prosesser.

Det finnes ulike balanseringsmetoder: enkle som f.eks round robin, og utvidet, når klientens økt huskes, og hver gang han havner på samme server som før. Vi ønsket å implementere det andre alternativet.

HAProxy bruker stick-tabeller for å lagre klientøkter med denne mekanismen. De lagrer klientens opprinnelige IP-adresse, den valgte måladressen (backend) og noe tjenesteinformasjon. Vanligvis brukes pinnetabeller til å lagre et kilde-IP + destinasjons-IP-par, noe som er spesielt nyttig for applikasjoner som ikke kan overføre brukerøktkontekst når du bytter til en annen balanserer, for eksempel i RoundRobin-balanseringsmodus.

Hvis et pinnebord læres å bevege seg mellom ulike HAProxy-prosesser (mellom hvilke balansering skjer), vil balanseapparatene våre kunne jobbe med ett basseng med pinnebord. Dette vil gjøre det mulig å sømløst bytte klientens nettverk hvis en av balanserne svikter; arbeidet med klientøkter vil fortsette på de samme backends som ble valgt tidligere.

For riktig drift må problemet med kilde-IP-adressen til balanseringsenheten som økten ble etablert fra, løses. I vårt tilfelle er dette en dynamisk adresse på loopback-grensesnittet.

Riktig arbeid av jevnaldrende oppnås bare under visse forhold. Det vil si at TCP-tidsavbrudd må være store nok eller bytte må være raskt nok slik at TCP-økten ikke rekker å avsluttes. Det gir imidlertid mulighet for sømløs veksling.

I IaaS har vi en tjeneste bygget med samme teknologi. Dette Load Balancer som en tjeneste for OpenStack, som kalles Octavia. Den er basert på to HAProxy-prosesser og inkluderer i utgangspunktet støtte for jevnaldrende. De har vist seg utmerket i denne tjenesten.

Bildet viser skjematisk bevegelsen av peer-tabeller mellom tre HAProxy-forekomster, en konfigurasjon er foreslått for hvordan dette kan konfigureres:

Hvordan feiltolerant nettarkitektur implementeres i Mail.ru Cloud Solutions-plattformen
HAProxy Peers (øktsynkronisering)

Hvis du implementerer den samme ordningen, må driften testes nøye. Det er ikke et faktum at det vil fungere i samme form 100% av tiden. Men du vil i det minste ikke miste pinnetabeller når du trenger å huske klientens kilde-IP.

Begrensning av antall samtidige forespørsler fra samme klient

Alle tjenester som er offentlig tilgjengelige, inkludert API-ene våre, kan bli utsatt for snøskred av forespørsler. Årsakene til dem kan være helt forskjellige, fra brukerfeil til målrettede angrep. Vi blir periodisk DDoSed av IP-adresser. Kunder gjør ofte feil i skriptene sine og gir oss mini-DDoS-er.

På en eller annen måte må det gis ekstra beskyttelse. Den åpenbare løsningen er å begrense antallet API-forespørsler og ikke kaste bort CPU-tid på å behandle ondsinnede forespørsler.

For å implementere slike restriksjoner bruker vi takstgrenser, organisert på grunnlag av HAProxy, ved å bruke de samme pinnetabellene. Å sette opp grenser er ganske enkelt og lar deg begrense brukeren med antall forespørsler til API. Algoritmen husker kilde-IP-en som forespørsler sendes fra og begrenser antall samtidige forespørsler fra én bruker. Selvfølgelig beregnet vi den gjennomsnittlige API-belastningsprofilen for hver tjeneste og satte en grense på ≈ 10 ganger denne verdien. Vi fortsetter å følge situasjonen nøye og holde fingeren på pulsen.

Hvordan ser dette ut i praksis? Vi har kunder som bruker våre autoskalerings-APIer hele tiden. De lager omtrent to til tre hundre virtuelle maskiner om morgenen og sletter dem om kvelden. For OpenStack krever det å lage en virtuell maskin, også med PaaS-tjenester, minst 1000 API-forespørsler, siden interaksjon mellom tjenester også skjer gjennom API.

Slik overføring av oppgaver forårsaker en ganske stor belastning. Vi vurderte denne belastningen, samlet inn daglige topper, tidoblet dem, og dette ble vår rategrense. Vi holder fingeren på pulsen. Vi ser ofte bots og skannere som prøver å se på oss for å se om vi har noen CGA-skript som kan kjøres, vi kutter dem aktivt.

Slik oppdaterer du kodebasen din uten at brukerne merker det

Vi implementerer også feiltoleranse på nivået av kodedistribusjonsprosesser. Det kan være feil under utrullinger, men deres innvirkning på tjenestetilgjengeligheten kan minimeres.

Vi oppdaterer kontinuerlig tjenestene våre og må sørge for at kodebasen oppdateres uten å påvirke brukerne. Vi klarte å løse dette problemet ved å bruke administrasjonsfunksjonene til HAProxy og implementeringen av Graceful Shutdown i våre tjenester.

For å løse dette problemet var det nødvendig å sikre kontroll over balanseren og "riktig" nedleggelse av tjenester:

  • Når det gjelder HAProxy, utføres kontroll gjennom en statistikkfil, som i hovedsak er en socket og er definert i HAProxy-konfigurasjonen. Du kan sende kommandoer til den via stdio. Men vårt hovedkonfigurasjonskontrollverktøy er mulig, så det har en innebygd modul for å administrere HAProxy. Som vi bruker aktivt.
  • De fleste av API- og Engine-tjenestene våre støtter elegante avslutningsteknologier: når de stenges av, venter de på at den gjeldende oppgaven skal fullføres, enten det er en http-forespørsel eller en tjenesteoppgave. Det samme skjer med arbeideren. Den kjenner alle oppgavene den gjør og slutter når den har fullført alt.

Takket være disse to punktene ser den sikre algoritmen for distribusjonen vår slik ut.

  1. Utvikleren setter sammen en ny kodepakke (for oss er dette RPM), tester den i dev-miljøet, tester den i scenen og lar den ligge i scenedepotet.
  2. Utvikleren setter oppgaven for distribusjon med den mest detaljerte beskrivelsen av "artefaktene": versjonen av den nye pakken, en beskrivelse av den nye funksjonaliteten og andre detaljer om distribusjonen om nødvendig.
  3. Systemadministratoren starter oppdateringen. Lanserer Ansible playbook, som igjen gjør følgende:
    • Tar en pakke fra scenelageret og bruker det til å oppdatere versjonen av pakken i produktlageret.
    • Kompilerer en liste over backends av den oppdaterte tjenesten.
    • Slår av den første tjenesten som skal oppdateres i HAProxy og venter på at prosessene kjører ferdig. Takket være en grasiøs nedleggelse er vi sikre på at alle gjeldende kundeforespørsler vil fullføres.
    • Etter at API og arbeidere er fullstendig stoppet, og HAProxy er slått av, blir koden oppdatert.
    • Ansible driver tjenester.
    • For hver tjeneste trekkes det i visse "håndtak", som utfører enhetstester på en rekke forhåndsdefinerte nøkkeltester. En grunnleggende sjekk av den nye koden finner sted.
    • Hvis ingen feil ble funnet i forrige trinn, aktiveres backend.
    • La oss gå videre til neste backend.
  4. Etter at alle backends er oppdatert, lanseres funksjonstester. Hvis de mangler, ser utvikleren på enhver ny funksjonalitet han har laget.

Dette fullfører distribusjonen.

Hvordan feiltolerant nettarkitektur implementeres i Mail.ru Cloud Solutions-plattformen
Tjenesteoppdateringssyklus

Denne ordningen ville ikke fungert hvis vi ikke hadde én regel. Vi støtter både den gamle og nye versjonen i kamp. På forhånd, på stadiet av programvareutvikling, er det fastsatt at selv om det er endringer i tjenestedatabasen, vil de ikke bryte den forrige koden. Som et resultat oppdateres kodebasen gradvis.

Konklusjon

Når jeg deler mine egne tanker om en feiltolerant WEB-arkitektur, vil jeg nok en gang notere hovedpunktene:

  • fysisk feiltoleranse;
  • nettverksfeiltoleranse (balansere, BGP);
  • feiltoleranse for programvaren som brukes og utvikles.

Stabil oppetid alle sammen!

Kilde: www.habr.com

Legg til en kommentar