A hibatűrő webarchitektúra megvalósítása a Mail.ru Cloud Solutions platformon

A hibatűrő webarchitektúra megvalósítása a Mail.ru Cloud Solutions platformon

Szia Habr! Artem Karamishev vagyok, a rendszeradminisztrációs csapat vezetője Mail.Ru Cloud Solutions (MCS). Az elmúlt évben számos új termék bevezetésére került sor. Biztosítani akartuk, hogy az API-szolgáltatások könnyen méretezhetők, hibatűrők legyenek, és készen álljanak a felhasználói terhelés gyors növekedésére. Platformunk OpenStack-en van megvalósítva, és szeretném elmondani, hogy milyen komponensek hibatűrési problémáit kellett megoldanunk, hogy hibatűrő rendszert kapjunk. Szerintem ez azok számára lesz érdekes, akik OpenStack-en is fejlesztenek termékeket.

A platform általános hibatűrése az összetevőinek rugalmasságából áll. Tehát fokozatosan végigmegyünk minden szinten, ahol a kockázatokat azonosítottuk és lezártuk.

Ennek a történetnek a videós változata, melynek elsődleges forrása az Uptime day 4 konferencián készült riport volt, melyet szerveztek ITSumma, láthatod az Uptime Community YouTube-csatornán.

A fizikai architektúra rugalmassága

Az MCS felhő publikus része immár két Tier III adatközpontban épül fel, köztük van egy saját, fizikai szinten különböző útvonalakon lefoglalt, 200 Gbit/s átviteli sebességű sötét szál. A III. szint biztosítja a szükséges hibatűrési szintet a fizikai infrastruktúra számára.

A sötét rost fizikai és logikai szinten is fenntartva van. A csatornafoglalási folyamat iteratív volt, problémák merültek fel, folyamatosan fejlesztjük az adatközpontok közötti kommunikációt.

Nemrég például az egyik adatközpont melletti kútban végzett munka közben egy kotrógép eltört egy csövet, és ebben a csőben volt egy fő- és egy tartalék optikai kábel is. Hibatűrő kommunikációs csatornánk az adatközponttal egy ponton, a kútban bizonyult sebezhetőnek. Ennek megfelelően az infrastruktúra egy részét elveszítettük. Következtetéseket vontunk le és számos intézkedést megtettünk, beleértve a további optikát a szomszédos kútba.

Az adatközpontokban vannak olyan kommunikációs szolgáltatók jelenléti pontjai, akiknek BGP-n keresztül továbbítjuk az előtagjainkat. Minden hálózati irányhoz a legjobb mérőszám kerül kiválasztásra, amely lehetővé teszi a különböző ügyfelek számára a legjobb kapcsolatminőség biztosítását. Ha az egyik szolgáltatón keresztüli kommunikáció megszakad, az elérhető szolgáltatókon keresztül újjáépítjük az útválasztást.

Ha egy szolgáltató meghibásodik, automatikusan átváltunk a következőre. Az egyik adatközpont meghibásodása esetén a második adatközpontban van szolgáltatásaink tükörmásolata, amely a teljes terhelést átvállalja.

A hibatűrő webarchitektúra megvalósítása a Mail.ru Cloud Solutions platformon
A fizikai infrastruktúra rugalmassága

Mit használunk az alkalmazás szintű hibatűréshez

Szolgáltatásunk számos nyílt forráskódú komponensre épül.

ExaBGP egy olyan szolgáltatás, amely számos funkciót valósít meg a BGP-alapú dinamikus útválasztási protokoll használatával. Aktívan használjuk az engedélyezőlistán szereplő IP-címeink hirdetésére, amelyeken keresztül a felhasználók hozzáférnek az API-hoz.

HAProxy egy nagy terhelésű kiegyenlítő, amely lehetővé teszi nagyon rugalmas forgalomelosztási szabályok konfigurálását az OSI modell különböző szintjein. Használjuk egyensúlyozásra minden szolgáltatás előtt: adatbázisok, üzenetközvetítők, API szolgáltatások, webszolgáltatások, belső projektjeink – minden a HAProxy mögött van.

