Health check feiler, men appen fungerer
Når en health check feiler under utrulling, selv om appen fungerer fint lokalt, skyldes det vanligvis én av fem årsaker. Gå gjennom binding, path, port, timing og avhengigheter i den rekkefølgen som finner feilen raskest.
Det finnes en helt egen type fastlåst situasjon der en health check som feiler under utrulling blokkerer alle releaser, selv om applikasjonen etter alle mål du kan nå, fungerer helt fint. Den kjører lokalt. Den kjører i Docker lokalt. Loggene viser at den lytter. Likevel rapporterer plattformen feil etter feil, noen ganger tjue på rad, uten at noen request dukker opp i access-loggen.
Den siste detaljen er viktig, og snevrer umiddelbart inn feilsøkingen. Hvis applikasjonen aldri logget requesten, nådde sjekken aldri applikasjonen — og da er det ingenting i applikasjonskoden som kan forklare det.
Her er de fem årsakene, i den rekkefølgen som finner problemet raskest.
1. Du er bundet til localhost
Dette er den klart vanligste årsaken, og den forklarer nøyaktig symptomet «ingen trafikk kommer frem».
Inne i en container betyr 127.0.0.1 loopback-adressen til selve containeren. En health check som kommer utenfra containeren, kan ikke nå den. Prosessen lytter, loggene dine bekrefter det, og socketen er utilgjengelig fra alle steder som faktisk betyr noe.
// Unreachable from outside the container
app.listen(3000, '127.0.0.1')
// Correct
app.listen(3000, '0.0.0.0')
Frameworks har ulike standardverdier, og flere av dem har endret standarden mellom hovedversjoner. Sjekk hva frameworket faktisk binder til, i stedet for hva du husker at det binder til.
# Confirm from inside the running container
dockup exec "ss -ltn || netstat -ltn" my-project/my-api
Hvis lytteadressen er 127.0.0.1:3000 i stedet for 0.0.0.0:3000, har du funnet feilen, og ingenting annet på denne listen betyr noe.
2. Porten plattformen sjekker, er ikke porten du serverer på
To porter er involvert, og de er enkle å blande sammen: porten prosessen lytter på inne i containeren, og porten plattformen ruter til. Hvis appen leser PORT fra miljøet, samtidig som du har hardkodet 3000 et sted i en Dockerfile, kan de to være uenige uten at det vises tydelig.
Det pålitelige mønsteret er å la plattformen fortelle deg hvilken port du skal bruke:
const port = process.env.PORT || 3000
app.listen(port, '0.0.0.0')
Angi deretter tjenestens port én gang, på plattformen, og slutt å vedlikeholde tallet to steder.
3. Pathen returnerer noe annet enn en success-status
En health check-path matches nøyaktig, og overraskende mange feil skyldes en redirect. Hvis appen din redirecter /healthz til /healthz/, eller tvinger HTTPS med en 301, vil en sjekker som bare behandler 2xx som success, feile hver gang, mens en nettleser følger redirecten og viser deg en side som fungerer.
Tre konkrete feller:
- Redirects på grunn av trailing slash.
/healthz→/healthz/er en 301. - Tvunget HTTPS. Den interne sjekken kommer vanligvis via vanlig HTTP på loopback. En ubetinget HTTPS-redirect får den til å feile.
- Auth-middleware. En global authentication guard som kjører før routing, returnerer også 401 for health-pathen.
Ekskluder health-pathen eksplisitt fra auth og HTTPS-håndheving. Dette er den ene routen som bør være kjedelig.
4. Sjekken er raskere enn cold start
Hvis sjekken feiler noen ganger og deretter består, eller feiler under utrulling og består når du prøver på nytt, skyldes det timing og ikke konfigurasjon.
Budsjettet du trenger, er ikke ett forsøk — det er intervall × retries. En applikasjon som bruker tolv sekunder på å koble til databasen og varme opp en cache, trenger et totalbudsjett på mer enn tolv sekunder. Ellers vil du feile på hver release og til slutt slå av gate-funksjonen, noe som fjerner det eneste som står mellom en ødelagt build og brukerne dine.
dockup info my-project/my-api --json | grep -A6 healthCheck
Sett timeout-verdien høyere enn den tregeste legitime enkeltkjøringen, og sett antall retries slik at intervall × retries ligger komfortabelt over den tregeste legitime oppstarten. Mål oppstarten i stedet for å gjette — loggene har tidsstempler.
5. Applikasjonen er faktisk ikke klar
Det siste tilfellet er grunnen til at sjekken finnes: Appen startet, fikk ikke kontakt med en avhengighet og prøver på nytt. Den har ikke krasjet, så ingenting starter den på nytt. Den kan ikke håndtere requests, så sjekken feiler. Systemet fungerer nøyaktig slik det er laget for å fungere, og forteller deg at denne releasen ikke bør motta trafikk.
Måten du skiller dette fra de fire andre på, er at applikasjonen logget requesten og svarte med en status som ikke er 2xx. Hvis requesten vises i loggene, er årsak 1 til 3 eliminert.
Feilsøkingsrekkefølgen som sparer tid
# 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
Steg 3 løser de fleste av disse problemene. En curl fra innsiden av containeren fjerner alle nettverksvariabler på én gang: Hvis den returnerer 200 der, mens plattformen fortsatt feiler, ligger problemet i adressen eller porten, ikke i appen. Hvis den returnerer en 301 eller 401, har du funnet årsaken uten å endre noe på plattformen.
Hvorfor det er verdt å beholde gaten
Etter den fjerde mislykkede utrullingen er det fristende å deaktivere health check og få releasen ut. Det er verdt å huske hva du slår av.
På Dockup er health gate det som holder en ødelagt release borte fra brukerne dine. Den nye versjonen bygges og startes mens den nåværende fortsetter å svare; trafikken flyttes først når den nye versjonen svarer. Slår du av gaten, aktiverer du feilmodusen på nytt: en container som starter, men ikke fungerer, erstatter en versjon som var i orden.
En sjekk som feiler fire releaser på rad, er irriterende. En sjekk som alltid består, er en sjekk som ikke stopper utrullingen som faktisk betyr noe.
Vanlige spørsmål
Hvorfor feiler health check når appen fungerer lokalt?
Nesten alltid fordi containeren binder til 127.0.0.1 i stedet for 0.0.0.0. Lokalt kobler du til via den samme loopback-adressen; utenfra containeren er adressen utilgjengelig.
Bør health-endepunktet kreve authentication? Nei. Ekskluder det fra global auth-middleware, ellers får sjekken en 401 og utrullingen feiler selv om appen fungerer.
Hvilken timeout bør jeg bruke? Lengre enn den tregeste legitime enkeltkjøringen, med retries som dekker den tregeste legitime cold start. Les oppstartstiden ut av loggene i stedet for å gjette.
Er det trygt å deaktivere health check for å få gjennom en release? Det får releasen gjennom og fjerner beskyttelsen som hindrer en ødelagt versjon i å motta trafikk. Fiks sjekken i stedet — i de fleste tilfeller skyldes problemet en bind-adresse eller en redirect, og det tar bare noen minutter.
