Indeks dnevnikaDockup / bilješka s terena
Note / health-check-failing-deployment

Health check ne prolazi, ali aplikacija radi

Health check koji ne prolazi tijekom deploymenta, dok aplikacija lokalno radi bez problema, obično se svodi na pet uzroka. Provjerite binding, putanju, port, vremensko ograničenje i ovisnosti redoslijedom kojim ćete najbrže pronaći problem.

Postoji posebna vrsta zastoja u kojoj health check koji ne prolazi blokira deployment pri svakom releaseu, dok je aplikacija, prema svemu što možete provjeriti, potpuno ispravna. Radi lokalno. Radi lokalno u Dockeru. Logovi pokazuju da osluškuje. A platforma prijavljuje neuspjeh za neuspjehom, ponekad dvadeset puta zaredom, a da se nijedan request ne pojavi u vašem access logu.

Upravo je taj posljednji detalj važan jer odmah sužava područje potrage. Ako vaša aplikacija nikad nije zapisala request, on do nje nije ni stigao — zato ništa u kodu aplikacije neće objasniti problem.

Ovo je pet uzroka, poredanih tako da problem pronađete najbrže.

1. Binding je postavljen na localhost

To je najčešći uzrok i savršeno objašnjava simptom „promet nikad ne stiže”.

Unutar containera 127.0.0.1 znači vlastiti loopback tog containera. Health check koji dolazi izvana ne može doći do njega. Proces osluškuje, logovi to potvrđuju, a socket je nedostupan s bilo kojeg mjesta koje je važno.

// Unreachable from outside the container
app.listen(3000, '127.0.0.1')

// Correct
app.listen(3000, '0.0.0.0')

Frameworkovi se razlikuju po zadanim postavkama, a neki su ih promijenili između glavnih verzija. Provjerite na što se vaš framework stvarno veže, umjesto da se oslanjate na ono čega se sjećate.

# Confirm from inside the running container
dockup exec "ss -ltn || netstat -ltn" my-project/my-api

Ako je adresa na kojoj proces osluškuje 127.0.0.1:3000, a ne 0.0.0.0:3000, pronašli ste uzrok i ništa drugo s ovog popisa više nije važno.

2. Port koji platforma provjerava nije port na kojem poslužujete aplikaciju

Uključena su dva porta i lako ih je zamijeniti: port na kojem vaš proces osluškuje unutar containera i port na koji platforma usmjerava promet. Ako aplikacija čita PORT iz environmenta, a vi ste negdje u Dockerfileu hardkodirali 3000, ta se dva porta mogu tiho razlikovati.

Pouzdan je pristup prepustiti platformi da vam kaže koji port treba koristiti:

const port = process.env.PORT || 3000
app.listen(port, '0.0.0.0')

Zatim port servisa postavite jednom, na platformi, i prestanite održavati isti broj na dva mjesta.

3. Putanja vraća nešto drugo, a ne uspješan status

Putanja health checka uspoređuje se točno i iznenađujuće velik broj neuspjeha zapravo su redirecti. Ako vaša aplikacija preusmjerava /healthz na /healthz/ ili prisiljava HTTPS statusom 301, checker koji samo 2xx statuse smatra uspjehom svaki će put prijaviti neuspjeh, dok će browser slijediti redirect i prikazati vam ispravnu stranicu.

Tri konkretne zamke:

  • Redirecti zbog završne kose crte. /healthz/healthz/ je 301.
  • Prisilni HTTPS. Interni check obično dolazi putem običnog HTTP-a preko loopbacka. Bezuvjetni HTTPS redirect neće proći.
  • Auth middleware. Globalni authentication guard koji se izvršava prije routinga vratit će 401 i za health path.

Health path izričito izuzmite iz autha i prisiljavanja HTTPS-a. To je jedina ruta koja treba biti dosadna.

4. Check je brži od cold starta

Ako check nekoliko puta ne uspije, a zatim prođe, ili ne uspije pri deploymentu, ali prođe kad ga ponovite, problem je u vremenu, a ne u konfiguraciji.