API alkalmazás — python nyelven írt webes alkalmazás, amellyel a felhasználó kezeli infrastruktúráját és szolgáltatását.

Munkás alkalmazás (a továbbiakban egyszerűen dolgozó) - az OpenStack szolgáltatásokban ez egy infrastruktúra démon, amely lehetővé teszi API-parancsok sugárzását az infrastruktúrába. Például a lemez létrehozása a dolgozóban, a létrehozási kérés pedig az alkalmazás API-ban történik.

Szabványos OpenStack alkalmazásarchitektúra

A legtöbb OpenStack-hez fejlesztett szolgáltatás egyetlen paradigmát próbál követni. Egy szolgáltatás általában 2 részből áll: API-ból és dolgozókból (backend végrehajtók). Az API általában egy WSGI-alkalmazás a pythonban, amely vagy független folyamatként (démonként), vagy egy kész Nginx vagy Apache webszerver segítségével indul el. Az API feldolgozza a felhasználói kérelmet, és további utasításokat ad át a dolgozó alkalmazásnak végrehajtásra. Az átvitel egy üzenetközvetítő segítségével történik, általában RabbitMQ, a többi rosszul támogatott. Amikor az üzenetek megérkeznek a közvetítőhöz, a dolgozók feldolgozzák azokat, és szükség esetén választ küldenek.

Ez a paradigma elszigetelt közös hibapontokat foglal magában: RabbitMQ és az adatbázis. De a RabbitMQ egy szolgáltatáson belül el van szigetelve, és elméletileg minden szolgáltatáshoz egyedi lehet. Tehát az MCS-nél ezeket a szolgáltatásokat lehetőség szerint szétválasztjuk, minden egyes projekthez külön adatbázist, külön RabbitMQ-t hozunk létre. Ez a megközelítés azért jó, mert egy-egy sérülékeny ponton bekövetkező baleset esetén nem a teljes szolgáltatás hibásodik meg, hanem annak csak egy része.

A dolgozói alkalmazások száma korlátlan, így az API könnyen skálázható vízszintesen a kiegyenlítők mögé a teljesítmény és a hibatűrés növelése érdekében.

Egyes szolgáltatások a szolgáltatáson belüli koordinációt igényelnek, ha összetett, egymást követő műveletek történnek az API-k és a dolgozók között. Ebben az esetben egyetlen koordinációs központot használnak, egy klaszterrendszert, mint például a Redis, a Memcache stb., amely lehetővé teszi, hogy az egyik dolgozó elmondja a másiknak, hogy ez a feladat neki van kijelölve („kérem, ne vegye el”). Használunk stb. A dolgozók általában aktívan kommunikálnak az adatbázissal, onnan írnak és olvasnak információkat. Adatbázisként a mariadb-t használjuk, amely egy multimaster fürtben található.

Ez a klasszikus egyetlen szolgáltatás az OpenStack számára általánosan elfogadott módon van megszervezve. Zárt rendszernek tekinthető, amelynél a skálázás és a hibatűrés módszerei meglehetősen kézenfekvőek. Például az API hibatűréshez elég egy kiegyensúlyozót tenni eléjük. A dolgozók skálázását számuk növelésével érik el.

Az egész rendszer gyenge pontja a RabbitMQ és a MariaDB. Ezek architektúrája külön cikket érdemel, ebben a cikkben az API hibatűrésére szeretnék összpontosítani.

A hibatűrő webarchitektúra megvalósítása a Mail.ru Cloud Solutions platformon
Openstack alkalmazásarchitektúra. A felhőplatform kiegyensúlyozása és hibatűrése

A HAProxy kiegyensúlyozó hibatűrővé tétele ExaBGP használatával

Annak érdekében, hogy API-jaink méretezhetőek, gyorsak és hibatűrőek legyenek, egy terheléselosztót helyezünk eléjük. A HAProxy-t választottuk. Véleményem szerint minden feladatunkhoz szükséges tulajdonsággal rendelkezik: kiegyensúlyozás több OSI szinten, kezelőfelület, rugalmasság és skálázhatóság, nagyszámú kiegyenlítési módszer, munkamenet táblák támogatása.

