
Hej, Habr! Jeg er Artem Karamyshev, leder af systemadministrationsteamet . Vi har haft en masse nye produktlanceringer i løbet af det seneste år. Vi ønskede at sikre, at API-tjenester var let skalerbare, fejltolerante og klar til hurtig vækst i brugerbelastning. Vores platform er implementeret på OpenStack, og jeg vil gerne fortælle dig, hvilke komponentfejltoleranceproblemer vi var nødt til at lukke for at få et fejltolerant system. Jeg tror, det vil være interessant for dem, der også udvikler produkter på OpenStack.
Den overordnede fejltolerance af en platform bestemmes af stabiliteten af dens komponenter. Så vi vil gradvist gennemgå alle de niveauer, hvor vi har identificeret risici og lukket dem.
En videoversion af denne historie, hvis originale kilde var en præsentation på Uptime day 4-konferencen, arrangeret af , kan du tage et kig .
Fysisk arkitektur fejltolerance
Den offentlige del af MCS-skyen er i øjeblikket baseret i to Tier III-datacentre, mellem hvilke der er en privat mørk fiber, reserveret på fysisk niveau af forskellige ruter med en gennemstrømning på 200 Gbps. Tier III-niveau giver det nødvendige niveau af fejltolerance for den fysiske infrastruktur.
Mørke fibre er reserveret på både det fysiske og logiske niveau. Processen med kanalreservation var iterativ, der opstod problemer, og vi forbedrer konstant forbindelsen mellem datacentre.
For eksempel, for ikke så længe siden, mens man arbejdede i en brønd nær et af datacentrene, gennemborede en gravemaskine et rør, og både de optiske hovedkabler og backup-kabler blev fundet inde i dette rør. Vores fejlsikre kommunikationskanal med datacentret viste sig at være sårbar på et tidspunkt, i brønden. Derfor mistede vi en del af infrastrukturen. Vi trak konklusioner og tog en række handlinger, herunder at installere yderligere optik langs den tilstødende brønd.
I datacentre er der tilstedeværelsespunkter for kommunikationsudbydere, som vi udsender vores præfikser til via BGP. For hver netværksretning vælges den bedste metrik, hvilket giver os mulighed for at levere den bedste forbindelseskvalitet til forskellige klienter. Hvis forbindelsen gennem én udbyder afbrydes, genopbygger vi vores routing gennem tilgængelige udbydere.
I tilfælde af fejl hos udbyderen, skifter vi automatisk til den næste. I tilfælde af fejl på et af datacentrene har vi en spejlkopi af vores tjenester i det andet datacenter, som tager hele belastningen.

