Rejstřík deníkuDockup / terénní poznámka
Note / health-check-failing-deployment

Health check selhává, ale aplikace funguje

Když health check při nasazení selhává, zatímco aplikace lokálně funguje bez problémů, obvykle jde o jednu z pěti příčin. Projděte postupně binding, cestu, port, načasování a závislosti v pořadí, které problém odhalí nejrychleji.

Existuje specifický druh situace, kdy selhávající health check při nasazení zablokuje každé vydání, přestože je aplikace podle všech dostupných měřítek naprosto v pořádku. Lokálně funguje. Lokálně funguje i v Dockeru. Z logů je vidět, že naslouchá. Platforma ale hlásí jedno selhání za druhým, někdy i dvacet po sobě, aniž by se v access logu objevil jediný požadavek.

Právě tento poslední detail je důležitý a okamžitě zužuje okruh možností. Pokud aplikace požadavek nikdy nezalogovala, kontrola se k aplikaci vůbec nedostala — takže vysvětlení nenajdete v kódu aplikace.

Zde je pět příčin v pořadí, které problém odhalí nejrychleji.

1. Nasloucháte pouze na localhostu

To je vůbec nejčastější příčina a přesně vysvětluje situaci, kdy „nikdy nepřichází žádný provoz“.

Uvnitř kontejneru 127.0.0.1 znamená loopback tohoto kontejneru. Health check přicházející zvenčí se k němu nedostane. Proces naslouchá, logy to potvrzují, ale socket je z míst, na kterých záleží, nedostupný.

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

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

Jednotlivé frameworky se ve výchozím nastavení liší a několik z nich toto výchozí nastavení mezi hlavními verzemi změnilo. Ověřte, na čem váš framework skutečně naslouchá, místo abyste se spoléhali na to, co si pamatujete.

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

Pokud je naslouchající adresa 127.0.0.1:3000 místo 0.0.0.0:3000, našli jste příčinu a na ničem dalším z tohoto seznamu nezáleží.

2. Port, na kterém platforma provádí kontrolu, není port, na kterém obsluhujete požadavky

Ve hře jsou dva porty a snadno se zamění: port, na kterém váš proces naslouchá uvnitř kontejneru, a port, na který platforma směruje provoz. Pokud aplikace čte PORT z prostředí a někde v Dockerfile máte natvrdo nastavený port 3000, mohou se tyto hodnoty tiše rozcházet.

Spolehlivý postup je nechat platformu, aby vám port předala:

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

Poté nastavte port služby jednou na platformě a přestaňte jeho hodnotu udržovat na dvou místech.

3. Cesta vrací něco jiného než úspěšný stav

Cesta health checku se porovnává přesně a překvapivě mnoho selhání způsobují redirecty. Pokud aplikace přesměrovává /healthz na /healthz/ nebo vynucuje HTTPS pomocí kódu 301, checker, který považuje za úspěch pouze stav 2xx, selže pokaždé, zatímco prohlížeč redirect následuje a zobrazí vám fungující stránku.

Tři konkrétní nástrahy:

  • Redirecty kvůli koncovému lomítku. /healthz/healthz/ je 301.
  • Vynucené HTTPS. Interní kontrola obvykle přichází přes běžné HTTP na loopbacku. Bezpodmínečný redirect na HTTPS ji znefunkční.
  • Auth middleware. Globální autentizační guard, který se spouští před routováním, vrátí 401 i pro health path.

Health path explicitně vyjměte z auth i z vynucování HTTPS. Je to jediná route, která má být zcela nudná.

4. Kontrola je rychlejší než cold start

Pokud kontrola několikrát selže a potom projde, případně selže při nasazení a projde při opakování, jde o načasování, nikoli o konfiguraci.

Potřebný limit není jeden pokus — je to interval × počet opakování. Aplikace, které připojení k databázi a naplnění cache trvá dvanáct sekund, potřebuje celkový limit vyšší než dvanáct sekund. Jinak selže každé vydání a nakonec kontrolu vypnete, čímž odstraníte jedinou překážku mezi nefunkčním buildem a uživateli.

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

Nastavte timeout nad hodnotu nejpomalejšího legitimního jednotlivého pokusu a počet opakování tak, aby interval × počet opakování s rezervou překonal nejpomalejší legitimní start. Dobu startu změřte, místo abyste ji odhadovali — logy obsahují časová razítka.

5. Aplikace skutečně není připravená

Poslední případ je přesně ten, pro který health check existuje: aplikace se spustila, nedokázala se připojit k závislosti a opakuje pokusy. Nespadla, takže se nic nerestartuje. Nemůže obsluhovat požadavky, takže kontrola selhává. Systém funguje přesně podle návrhu a říká vám, že toto vydání nemá dostat žádný provoz.

Od ostatních čtyř případů ho odlišíte podle toho, že aplikace požadavek zalogovala a odpověděla stavem jiným než 2xx. Pokud se požadavek objeví v logu, příčiny 1 až 3 jsou vyloučené.

Diagnostické pořadí, které šetří čas

# 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

Krok 3 vyřeší většinu těchto případů. curl spuštěný uvnitř kontejneru odstraní všechny síťové proměnné najednou: pokud tam vrátí 200 a platforma stále selhává, problém je v adrese nebo portu, nikoli v aplikaci. Pokud vrátí 301 nebo 401, našli jste příčinu, aniž byste se platformy vůbec dotkli.

Proč se vyplatí kontrolu zachovat

Po čtvrtém neúspěšném nasazení je lákavé health check vypnout a vydání dostat ven. Stojí za to připomenout si, co tím vypínáte.

Na Dockup je health gate mechanismus, který brání tomu, aby se nefunkční vydání dostalo k uživatelům. Nová verze se sestaví a spustí, zatímco aktuální verze dál obsluhuje požadavky; provoz se přesměruje teprve ve chvíli, kdy nová verze začne odpovídat. Vypnutím gate znovu povolíte scénář, kdy kontejner, který se sice spustí, ale nedokáže fungovat, nahradí kontejner, který byl v pořádku.

Kontrola, která čtyři vydání po sobě selhává, je otravná. Kontrola, která bezpodmínečně prochází, je kontrola, která nezastaví nasazení, na kterém skutečně záleží.

Často kladené otázky

Proč health check selhává, když aplikace lokálně funguje? Téměř vždy proto, že kontejner naslouchá na 127.0.0.1 místo na 0.0.0.0. Lokálně se připojujete přes stejný loopback, ale zvenčí kontejneru je tato adresa nedostupná.

Měl by health endpoint vyžadovat autentizaci? Ne. Vyjměte ho z globálního auth middleware, jinak checker dostane 401 a nasazení selže, přestože aplikace funguje.

Jaký timeout mám použít? Delší, než trvá nejpomalejší legitimní jednotlivý pokus, přičemž počet opakování musí pokrýt nejpomalejší legitimní cold start. Dobu startu odečtěte z logů, místo abyste ji odhadovali.

Je bezpečné health check vypnout, abych odblokoval vydání? Vydání tím odblokujete, ale zároveň odstraníte ochranu, která brání tomu, aby nefunkční verze začala obsluhovat provoz. Místo toho opravejte kontrolu — ve většině případů je příčinou adresa bindingu nebo redirect a oprava zabere několik minut.