Si arritëm deploy-et në zero gabime 502
Çdo herë që deploy-onim backend-in e Mythex, një grusht kërkesash dështonin me 502 Bad Gateway. Jo shumë — por një 502 gjatë deploy-it do të thotë se klikimi i dikujt nuk bëri asgjë, ose një ndërtim që po e shikonte u ndal për një moment. E gjurmuam në dy probleme të ndara: një watchdog vetë-shëruese që luftonte me deploy-et, dhe një boshllëk midis kohës kur një server ndalon së dëgjuari dhe kohës kur load balancer-i ndalon t’i dërgojë trafik. Gjatë tre deploy-eve të njëpasnjëshme të prodhimit pas rregullimeve, numri shkoi nga 10 në 6 dhe në 0.
Konfigurimi
API-ja dhe orkestratori ynë ekzekutohen si kontejnerë në një grup të vogël serverësh pas një Application Load Balancer (ALB) të AWS. Një deploy përditëson një server në një kohë: tërheq kontejnerët e vjetër, nis të rinj dhe kalon te tjetri. Një watchdog në secilin server kontrollon që kontejnerët janë lart dhe rinis çdo gjë që nuk është — ekziston që një proces që u rrëzua në 3 të natës të kthehet pa e zgjuar kush.
Të dyja janë të arsyeshme veç e veç. Së bashku nuk ishin.
Problemi 1: watchdog garonte me deploy-in
Watchdog-u ekzekutohej një herë në minutë dhe "shëronte" pas një health check-u të dështuar. Një deploy me qëllim tërheq shërbimet deri në 150 sekonda — gjatë të cilave një health check duhet të dështojë. Kështu watchdog-u zjarrosej gjatë çdo deploy-i.
Shumicën e herëve e humbiste garën pa dëm. Një herë e fitoi: rishkroi skedarin compose (përshkrimin e kontejnerëve që duhet të ekzekutohen) në mes të një deploy-i, dhe la një server pa asnjë kontejner gateway — ndërsa punë e deploy-it raportoi sukses.
Rregullimet:
- Një bllokim. Deploy-i tani merr të njëjtën bllokim skedari si skripti i bootstrap-it të serverit, që watchdog-u dhe një deploy të mos mund të ekzekutohen kurrë njëkohësisht.
- Katër goditje, jo një. Watchdog-u ka nevojë për katër health check-e radhazi të dështuara përpara se të veprojë. Një shërbim që po tërhiqet nuk duket më i vdekur.
- Një përcaktim. Skedari compose tani ka një burim të vetëm, të publikuar te serverët, në vend të kopjeve që mund të ndahen.
- Verifiko, mos beso. Punës së deploy-it nuk i mjafton më që një komandë të kthejë sukses. Ajo kontrollon API-n, orkestratorin, gateway-in dhe worker-in e publikimit të çdo serveri përpara se të mund të kalojë.
Problemi 2: serveri u largua përpara trafikut
Problemi i dytë ishte më i heshtur. Kur një deploy tërheq një server, serveri fillon t’i përgjigjet endpoint-it të tij të shëndetit me 503 që load balancer-i ta nxjerrë nga qarkullimi. Yni e bënte këtë për 5 sekonda, pastaj mbyllej.
Por ALB kontrollonte shëndetin vetëm herë pas here, dhe kishte nevojë për disa dështime radhazi përpara se të ndalonte dërgimin te një target — deri rreth 60 sekonda gjithsej. Kështu për rreth 55 sekonda, load balancer-i vazhdonte t’i dërgonte kërkesa reale një serveri që kishte ndalur së dëgjuari. Ato ishin 502-at tona.
Zgjidhja ishte t’i bënim dy afatet të përputhen:
- Target group tani kontrollon çdo 5 sekonda dhe shënon një target si të pashëndetshëm pas 2 dështimesh — rreth 10 sekonda për ta vënë re.
- Tërheqja vazhdon t’i shërbejë trafikut real për 15 sekonda pasi fillon të dështojë health check-et, kështu që gjithmonë ka mbivendosje.
- Këto cilësime jetojnë në skriptin që siguron infrastrukturën, jo në një konsolë që dikush e klikoi një herë.
Mbajtja e numrave që mbështesin njëri-tjetrin
Kohët e tilla varen nga njëra-tjetra. Tërheqja duhet të zgjasë më shumë sesa i duhet balancer-it për ta vënë re; tërheqja plus mbyllja duhet të përfundojë përpara se kontejneri të vritet me forcë. Është e lehtë që dikush të ndryshojë një numër në gjashtë muaj dhe pa e vënë re të rikthejë gabimin.
Kështu i shkruam këto marrëdhënie si teste: tërheqja duhet të jetë më e gjatë se intervali i health check-ut shumëzuar me pragun e dështimit; koha për t’u shënuar sërish i shëndetshëm duhet të mbetet e shkurtër; e gjithë tërheqja duhet të futet brenda periudhës së ndalimit të kontejnerit. Ndrysho një numër pa të tjerët dhe CI dështon. I mutacionuam vetë testet — ndryshuam çdo numër në një vlerë të gabuar dhe kontrolluam që një test ta kapte.
Matja e një deploy-i është e lehtë të gabohet
Matja e parë thoshte se një deploy i pastër ishte rreth 3% i prishur. Nuk ishte. Po numronim çdo përgjigje jo-200, dhe një 503 nga një health endpoint gjatë tërheqjes nuk është dështim — është tërheqja që bën punën e vet, duke i thënë balancer-it të mos zgjedhë atë server ndërsa ai vazhdon të shërbejë kërkesa reale.
Gjithë ajo që ndjen përdoruesi është një 502: kërkesa nuk arriti asgjë. Numërimi vetëm i atyre dha pamjen e vërtetë — 10, pastaj 6, pastaj 0 gjatë tre deploy-eve të prodhimit.
Çfarë nxorëm nga kjo
- Vetë-shërimi duhet të dijë për ndërprerjet e planifikuara. Një watchdog që nuk mund të dallojë një deploy nga një rrëzim do të përplaset me deploy-in në fund.
- Tërheqja është një dorëdhënie. Serveri duhet të vazhdojë të shërbejë derisa load balancer-i të jetë vërtetuar që ka ndalur dërgimin e trafikut, dhe ai timing vendoset te balancer-i, jo te serveri.
- Koduar invariantët si teste. Numrat operacionalë shmangen nga vlerat e tyre. Testet që i lidhin me njëri-tjetrin nuk shmangen.
- Vendosni çfarë po matni përpara se ta matni. "Gabime" ishte metrika e gabuar; "kërkesa që nuk arritën asgjë" ishte e saktë.
Nëse publikoni një aplikacion në Mythex, asnjë nga kjo nuk është e dukshme për ju — dhe kjo është qëllimi. E lidhur: si qëndron aplikacioni yt i publikuar gjatë ripublikimit.