Fysisk infrastruktur modstandsdygtighed
Hvad vi bruger til fejltolerance på applikationsniveau
Vores service er bygget på en række opensource-komponenter.
ExaBGP — en tjeneste, der implementerer en række funktioner ved hjælp af den BGP-baserede dynamiske routingprotokol. Vi bruger det flittigt til at reklamere for vores hvide IP-adresser, hvorigennem brugere får adgang til API'en.
HAProxy — en high-load balancer, der giver dig mulighed for at konfigurere meget fleksible trafikbalanceringsregler på forskellige niveauer af OSI-modellen. Vi bruger det til belastningsbalancering foran alle tjenester: databaser, meddelelsesmæglere, API-tjenester, webtjenester, vores interne projekter - alt er bag HAProxy.
API-applikation — en webapplikation skrevet i Python, ved hjælp af hvilken brugeren administrerer sin infrastruktur, sin service.
Arbejderansøgning (herefter blot worker) - i OpenStack-tjenester er dette en infrastruktur-dæmon, der tillader API-kommandoer at blive transmitteret til infrastrukturen. For eksempel sker oprettelse af disk i arbejderen, og anmodningen om oprettelse sker i API-applikationen.
OpenStack Application Standard Architecture
De fleste tjenester udviklet til OpenStack forsøger at følge et enkelt paradigme. Tjenesten består normalt af 2 dele: API og workers (backend performers). Typisk er en API en WSGI-applikation i Python, som lanceres enten som en selvstændig proces (dæmon) eller ved hjælp af en færdiglavet webserver Nginx, Apache. API'en behandler brugerens anmodning og sender yderligere instruktioner til udførelse til arbejderapplikationen. Overførslen sker ved hjælp af en meddelelsesmægler, normalt RabbitMQ, de andre er dårligt understøttet. Når beskeder når mægleren, behandles de af arbejdere, og om nødvendigt returneres et svar.
Dette paradigme indebærer isolerede fælles fejlpunkter: RabbitMQ og databasen. Men RabbitMQ er isoleret inden for én tjeneste og kan i teorien være individuel for hver tjeneste. Så hos MCS adskiller vi disse tjenester så meget som muligt, for hvert enkelt projekt opretter vi en separat database, en separat RabbitMQ. Denne tilgang er god, fordi i tilfælde af en ulykke på nogle sårbare punkter, går ikke hele tjenesten i stykker, men kun en del af den.
Der er ingen grænse for antallet af arbejderapplikationer, så API'et kan nemt skaleres horisontalt bag balancere for at øge ydeevnen og fejltolerancen.
Nogle tjenester kræver koordinering inden for tjenesten - når komplekse sekventielle operationer forekommer mellem API'er og arbejdere. I dette tilfælde bruges et enkelt koordinationscenter, et klyngesystem som Redis, Memcache, etcd, som gør det muligt for en arbejder at fortælle en anden, at denne opgave er tildelt ham ("tak det ikke"). Vi bruger etcd. Som regel kommunikerer arbejdere aktivt med databasen, skriver og læser information fra den. Som database bruger vi mariadb, som er placeret i vores multi-master cluster.
Denne klassiske enkelttjeneste er organiseret på den almindeligt accepterede OpenStack-måde. Det kan ses som et lukket system, hvor skalering og fejltolerance metoder er ret indlysende. For eksempel, for at sikre API-fejltolerance, er det nok at installere en balancer foran dem. Skalering af arbejdere opnås ved at øge deres antal.
Det svage punkt i hele ordningen er RabbitMQ og MariaDB. Deres arkitektur fortjener en separat artikel. I denne artikel vil jeg fokusere på API-fejltolerance.

Openstack applikationsarkitektur. Balancering og fejltolerance af cloud-platformen
Gør HAProxy Load Balancer fejltolerant med ExaBGP
For at gøre vores API'er skalerbare, hurtige og fejltolerante sætter vi en load balancer foran dem. Vi valgte HAProxy. Efter min mening har den alle de nødvendige egenskaber til vores opgave: Balancering på flere OSI-niveauer, styringsgrænseflade, fleksibilitet og skalerbarhed, en lang række afbalanceringsmetoder, understøttelse af sessionstabeller.
Det første problem, der skulle løses, var selve balancerens fejltolerance. Blot at installere en balancer skaber også et fejlpunkt: balanceren går i stykker - tjenesten går ned. For at forhindre dette i at ske, brugte vi HAProxy i forbindelse med ExaBGP.
ExaBGP gør det muligt at implementere en mekanisme til kontrol af servicestatus. Vi brugte denne mekanisme til at kontrollere, om HAProxy virker, og til at deaktivere HAProxy-tjenesten fra BGP i tilfælde af problemer.
ExaBGP+HAProxy-skema
- Vi installerer den nødvendige software, ExaBGP og HAProxy, på tre servere.
- På hver af serverne opretter vi en loopback-grænseflade.
- På alle tre servere registrerer vi den samme hvide IP-adresse på denne grænseflade.
- Den hvide IP-adresse annonceres til internettet via ExaBGP.
Fejltolerance opnås ved at reklamere for den samme IP-adresse fra alle tre servere. Fra et netværksperspektiv er den samme adresse tilgængelig fra tre forskellige næste hop. Routeren ser tre identiske ruter, vælger den højeste prioritet baseret på sin egen metrik (dette er normalt den samme mulighed), og trafikken går kun til en af serverne.
I tilfælde af problemer med HAProxy-drift eller serverfejl, stopper ExaBGP med at annoncere for ruten, og trafikken skifter uden problemer til en anden server.
På denne måde opnåede vi fejltolerance af balanceren.