Az első probléma, amit meg kellett oldani, magának a kiegyenlítőnek a hibatűrése volt. A kiegyensúlyozó egyszerű felszerelése is meghibásodási pontot jelent: a kiegyenlítő elromlik, és a szolgáltatás összeomlik. Ennek elkerülése érdekében a HAProxy-t az ExaBGP-vel együtt használtuk.

Az ExaBGP lehetővé teszi a szolgáltatás állapotának ellenőrzésére szolgáló mechanizmus megvalósítását. Ezzel a mechanizmussal ellenőriztük a HAProxy működőképességét, és probléma esetén letiltottuk a HAProxy szolgáltatást a BGP-ről.

ExaBGP+HAProxy séma

  1. Három szerverre telepítjük a szükséges szoftvereket, az ExaBGP-t és a HAProxy-t.
  2. Minden szerveren létrehozunk egy loopback felületet.
  3. Mindhárom szerveren ugyanazt a fehér IP-címet rendeljük ehhez az interfészhez.
  4. Az ExaBGP-n keresztül fehér IP-címet hirdetnek az interneten.

A hibatűrést úgy érik el, hogy mindhárom szerverről ugyanazt az IP-címet hirdetik. Hálózati szempontból ugyanaz a cím három különböző következő ugrásból érhető el. A router három azonos útvonalat lát, ezek közül kiválasztja a legmagasabb prioritást a saját mérőszáma alapján (ez általában ugyanaz az opció), és a forgalom csak az egyik szerverre megy.

A HAProxy működésével kapcsolatos problémák vagy szerverhiba esetén az ExaBGP leállítja az útvonal bejelentését, és a forgalom zökkenőmentesen átkapcsol egy másik szerverre.

Így elértük a kiegyenlítő hibatűrését.

A hibatűrő webarchitektúra megvalósítása a Mail.ru Cloud Solutions platformon
A HAProxy kiegyensúlyozók hibatűrése

A séma tökéletlennek bizonyult: megtanultuk a HAProxy lefoglalását, de nem tanultuk meg a terhelés elosztását a szolgáltatásokon belül. Ezért ezt a sémát kicsit kibővítettük: áttértünk a több fehér IP-cím közötti egyensúlyozásra.

Kiegyensúlyozás DNS és BGP alapján

A HAProxy terheléselosztásának kérdése továbbra is megoldatlan. Ez azonban egészen egyszerűen megoldható, ahogy itt is tettük.

Három szerver egyensúlyához 3 fehér IP-címre és jó öreg DNS-re lesz szüksége. Ezen címek mindegyikét az egyes HAProxy visszacsatolási felületén határozzák meg, és hirdetik az interneten.

Az OpenStackben az erőforrások kezelésére egy szolgáltatási címtárat használnak, amely egy adott szolgáltatás végpont API-ját határozza meg. Ebben a könyvtárban regisztrálunk egy domain nevet - public.infra.mail.ru, amelyet a DNS-en keresztül három különböző IP-cím old fel. Ennek eredményeként a DNS-en keresztül három cím közötti terheléselosztást kapunk.

De mivel a fehér IP-címek bejelentésekor nem szabályozzuk a szerverkiválasztási prioritásokat, ez még nem egyensúlyoz. Általában csak egy kiszolgáló kerül kiválasztásra az IP-cím rangsorolása alapján, a másik kettő pedig tétlen lesz, mivel a BGP-ben nincs megadva mérőszám.

Elkezdtük az útvonalak küldését az ExaBGP-n keresztül különböző mérőszámokkal. Mindegyik kiegyenlítő hirdeti mind a három fehér IP-címet, de ezek közül az egyik, a kiegyensúlyozó fő címe a minimális mérőszámmal van hirdetve. Tehát amíg mindhárom kiegyenlítő működik, az első IP-címre érkező hívások az első balanszra, a másodikra ​​a másodikra, a harmadikra ​​a harmadikra ​​érkeznek.

