Deploy lykkedes, men sitet er nede
Dit dashboard siger, at tjenesten kører, mens dine brugere ser en fejl. Lær, hvorfor et vellykket deploy og applikationens health er forskellige signaler, og hvordan du får et grønt deploy til at betyde, at appen faktisk svarer.
Der findes en helt særlig slags dårlig morgen, som begynder med et grønt flueben. Deployet lykkedes, men sitet er nede, dashboardet siger kører, og nogen sender dig et screenshot af en 502-fejl.
Det er ikke et sjældent edge case. Det er det forudsigelige resultat af, at en platform rapporterer én ting og måler noget andet, og det er værd at forstå præcist, fordi løsningen ikke er "at tjekke grundigere" — det handler om at ændre, hvad ordet kører må betyde.
Tre forskellige spørgsmål, én statusindikator
Når en platform siger, at en service kører, kan den svare på et af disse spørgsmål:
- Startede containeren? Processen eksisterer og er ikke afsluttet.
- Er porten åben? Der lytter noget dér, hvor platformen forventer det.
- Svarer applikationen korrekt? En request får et svar, der betyder, at appen er klar til at arbejde.
Det er vidt forskellige garantier, og de fleste incidents af denne type opstår, fordi dashboardet svarer på spørgsmål 1, mens du antog, at det svarede på spørgsmål 3.
En Node-proces, der starter, ikke kan oprette forbindelse til sin database og bliver ved med at prøve igen, opfylder spørgsmål 1 for evigt. Den er ikke crashet. Den kommer aldrig til at håndtere en request. Containeren er "kørende" i enhver betydning, som orchestratoren interesserer sig for.
Gabet, hvor driftsafbrydelsen opstår
Det farlige tidsrum ligger mellem "den nye version er startet" og "den nye version kan fungere". I det tidsrum har en naiv platform allerede flyttet trafikken, fordi opstart var det eneste, den målte.
Det, der gør dette værre end et almindeligt crash, er rollback-forløbet. En crash-loop larmer: containeren afsluttes, genstartes, afsluttes igen, og platformen opdager det til sidst. En proces, der starter og hænger, er lydløs. Intet genstartes, ingen alarmer går, og den tidligere fungerende version er som regel allerede fjernet.
Det sidste er den egentlige skade. Den gamle version fungerede fint. Den blev fjernet, fordi en ny version startede, og opstart blev forvekslet med funktionalitet.
Hvad et rigtigt health gate gør
Løsningen er strukturel, ikke proceduremæssig. Trafikken bør ikke flyttes, før den nye version har svaret på en request.
På Dockup foregår en release sådan: Den nye version bygges isoleret, startes ved siden af den version, der aktuelt håndterer trafikken, og bliver derefter stillet et spørgsmål. Først når den svarer, peger domænet på den. Hvis den aldrig svarer, stopper releasen dér, og den tidligere version fortsætter med at håndtere trafikken — ingen uden for dit dashboard opdager, at et deploy blev forsøgt.
Derfor er et mislykket deploy på Dockup ikke en driftsafbrydelse. Den gamle container blev aldrig fjernet ud fra antagelsen om, at den nye ville fungere.
# The health gate is per-service configuration, not a platform default you inherit
dockup info my-project/my-api --json
Blokken healthCheck i outputtet er hele kontrakten: hvilken sti der forespørges på, hvor længe der skal ventes på et svar, hvor mange gange der skal prøves, og hvor længe der skal gå mellem forsøgene.
Konfigurer checket, så det besvarer spørgsmål 3
Et health endpoint, der altid returnerer 200, er værre end slet ikke at have et, fordi det forvandler et reelt gate til et gummistempel. Formålet med checket er at fejle, når applikationen ikke kan udføre sit arbejde.
Et nyttigt readiness endpoint verificerer de ting, som appen ikke kan fungere uden:
// 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 })
}
})
To regler får dette til at fungere i praksis:
Tjek de dependencies, du ikke kan håndtere requests uden, og intet andet. Hvis din app kan fungere med begrænset funktionalitet, når søgeindekset er nede, skal du ikke lade readiness afhænge af søgeindekset — så blokerer du deploys på grund af noget, der ikke er en driftsafbrydelse.
Hold det billigt. Endpointet kaldes gentagne gange under hver release. Et readiness-check, der kører en dyr query, skaber et load-problem, du selv har forårsaget.
Giv det nok tid, men ikke ubegrænset tid
To indstillinger afgør, om gaten hjælper eller gør skade:
- Timeout pr. forsøg skal være længere end din langsomste legitime cold start. En app, der opretter forbindelse til en database og varmer en cache op på otte sekunder, vil fejle et check på tre sekunder hver gang, og du vil "løse" det ved at deaktivere gaten — hvilket bringer dig tilbage til udgangspunktet.
- Retries skal dække den samlede opstartstid, ikke ét enkelt forsøg. Interval × retries er det reelle tidsbudget.
På Dockup hedder de healthCheckInterval, healthCheckTimeout og healthCheckRetries, og de angives pr. service, fordi en Rails-monolit og en Go-sidecar ikke starter efter samme tidsplan.
Når sitet allerede er nede
Hvis du læser dette midt i et incident, er dette den rækkefølge, der løser det hurtigst:
- Tjek, om appen svarer direkte, uden om domænet. Hvis den svarer på sin port, men ikke gennem domænet, er det et routingproblem og ikke et applikationsproblem, så du bør stoppe med at debugge din kode.
- Læs runtime-loggene, ikke build-loggene. Buildet lykkedes — det er udgangspunktet. Det, du vil vide, er, hvad processen gjorde, efter den startede.
- Rul tilbage, før du diagnosticerer. Diagnosticering er billigere, når ingen venter på dig.
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
På Dockup er en rollback et skift i stedet for et nyt build, fordi den tidligere version stadig ligger på disken. Det betyder noget klokken 3 om natten: Den hurtigste recovery er den, der ikke skal kompilere noget.
Spørgsmålet, du bør stille en platform
Når du vælger, hvor du vil køre production, er dette noget, du med fordel kan teste bevidst: Deploy en applikation, der starter korrekt, men derefter ikke kan få forbindelse til sin database. Se, hvad dashboardet siger.
Hvis det siger kører, ved du nu præcis, hvad det ord vil være værd under dit næste incident.
Ofte stillede spørgsmål
Hvorfor siger mit dashboard, at tjenesten kører, når sitet er nede? Fordi "kører" som regel betyder, at containerprocessen eksisterer, ikke at applikationen kan håndtere en request. En proces, der sidder fast i gentagne forsøg på at oprette forbindelse til en database, opfylder denne definition på ubestemt tid.
Bør et health check kontakte databasen? Ja, hvis din applikation ikke kan håndtere requests uden den. Tjek de dependencies, du reelt har brug for, og spring dem over, som appen kan fungere uden.
Hvad er forskellen på liveness og readiness? Liveness spørger, om processen bør genstartes. Readiness spørger, om den bør modtage trafik. Det gate, der forhindrer denne fejl, er readiness, og det skal køre, før trafikken flyttes.
Hvordan forhindrer jeg, at et dårligt deploy overhovedet tager sitet ned? Skift kun trafikken, når den nye version svarer på en rigtig request, og behold den tidligere version, indtil skiftet er bekræftet. Så bliver en mislykket release til en release, der aldrig fandt sted, i stedet for en driftsafbrydelse.