HAProxy Load Balancer fejltolerance
Ordningen viste sig at være ufuldkommen: vi lærte at reservere HAProxy, men vi lærte ikke at fordele belastningen inden for tjenester. Derfor udvidede vi denne ordning lidt: vi gik over til at balancere mellem flere hvide IP-adresser.
DNS baseret balancering plus BGP
Spørgsmålet om belastningsbalancering foran vores HAProxy forbliver uløst. Det kan dog løses ganske enkelt, som vi gjorde i vores tilfælde.
For at balancere tre servere skal du bruge 3 hvide IP-adresser og god gammel DNS. Hver af disse adresser er defineret på loopback-grænsefladen for hver HAProxy og annonceret til internettet.
OpenStack bruger et servicekatalog til at administrere ressourcer, som specificerer API-endepunktet for en bestemt tjeneste. I denne mappe registrerer vi domænenavnet - public.infra.mail.ru, som løses via DNS af tre forskellige IP-adresser. Som et resultat får vi belastningsfordeling mellem tre adresser via DNS.
Men da vi ikke kontrollerer servervalgsprioriteterne, når vi annoncerer hvide IP-adresser, er dette ikke balanceret endnu. Typisk vil kun én server blive valgt baseret på IP-adresseanciennitet, og de to andre vil være inaktive, da der ikke er angivet metrics i BGP.
Vi begyndte at betjene ruter via ExaBGP med forskellige målinger. Hver balancer annoncerer alle tre hvide IP-adresser, men en af dem, den vigtigste for denne balancer, annonceres med minimumsmetrikken. Så mens alle tre balancerer er operationelle, går anmodninger til den første IP-adresse til den første balancer, anmodninger til den anden til den anden og anmodninger til den tredje til den tredje.
Hvad sker der, når en af balancerne går ned? Hvis en balancer fejler, annonceres dens hovedadresse stadig fra de to andre, og trafikken omfordeles mellem dem. Således giver vi brugeren flere IP-adresser på én gang via DNS. Ved at balancere efter DNS og forskellige målinger opnår vi en jævn fordeling af belastningen på alle tre balancere. Og samtidig mister vi ikke fejltolerancen.

HAProxy Load Balancing Baseret på DNS + BGP
Interaktion mellem ExaBGP og HAProxy
Så vi har implementeret fejltolerance i tilfælde af serverfejl, baseret på afslutning af rutemeddelelser. Men HAProxy kan også lukke ned af andre årsager end serverfejl: administrationsfejl, fejl i tjenesten. Vi ønsker også at fjerne den ødelagte balancer fra under belastningen i disse tilfælde, og vi har brug for en anden mekanisme.
Derfor, ved at udvide den tidligere ordning, implementerede vi hjerteslag mellem ExaBGP og HAProxy. Dette er en softwareimplementering af interaktionen mellem ExaBGP og HAProxy, hvor ExaBGP bruger brugerdefinerede scripts til at kontrollere status for applikationer.
For at gøre dette skal du konfigurere en sundhedstjek i ExaBGP-konfigurationen, som vil være i stand til at kontrollere HAProxy-statussen. I vores tilfælde konfigurerede vi sundheds-backend i HAProxy, og fra ExaBGP-siden tjekker vi med en simpel GET-anmodning. Hvis annonceringen holder op med at ske, kører HAProxy højst sandsynligt ikke, og der er ingen grund til at annoncere den.

