Päiväkirjan hakemistoDockup / kenttämuistio
Note / health-check-failing-deployment

Terveystarkistus epäonnistuu, vaikka sovellus toimii

Käyttöönoton yhteydessä epäonnistuva terveystarkistus, vaikka sovellus toimii paikallisesti ongelmitta, johtuu yleensä viidestä syystä. Selvitä sidontaosoite, polku, portti, ajoitus ja riippuvuudet järjestyksessä, jolla ongelma löytyy nopeimmin.

On erityisen turhauttavaa, kun käyttöönoton epäonnistuva terveystarkistus estää jokaisen julkaisun, vaikka sovellus on kaikin käytettävissä olevin mittarein täysin kunnossa. Se toimii paikallisesti. Se toimii paikallisessa Dockerissa. Lokit kertovat sen kuuntelevan. Alusta kuitenkin raportoi epäonnistumisesta kerta toisensa jälkeen, joskus kaksikymmentä kertaa peräkkäin, eikä käyttöoikeuslokiin ilmesty yhtäkään pyyntöä.

Juuri viimeinen yksityiskohta on tärkeä, ja se rajaa vaihtoehdot heti. Jos sovelluksesi ei koskaan kirjannut pyyntöä, pyyntö ei saavuttanut sovellustasi — joten mikään sovelluskoodissa ei selitä ongelmaa.

Tässä ovat viisi syytä järjestyksessä, jolla ongelma löytyy nopeimmin.

1. Olet sitonut sovelluksen localhost-osoitteeseen

Tämä on yleisin syy, ja se selittää täsmälleen oireen, jossa liikennettä ei saavu koskaan.

Säilön sisällä 127.0.0.1 tarkoittaa vain kyseisen säilön omaa loopback-osoitetta. Säilön ulkopuolelta saapuva terveystarkistus ei pääse siihen käsiksi. Prosessi kuuntelee, lokit vahvistavat sen, mutta socketiin ei pääse mistään merkityksellisestä sijainnista.

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

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

Frameworkien oletusasetukset vaihtelevat, ja useat niistä ovat muuttaneet oletusta pääversioiden välillä. Tarkista, mihin frameworkisi todella sitoutuu, äläkä luota muistikuvasi mukaiseen asetukseen.

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

Jos kuunteleva osoite on 127.0.0.1:3000 eikä 0.0.0.0:3000, olet löytänyt syyn, eikä muilla tämän listan kohdilla ole merkitystä.

2. Alustan tarkistama portti ei ole sama kuin portti, jossa palvelet

Mukana on kaksi porttia, jotka on helppo sekoittaa keskenään: portti, jossa prosessisi kuuntelee säilön sisällä, ja portti, johon alusta reitittää liikenteen. Jos sovelluksesi lukee PORT-arvon ympäristöstä ja olet kovakoodannut Dockerfilessä jonnekin arvon 3000, nämä kaksi voivat poiketa toisistaan täysin huomaamatta.

Luotettava tapa on antaa alustan kertoa portti:

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

Aseta sitten palvelun portti kerran alustassa ja lopeta saman numeron ylläpito kahdessa paikassa.

3. Polku palauttaa jotain muuta kuin onnistumisen

Terveystarkistuksen polku täsmäytetään tarkasti, ja yllättävän moni epäonnistuminen johtuu uudelleenohjauksesta. Jos sovelluksesi ohjaa /healthz-polun osoitteeseen /healthz/ tai pakottaa HTTPS:n käyttöön 301-uudelleenohjauksella, vain 2xx-vastaukset onnistumiseksi tulkitseva tarkistin epäonnistuu joka kerta, vaikka selain seuraa uudelleenohjausta ja näyttää toimivan sivun.

Kolme erityistä ansaa:

  • Loppusulkeiden uudelleenohjaukset. /healthz/healthz/ on 301.
  • Pakotettu HTTPS. Sisäinen tarkistus saapuu yleensä tavallisena HTTP-liikenteenä loopback-osoitteeseen. Ehdoton HTTPS-uudelleenohjaus epäonnistuttaa sen.
  • Autentikointiväliohjelmisto. Ennen reititystä suoritettava yleinen autentikointivartija palauttaa myös terveystarkistuspolulle vastauksen 401.

Jätä terveystarkistuspolku nimenomaisesti autentikoinnin ja HTTPS-pakotuksen ulkopuolelle. Sen pitäisi olla ainoa tylsä reitti.

4. Tarkistus on nopeampi kuin cold start

Jos tarkistus epäonnistuu muutaman kerran ja onnistuu sitten tai epäonnistuu käyttöönoton aikana mutta onnistuu uudelleen yritettäessä, kyse on ajoituksesta eikä määrityksestä.

