Blog

Cum am ajuns la zero erori 502 la deploy-uri

De fiecare dată când făceam deploy la backend-ul Mythex, câteva cereri eșuau cu 502 Bad Gateway. Nu multe — dar un 502 în timpul unui deploy înseamnă că un clic al cuiva nu a făcut nimic sau că o construcție urmărită s-a blocat o clipă. Am urmărit problema până la două probleme separate: un watchdog cu auto-vindecare care se certa cu deploy-urile și o diferență între momentul în care un server încetează să mai asculte și momentul în care load balancer-ul încetează să-i mai trimită trafic. La trei deploy-uri consecutive de producție după remedieri, numărul a scăzut de la 10 la 6 și apoi la 0.

Configurația

API-ul și orchestratorul nostru rulează ca containere pe un grup mic de servere în spatele unui AWS Application Load Balancer (ALB). Un deploy actualizează câte un server pe rând: scoate din uz containerele vechi, pornește cele noi și trece mai departe. Un watchdog de pe fiecare server verifică dacă containerele sunt pornite și repornește orice nu este — există pentru ca un proces căzut la 3 dimineața să revină fără ca cineva să fie trezit.

Amândouă sunt rezonabile separat. Împreună nu funcționau.

Problema 1: watchdog-ul se întrecea cu deploy-ul

Watchdog-ul rula o dată pe minut și se „vindeca” la o singură verificare de sănătate eșuată. Un deploy oprește intenționat serviciile timp de până la 150 de secunde — în timpul acesta, o verificare de sănătate trebuie să eșueze. Așadar watchdog-ul intervenea în timpul fiecărui deploy.

De cele mai multe ori pierdea cursa fără efecte. O dată a câștigat-o: a rescris fișierul compose (descrierea containerelor care ar trebui să ruleze) chiar în mijlocul unui deploy și a lăsat un server fără niciun container gateway — în timp ce job-ul de deploy a raportat succes.

Remedierile:

  • Un singur lock. Deploy-ul preia acum același lock de fișier ca scriptul de bootstrap al serverului, astfel încât watchdog-ul și deploy-ul nu pot rula niciodată simultan.
  • Patru lovituri, nu una. Watchdog-ul are nevoie de patru verificări consecutive eșuate înainte să acționeze. Un serviciu aflat în drenare nu mai arată ca mort.
  • O singură definiție. Fișierul compose are acum o singură sursă, publicată pe servere, în loc de copii care puteau diverge.
  • Verifică, nu presupune. Job-ul de deploy nu mai trece doar pentru că o comandă a returnat un rezultat. Verifică API-ul, orchestratorul, gateway-ul și worker-ul de publicare de pe fiecare server înainte să poată reuși.

Problema 2: serverul a plecat înaintea traficului

A doua problemă era mai discretă. Când un deploy scoate un server din uz, serverul începe să răspundă la endpoint-ul de sănătate cu 503, ca load balancer-ul să îl scoată din rotație. Al nostru făcea asta timp de 5 secunde, apoi se oprea.

Dar ALB verifica sănătatea doar din când în când și avea nevoie de mai multe verificări eșuate la rând înainte să înceteze rutarea către o țintă — până la aproximativ 60 de secunde în total. Deci, timp de aproximativ 55 de secunde, load balancer-ul continua să trimită cereri reale unui server care deja încetase să mai asculte. Acelea erau erorile noastre 502.

Remedierea a fost să aliniem cele două linii de timp:

  • Grupul de ținte verifică acum la fiecare 5 secunde și marchează o țintă ca nesănătoasă după 2 eșecuri — adică aproximativ 10 secunde până o observă.
  • Drenarea continuă să servească trafic real timp de 15 secunde după ce începe să eșueze verificările de sănătate, deci există mereu suprapunere.
  • Aceste setări trăiesc în scriptul care provizionează infrastructura, nu într-o consolă pe care cineva a dat clic o dată.

Ca numerele să se susțină reciproc

Timeout-urile de acest fel depind unele de altele. Drenarea trebuie să dureze mai mult decât timpul în care balancer-ul are nevoie să observe; drenarea plus oprirea trebuie să se termine înainte ca containerul să fie oprit forțat. Este ușor ca cineva să schimbe un număr peste șase luni și să reintroducă discret eroarea.

Așa că am scris aceste relații ca teste: drenarea trebuie să fie mai lungă decât intervalul verificării de sănătate înmulțit cu pragul de eșec; timpul până când o țintă redevine sănătoasă trebuie să rămână scurt; întreaga drenare trebuie să încapă în perioada de grație a opririi containerului. Schimbi un număr fără celelalte și CI eșuează. Am testat prin mutație chiar testele — am schimbat fiecare număr cu o valoare greșită și am verificat că un test o prinde.

Măsurarea unui deploy este ușor de greșit

Prima noastră măsurătoare spunea că un deploy curat era stricat în proporție de aproximativ 3%. Nu era adevărat. Număram orice răspuns care nu era 200, iar un 503 de la un endpoint de sănătate în timpul drenării nu este un eșec — este drenarea care își face treaba, spunând balancer-ului să nu aleagă acel server cât timp continuă să servească cereri reale.

Ce simte utilizatorul este un 502: cererea nu a ajuns la nimic. Numărând doar acestea, am obținut imaginea reală — 10, apoi 6, apoi 0 la trei deploy-uri de producție.

Ce am reținut

  • Auto-vindecarea trebuie să știe despre întreruperile planificate. Un watchdog care nu poate deosebi un deploy de o cădere se va certa, până la urmă, cu deploy-ul.
  • Drenarea este o strângere de mână. Serverul trebuie să continue să servească până când load balancer-ul a dovedit că nu mai trimite trafic, iar această temporizare se setează pe balancer, nu pe server.
  • Codifică invarianții ca teste. Numerele operaționale se modifică în timp. Testele care le leagă între ele nu.
  • Decide ce măsori înainte să măsori. „Erori” era metrica greșită; „cereri care nu au ajuns la nimic” era cea corectă.

Dacă publici o aplicație pe Mythex, nimic din toate acestea nu ți se vede — și acesta este scopul. Înrudit: cum rămâne aplicația ta publicată online în timp ce o republici.

Mythex · Pricing · Construiește orice cu AI · Șabloane pentru site-uri și aplicații · Mythex comparat · Glosar pentru crearea de aplicații cu AI · Instrumente gratuite pentru crearea de aplicații cu AI · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)