Jinsi ya kuandika Docker Compose healthcheck sahihi
Jifunze jinsi Docker Compose inavyotathmini hali ya container na kwa nini depends_on pekee haitoshi. Pata mbinu bora za kuandika readiness checks kwa Postgres na programu yako.
Kile ambacho Docker Compose healthcheck hufanya kwa hakika
Docker Compose healthcheck ni amri moja ambayo Docker huiendesha ndani ya container kwa kutumia kipima muda. Docker haisomi logi zako, haifuatilii port yako, wala haikagui orodha ya process zako. Inaendesha amri hiyo, inasoma exit code, na kuhifadhi hali moja kwenye container: starting, healthy, au unhealthy. Exit code 0 inamaanisha kuwa huduma ni nzima (healthy). Exit code nyingine yoyote inamaanisha kuwa huduma si nzima (unhealthy), na exit code 2 imetengwa na Docker, kwa hivyo usiitumie kwa makusudi.
Huo ndio utaratibu mzima. Karibu kila tatizo la healthcheck ni tatizo lilelile: amri uliyoandika inajibu swali tofauti na lile ulilokusudia kuuliza. Mwongozo huu unachukulia kuwa tayari unajua jinsi ya kuandika faili ya compose kwenye VPS, na unaendelea pale ambapo stack inaanza kwa mpangilio usio sahihi.
services:
api:
image: ghcr.io/example/api:1.4.0
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/healthz"]
interval: 10s
timeout: 3s
retries: 5
start_period: 30sThamani ya test ina aina mbili muhimu. Orodha inayoanza na CMD huendesha amri moja kwa moja, bila shell, kwa hivyo pipes na && na upanuzi wa variable havifanyi kazi. Orodha inayoanza na CMD-SHELL hupitisha sehemu iliyobaki kama string moja kwenda kwa /bin/sh -c ndani ya container, jambo ambalo unahitaji wakati wowote check inapohitaji syntax ya shell. String ya kawaida huchukuliwa kama CMD-SHELL. Orodha ya ["NONE"] pekee huondoa healthcheck ambayo image ilikuwa nayo kupitia Dockerfile yake.
Check huendeshwa ndani ya container, kwa hivyo kila binary inayotajwa lazima iwepo kwenye image hiyo. Thibitisha hilo kwanza, kwa sababu image ndogo isiyo na curl hutengeneza container ambayo ni unhealthy daima kwa sababu isiyoonekana kwenye logi ya programu. Ijaribu kwa mkono:
docker compose exec api curl --versionBinary inayokosekana hujibu kwa OCI runtime exec failed: exec: "curl": executable file not found in $PATH: unknown. Image zinazotegemea Alpine kwa kawaida huwa na BusyBox wget badala yake, kwa hivyo check inakuwa ["CMD", "wget", "-q", "-O", "-", "http://localhost:8080/healthz"].
Jinsi interval, retries na start_period vinavyofanya kazi pamoja
Mipangilio mitano hudhibiti muda. Thamani zake za awali hutoka kwenye Docker Engine, si kwenye Compose.
interval: muda kati ya majaribio mawili baada ya container kumaliza muda wake wa kuanza (start period). Thamani ya awali ni 30s.timeout: muda ambao jaribio moja linaweza kuchukua kabla Docker halijalisitisha na kulihesabu kama limefeli. Thamani ya awali ni 30s.retries: idadi ya kufeli mfululizo kunakohitajika kabla ya hali ya container kubadilika na kuwaunhealthy. Thamani ya awali ni 3.start_period: muda wa ziada wa kusubiri baada ya container kuanza. Thamani ya awali ni 0s.start_interval: marudio ya jaribio wakati wa start period. Thamani ya awali ni 5s, na inahitaji Docker Engine 25.0 au mpya zaidi.
Kanuni muhimu ni hii: wakati wa start period, jaribio linalofeli halijahesabiwa kuelekea retries, na container inabaki katika hali ya starting. Mara ya kwanza jaribio linapofaulu, container inakuwa healthy na start period inaisha mara moja, hata kama muda mwingi ulikuwa haujatumika. Ikiwa start period inaisha wakati jaribio bado linafeli, hesabu ya kawaida huanza, na container inahitaji retries kufeli mfululizo kabla ya kuwekwa alama ya unhealthy.
Kwa hiyo, muda mbaya zaidi kuanzia container kuanza hadi kuwa unhealthy ni start_period pamoja na retries kuzidishwa kwa interval pamoja na timeout. Kwa kutumia thamani zilizopo kwenye faili hapo juu, hiyo ni 30 pamoja na 5 mara 13, ambayo ni sekunde 95. Andika namba hiyo kabla ya kuweka deploy timeout, kwa sababu rollout inayokata tamaa baada ya sekunde 60 haitawahi kuona container hii ikifikia hali ya mwisho.
Kosa la kawaida hapa ni kuongeza retries ili kufidia kuanza kwa polepole. Hilo hufanya kazi mara moja kisha huleta madhara ya kudumu: huduma iliyohitaji retries 8 ili kuwaka sasa itavumilia kufeli 8 mfululizo katika production kabla ya mfumo kugundua tatizo. Tumia start_period badala yake, kwa sababu inatumika tu kabla ya mafanikio ya kwanza.
Kwa nini depends_on pekee haitoi uhakikisho wowote
Muundo mfupi wa depends_on ndio chanzo cha mkanganyiko mwingi.
api:
depends_on:
- dbHii inamaanisha jambo moja: anzisha kontena la db kabla ya kontena la api. Compose husubiri kontena kuundwa na kuanzishwa. Haisubiri PostgreSQL kumaliza uanzishaji wake wa mara ya kwanza, na haisubiri port 5432 kukubali muunganisho. Programu yako huanza sekunde moja baadaye, inajaribu kuunganisha kwenye port ambayo bado haijasikiliza, na kisha inajifunga. Kwenye logi utaona Connection refused, au FATAL: the database system is starting up wakati seva imewaka lakini bado inajirekebisha.
Muundo mrefu ndio ambao watu wanahitaji kihalisi:
api:
depends_on:
db:
condition: service_healthy
restart: true
migrate:
condition: service_completed_successfullycondition ina thamani tatu. service_started ni sawa na muundo mfupi. service_healthy huizuia huduma tegemezi hadi pale tegemezi hiyo itakaporipoti kuwa iko "healthy", jambo ambalo lina maana tu ikiwa tegemezi hiyo imefafanua healthcheck, iwe kwenye faili ya compose au kwenye image yake. service_completed_successfully husubiri kontena la aina ya "one shot", kama vile migration ya database, imalize kazi yake kwa status 0.
Sehemu mbili za ziada zipo kando ya condition. restart: true huiambia Compose kuanzisha upya huduma hii baada ya kusasisha huduma tegemezi. required: false hupunguza uzito wa tegemezi inayokosekana kutoka kuwa kosa (error) hadi kuwa onyo (warning).
Sasa, kikwazo kinachowatatiza watu wengi. Masharti haya hutathminiwa wakati stack inapoanza. Hii ni mpangilio wa kuanza (start ordering), si sheria ya usimamizi (supervision rule). Ikiwa database itaanza upya saa tisa usiku, hakuna kinachotathmini upya service_healthy na hakuna kinachozimisha na kuwasha upya programu yako ili kutimiza sharti hilo tena. Nambari ya programu yako bado lazima iwe na uwezo wa kuunganisha upya yenyewe. docker compose up --no-deps api huruka utaratibu huu mzima kwa makusudi, na vivyo hivyo kuanzisha kontena moja kwa moja kwa kutumia docker start.
Andika ukaguzi unaopima utayari, si kuwepo kwa mchakato
Ukaguzi kama pgrep nginx unathibitisha tu kuwa mchakato upo kwenye orodha ya mifumo. Haukuthibitishi kama huduma inaweza kujibu ombi. Programu ya wavuti inaweza kuacha socket yake wazi muda mrefu baada ya pool yake ya database kufa, na ukaguzi wa mchakato utaendelea kuonyesha kijani wakati wote wa hitilafu hiyo.
Iambie container ifanye kazi ambayo imekusudiwa kuifanya:
- Kwa huduma ya HTTP, omba endpoint halisi.
curl -fsSinatoka kwa exit code isiyo sifuri kwa status yoyote ya 400 au zaidi kwa sababu ya-f, kwa hivyo status 500 kutoka kwa programu iliyoharibika ni ukaguzi ulioshindwa. - Kwa PostgreSQL, tumia
pg_isready, ambayo inatoa exit code 0 wakati seva inakubali miunganisho, 1 wakati inakataa, 2 wakati haijibu kabisa, na 3 wakati vigezo ulivyoweka si sahihi. - Kwa Redis, tumia
redis-cli ping, ambayo inachapishaPONGna kutoka kwa exit code 0. - Kwa MariaDB, image rasmi inakuja na script ya
healthcheck.sh, nahealthcheck.sh --connect --innodb_initializedndiyo njia ambayo watunza programu hiyo wameielekeza.
pg_isready ina mtego mmoja unaopaswa kuujua. Katika kuanza kwake kwa mara ya kwanza na saraka ya data tupu, image rasmi ya postgres huendesha uanzishaji wake dhidi ya seva ya muda inayosikiliza kwenye Unix socket pekee. pg_isready bila hoja ya host hutumia socket hiyo, kwa hivyo inaweza kujibu "inakubali miunganisho" wakati TCP port 5432 bado imefungwa kwa programu yako. Elekeza ukaguzi kwenye TCP moja kwa moja na tatizo litaisha, kwa sababu seva ya muda haijibu huko.
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
start_period: 30sAlama mbili za dola si kosa la uchapaji. Compose inapanua $VAR yenyewe wakati wa kusoma faili, jambo ambalo lingeingiza thamani kutoka kwa host environment yako kwenye ukaguzi. $$ inafanya escape hadi kuwa $ moja, kwa hivyo shell iliyo ndani ya container inapanua thamani hiyo dhidi ya mazingira ya container yenyewe.
Stack ya postgres na app inayowaka kwa mpangilio sahihi
services:
db:
image: postgres:17.5
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in .env}
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
start_period: 30s
restart: unless-stopped
api:
image: ghcr.io/example/api:1.4.0
environment:
DATABASE_URL: postgres://appuser:${DB_PASSWORD}@db:5432/appdb
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/healthz"]
interval: 10s
timeout: 3s
retries: 5
start_period: 30s
ports:
- "127.0.0.1:8080:8080"
restart: unless-stopped
volumes:
pgdata:Iwashe na uangalie mabadiliko ya hali:
docker compose up -d
docker compose psSafu ya STATUS inaonyesha hali ya afya kwenye mabano. Jozi iliyo salama huonyesha Up 41 seconds (healthy) kwenye safu zote mbili. Wakati database bado inajiandaa, db huonyesha Up 4 seconds (health: starting) na api haipo kwenye orodha, kwa sababu Compose haijaiunda bado.
Ili kuona kwa nini ukaguzi umefaulu au kufeli, soma logi ya afya:
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q db)"Docker huhifadhi matokeo machache ya mwisho, kila moja ikiwa na muda wa kuanza, muda wa kumaliza, ExitCode na Output ya amri husika. Matokeo yaliyohifadhiwa hukatwa, kwa hivyo ukaguzi unaochapisha mwili mkubwa wa ukurasa utakupa logi isiyo na maana. Weka ukaguzi uwe wa kimya.
Nini Docker hufanya wakati container inapokuwa na hali mbaya (unhealthy)
Hakuna. Hili ndilo jibu linalowashangaza watu wengi.
Docker Engine kwenye seva moja haianzishi upya container iliyo na hali mbaya. Sera ya restart: unless-stopped hujibu mchakato mkuu (main process) unapoacha kufanya kazi, na container iliyo na hali mbaya haijaacha kufanya kazi. Inaweza kukaa katika hali ya unhealthy kwa wiki nzima huku Compose ikiwa imeiacha. Swarm mode hubadilisha tasks zenye hali mbaya, lakini stack ya kawaida ya Compose kwenye seva moja haifanyi hivyo.
Hiyo inakuacha na chaguo mbili za kweli. Fanya mchakato uache kufanya kazi pale unapojua kuwa umeharibika, ili sera ya kuanzisha upya (restart policy) iwe na kitu cha kufanyia kazi. Au fuatilia hali hiyo kutoka nje na utoe tahadhari. Kuelekeza monitor ya Uptime Kuma kwenye endpoint ileile ambayo healthcheck yako inaita, inamaanisha kuwa dependency iliyoharibika itaonekana katika maeneo yote mawili, na utapata taarifa kutoka kwa monitor badala ya kusubiri mtumiaji akwambie. Ikiwa trafiki inafika kwenye app kupitia reverse proxy ya Traefik, kumbuka kuwa mtazamo wa proxy yenyewe kuhusu backend ni tofauti na hali ya afya ya Docker, kwa hivyo moja haichukui nafasi ya nyingine.
Utatuzi wa hundi isiyokuwa na hali ya afya
Tekeleza amri hiyo hiyo mwenyewe, ndani ya container ileile, na uangalie exit code:
docker compose exec api curl -fsS http://localhost:8080/healthz; echo "exit=$?"exit=0 hapa wakati container bado inaripoti kuwa haina afya inamaanisha kuwa test yako ya compose inatofautiana na ulichoandika sasa hivi, mara nyingi kwa sababu CMD ilitumika mahali ambapo syntax ya shell ilihitajika.
Makosa mawili ndiyo chanzo cha matatizo mengi mengine. La kwanza ni port isiyo sahihi. Healthcheck hufanyika ndani ya container, kwa hivyo lazima itumie port ya container, si port ya host iliyochapishwa. Kwa ports: - "8080:3000", programu husikiliza kwenye 3000, na hundi dhidi ya http://localhost:8080 itafeli milele wakati tovuti inafanya kazi vizuri kwenye kivinjari. La pili ni host isiyo sahihi. Ndani ya hundi, localhost ni container ileile, ambayo ni sahihi kwa kujikagua yenyewe lakini si sahihi kwa kukagua jirani, ambapo unahitaji jina la huduma, kwa mfano db.
Kisa kimoja cha mwisho kinastahili kutajwa: healthcheck inafaulu wakati watumiaji wanaona makosa. Hilo hutokea wakati endpoint inarudisha 200 ya kudumu bila kugusa kitu chochote cha kweli. Readiness endpoint ambayo haiulizi database kamwe haiwezi kukuambia kuwa database imepotea. Ifanye itekeleze query moja rahisi ya kweli.
FAQ
Kwa nini programu yangu bado inashindwa kuunganisha wakati depends_on inasema database iko sawa?
Kwa sababu condition: service_healthy hutathminiwa mara moja tu, wakati stack inapoanza. Haifuatilii chochote baada ya hapo. Ikiwa container ya database itaanza upya baadaye, Compose haianzishi upya programu yako ili kutimiza sharti hilo tena, kwa hivyo code ya programu yako inahitaji mantiki yake yenyewe ya kuunganisha tena na kujaribu tena. Sharti hili pia halifanyi kazi unapoanzisha container moja kwa kutumia docker start au docker compose up --no-deps.
Je, ninahitaji healthcheck ikiwa image tayari imeshaifafanua?
Kwa kawaida si lazima, na kuibatilisha mara nyingi ni hatua ya kurudi nyuma, kwa sababu mtunza image anajua nini maana ya utayari kwa programu hiyo. Ongeza yako mwenyewe pale tu ambapo ukaguzi wa image si sahihi kwa usanidi wako, kwa mfano wakati unapima port uliyoihamisha. Ili kuzima healthcheck ya image, weka test: ["NONE"] au disable: true kwenye huduma hiyo.
Je, healthcheck inapaswa kutumia curl au wget?
Tumia kile ambacho tayari kipo kwenye image, na ukithibitishe kwa docker compose exec <service> curl --version kabla ya kukitegemea. Image nyingi zinazotokana na Debian hazina vyote viwili. Image zinazotokana na Alpine zina BusyBox wget. Usiongeze kifurushi kwenye image ili tu kufanya healthcheck wakati programu hiyo inakuja na client yake yenyewe, kama vile pg_isready au redis-cli.
Je, container isiyo na afya (unhealthy) huanzishwa upya kiotomatiki?
Si kwa Docker Engine kwenye seva moja. Sera za kuanzisha upya (restart policies) hujibu mchakato unapoacha kufanya kazi, si hali ya afya, kwa hivyo container isiyo na afya inabaki ikiwa imewashwa na ikiwa imevunjika hadi kitu kingine kishughulike nayo. Aidha fanya mchakato uache kufanya kazi unapotambua hitilafu, au endesha kifuatiliaji cha nje kinachotoa tahadhari kuhusu hali hiyo.
start_period inapaswa kuwa ndefu kiasi gani?
Iwe ndefu kutosha kwa ajili ya kuanza kwa mara ya kwanza kwa njia halali na polepole zaidi uliyopima, pamoja na ziada kidogo. Ipimie muda kwa kutumia docker compose up dhidi ya volume tupu, kwa sababu kuanza kwa mara ya kwanza kwa database ni polepole zaidi kuliko kila kuanza kunakofuata. Muda wa kuanza (start period) ambao ni mrefu sana huchelewesha tu uamuzi wa kwanza wa unhealthy. Majaribio mengi sana ya kurudia hudhoofisha ukaguzi kwa maisha yote ya container, jambo ambalo ni hitilafu mbaya zaidi.