Mi történik, ha az egyik egyensúlyozó leesik? Ha valamelyik kiegyenlítő meghibásodik, a fő címét továbbra is a másik kettőről hirdetik, és a forgalmat újraosztják közöttük. Így DNS-en keresztül egyszerre több IP-címet adunk a felhasználónak. A DNS és a különböző mérőszámok alapján történő kiegyenlítéssel a terhelés egyenletes eloszlását kapjuk mindhárom egyensúlyozó között. És ugyanakkor nem veszítjük el a hibatűrést.

A hibatűrő webarchitektúra megvalósítása a Mail.ru Cloud Solutions platformon
HAProxy kiegyensúlyozása DNS + BGP alapján

Az ExaBGP és a HAProxy közötti kölcsönhatás

Tehát hibatűrést vezettünk be arra az esetre, ha a szerver távozna, az útvonalak bejelentésének leállítása alapján. A HAProxy azonban leállhat más okok miatt is, mint a szerverhiba: adminisztrációs hibák, a szolgáltatáson belüli hibák. A törött kiegyensúlyozót ezekben az esetekben is szeretnénk eltávolítani a terhelés alól, más mechanizmusra van szükségünk.

Ezért az előző sémát kibővítve szívverést valósítottunk meg az ExaBGP és a HAProxy között. Ez az ExaBGP és a HAProxy közötti interakció szoftveres megvalósítása, amikor az ExaBGP egyéni parancsfájlokat használ az alkalmazások állapotának ellenőrzésére.

Ehhez be kell állítani egy állapotellenőrzőt az ExaBGP konfigurációban, amely képes ellenőrizni a HAProxy állapotát. Esetünkben HAProxyban konfiguráltuk az állapot hátteret, az ExaBGP oldalról pedig egy egyszerű GET kéréssel ellenőrizzük. Ha a bejelentés megszűnik, akkor a HAProxy valószínűleg nem működik, és nem kell hirdetni.

A hibatűrő webarchitektúra megvalósítása a Mail.ru Cloud Solutions platformon
HAProxy állapotfelmérés

HAProxy Peers: munkamenet szinkronizálás

A következő lépés a munkamenetek szinkronizálása volt. Az elosztott kiegyensúlyozókon keresztüli munka során nehéz megszervezni az ügyfélmunkamenetekkel kapcsolatos információk tárolását. A HAProxy azonban azon kevés kiegyensúlyozók egyike, amely képes erre a Peers funkciónak köszönhetően – a munkamenettáblázatok átvitelének képessége a különböző HAProxy folyamatok között.

Különféle egyensúlyozási módszerek léteznek: egyszerűek, mint pl kerek vörösbegy, és kiterjesztve, amikor a kliens munkamenetére emlékeznek, és minden alkalommal, amikor ugyanazon a szerveren végzi, mint korábban. A második lehetőséget szerettük volna megvalósítani.

A HAProxy stick-táblákat használ a mechanizmus kliensmunkameneteinek mentésére. Elmentik az ügyfél eredeti IP-címét, a kiválasztott célcímet (backend) és néhány szolgáltatási információt. Általában a stick-táblázatokat a forrás-IP + cél-IP pár tárolására használják, ami különösen hasznos azoknál az alkalmazásoknál, amelyek nem tudják átvinni a felhasználói munkamenet-kontextust, amikor egy másik kiegyenlítőre váltanak, például RoundRobin kiegyensúlyozó módban.

Ha egy pálcikaasztalt megtanítanak a különböző HAProxy folyamatok közötti mozgásra (amelyek között kiegyensúlyozás történik), akkor a kiegyensúlyozóink képesek lesznek egyetlen botasztallal dolgozni. Ez lehetővé teszi a kliens hálózatának zökkenőmentes váltását, ha valamelyik kiegyenlítő meghibásodik; az ügyfélmunkamenetekkel végzett munka ugyanazokon a háttérrendszereken folytatódik, amelyeket korábban kiválasztottak.

A megfelelő működés érdekében meg kell oldani a kiegyenlítő forrás IP-címének problémáját, amelyből a munkamenet létrejött. Esetünkben ez egy dinamikus cím a visszacsatolási felületen.