Tarvitsemasi budjetti ei ole yksi yritys — se on aikaväli × uudelleenyritykset. Sovellus, jolla kestää kaksitoista sekuntia muodostaa yhteys tietokantaan ja lämmittää välimuisti, tarvitsee kokonaisbudjetin, joka ylittää kaksitoista sekuntia. Muuten jokainen julkaisu epäonnistuu ja lopulta poistat portinvartijan käytöstä, jolloin ainoa rikkinäisen koontiversion ja käyttäjiesi välissä oleva suojaus katoaa.

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

Aseta aikakatkaisu pidemmäksi kuin hitaimman oikeutetun yksittäisen yrityksen kesto ja määritä uudelleenyritykset niin, että aikaväli × uudelleenyritykset ylittää selvästi hitaimman oikeutetun käynnistyksen. Mittaa käynnistysajan kesto arvailun sijaan — lokeissa on aikaleimat.

5. Sovellus ei oikeasti ole valmis

Viimeinen tapaus on se, jota varten tarkistus on olemassa: sovellus käynnistyi, ei saanut yhteyttä riippuvuuteen ja yrittää uudelleen. Se ei ole kaatunut, joten mikään ei käynnistä sitä uudelleen. Se ei pysty palvelemaan pyyntöjä, joten tarkistus epäonnistuu. Järjestelmä toimii täsmälleen suunnitellusti ja kertoo, ettei tälle julkaisulle pidä ohjata liikennettä.

Tämän erottaminen neljästä muusta tapauksesta onnistuu siitä, että sovelluksesi kirjasi pyynnön ja vastasi muulla kuin 2xx-vastauksella. Jos pyyntö näkyy lokeissa, syyt 1–3 on suljettu pois.

Vianmääritysjärjestys, joka säästää aikaa

# 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

Kohta 3 ratkaisee suurimman osan näistä tapauksista. Säilön sisältä suoritettava curl poistaa kaikki verkkoympäristöön liittyvät muuttujat kerralla: jos se palauttaa siellä 200:n ja alusta epäonnistuu edelleen, ongelma on osoitteessa tai portissa, ei sovelluksessa. Jos se palauttaa 301:n tai 401:n, olet löytänyt syyn koskematta alustaan lainkaan.

Miksi portinvartija kannattaa pitää käytössä

Neljännen epäonnistuneen käyttöönoton jälkeen on houkuttelevaa poistaa terveystarkistus käytöstä ja saada julkaisu tuotantoon. Kannattaa kuitenkin muistaa, minkä olet poistamassa käytöstä.

Dockupissa terveysportinvartija pitää rikkinäisen julkaisun poissa käyttäjiltäsi. Uusi versio rakennetaan ja käynnistetään samalla, kun nykyinen versio jatkaa palvelemista; liikenne siirtyy uudelle versiolle vasta, kun se vastaa. Kun poistat portinvartijan käytöstä, otat jälleen käyttöön tilanteen, jossa käynnistyvä mutta toimimaton säilö korvaa kunnossa olevan version.

Neljän julkaisun ajan epäonnistuva tarkistus on ärsyttävä. Ehdollisesti onnistuva tarkistus ei kuitenkaan pysäytä sitä käyttöönottoa, jolla on merkitystä.

Usein kysyttyjä kysymyksiä

Miksi terveystarkistus epäonnistuu, vaikka sovellus toimii paikallisesti? Lähes aina siksi, että säilö sitoutuu osoitteeseen 127.0.0.1 eikä osoitteeseen 0.0.0.0. Paikallisesti muodostat yhteyden saman loopback-osoitteen kautta, mutta säilön ulkopuolelta kyseiseen osoitteeseen ei pääse.

Pitäisikö terveystarkistuksen päätepisteen vaatia autentikointia? Ei. Jätä se yleisen autentikointiväliohjelmiston ulkopuolelle, tai tarkistin saa vastauksen 401 ja käyttöönotto epäonnistuu, vaikka sovellus toimii.

Mitä aikakatkaisua pitäisi käyttää? Pidempää kuin hitaimman oikeutetun yksittäisen yrityksen kestoa, ja määritä uudelleenyritykset kattamaan hitaimman oikeutetun cold startin. Selvitä käynnistysaika lokeista sen sijaan, että arvaisit.

Onko terveystarkistuksen poistaminen käytöstä turvallista julkaisun vapauttamiseksi? Se vapauttaa julkaisun ja poistaa suojauksen, joka estää rikkinäistä versiota vastaanottamasta liikennettä. Korjaa tarkistus sen sijaan — useimmissa tapauksissa syynä on sidontaosoite tai uudelleenohjaus, ja korjaus vie vain muutaman minuutin.