Indexul jurnaluluiDockup / notă de teren
Note / deploy-succeeded-but-site-is-down

Deploy-ul a reușit, dar site-ul este indisponibil

Dashboardul arată că aplicația rulează, iar utilizatorii văd o eroare. Află de ce succesul unui deploy și starea de sănătate a aplicației sunt semnale diferite și cum poți face ca un deploy marcat cu verde să însemne că aplicația răspunde efectiv.

Există un anumit tip de dimineață proastă care începe cu o bifă verde. Deploy-ul a reușit, dar site-ul este indisponibil, dashboardul arată running, iar cineva îți trimite o captură de ecran cu un 502.

Nu este un caz rar și neobișnuit. Este rezultatul previzibil al unei platforme care raportează un lucru și măsoară altul și merită să înțelegi exact de ce, deoarece soluția nu este „verifică mai atent” — ci schimbarea sensului pe care îl poate avea cuvântul running.

Trei întrebări diferite, un singur indicator de stare

Când o platformă spune că un serviciu rulează, este posibil să răspundă la oricare dintre următoarele întrebări:

  1. A pornit containerul? Procesul există și nu s-a oprit.
  2. Este portul deschis? Există ceva care ascultă acolo unde se așteaptă platforma.
  3. Răspunde aplicația corect? O cerere primește un răspuns care înseamnă că aplicația este pregătită să funcționeze.

Acestea sunt garanții complet diferite, iar majoritatea incidentelor de acest tip apar când dashboardul răspunde la întrebarea 1, în timp ce tu ai presupus că răspunde la întrebarea 3.

Un proces Node care pornește, nu reușește să se conecteze la baza de date și rămâne într-o buclă de reîncercare îndeplinește pentru totdeauna condiția din întrebarea 1. Nu s-a prăbușit. Nu va răspunde niciodată la vreo cerere. Containerul este „running” în toate sensurile care contează pentru orchestrator.

Spațiul în care se produce indisponibilitatea

Fereastra periculoasă este intervalul dintre „a pornit versiunea nouă” și „versiunea nouă poate funcționa”. În acest interval, o platformă naivă a direcționat deja traficul, deoarece singurul lucru măsurat a fost pornirea.

Ceea ce face situația mai gravă decât o simplă prăbușire este modul în care funcționează rollback-ul. O buclă de crash este zgomotoasă: containerul se oprește, repornește, se oprește din nou, iar platforma observă în cele din urmă problema. Un proces care pornește și apoi rămâne blocat este silențios. Nimic nu repornește, nicio alarmă nu se declanșează, iar versiunea anterioară, care funcționa, a fost de obicei deja eliminată.

Aceasta este partea cu adevărat gravă. Versiunea veche funcționa. A fost eliminată deoarece una nouă a pornit, iar pornirea a fost confundată cu funcționarea.

Cum funcționează un health gate real

Soluția este structurală, nu procedurală. Traficul nu ar trebui direcționat către versiunea nouă până când aceasta nu a răspuns la o cerere.

Pe Dockup, un release se desfășoară astfel: versiunea nouă este construită izolat, pornită alături de versiunea care deservește în prezent traficul și apoi i se adresează o întrebare. Domeniul este direcționat către ea numai după ce răspunde. Dacă nu răspunde niciodată, release-ul se oprește acolo, iar versiunea anterioară continuă să deservească traficul — nimeni din afara dashboardului tău nu află vreodată că s-a încercat un deploy.

De aceea, un deploy eșuat pe Dockup nu înseamnă o indisponibilitate. Containerul vechi nu a fost eliminat pe baza presupunerii că cel nou va funcționa.

# The health gate is per-service configuration, not a platform default you inherit
dockup info my-project/my-api --json

Blocul healthCheck din acel output reprezintă întregul contract: ce path este solicitat, cât timp se așteaptă un răspuns, de câte ori se încearcă și cât timp se așteaptă între încercări.

Configurează verificarea pentru a răspunde la întrebarea 3

Un endpoint de health care returnează necondiționat 200 este mai rău decât absența oricărui endpoint, deoarece transformă un gate real într-o simplă formalitate. Scopul verificării este să eșueze atunci când aplicația nu își poate îndeplini rolul.

Un endpoint util de readiness verifică lucrurile fără de care aplicația nu poate funcționa:

// Not this — it proves only that the process is alive
app.get('/healthz', (req, res) => res.send('ok'))