A társak helyes munkája csak bizonyos feltételek mellett érhető el. Vagyis a TCP-időtúllépéseknek elég nagynak kell lenniük, vagy a váltásnak elég gyorsnak kell lennie ahhoz, hogy a TCP-munkamenetnek ne legyen ideje leállni. Ez azonban zökkenőmentes váltást tesz lehetővé.

Az IaaS-ben van egy szolgáltatásunk, amely ugyanazt a technológiát használja. Ez Load Balancer az OpenStack szolgáltatásaként, amit Octaviának hívnak. Két HAProxy folyamaton alapul, és kezdetben a társak támogatását tartalmazza. Kiválóan bizonyították magukat ebben a szolgáltatásban.

A kép vázlatosan mutatja a peer táblák mozgását három HAProxy-példány között, egy konfigurációt javasolunk ennek konfigurálására:

A hibatűrő webarchitektúra megvalósítása a Mail.ru Cloud Solutions platformon
HAProxy Peers (munkamenet szinkronizálás)

Ha ugyanazt a sémát hajtja végre, akkor annak működését alaposan meg kell vizsgálni. Nem tény, hogy az esetek 100%-ában ugyanúgy fog működni. De legalább nem veszítheti el a stick táblákat, amikor emlékeznie kell az ügyfél forrás IP-jére.

Az ugyanazon ügyféltől érkező egyidejű kérések számának korlátozása

Bármely nyilvánosan elérhető szolgáltatás, beleértve az API-jainkat is, számos kérés tárgyát képezheti. Ezek okai teljesen eltérőek lehetnek, a felhasználói hibáktól a célzott támadásokig. Időnként IP-címek alapján DDoSed-elünk. Az ügyfelek gyakran hibáznak a szkriptekben, és mini-DDoS-ket adnak nekünk.

Így vagy úgy, további védelmet kell biztosítani. A kézenfekvő megoldás az API-kérelmek számának korlátozása, és nem vesztegeti a CPU-időt a rosszindulatú kérések feldolgozásával.

Az ilyen korlátozások megvalósításához HAProxy alapján rendszerezett sebességkorlátokat alkalmazunk, ugyanazon bottáblázatok segítségével. A korlátok beállítása meglehetősen egyszerű, és lehetővé teszi a felhasználó korlátozását az API-hoz intézett kérések számával. Az algoritmus megjegyzi a forrás IP-címét, ahonnan a kérések származnak, és korlátozza az egy felhasználótól érkező egyidejű kérések számát. Természetesen minden szolgáltatáshoz kiszámítottuk az átlagos API terhelési profilt, és ennek az értéknek a ≈ 10-szeresét állítottuk be. Továbbra is szorosan figyelemmel kísérjük a helyzetet, és tartjuk a pulzust.

Hogy néz ez ki a gyakorlatban? Vannak ügyfeleink, akik folyamatosan használják az automatikus skálázási API-jainkat. Körülbelül két-háromszáz virtuális gépet hoznak létre reggel, majd este törlik őket. Az OpenStack esetében egy virtuális gép létrehozásához, PaaS szolgáltatásokkal is, legalább 1000 API-kérés szükséges, mivel a szolgáltatások közötti interakció is az API-n keresztül történik.

Az ilyen feladatok átadása meglehetősen nagy terhelést okoz. Felmértük ezt a terhelést, összegyűjtöttük a napi csúcsokat, megtízszereztük, és ez lett a mértékhatárunk. Tartjuk az ujjunkat a pulzuson. Gyakran látunk botokat és szkennereket, akik megpróbálnak ránk nézni, hogy vannak-e futtatható CGA szkriptjeink, aktívan vágjuk őket.

Hogyan frissítheti kódbázisát anélkül, hogy a felhasználók észrevennék

A hibatűrést a kódtelepítési folyamatok szintjén is megvalósítjuk. Előfordulhatnak hibák a bevezetés során, de ezeknek a szolgáltatás elérhetőségére gyakorolt ​​hatása minimálisra csökkenthető.

