Rejstřík deníkuDockup / terénní poznámka
Note / deploy-succeeded-but-site-is-down

Nasazení proběhlo úspěšně, ale web je nedostupný

Dashboard hlásí, že aplikace běží, ale uživatelé vidí chybu. Zjistěte, proč jsou úspěšné nasazení a stav aplikace dva odlišné signály a jak zajistit, aby zelené nasazení znamenalo, že aplikace skutečně odpovídá.

Existuje specifický druh špatného rána, které začíná zelenou fajfkou. Nasazení proběhlo úspěšně, ale web je nedostupný, dashboard hlásí běží a někdo vám posílá screenshot chyby 502.

Nejde o vzácný okrajový případ. Je to předvídatelný důsledek toho, že platforma hlásí jednu věc a měří jinou. Je důležité tomu přesně rozumět, protože řešením není „kontrolovat důkladněji“ — je potřeba změnit, co smí slovo běží znamenat.

Tři různé otázky, jeden stavový indikátor

Když platforma řekne, že služba běží, může odpovídat na kteroukoli z těchto otázek:

  1. Spustil se kontejner? Proces existuje a neskončil.
  2. Je port otevřený? Něco naslouchá tam, kde to platforma očekává.
  3. Odpovídá aplikace správně? Požadavek dostane odpověď, která znamená, že je aplikace připravená pracovat.

Jde o výrazně odlišné garance a většina incidentů tohoto typu vzniká proto, že dashboard odpovídá na otázku 1, zatímco vy předpokládáte otázku 3.

Proces Node, který se spustí, nedokáže se připojit k databázi a zůstane ve smyčce opakovaných pokusů, splňuje otázku 1 navždy. Nespadl. Nikdy neobslouží požadavek. Kontejner je „běžící“ ve všech ohledech, které zajímají orchestrátor.

Mezera, ve které vzniká výpadek

Nebezpečné období nastává mezi okamžikem, kdy se spustí nová verze, a okamžikem, kdy může fungovat. V tomto období už naivní platforma přesměrovala provoz, protože měřila pouze spuštění.

Horší než obyčejný pád je to kvůli rollbacku. Smyčka pádů je hlasitá: kontejner skončí, znovu se spustí, znovu skončí a platforma si toho nakonec všimne. Spuštění a následné zamrznutí je tiché. Nic se nerestartuje, nic nevyvolá alarm a předchozí funkční verze už obvykle byla odstraněna.

Právě to je skutečná škoda. Stará verze byla v pořádku. Byla odstraněna proto, že se spustila nová, a spuštění bylo mylně považováno za fungování.

Jak funguje skutečná health gate

Řešení je strukturální, nikoli procedurální. Provoz by se neměl přesměrovat, dokud nová verze neodpoví na požadavek.

Na Dockup probíhá release takto: nová verze se sestaví izolovaně, spustí se vedle verze, která právě obsluhuje provoz, a poté dostane otázku. Teprve když odpoví, doména na ni začne směřovat. Pokud nikdy neodpoví, release se zde zastaví a předchozí verze dál obsluhuje provoz — nikdo mimo váš dashboard se nikdy nedozví, že se o nasazení někdo pokusil.

Proto neúspěšné nasazení na Dockup neznamená výpadek. Starý kontejner nebyl nikdy odstraněn na základě předpokladu, že nový bude v pořádku.

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

Blok healthCheck v tomto výstupu představuje celou smlouvu: která cesta se požaduje, jak dlouho se čeká na odpověď, kolikrát se požadavek opakuje a jak dlouho se mezi pokusy čeká.

Nastavte kontrolu tak, aby odpovídala na otázku 3

Health endpoint, který bezpodmínečně vrací 200, je horší než žádný, protože skutečnou bránu mění v pouhé razítko. Smyslem kontroly je selhat ve chvíli, kdy aplikace nemůže plnit svou úlohu.

Užitečný readiness endpoint ověřuje věci, bez kterých aplikace nemůže fungovat:

