Docker Compose healthcheck कसे योग्य लिहावे
Docker Compose healthcheck exit code कसा तपासते, depends_on readiness का थांबवत नाही, आणि Postgres व app साठी विश्वासार्ह readiness checks कसे लिहायचे ते जाणून घ्या.
Docker Compose healthcheck प्रत्यक्षात काय करते
Docker Compose healthcheck ही एक command आहे, जी Docker ठरावीक कालांतराने container मध्ये चालवते. Docker तुमचे logs वाचत नाही, तुमचा port monitor करत नाही किंवा तुमची process list तपासत नाही. ते command चालवते, exit code वाचते आणि container वर एकच state साठवते: starting, healthy किंवा unhealthy. Exit code 0 म्हणजे healthy. इतर कोणताही exit code म्हणजे unhealthy. Exit code 2 हा Docker साठी राखीव आहे, त्यामुळे तो जाणीवपूर्वक कधीही return करू नका.
हीच संपूर्ण कार्यपद्धती आहे. जवळपास प्रत्येक healthcheck समस्या एकाच कारणामुळे निर्माण होते: तुम्ही लिहिलेली command तुम्हाला तपासायचे असलेले वेगळेच उत्तर देते. या मार्गदर्शकात तुम्हाला VPS वर compose file कशी लिहायची हे आधीच माहीत आहे असे गृहीत धरले आहे. Stack चुकीच्या क्रमाने सुरू होतो त्या टप्प्यापासून हा मार्गदर्शक पुढे जातो.
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: 30stest value चे दोन उपयुक्त प्रकार आहेत. CMD ने सुरू होणारी list command थेट चालवते, shell न वापरता. त्यामुळे pipes, && आणि variable expansion कार्य करत नाहीत. CMD-SHELL ने सुरू होणारी list उर्वरित भाग एकाच string म्हणून container मधील /bin/sh -c कडे पाठवते. Check साठी shell syntax आवश्यक असेल तेव्हा हा प्रकार वापरावा. साध्या string ला CMD-SHELL म्हणून हाताळले जाते. नेमक्या ["NONE"] घटकांची list वापरल्यास image च्या Dockerfile मधून आधीच जोडलेला healthcheck काढला जातो.
Check container मध्ये चालते. त्यामुळे त्यात नमूद केलेली प्रत्येक binary त्या image मध्ये उपलब्ध असणे आवश्यक आहे. हे आधी तपासा. कारण curl नसलेली slim image application log मध्ये कधीही न दिसणाऱ्या कारणामुळे container कायमचा unhealthy ठेवते. हे स्वतः चालवून तपासा:
docker compose exec api curl --versionBinary उपलब्ध नसल्यास OCI runtime exec failed: exec: "curl": executable file not found in $PATH: unknown मिळते. Alpine आधारित images मध्ये त्याऐवजी सहसा BusyBox wget उपलब्ध असते. त्यामुळे check असे होते: ["CMD", "wget", "-q", "-O", "-", "http://localhost:8080/healthz"].
interval, retries आणि start_period एकत्र कसे कार्य करतात
वेळेचे नियंत्रण करणाऱ्या पाच सेटिंग्ज आहेत. त्यांची डीफॉल्ट मूल्ये Compose कडून नव्हे, तर Docker Engine कडून येतात.
interval: कंटेनरचा start period संपल्यानंतर दोन तपासण्यांमधील अंतर. डीफॉल्ट 30s.timeout: तपासणीची एक फेरी पूर्ण होण्यासाठी अनुमत कमाल वेळ. ही वेळ संपल्यास Docker ती फेरी बंद करून अपयशी फेरी म्हणून मोजते. डीफॉल्ट 30s.retries: स्थितीunhealthyहोण्यासाठी आवश्यक सलग अपयशांची संख्या. डीफॉल्ट 3.start_period: कंटेनर सुरू झाल्यानंतरचा सवलतीचा कालावधी. डीफॉल्ट 0s.start_interval: start period दरम्यान तपासणी किती वेळाने चालवायची. डीफॉल्ट 5s. यासाठी Docker Engine 25.0 किंवा त्यानंतरची आवृत्ती आवश्यक आहे.
महत्त्वाचा नियम असा आहे: start period दरम्यान तपासणी अपयशी झाली तरी ती retries मध्ये मोजली जात नाही आणि कंटेनर starting स्थितीत राहतो. तपासणी पहिल्यांदा यशस्वी होताच कंटेनर healthy होतो आणि वापरायचा वेळ शिल्लक असला तरी start period तत्काळ संपतो. तपासणी अद्याप अपयशी असताना start period संपल्यास नेहमीची गणना सुरू होते. त्यानंतर कंटेनरला retries सलग अपयशे झाल्यावरच त्याला unhealthy म्हणून चिन्हांकित केले जाते.
त्यामुळे कंटेनर सुरू झाल्यापासून unhealthy होईपर्यंत लागणारा कमाल वेळ start_period अधिक retries गुणिले interval अधिक timeout इतका असतो. वरील फाइलमधील मूल्यांनुसार तो 30 अधिक 5 गुणिले 13, म्हणजे 95 सेकंद आहे. deploy timeout सेट करण्यापूर्वी ही संख्या लिहून ठेवा. 60 सेकंदांनंतर थांबणाऱ्या rollout ला हा कंटेनर अंतिम स्थितीपर्यंत पोहोचलेला कधीही दिसणार नाही.
येथे होणारी सामान्य चूक म्हणजे संथ सुरुवात भरून काढण्यासाठी retries वाढवणे. यामुळे एकदा समस्या सुटते, पण पुढे कायम त्रास होतो: सुरू होण्यासाठी 8 retries लागणारी सेवा उत्पादन वातावरणात काहीही लक्षात येण्यापूर्वी 8 सलग अपयशे सहन करते. त्याऐवजी start_period वापरा, कारण ते फक्त पहिल्या यशस्वी तपासणीपूर्वी लागू होते.
depends_on स्वतः काहीही हमी देत नाही
depends_on चे संक्षिप्त स्वरूप हा बहुतेक गोंधळाचा स्रोत आहे.
api:
depends_on:
- dbयाचा एकच अर्थ आहे: db container, api container च्या आधी सुरू करा. Compose container तयार होईपर्यंत आणि सुरू होईपर्यंत प्रतीक्षा करते. PostgreSQL चे प्रथमच होणारे प्रारंभिकीकरण पूर्ण होईपर्यंत ती प्रतीक्षा करत नाही. तसेच port 5432 वर connection स्वीकारले जाईपर्यंतही ती प्रतीक्षा करत नाही. तुमचे app सुमारे एक सेकंदानंतर सुरू होते, त्या वेळी अद्याप कोणीही ऐकत नसलेल्या port शी connect करण्याचा प्रयत्न करते आणि बंद होते. log मध्ये तुम्हाला Connection refused दिसते. Server सुरू झाला असेल पण अजून recovering अवस्थेत असेल, तर FATAL: the database system is starting up दिसते.
लोकांना प्रत्यक्षात हवे असलेले स्वरूप दीर्घ स्वरूप आहे:
api:
depends_on:
db:
condition: service_healthy
restart: true
migrate:
condition: service_completed_successfullycondition ला तीन values असतात. service_started हे संक्षिप्त स्वरूपासारखेच आहे. service_healthy dependent service ला dependency healthy असल्याचे कळवेपर्यंत थांबवते. हे तेव्हाच अर्थपूर्ण असते, जेव्हा त्या dependency मध्ये healthcheck परिभाषित केलेले असते. ते compose file मध्ये किंवा तिच्या image मध्ये असू शकते. service_completed_successfully one shot container साठी प्रतीक्षा करते, उदाहरणार्थ database migration साठी, आणि तो status 0 सह बंद होईपर्यंत थांबते.
condition च्या शेजारी आणखी दोन fields असतात. restart: true dependency service update केल्यानंतर या service ला restart करण्यास Compose ला सांगते. required: false उपलब्ध नसलेल्या dependency ला error ऐवजी warning म्हणून हाताळते.
आता लोकांना अडचणीत आणणारी मर्यादा पाहू. या conditions stack सुरू होताना तपासल्या जातात. त्या start ordering साठी आहेत; supervision rule नाहीत. Database पहाटे 3 वाजता restart झाला, तरी service_healthy पुन्हा तपासले जात नाही आणि ते पुन्हा पूर्ण करण्यासाठी तुमचे app restart केले जात नाही. तुमच्या application code ने स्वतःहून पुन्हा connect केले पाहिजे. docker compose up --no-deps api हे संपूर्ण mechanism जाणीवपूर्वक वगळते. docker start वापरून container थेट सुरू केल्यासही तेच होते.
प्रक्रिया अस्तित्वात आहे की नाही हे नव्हे, सज्जता तपासणारी तपासणी लिहा
pgrep nginx सारखी तपासणी प्रक्रिया-सारणीतील नोंद अस्तित्वात असल्याचे सिद्ध करते. सेवा विनंतीला उत्तर देऊ शकते की नाही, हे त्यातून सिद्ध होत नाही. वेब अनुप्रयोगाचा database pool बंद पडल्यानंतरही त्याचा listening socket बराच वेळ खुला राहू शकतो आणि संपूर्ण व्यत्ययादरम्यान प्रक्रिया तपासणी यशस्वी दिसत राहते.
Container कडून तो ज्या कार्यासाठी चालवला आहे ते कार्य करून घ्या:
- HTTP सेवेसाठी प्रत्यक्ष endpoint ला विनंती करा.
curl -fsSकोणत्याही 400 किंवा त्याहून अधिक status वर non zero exit करते, कारण-fवापरलेले आहे. त्यामुळे बिघडलेल्या अनुप्रयोगाकडून मिळणारा 500 हा अयशस्वी तपासणी मानला जातो. - PostgreSQL साठी
pg_isreadyवापरा. Server connections स्वीकारत असल्यास ते 0 exit करते, connections नाकारत असल्यास 1, अजिबात प्रतिसाद देत नसल्यास 2 आणि दिलेले parameters चुकीचे असल्यास 3 exit करते. - Redis साठी
redis-cli pingवापरा. तेPONGछापते आणि 0 exit करते. - MariaDB साठी अधिकृत image मध्ये
healthcheck.shscript समाविष्ट असते आणिhealthcheck.sh --connect --innodb_initializedहा त्याच्या maintainers ने दस्तऐवजीकरण केलेला प्रकार आहे.
pg_isready मध्ये एक महत्त्वाचा अडथळा आहे. रिकाम्या data directory सह पहिल्यांदा सुरू करताना अधिकृत postgres image चे initialization केवळ Unix socket वर listening करणाऱ्या तात्पुरत्या server विरुद्ध चालते. Host argument शिवाय pg_isready तो socket वापरते. त्यामुळे TCP port 5432 तुमच्या application साठी अद्याप बंद असतानाही ती "accepting connections" असे उत्तर देऊ शकते. तपासणीला TCP स्पष्टपणे निर्दिष्ट करा. त्यामुळे समस्या दूर होते, कारण तात्पुरता server त्या मार्गावर उत्तर देत नाही.
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दुप्पट dollar signs ही टंकलेखनातील चूक नाही. File वाचताना Compose स्वतः $VAR expand करते. त्यामुळे तुमच्या host environment मधील value तपासणीत स्थिरपणे समाविष्ट होऊ शकते. $$ त्याला एका $ मध्ये escape करते. त्यामुळे container मधील shell ते container च्या स्वतःच्या environment विरुद्ध expand करते.
योग्य क्रमाने सुरू होणारा postgres आणि app स्टॅक
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:स्टॅक सुरू करा आणि स्थितींमधील बदल पाहा:
docker compose up -d
docker compose psSTATUS स्तंभ चौकोनात आरोग्यस्थिती दाखवतो. आरोग्यपूर्ण जोडीतील दोन्ही ओळींमध्ये Up 41 seconds (healthy) दिसते. डेटाबेसचे initialization अद्याप सुरू असताना, db मध्ये Up 4 seconds (health: starting) दिसते आणि सूचीमध्ये api नसते, कारण Compose ने ते अद्याप तयार केलेले नसते.
तपासणी यशस्वी किंवा अयशस्वी का झाली हे पाहण्यासाठी health log वाचा:
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q db)"Docker शेवटचे काही निकाल जतन करते. प्रत्येक निकालामध्ये सुरुवातीची वेळ, समाप्तीची वेळ, ExitCode आणि command चे Output असते. जतन केलेले output संक्षिप्त केले जाते. त्यामुळे मोठ्या page body चे output देणाऱ्या तपासणीमुळे log entry निरुपयोगी ठरते. तपासण्या संक्षिप्त ठेवा.
कंटेनर unhealthy झाल्यावर Docker काय करते
काहीही नाही. हेच उत्तर बहुतेक लोकांना सर्वाधिक आश्चर्यचकित करते.
एकाच host वरील Docker Engine unhealthy कंटेनर पुन्हा सुरू करत नाही. restart: unless-stopped policy मुख्य process बंद झाल्यावर प्रतिसाद देते, परंतु unhealthy कंटेनर बंद झालेला नसतो. Compose त्याच्याकडे दुर्लक्ष करत असताना तो unhealthy स्थितीत आठवडाभर राहू शकतो. Swarm mode unhealthy tasks बदलते, परंतु एका server वरील साधा Compose stack तसे करत नाही.
यामुळे दोन व्यवहार्य पर्याय उरतात. Process बिघडल्याचे कळल्यावर तो बंद होईल अशी रचना करा, म्हणजे restart policy कडे कार्य करण्यासाठी काहीतरी असेल. किंवा बाहेरून स्थितीवर लक्ष ठेवा आणि त्यावर alert द्या. तुमचे healthcheck ज्या endpoint ला call करते त्याच endpoint वर Uptime Kuma monitor ठेवले, तर बिघडलेली dependency दोन्ही ठिकाणी दिसते आणि वापरकर्त्याकडून कळण्याऐवजी monitor कडून तिची माहिती मिळते. App पर्यंतचा traffic Traefik reverse proxy द्वारे पोहोचत असल्यास, proxy चे backend विषयीचे स्वतःचे निरीक्षण Docker health state पासून स्वतंत्र असते, हे लक्षात ठेवा. त्यामुळे एकाची माहिती दुसऱ्याची जागा घेत नाही.
कधीही healthy न होणारी तपासणी डीबग करणे
अगदी तीच कमांड, त्याच container मध्ये स्वतः चालवा आणि exit code पाहा:
docker compose exec api curl -fsS http://localhost:8080/healthz; echo "exit=$?"container अजूनही unhealthy असल्याचे दाखवत असताना येथे exit=0 दिसत असल्यास, तुमचे compose test तुम्ही नुकतेच टाइप केलेल्या मजकुरापेक्षा वेगळे आहे. सामान्यतः shell syntax आवश्यक असताना CMD वापरल्यामुळे असे होते.
उरलेल्या बहुतेक समस्यांचे कारण दोन चुका असतात. पहिली म्हणजे चुकीचा port. healthcheck container च्या आत चालते. त्यामुळे त्यात container port वापरला पाहिजे; प्रकाशित host port कधीही वापरू नका. ports: - "8080:3000" सह application 3000 वर ऐकते. अशावेळी http://localhost:8080 विरुद्ध केलेली तपासणी कायम अपयशी ठरते, जरी site browser मध्ये व्यवस्थित उघडत असली तरी. दुसरी म्हणजे चुकीचा host. तपासणीच्या आत localhost म्हणजे तोच container असतो. स्वतःची तपासणी करण्यासाठी हे योग्य आहे, पण शेजारील container तपासण्यासाठी चुकीचे आहे. त्यासाठी service name आवश्यक असते, उदाहरणार्थ db.
शेवटच्या एका प्रकरणाला स्वतंत्रपणे लक्षात घ्या: healthcheck यशस्वी होते, पण users ना errors दिसतात. endpoint कोणत्याही वास्तविक घटकाशी संपर्क न साधता static 200 परतवतो तेव्हा असे होते. database ची चौकशी न करणारा readiness endpoint database उपलब्ध नाही हे सांगू शकत नाही. त्यात एक स्वस्त, वास्तविक query चालवा.
FAQ
अवलंबित्वामध्ये डेटाबेस healthy असल्याचे नमूद केले असतानाही माझे app connect करण्यात अपयशी का ठरते?
कारण condition: service_healthy चे मूल्यमापन stack सुरू होताना एकदाच केले जाते. त्यानंतर ते कोणत्याही गोष्टीचे निरीक्षण करत नाही. डेटाबेस container नंतर पुन्हा सुरू झाल्यास, अट पुन्हा पूर्ण करण्यासाठी Compose तुमचे application restart करत नाही. त्यामुळे तुमच्या application code मध्ये स्वतःची reconnect आणि retry logic असणे आवश्यक आहे. docker start किंवा docker compose up --no-deps वापरून एकच container सुरू केल्यास ही अट काहीही करत नाही.
image मध्ये healthcheck आधीच परिभाषित असल्यास मला आणखी healthcheck आवश्यक आहे का?
सामान्यतः नाही. तो बदलणे अनेकदा मागे जाण्यासारखे ठरते, कारण त्या software साठी readiness चा अर्थ काय आहे हे image maintainer ला माहीत असते. तुमच्या setup साठी image मधील check चुकीचा असेल, तेव्हाच स्वतःचा check जोडा. उदाहरणार्थ, check ने तुम्ही बदललेल्या port ची तपासणी केली असल्यास. Image healthcheck बंद करण्यासाठी service वर test: ["NONE"] किंवा disable: true सेट करा.
healthcheck मध्ये curl वापरावे की wget?
image मध्ये आधीपासून उपलब्ध असलेले वापरा. त्यावर अवलंबून राहण्यापूर्वी docker compose exec <service> curl --version वापरून ते उपलब्ध असल्याची खात्री करा. अनेक Debian based images मध्ये दोन्हीपैकी एकही उपलब्ध नसतो. Alpine based images मध्ये BusyBox wget असतो. Software स्वतःचा client पुरवत असल्यास, केवळ healthcheck चालवण्यासाठी image मध्ये package जोडू नका. उदाहरणार्थ, pg_isready किंवा redis-cli उपलब्ध असल्यास ते वापरा.
unhealthy container आपोआप restart होतो का?
एकाच host वरील Docker Engine कडून नाही. Restart policies health state वर नव्हे, तर process बंद झाल्यावर प्रतिक्रिया देतात. त्यामुळे unhealthy container सुरूच राहतो आणि काहीतरी दुसरी कृती करेपर्यंत त्याची समस्या कायम राहते. Failure आढळल्यावर process बंद होईल अशी रचना करा किंवा state वर alert देणारा external monitor चालवा.
start_period किती असावा?
तुम्ही मोजलेल्या सर्वांत धीम्या वैध first start साठी पुरेसा, तसेच त्यावर काही अतिरिक्त margin असलेला असावा. रिकाम्या volume वर docker compose up वापरून वेळ मोजा, कारण database चा first start त्यानंतरच्या प्रत्येक start पेक्षा खूप धीमा असतो. start period खूप मोठा असल्यास पहिला unhealthy verdict फक्त उशिरा मिळतो. Retries ची संख्या खूप मोठी असल्यास container च्या संपूर्ण आयुष्यात check कमकुवत राहतो. ही अधिक गंभीर समस्या आहे.