Szolgáltatásainkat folyamatosan frissítjük, és gondoskodnunk kell arról, hogy a kódbázis frissítésre kerüljön anélkül, hogy ez a felhasználókat érintené. Ezt a problémát a HAProxy felügyeleti képességeivel és szolgáltatásainkban a Graceful Shutdown implementációjával sikerült megoldanunk.

A probléma megoldásához biztosítani kellett a kiegyenlítő vezérlését és a szolgáltatások „helyes” leállítását:

  • A HAProxy esetében a vezérlés egy statisztikai fájlon keresztül történik, amely lényegében egy socket, és a HAProxy konfigurációjában van meghatározva. Parancsokat küldhet neki stdio-n keresztül. A fő konfigurációs vezérlő eszközünk azonban lehetséges, így beépített modullal rendelkezik a HAProxy kezelésére. Amit aktívan használunk.
  • A legtöbb API és Engine szolgáltatásunk támogatja a kecses leállítási technológiákat: leállításkor megvárják az aktuális feladat befejezését, legyen az http kérés vagy valamilyen szolgáltatási feladat. Ugyanez történik a dolgozóval is. Tudja az összes feladatot, amit csinál, és akkor fejeződik be, amikor mindent sikeresen végrehajtott.

Ennek a két pontnak köszönhetően a telepítésünk biztonságos algoritmusa így néz ki.

  1. A fejlesztő összeállít egy új kódcsomagot (nálunk ez az RPM), teszteli a fejlesztői környezetben, teszteli a szakaszban, és a színpadi tárolóban hagyja.
  2. A fejlesztő a „műtermékek” legrészletesebb leírásával határozza meg a telepítés feladatát: az új csomag verzióját, az új funkciók leírását és szükség esetén a telepítés egyéb részleteit.
  3. A rendszergazda elindítja a frissítést. Elindítja az Ansible játékkönyvet, amely a következőket teszi:
    • Elvesz egy csomagot a szakasz lerakatából, és ezzel frissíti a csomag verzióját a terméktárban.
    • Összeállítja a frissített szolgáltatás háttérprogramjainak listáját.
    • Leállítja az első frissítendő szolgáltatást a HAProxyban, és megvárja, amíg a folyamatok futnak. A kecses leállításnak köszönhetően biztosak vagyunk abban, hogy minden jelenlegi ügyfélkérelem sikeresen teljesít.
    • Miután az API és a dolgozók teljesen leálltak, és a HAProxy ki van kapcsolva, a kód frissül.
    • Az Ansible szolgáltatásokat nyújt.
    • Minden szolgáltatáshoz meghúznak bizonyos „fogantyúkat”, amelyek egységtesztet hajtanak végre számos előre meghatározott kulcsteszten. Megtörténik az új kód alapvető ellenőrzése.
    • Ha az előző lépésben nem találtunk hibát, a háttérrendszer aktiválódik.
    • Térjünk át a következő háttérprogramra.
  4. Az összes háttérprogram frissítése után működési tesztek indulnak el. Ha hiányoznak, akkor a fejlesztő megvizsgálja az általa létrehozott új funkciókat.

Ezzel befejeződik a telepítés.

A hibatűrő webarchitektúra megvalósítása a Mail.ru Cloud Solutions platformon
Szolgáltatás frissítési ciklus

Ez a rendszer nem működne, ha nem lenne egyetlen szabályunk. A harcban a régi és az új verziókat egyaránt támogatjuk. Előzetesen a szoftverfejlesztés szakaszában le van írva, hogy még ha változások is történnek a szolgáltatási adatbázisban, azok nem törik meg a korábbi kódot. Ennek eredményeként a kódbázis fokozatosan frissül.

Következtetés

Megosztva a saját gondolataimat a hibatűrő WEB-architektúrával kapcsolatban, szeretném még egyszer megjegyezni a legfontosabb pontjait:

  • fizikai hibatűrés;
  • hálózati hibatűrés (balancers, BGP);
  • a használt és fejlesztett szoftver hibatűrése.

Stabil üzemidő mindenkinek!

Forrás: will.com

Hozzászólás