HAProxy Sundhedstjek
HAProxy Peers: Sessionssynkronisering
Det næste, der skulle gøres, var at synkronisere sessionerne. Når du arbejder gennem distribuerede balancere, er det vanskeligt at organisere lagringen af information om klientsessioner. Men HAProxy er en af de få balancere, der kan gøre dette takket være Peers-funktionaliteten - evnen til at overføre en sessionstabel mellem forskellige HAProxy-processer.
Der findes forskellige metoder til balancering: simple, som f.eks , og udvidede, når klientens session huskes, og hver gang han kommer til den samme server som før. Vi ønskede at implementere den anden mulighed.
HAProxy bruger stick-tabeller til at gemme klientsessioner af denne mekanisme. De gemmer klientens originale IP-adresse, den valgte måladresse (backend) og nogle serviceoplysninger. Stick-tabeller bruges typisk til at gemme kilde-IP + destination-IP-par, hvilket er særligt nyttigt for applikationer, der ikke kan passere brugersessionskontekst, når der skiftes til en anden belastningsbalancer, såsom i RoundRobin belastningsbalanceringstilstand.
Hvis vi lærer stokkebordet at bevæge sig mellem forskellige HAProxy-processer (mellem hvilke balancering sker), vil vores balancere kunne arbejde med en enkelt pulje af stokkeborde. Dette vil muliggøre problemfri skift af klientnetværket, når en af balancerne svigter; arbejdet med klientsessioner vil fortsætte på de samme backends, som blev valgt tidligere.
For korrekt drift skal kilde-IP-adressen for balanceren, hvorfra sessionen er etableret, løses. I vores tilfælde er dette en dynamisk adresse på loopback-grænsefladen.
Korrekt drift af peers opnås kun under visse betingelser. Det vil sige, at TCP-timeouts skal være store nok, eller skift skal være hurtigt nok, så TCP-sessionen ikke når at bryde. Dette giver dog mulighed for problemfri skift.
Vi har en tjeneste i IaaS bygget ved hjælp af samme teknologi. Denne , som hedder Octavia. Den er baseret på to HAProxy-processer og har indbygget peer-support. De har bevist, at de er fremragende i denne service.
Billedet viser skematisk bevægelsen af peer-tabeller mellem tre HAProxy-instanser, og der tilbydes en konfiguration af, hvordan dette kan sættes op:

