Deploy onnistui, mutta sivusto on alhaalla
Dashboard näyttää tilaksi running, mutta käyttäjät näkevät virheen. Opi, miksi deployn onnistuminen ja sovelluksen toimintakunto ovat eri asioita ja miten saat vihreän deployn tarkoittamaan, että sovellus todella vastaa pyyntöihin.
On olemassa tietynlainen huono aamu, joka alkaa vihreästä valintamerkistä. Deploy onnistui, mutta sivusto on alhaalla, dashboard näyttää tilaksi running, ja joku lähettää sinulle kuvakaappauksen 502-virheestä.
Tämä ei ole harvinainen poikkeustapaus. Se on ennakoitava seuraus siitä, että alusta raportoi yhtä asiaa ja mittaa toista. Tämä on tärkeää ymmärtää tarkasti, sillä ratkaisu ei ole ”tarkista asia huolellisemmin” — vaan sen muuttaminen, mitä sanan running annetaan tarkoittaa.
Kolme eri kysymystä, yksi tilavalo
Kun alusta ilmoittaa palvelun olevan käynnissä, se voi vastata mihin tahansa näistä kysymyksistä:
- Käynnistyikö container? Prosessi on olemassa eikä ole päättynyt.
- Onko portti auki? Jokin kuuntelee siinä portissa, josta alusta odottaa palvelua.
- Vastaako sovellus oikein? Pyyntö saa vastauksen, joka tarkoittaa sovelluksen olevan valmis toimimaan.
Nämä ovat täysin erilaisia takeita, ja useimmat tällaiset incidentit syntyvät siitä, että dashboard vastaa kysymykseen 1, vaikka sinä oletit sen vastaavan kysymykseen 3.
Node-prosessi, joka käynnistyy, ei saa yhteyttä tietokantaan ja jää retry-looppiin, täyttää kysymyksen 1 ehdot ikuisesti. Se ei ole kaatunut. Se ei tule koskaan käsittelemään pyyntöä. Container on ”running” kaikissa niissä merkityksissä, joista orchestrator välittää.
Katkos syntyy näiden kahden vaiheen väliin
Vaarallinen aikaikkuna on uuden version käynnistymisen ja sen välillä, että uusi versio pystyy toimimaan. Tämän aikaikkunan aikana naiivi alusta on jo siirtänyt liikenteen uudelle versiolle, koska se mittasi vain käynnistymistä.
Tilanne on tavallista crashia pahempi rollbackin kannalta. Crash loop on äänekäs: container sammuu, käynnistyy uudelleen, sammuu jälleen, ja alusta huomaa tilanteen lopulta. Käynnistyvä mutta jumiutuva sovellus on hiljainen. Mikään ei käynnisty uudelleen, mikään ei hälytä, ja aiemmin toiminut versio on yleensä jo purettu.
Tuo viimeinen kohta aiheuttaa todellisen vahingon. Vanha versio toimi. Se poistettiin, koska uusi versio käynnistyi, ja käynnistyminen tulkittiin virheellisesti toimimiseksi.
Miten todellinen health gate toimii
Ratkaisu on rakenteellinen, ei menettelytapaohje. Liikennettä ei pidä siirtää ennen kuin uusi versio on vastannut pyyntöön.
Dockupissa release toimii näin: uusi versio rakennetaan erillään, käynnistetään parhaillaan liikennettä palvelevan version rinnalle ja sille esitetään kysymys. Vasta kun se vastaa, domain ohjataan siihen. Jos se ei koskaan vastaa, release pysähtyy siihen ja aiempi versio jatkaa liikenteen palvelemista — dashboardisi ulkopuolella kukaan ei edes tiedä, että deployta yritettiin.
Siksi epäonnistunut deploy Dockupissa ei aiheuta käyttökatkoa. Vanhaa containeria ei poistettu olettaen, että uusi toimisi ongelmitta.
# The health gate is per-service configuration, not a platform default you inherit
dockup info my-project/my-api --json
Tulosteen healthCheck-lohko sisältää koko sopimuksen: mitä polkua pyydetään, kuinka kauan vastausta odotetaan, kuinka monta kertaa yritetään ja kuinka pitkä tauko yritysten välillä pidetään.
Määritä tarkistus vastaamaan kysymykseen 3
Health endpoint, joka palauttaa aina tilan 200, on pahempi kuin ei endpointia lainkaan, koska se muuttaa todellisen portinvartijan pelkäksi leimaksi. Tarkistuksen tarkoitus on epäonnistua, kun sovellus ei pysty tekemään työtään.
Hyödyllinen readiness endpoint tarkistaa asiat, joita ilman sovellus ei voi toimia:
// 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 })
}
})
Kaksi sääntöä tekevät tästä käytännössä toimivan:
Tarkista riippuvuudet, joita ilman et voi palvella pyyntöjä — älä mitään muuta. Jos sovelluksesi pystyy toimimaan rajoitetusti search indexin ollessa alhaalla, älä tee readiness-tarkistuksesta riippuvaista search indexistä — muuten estät deployt asian takia, joka ei aiheuta käyttökatkoa.
Pidä tarkistus kevyenä. Endpointia kutsutaan toistuvasti jokaisen releasen aikana. Jos readiness-tarkistus suorittaa kalliin kyselyn, aiheutat itse ylimääräisen kuormitusongelman.
Anna sille riittävästi aikaa, mutta älä rajattomasti
Kaksi asetusta ratkaisee, auttaako gate vai haittaako se:
- Yrityskohtaisen timeoutin on oltava pidempi kuin pisin mahdollinen normaali cold start. Sovellus, joka muodostaa yhteyden tietokantaan ja lämmittää cachen kahdeksassa sekunnissa, epäonnistuu joka kerta kolmen sekunnin tarkistuksessa. Silloin saatat ”korjata” tilanteen poistamalla gaten käytöstä — ja päädyt takaisin lähtötilanteeseen.
- Retry-yritysten on katettava koko käynnistymiseen kuluva aika, ei vain yhtä yritystä. Intervalli × retry-yritysten määrä on todellinen aikabudjetti.
Dockupissa nämä ovat healthCheckInterval, healthCheckTimeout ja healthCheckRetries, ja ne määritetään palvelukohtaisesti, koska Rails-monoliitti ja Go-sidecar eivät käynnisty samassa aikataulussa.
Kun sivusto on jo alhaalla
Jos luet tätä kesken incidentin, nopeimmin ratkaiseva etenemisjärjestys on seuraava:
- Tarkista, vastaako sovellus suoraan, domain ohittaen. Jos se vastaa portissaan mutta ei domainin kautta, kyseessä on routing-ongelma, ei sovellusongelma, joten lopeta koodin debuggaaminen.
- Lue runtime-logit, älä build-lokeja. Build onnistui — se on lähtökohta. Haluat tietää, mitä prosessi teki käynnistymisensä jälkeen.
- Tee rollback ennen diagnosointia. Diagnosointi on halvempaa, kun kukaan ei seuraa tilannetta.
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
Dockupissa rollback on vaihto eikä uusi build, koska aiempi versio on edelleen levyllä. Tämä on tärkeää aamuyöllä: nopein palautus on sellainen, jossa mitään ei tarvitse kääntää uudelleen.
Millainen kysymys alustalle kannattaa esittää
Kun valitset, missä ajat tuotantoa, tätä kannattaa testata tarkoituksella: deployaa sovellus, joka käynnistyy onnistuneesti mutta ei saa yhteyttä tietokantaansa. Katso, mitä dashboard näyttää.
Jos siinä lukee running, tiedät nyt tarkalleen, minkä arvoinen tuo sana on seuraavan incidentin aikana.
Usein kysytyt kysymykset
Miksi dashboard näyttää tilaksi running, vaikka sivusto on alhaalla? Koska ”running” tarkoittaa yleensä, että container-prosessi on olemassa — ei sitä, että sovellus pystyisi palvelemaan pyyntöä. Tietokantayhteyttä uudelleen yrittävä jumittunut prosessi täyttää tämän määritelmän loputtomasti.
Pitäisikö health checkin käyttää tietokantaa? Kyllä, jos sovellus ei pysty palvelemaan pyyntöjä ilman sitä. Tarkista aidosti tarvitsemasi riippuvuudet ja ohita ne, joiden toiminnasta voit joustaa.
Mitä eroa on liveness- ja readiness-tarkistuksilla? Liveness kysyy, pitäisikö prosessi käynnistää uudelleen. Readiness kysyy, voidaanko sille ohjata liikennettä. Tämän virheen estävä gate on readiness, ja sen on suoritettava tarkistus ennen liikenteen siirtämistä.
Miten estän virheellistä deployta kaatamasta sivustoa? Siirrä liikenne vasta, kun uusi versio vastaa oikeaan pyyntöön, ja pidä aiempi versio käytössä, kunnes vaihto on vahvistettu. Tällöin epäonnistunut release on release, jota ei koskaan tapahtunut, eikä käyttökatko.