Budžet koji vam treba nije jedan pokušaj — to je interval × broj ponavljanja. Aplikaciji kojoj treba dvanaest sekundi da se poveže s bazom podataka i zagrije cache treba ukupni budžet veći od dvanaest sekundi. U suprotnom nećete proći nijedan release i na kraju ćete isključiti gate, čime uklanjate jedinu prepreku između neispravnog builda i svojih korisnika.

dockup info my-project/my-api --json | grep -A6 healthCheck

Postavite timeout iznad trajanja najsporijeg legitimnog pojedinačnog pokušaja, a broj ponavljanja tako da interval × broj ponavljanja s dovoljnom rezervom premaši najsporiji legitimni boot. Izmjerite trajanje boota umjesto da nagađate — logovi imaju vremenske oznake.

5. Aplikacija doista nije spremna

Posljednji je slučaj onaj zbog kojeg check postoji: aplikacija se pokrenula, nije se mogla povezati s ovisnošću i pokušava ponovno. Nije se srušila, pa je ništa ne pokreće ispočetka. Ne može posluživati zahtjeve, pa check ne prolazi. Sustav radi točno onako kako je dizajniran i govori vam da ovaj release ne bi trebao primati promet.

Od ostala četiri slučaja razlikujete ga po tome što je vaša aplikacija zapisala request i odgovorila statusom koji nije 2xx. Ako se request pojavljuje u logovima, uzroci od 1 do 3 su isključeni.

Dijagnostički redoslijed koji štedi vrijeme

# 1. Did the request reach the app at all?
dockup logs my-project/my-api --follow

# 2. What is the process actually bound to?
dockup exec "ss -ltn || netstat -ltn" my-project/my-api

# 3. Does the path answer from inside the container?
dockup exec "curl -si localhost:3000/healthz" my-project/my-api

# 4. What is the gate configured to expect?
dockup info my-project/my-api --json

Treći korak rješava većinu ovih problema. curl iz containera uklanja sve mrežne varijable odjednom: ako tamo vraća 200, a platforma i dalje prijavljuje neuspjeh, problem je u adresi ili portu, a ne u aplikaciji. Ako vraća 301 ili 401, pronašli ste uzrok bez ikakve izmjene na platformi.

Zašto vrijedi zadržati gate

Nakon četvrtog neuspjelog deploymenta primamljivo je isključiti health check i objaviti release. Vrijedi se prisjetiti što time isključujete.

Na Dockupu je health gate ono što neispravan release drži podalje od vaših korisnika. Nova se verzija izgradi i pokrene dok trenutačna i dalje poslužuje promet; promet se prebacuje tek kada nova verzija odgovori. Isključite gate i ponovno omogućujete scenarij u kojem container koji se pokrene, ali ne može raditi, zamjenjuje onaj koji je bio ispravan.

Check koji ne prolazi četiri releasea zaredom je neugodan. Check koji bezuvjetno prolazi neće zaustaviti deployment koji je doista važan.

Često postavljana pitanja

Zašto health check ne prolazi kada aplikacija radi lokalno? Gotovo uvijek zato što se container veže na 127.0.0.1, a ne na 0.0.0.0. Lokalno se povezujete preko istog loopbacka; izvana je ta adresa nedostupna.

Treba li health endpoint zahtijevati authentication? Ne. Izuzmite ga iz globalnog auth middlewarea jer će checker dobiti 401, a deployment neće proći iako je aplikacija ispravna.

Koji timeout trebam koristiti? Duži od vašeg najsporijeg legitimnog pojedinačnog pokušaja, uz dovoljan broj ponavljanja za najsporiji legitimni cold start. Trajanje boota pročitajte iz logova umjesto da nagađate.

Je li sigurno isključiti health check da bih odblokirao release? Time ćete odblokirati release, ali i ukloniti zaštitu koja sprečava da neispravna verzija preuzme promet. Umjesto toga popravite check — u većini slučajeva uzrok je bind adresa ili redirect, a rješenje traje nekoliko minuta.