HAProxy Peers (sessionssynkronisering)
Hvis du implementerer den samme ordning, skal dens funktion testes omhyggeligt. Det er ikke et faktum, at det vil fungere i samme form i 100% af tilfældene. Men du mister i det mindste ikke stikborde, når du skal huske klientens kilde-IP.
Begrænsning af antallet af samtidige anmodninger fra den samme klient
Enhver offentlig tilgængelig tjeneste, inklusive vores API'er, kan være genstand for laviner af anmodninger. Årsagerne til dette kan være helt forskellige, lige fra brugerfejl til målrettede angreb. Vi er periodisk DDoS'd af IP-adresser. Klienter laver ofte fejl i deres scripts og forårsager mini-DDoS-angreb på os.
På en eller anden måde er det nødvendigt at yde yderligere beskyttelse. Den oplagte løsning er at begrænse antallet af anmodninger til API'en og ikke spilde processortid på at behandle ondsindede anmodninger.
For at implementere sådanne begrænsninger bruger vi hastighedsgrænser, organiseret på basis af HAProxy, ved hjælp af de samme pindetabeller. Grænserne er sat ganske enkelt op og giver dig mulighed for at begrænse brugeren med antallet af anmodninger til API'en. Algoritmen husker den kilde-IP, hvorfra anmodninger foretages, og begrænser antallet af samtidige anmodninger fra én bruger. Selvfølgelig beregnede vi den gennemsnitlige API-belastningsprofil for hver tjeneste og satte grænsen til ≈10 gange denne værdi. Vi følger fortsat situationen nøje og holder fingeren på pulsen.
Hvordan ser det ud i praksis? Vi har kunder, der bruger vores API'er til autoskalering hele tiden. De skaber omkring to til tre hundrede virtuelle maskiner tættere på morgenen og sletter dem tættere på aftenen. For OpenStack kræver oprettelse af en virtuel maskine, også med PaaS-tjenester, mindst 1000 API-anmodninger, da interaktion mellem tjenester også sker gennem API.
Sådan opgaveskift forårsager en del stress. Vi vurderede denne belastning, indsamlede daglige toppe, øgede dem tidoblet, og dette blev vores hastighedsgrænse. Vi holder fingeren på pulsen. Vi ser ofte bots og scannere, der forsøger at se, om vi har nogen CGA-scripts, der kan køres, vi klipper dem aktivt.
Sådan opdaterer du din kodebase uden at brugerne opdager det
Vi implementerer også fejltolerance på niveauet af kodeimplementeringsprocesser. Der er fejl under udrulning, men deres indvirkning på servicetilgængeligheden kan minimeres.
Vi opdaterer konstant vores tjenester og skal sikre, at processen med at opdatere kodebasen ikke påvirker brugerne. Vi var i stand til at løse dette problem ved at bruge HAProxy-administrationsfunktioner og implementere Graceful Shutdown i vores tjenester.
For at løse dette problem var det nødvendigt at sikre balancerstyring og "korrekt" lukning af tjenester:
- I tilfælde af HAProxy udføres kontrol gennem en statistikfil, som i det væsentlige er en socket og er defineret i HAProxy-konfigurationen. Du kan sende kommandoer til den via stdio. Men vores vigtigste konfigurationskontrolværktøj er muligt, så det har et indbygget modul til styring af HAProxy. Som vi bruger aktivt.
- De fleste af vores API- og Engine-tjenester understøtter yndefulde nedlukningsteknologier: Når de lukker ned, venter de, indtil den aktuelle opgave, det være sig en http-anmodning eller en serviceopgave, er fuldstændig fuldført. Det samme sker med arbejderen. Han kan alle de opgaver, han laver, og er færdig, når han har gennemført alt med succes.
Takket være disse to punkter ser vores sikre implementeringsalgoritme sådan ud.
- Udvikleren bygger en ny kodepakke (i vores tilfælde er det RPM), tester den i dev-miljøet, tester den i scenen og efterlader den i stage-lageret.
- Udvikleren sætter en opgave til implementering med den mest detaljerede beskrivelse af "artefakter": versionen af den nye pakke, en beskrivelse af den nye funktionalitet og andre detaljer om implementeringen, hvis det er nødvendigt.
- Systemadministratoren starter opdateringen. Kører Ansible playbook, som igen gør følgende:
- Tager en pakke fra scenelageret og opdaterer pakkeversionen i produktlageret baseret på den.
- Kompilerer en liste over backends af tjenesten, der opdateres.
- Lukker den første tjeneste, der skal opdateres i HAProxy, og venter på, at dens processer er færdige med at køre. Takket være den yndefulde nedlukning er vi overbeviste om, at alle aktuelle kundeønsker vil blive gennemført med succes.
- Efter et fuldstændigt stop af API'en, arbejdere og nedlukning af HAProxy opdateres koden.
- Ansible starter tjenester.
- For hver service trækker den visse "håndtag", der udfører enhedstest for en række foruddefinerede nøgletest. Grundlæggende kontrol af den nye kode udføres.
- Hvis der ikke blev fundet fejl i det foregående trin, aktiveres backend.
- Lad os gå videre til næste backend.
- Efter at alle backends er opdateret, køres funktionelle tests. Hvis de mangler, ser udvikleren på enhver ny funktionalitet, han har lavet.
Dette fuldender implementeringen.

Serviceopdateringscyklus
Denne ordning ville ikke fungere, hvis vi ikke havde én regel. Vi understøtter både den gamle og den nye version i kamp. Det antages på forhånd, på softwareudviklingsstadiet, at selvom der er ændringer i servicedatabasen, vil de ikke bryde den tidligere kode. Som følge heraf opdateres kodebasen gradvist.
Konklusion
Ved at dele mine egne tanker om fejltolerant WEB-arkitektur vil jeg gerne endnu en gang fremhæve dens nøglepunkter:
- fysisk fejltolerance;
- netværksfejltolerance (balancere, BGP);
- fejltolerance for den anvendte og udviklede software.
Hav en stabil oppetid alle sammen!
Kilde: www.habr.com