// 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 })
  }
})

V praxi tuto kontrolu zajišťují dvě pravidla:

Kontrolujte závislosti, bez kterých nemůžete obsluhovat požadavky, a nic dalšího. Pokud se vaše aplikace dokáže obejít bez vyhledávacího indexu, když je nedostupný, neoznačujte readiness jako neúspěšnou kvůli vyhledávacímu indexu — zablokovali byste nasazení kvůli něčemu, co není výpadek.

Udržujte kontrolu levnou. Endpoint se volá opakovaně během každého release. Readiness kontrola, která spouští nákladný dotaz, představuje problém se zátěží, který jste si způsobili sami.

Dejte jí dost času, ale ne neomezeně

O tom, zda brána pomůže, nebo uškodí, rozhodují dvě nastavení:

  • Timeout na jeden pokus by měl být delší než nejpomalejší legitimní cold start. Aplikace, která se připojuje k databázi a zahřívá cache osm sekund, selže při každé třísekundové kontrole a vy to „vyřešíte“ vypnutím brány — čímž se vrátíte na začátek.
  • Počet opakování by měl pokrýt celkovou dobu spuštění, nikoli jeden pokus. Skutečný rozpočet je interval × počet opakování.

Na Dockup jde o healthCheckInterval, healthCheckTimeout a healthCheckRetries. Nastavují se pro každou službu zvlášť, protože Rails monolit a Go sidecar se nespouštějí podle stejného časového plánu.

Když je web už nedostupný

Pokud to čtete během probíhajícího incidentu, nejrychleji ho vyřeší tento postup:

  1. Ověřte, zda aplikace odpovídá přímo, tedy s obejitím domény. Pokud odpovídá na svém portu, ale ne přes doménu, jde o problém s routingem, nikoli s aplikací, a měli byste přestat ladit kód.
  2. Pročtěte runtime logy, ne build logy. Build proběhl úspěšně — to je výchozí předpoklad. Potřebujete zjistit, co proces dělal po spuštění.
  3. Proveďte rollback ještě před diagnostikou. Diagnostika je levnější, když vás nikdo nesleduje.
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

Na Dockup je rollback přepnutí, nikoli nový build, protože předchozí verze je stále na disku. Ve tři ráno na tom záleží: nejrychlejší obnova je ta, při které není nutné nic kompilovat.

Otázka, kterou byste měli položit platformě

Když vybíráte, kde provozovat produkci, je dobré si to záměrně otestovat: nasaďte aplikaci, která se úspěšně spustí, ale následně se nedokáže připojit k databázi. Sledujte, co hlásí dashboard.

Pokud hlásí běží, teď přesně víte, jakou hodnotu bude toto slovo mít při vašem příštím incidentu.

Často kladené otázky

Proč můj dashboard hlásí, že aplikace běží, když je web nedostupný? Protože „běží“ obvykle znamená, že proces kontejneru existuje, nikoli že aplikace dokáže obsloužit požadavek. Proces, který uvízl při opakovaných pokusech o připojení k databázi, tuto definici splňuje neomezeně dlouho.

Měla by health check kontrolovat databázi? Ano, pokud bez ní vaše aplikace nedokáže obsluhovat požadavky. Kontrolujte závislosti, které skutečně potřebujete, a vynechte ty, bez kterých se dokážete obejít.

Jaký je rozdíl mezi liveness a readiness? Liveness se ptá, zda by měl být proces restartován. Readiness se ptá, zda by měl přijímat provoz. Bránou, která tomuto selhání zabrání, je readiness a musí proběhnout před přesměrováním provozu.

Jak zabráním tomu, aby špatné nasazení vůbec vyřadilo web? Přesměrujte provoz až poté, co nová verze odpoví na skutečný požadavek, a předchozí verzi ponechte v provozu, dokud nebude přepnutí potvrzeno. Neúspěšný release pak znamená release, ke kterému nikdy nedošlo, nikoli výpadek.