Hogyan jutottunk nulla 502-es hibához a deployoknál
A Mythex backendjének minden deployjánál néhány kérés 502 Bad Gateway hibával meghiúsult. Nem sok — de egy 502 deploy közben azt jelenti, hogy valakinek a kattintása semmit nem csinált, vagy egy build, amelyet figyelt, egy pillanatra megakadt. Két külön problémára vezettük vissza: egy önjavító watchdogra, amely a deployjainkkal versengett, és egy résre aközött, amikor egy szerver már nem hallgatózik, és amikor a terheléselosztó már nem küld neki forgalmat. Az egymás utáni három éles deploy a javítások után 10-ről 6-ra, majd 0-ra csökkent.
A felállás
Az API-nk és az orchestratorunk konténerekként fut egy kis szerver-csoporton, egy AWS Application Load Balancer (ALB) mögött. Egy deploy egyszerre egy szervert frissít: leüríti a régi konténereket, elindítja az újakat, és továbblép. Minden szerveren egy watchdog ellenőrzi, hogy a konténerek futnak-e, és újraindít minden leállt konténert — azért van, hogy egy hajnali 3-kor összeomló folyamat újra működjön anélkül, hogy valakit fel kellene ébreszteni.
Mindkettő önmagában ésszerű. Együtt nem voltak azok.
1. probléma: a watchdog versenyzett a deployjal
A watchdog percenként futott, és egyetlen sikertelen egészségügyi ellenőrzésre „gyógyított”. Egy deploy szándékosan legfeljebb 150 másodpercig üríti a szolgáltatásokat — ezalatt az egészségügyi ellenőrzésnek kellene bukni. Így a watchdog minden egyes deploy alatt tüzelt.
Az idők többségében ártalmatlanul vesztette el a versenyt. Egyszer nyert: átírta a compose-fájlt (a leírást arról, mely konténereknek kell futniuk) egy deploy közepén, és egy szervert gateway-konténer nélkül hagyott — miközben a deploy-feladat sikeresnek jelentette magát.
A javítások:
- Egy zár. A deploy most ugyanazt a fájlzárat veszi, mint a szerver bootstrap-szkriptje, így a watchdog és a deploy soha nem futhat egyszerre.
- Négy ütem, nem egy. A watchdognak négy egymást követő sikertelen ellenőrzés kell, mielőtt cselekszik. Egy leürülő szolgáltatás már nem tűnik halottnak.
- Egy definíció. A compose-fájlnak most egyetlen forrása van, amelyet a szerverekre publikálunk, nem olyan másolatok, amelyek eltérhetnek.
- Ellenőrizd, ne bízz. A deploy-feladat már nem azért sikeres, mert egy parancs visszatért. Minden szerver API-ját, orchestratorát, gateway-jét és publikáló munkását ellenőrzi, mielőtt sikeres lehetne.
2. probléma: a szerver előbb ment el, mint a forgalom
A második probléma csendesebb volt. Amikor egy deploy leüríti a szervert, az 503-mal válaszol az egészségügyi végpontjára, hogy a terheléselosztó kivegye a forgalomból. A miénk ezt 5 másodpercig tette, aztán leállt.
De az ALB csak időnként ellenőrzött, és egymás utáni több sikertelen ellenőrzés kellett ahhoz, hogy egy célpontra ne küldjön többet forgalmat — egészben nagyjából 60 másodperc. Így körülbelül 55 másodpercig a terheléselosztó olyan szervernek küldte az igazi kéréseket, amely már nem hallgatózott. Ezek voltak a mi 502-eseink.
A javítás az volt, hogy a két idővonal összhangba került:
- A célcsoport most 5 másodpercenként ellenőriz, és 2 sikertelen ellenőrzés után jelöli egy célpontot egészségtelennek — nagyjából 10 másodperc alatt észreveszi.
- A leürítés még 15 másodpercig szolgálja ki az igazi forgalmat, miután elkezdett sikertelen egészségügyi ellenőrzéseket adni, így mindig van átfedés.
- Ezek a beállítások az infrastruktúrát létrehozó szkriptben élnek, nem egy konzolban, amelyet valaki egyszer kattintgatott.
A számokat egymáshoz kötni
Az ilyen időtúllépések egymástól függnek. A leürítésnek tovább kell tartania, mint amennyi idő alatt a terheléselosztó észreveszi a változást; a leürítésnek és a leállásnak be kell fejeződnie, mielőtt a konténert kényszerítetten leállítják. Könnyű, hogy valaki hat hónap múlva megváltoztat egy számot, és csendben visszahozza a hibát.
Ezért ezeket a kapcsolatokat tesztekként rögzítettük: a leürítésnek hosszabbnak kell lennie, mint az egészségügyi ellenőrzés intervallumának és a sikertelenségi küszöbnek a szorzata; az újbóli egészségessé váláshoz szükséges idő rövid marad; az egész leürítésnek bele kell férnie a konténer leállási türelmi idejébe. Ha egy számot a többi nélkül változtatsz meg, a CI elbukik. A teszteket magukat is mutációteszteltük: minden számot rossz értékre állítottunk, és ellenőriztük, hogy egy teszt elkapja-e.
A deploy mérése könnyen elrontható
A kezdeti mérésünk szerint egy tiszta deploy körülbelül 3%-ban hibás volt. Nem volt az. Minden nem-200-as választ számoltunk, és egy 503 az egészségügyi végpontról leürítés közben nem hiba — a leürítés teszi a dolgát, jelzi a terheléselosztónak, hogy ne válassza ki azt a szervert, miközben az még szolgálja ki az igazi kéréseket.
Amit a felhasználók éreznek, az a 502: a kérés semmit nem ért el. Csak ezeket számolva kaptunk valós képet — 10, majd 6, majd 0 három éles deployon keresztül.
Amit ebből tanultunk
- Az önjavításnak tudnia kell a tervezett leállásokról. Egy watchdog, amely nem tudja megkülönböztetni a deployt az összeomlástól, előbb-utóbb a deployjal fog versenyezni.
- A leürítés kézfogás. A szervernek addig kell szolgálnia, amíg a terheléselosztó bizonyíthatóan nem küld neki több forgalmat, és ezt az időzítést a terheléselosztón kell beállítani, nem a szerveren.
- A változatlan feltételeket tesztekbe kódold. Az üzemeltetési számok eltérnek. Azok a tesztek, amelyek egymáshoz kötik őket, nem.
- Döntsd el, mit mérsz, mielőtt mérsz. Az „hibák” rossz mérőszám volt; a „kérések, amelyek semmit nem értek el” volt a helyes.
Ha a Mythexen publikálsz appot, ebből semmi nem látszik neked — ez a lényeg. Kapcsolódó: hogyan marad elérhető a publikált appod az újrapublikálás közben.