// This — it proves the app can actually serve a request
app.get('/healthz', async (req, res) => {
  try {
    await db.query('select 1')       // the dependency that is usually the problem
    if (!cacheReady) throw new Error('cache warming')
    res.status(200).json({ ok: true })
  } catch (err) {
    res.status(503).json({ ok: false, reason: err.message })
  }
})

Două reguli fac ca acest mecanism să funcționeze în practică:

Verifică dependențele fără de care nu poți deservi cereri și nimic în plus. Dacă aplicația poate funcționa cu capacitate redusă atunci când indexul de căutare este indisponibil, nu face readiness dependent de indexul de căutare — vei bloca deploy-urile din cauza unui lucru care nu reprezintă o indisponibilitate.

Păstrează verificarea simplă și rapidă. Endpointul este apelat în mod repetat la fiecare release. O verificare de readiness care execută un query costisitor creează o problemă de încărcare pe care ți-o provoci singur.

Acordă-i suficient timp, dar nu timp nelimitat

Două setări determină dacă gate-ul ajută sau înrăutățește situația:

  • Timeout-ul pentru fiecare încercare ar trebui să fie mai mare decât cel mai lent cold start legitim. O aplicație care se conectează la o bază de date și încălzește un cache în opt secunde va eșua de fiecare dată la o verificare de trei secunde, iar tu vei „rezolva” problema dezactivând gate-ul — ceea ce te readuce exact în punctul de pornire.
  • Reîncercările ar trebui să acopere timpul total de pornire, nu o singură încercare. Intervalul × numărul de reîncercări reprezintă bugetul real.

Pe Dockup, acestea sunt healthCheckInterval, healthCheckTimeout și healthCheckRetries și sunt configurate per serviciu, deoarece un monolit Rails și un sidecar Go nu pornesc după același program.

Când site-ul este deja indisponibil

Dacă citești acest articol în timpul unui incident, următoarea ordine îl rezolvă cel mai rapid:

  1. Verifică dacă aplicația răspunde direct, ocolind domeniul. Dacă răspunde pe portul său, dar nu și prin domeniu, ai o problemă de routing, nu una a aplicației, așa că ar trebui să încetezi depanarea codului.
  2. Citește logurile de runtime, nu logurile de build. Build-ul a reușit — aceasta este premisa. Ceea ce te interesează este ce a făcut procesul după ce a pornit.
  3. Fă rollback înainte să investighezi. Investigarea este mai ieftină când nu te urmărește nimeni.
dockup logs my-project/my-api --follow            # what the running process is saying
dockup deployments my-project/my-api              # what was live before this
dockup rollback <deployment-id> my-project/my-api # put that back

Pe Dockup, un rollback este o comutare, nu o reconstrucție, deoarece versiunea anterioară se află încă pe disc. Acest lucru contează la 3 dimineața: cea mai rapidă recuperare este cea care nu trebuie să compileze nimic.

Întrebarea pe care trebuie să o adresezi unei platforme

Când alegi unde să rulezi mediul de producție, merită să testezi intenționat următorul scenariu: deploy-uiește o aplicație care pornește cu succes, apoi nu mai reușește să ajungă la baza de date. Uită-te la ce spune dashboardul.

Dacă spune running, acum știi exact cât va valora acest cuvânt la următorul incident.

Întrebări frecvente

De ce dashboardul meu spune că aplicația rulează când site-ul este indisponibil? Deoarece „running” înseamnă de obicei că procesul containerului există, nu că aplicația poate răspunde la o cerere. Un proces blocat într-o buclă de reîncercare a conexiunii la baza de date îndeplinește această definiție pe termen nelimitat.

Ar trebui ca un health check să acceseze baza de date? Da, dacă aplicația nu poate răspunde la cereri fără ea. Verifică dependențele de care ai nevoie cu adevărat și omite-le pe cele fără de care aplicația poate funcționa cu capacitate redusă.

Care este diferența dintre liveness și readiness? Liveness verifică dacă procesul ar trebui repornit. Readiness verifică dacă procesul ar trebui să primească trafic. Gate-ul care previne această problemă este readiness și trebuie executat înainte ca traficul să fie direcționat.

Cum pot împiedica un deploy problematic să indisponibilizeze site-ul? Comută traficul numai după ce versiunea nouă răspunde la o cerere reală și păstrează versiunea anterioară până când comutarea este confirmată. Astfel, un release eșuat devine un release care nu s-a întâmplat niciodată, nu o indisponibilitate.