Docker Compose healthcheck சரியாக இயங்குவது எப்படி
Docker Compose healthcheck எவ்வாறு மதிப்பிடப்படுகிறது, `depends_on` ஏன் readiness-ஐ காத்திருக்காது, Postgres மற்றும் app-க்கு சரியான checks எழுதுவது எப்படி என்பதை அறிக.
Docker Compose healthcheck உண்மையில் செய்யும் செயல்
Docker Compose healthcheck என்பது, Docker ஒரு timer அடிப்படையில் container-க்குள் இயக்கும் ஒரு command ஆகும். Docker உங்கள் logs-ஐ வாசிக்காது, port-ஐ கண்காணிக்காது, process list-ஐ ஆய்வு செய்யாது. அது command-ஐ இயக்கி, exit code-ஐ வாசித்து, container-ல் ஒரே ஒரு state-ஐ சேமிக்கும்: starting, healthy, அல்லது unhealthy. Exit code 0 என்பது healthy நிலையைக் குறிக்கும். வேறு எந்த exit code-மும் unhealthy நிலையைக் குறிக்கும். exit code 2-ஐ Docker தனிப்பயனாக ஒதுக்கியுள்ளது; எனவே அதை நோக்கமுடன் ஒருபோதும் return செய்ய வேண்டாம்.
இதுவே முழு mechanism. பெரும்பாலான healthcheck பிரச்சினைகளின் காரணம் ஒன்றே: நீங்கள் எழுதிய command, நீங்கள் கேட்க நினைத்த கேள்விக்கு பதிலளிக்கவில்லை; அதற்கு பதிலாக வேறு கேள்விக்கு பதிலளிக்கிறது. VPS-ல் compose file எழுதும் முறையை நீங்கள் ஏற்கனவே அறிந்திருக்கிறீர்கள் என்று இந்த வழிகாட்டி கருதுகிறது. Stack தவறான order-ல் தொடங்கும் இடத்திலிருந்து இது தொடர்கிறது.
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 தேவைப்படும் நேரங்களில் இதுவே பயன்படுத்த வேண்டிய வடிவம். Plain 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 எவ்வாறு இணைந்து செயல்படுகின்றன
Timing-ஐ கட்டுப்படுத்தும் 5 settings உள்ளன. அவற்றின் default values Compose-இலிருந்து அல்ல; Docker Engine-இலிருந்து பெறப்படுகின்றன.
interval: container-ன் start period முடிந்த பிறகு, இரண்டு checks-க்கு இடையிலான நேரம். Default 30s.timeout: ஒரு check-ன் ஒற்றை run முடிவதற்கான அதிகபட்ச நேரம். இதை மீறினால் Docker அந்த run-ஐ நிறுத்தி, failure எனக் கணக்கிடும். Default 30s.retries: stateunhealthyஆக மாறுவதற்கு முன் தொடர்ச்சியாக ஏற்பட வேண்டிய failures-ன் எண்ணிக்கை. Default 3.start_period: container தொடங்கிய பிறகு வழங்கப்படும் grace window. Default 0s.start_interval: start period-இல் check எவ்வளவு அடிக்கடி இயங்க வேண்டும் என்பதைக் குறிப்பிடும் setting. Default 5s. இதற்கு Docker Engine 25.0 அல்லது அதற்குப் புதிய version தேவை.
முக்கியமான விதி இதுதான்: start period-இல் failing check, retries-க்கு கணக்கிடப்படாது. Container starting state-லேயே இருக்கும். Check முதன்முறையாக வெற்றி பெற்றவுடன், container healthy ஆக மாறும்; start period உடனடியாக முடியும். Start period-ன் பெரும்பகுதி பயன்படுத்தப்படாமல் இருந்தாலும் இது மாறாது. Check தொடர்ந்து failing நிலையில் இருக்கும்போது start period முடிந்துவிட்டால், வழக்கமான countdown தொடங்கும். அதன் பிறகு container retries தொடர்ச்சியான failures-ஐ சந்தித்த பிறகே unhealthy என mark செய்யப்படும்.
எனவே container தொடங்கிய நேரத்திலிருந்து unhealthy நிலைக்கு செல்லும் worst-case time, start_period + retries × interval + timeout ஆகும். மேலே உள்ள file-ல் உள்ள values-ஐ பயன்படுத்தினால், அது 30 + 5 × 13, அதாவது 95 seconds. Deploy timeout அமைப்பதற்கு முன் இந்த எண்ணை குறித்துக்கொள்ளுங்கள். 60 seconds கழித்து நிறுத்தப்படும் rollout, இந்த container இறுதி நிலையை அடைவதை ஒருபோதும் காணாது.
இங்கே பொதுவாக செய்யப்படும் தவறு, slow start-ஐ சமாளிக்க retries-ன் மதிப்பை அதிகரிப்பதாகும். இது ஒரு முறை சரியாக வேலை செய்யலாம்; ஆனால் பின்னர் நிரந்தரமாக பாதிப்பை ஏற்படுத்தும். Boot ஆக 8 retries தேவைப்பட்ட service-க்கு, production-ல் ஏதேனும் notice வருவதற்கு முன் 8 தொடர்ச்சியான failures பொறுத்துக்கொள்ளப்படும். அதற்கு பதிலாக start_period-ஐ பயன்படுத்துங்கள். இது முதல் success ஏற்படும் முன் மட்டுமே செயல்படும்.
depends_on மட்டும் பயன்படுத்தினால் ஏன் எந்த உத்தரவாதமும் கிடையாது
depends_on-ன் short form தான் பெரும்பாலான குழப்பங்களுக்கு காரணம்.
api:
depends_on:
- dbஇதன் பொருள் ஒன்று மட்டுமே: api container-க்கு முன் db container-ஐ start செய்ய வேண்டும். Compose, container உருவாக்கப்பட்டு தொடங்கும் வரை காத்திருக்கும். PostgreSQL-ன் முதல் initialisation முடியும் வரை அது காத்திருக்காது. Port 5432 connection-ஐ ஏற்கும் வரைவும் அது காத்திருக்காது. உங்கள் app சுமார் ஒரு வினாடிக்குப் பிறகு start ஆகிறது. அந்த நேரத்தில் எதுவும் listening இல்லாத port-க்கு அது connect செய்ய முயல்கிறது. பின்னர் அது exit ஆகிறது. Log-ல் Connection refused அல்லது server up நிலையில் இருந்தாலும் இன்னும் recovering நிலையில் இருந்தால் FATAL: the database system is starting up என்று காணலாம்.
நடைமுறையில் மக்கள் விரும்புவது long form ஆகும்:
api:
depends_on:
db:
condition: service_healthy
restart: true
migrate:
condition: service_completed_successfullycondition-க்கு மூன்று values உள்ளன. service_started என்பது short form-ல் உள்ள அதே அமைப்பாகும். service_healthy, dependency healthy என்று report செய்யும் வரை dependent service-ஐ தொடங்காமல் வைத்திருக்கும். இது, அந்த dependency compose file-ல் அல்லது அதன் image-ல் healthcheck வரையறுக்கப்பட்டிருந்தால் மட்டுமே பொருள் கொண்டதாகும். service_completed_successfully, database migration போன்ற one shot container status 0 உடன் exit ஆகும் வரை காத்திருக்கும்.
condition-க்கு அருகில் மேலும் இரண்டு fields உள்ளன. Dependency service-ஐ update செய்த பிறகு இந்த service-ஐ restart செய்ய Compose-க்கு restart: true தெரிவிக்கிறது. Missing dependency ஏற்பட்டால் error ஆக நிறுத்தாமல் warning ஆகக் காட்ட required: false செய்கிறது.
இப்போது பலரைப் பாதிக்கும் வரம்பைப் பார்க்கலாம். இந்த conditions stack தொடங்கும் நேரத்தில் மட்டுமே மதிப்பிடப்படுகின்றன. இவை start ordering மட்டுமே; supervision rule அல்ல. Database அதிகாலை 3 மணிக்கு restart ஆனால், service_healthy மீண்டும் மதிப்பிடப்படாது. அதை மீண்டும் பூர்த்தி செய்வதற்காக உங்கள் app-ஐயும் restart செய்யாது. உங்கள் application code தானாக reconnect செய்ய வேண்டும். docker compose up --no-deps api வடிவமைப்பின்படி இந்த முழு mechanism-ஐத் தவிர்க்கிறது. docker start மூலம் container-ஐ நேரடியாக start செய்வதும் இதேபோல் இந்த mechanism-ஐத் தவிர்க்கிறது.
Process இருப்பதை அல்ல, readiness-ஐச் சோதிக்கும் check-ஐ எழுதவும்
pgrep nginx போன்ற check, process table-ல் ஒரு entry இருப்பதை மட்டுமே உறுதிப்படுத்தும். Service ஒரு request-க்கு பதிலளிக்குமா என்பதை அது உறுதிப்படுத்தாது. ஒரு web application, அதன் database pool செயலிழந்த பிறகும் listening socket-ஐ நீண்ட நேரம் திறந்த நிலையில் வைத்திருக்கலாம். அந்த முழு outage காலத்திலும் process check வெற்றியாகவே இருக்கும்.
Container செய்ய வேண்டிய உண்மையான பணியை அதனிடமே செய்யச் சொல்லுங்கள்:
- HTTP service-க்கு, உண்மையான endpoint-ஐ request செய்யவும்.
curl -fsS,-fகாரணமாக 400 அல்லது அதற்கு மேற்பட்ட எந்த status-க்கும் non zero நிலையில் முடியும். எனவே செயலிழந்த app வழங்கும் 500, failed check ஆகும். - PostgreSQL-க்கு,
pg_isreadyபயன்படுத்தவும். Server connections-ஐ ஏற்கும்போது இது 0-ஆகவும், நிராகரிக்கும்போது 1-ஆகவும், எந்தப் பதிலும் அளிக்காதபோது 2-ஆகவும், வழங்கிய parameters தவறாக இருக்கும்போது 3-ஆகவும் முடியும். - Redis-க்கு,
redis-cli pingபயன்படுத்தவும். இதுPONG-ஐ அச்சிட்டு 0-ஆக முடியும். - MariaDB-க்கு, official image-ல்
healthcheck.shscript வழங்கப்படுகிறது.healthcheck.sh --connect --innodb_initializedஎன்பது அதன் maintainers ஆவணப்படுத்தும் வடிவமாகும்.
pg_isready-ல் தெரிந்துகொள்ள வேண்டிய ஒரு trap உள்ளது. காலியான data directory-யுடன் முதன்முறையாகத் தொடங்கும்போது, official postgres image-ன் initialisation-ஐ temporary server-ல் இயக்குகிறது. அந்த server Unix socket-ல் மட்டும் listening செய்கிறது. Host argument இல்லாத pg_isready, அந்த socket-ஐப் பயன்படுத்தும். எனவே TCP port 5432 இன்னும் உங்கள் application-க்கு மூடப்பட்டிருந்தாலும், அது "accepting connections" என்று பதிலளிக்கலாம். Check-ஐ TCP-ஐத் தெளிவாகக் குறிக்கும் வகையில் அமைக்கவும். அப்போது பிரச்சினை நீங்கும். காரணம், temporary 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 typo அல்ல. File-ஐ வாசிக்கும்போது Compose தானாகவே $VAR-ஐ expand செய்யும். இதனால் உங்கள் host environment-ல் உள்ள value check-க்குள் பதியப்படும். $$, அதை ஒரு $-ஆக escape செய்கிறது. எனவே container-க்குள் இயங்கும் shell, container-ன் சொந்த environment-க்கு எதிராக அதை expand செய்யும்.
சரியான வரிசையில் தொடங்கும் postgres மற்றும் app stack
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:அதைத் தொடங்கி, states எவ்வாறு மாறுகின்றன என்பதைப் பார்க்கவும்:
docker compose up -d
docker compose psSTATUS column, brackets-க்குள் health state-ஐ காட்டுகிறது. ஆரோக்கியமான pair-ன் இரண்டு rows-லும் Up 41 seconds (healthy) என்று இருக்கும். Database இன்னும் initialising நிலையில் இருக்கும்போது, db என்பது Up 4 seconds (health: starting) என்று காட்டும். api list-ல் இருக்காது, ஏனெனில் Compose அதை இன்னும் உருவாக்கவில்லை.
ஒரு check ஏன் passed அல்லது failed என்பதை அறிய, health log-ஐப் பார்க்கவும்:
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q db)"Docker கடைசியாகப் பதிவான சில results-ஐ வைத்திருக்கும். ஒவ்வொரு result-லும் start time, end time, ஒரு ExitCode, மற்றும் command-ன் Output இருக்கும். சேமிக்கப்பட்ட output-ன் நீளம் வரையறுக்கப்பட்டதாக இருக்கும். ஆகவே, ஒரு check பெரிய page body-ஐ print செய்தால், log entry பயனற்றதாகிவிடும். Checks-ஐ சுருக்கமாக வைத்திருக்கவும்.
Container unhealthy நிலையில் மாறும்போது Docker என்ன செய்கிறது
எதுவும் செய்யாது. இதுதான் பெரும்பாலானவர்களை ஆச்சரியப்படுத்தும் பதில்.
ஒரே host-ல் இயங்கும் Docker Engine, unhealthy நிலையில் உள்ள container-ஐ restart செய்யாது. restart: unless-stopped policy, main process வெளியேறும் போது செயல்படும். ஆனால் unhealthy container வெளியேறியிருக்காது. Compose எந்த நடவடிக்கையும் எடுக்காமல், அது unhealthy நிலையில் ஒரு வாரம் இருக்கலாம். Swarm mode unhealthy tasks-ஐ மாற்றும். ஆனால் ஒரு server-ல் இயங்கும் சாதாரண Compose stack அவ்வாறு செய்யாது.
இதற்கு இரண்டு நடைமுறை விருப்பங்கள் உள்ளன. Process பழுதடைந்ததை அறிந்தவுடன் exit ஆகுமாறு அமைக்கலாம். அப்போது restart policy செயல்பட முடியும். அல்லது வெளிப்புறத்திலிருந்து state-ஐ monitor செய்து alert உருவாக்கலாம். Healthcheck அழைக்கும் அதே endpoint-க்கு Uptime Kuma monitor-ஐ அமைத்தால், பழுதடைந்த dependency இரண்டு இடங்களிலும் தெரியும். பயனர் தெரிவிப்பதற்கு முன்பே monitor மூலம் அதை அறியலாம். Traffic, Traefik reverse proxy வழியாக app-ஐ அடைந்தால், backend குறித்த proxy-ன் பார்வை Docker health state-இலிருந்து தனித்துவமானது என்பதை நினைவில் கொள்ளுங்கள். எனவே, ஒன்று மற்றொன்றை மாற்றாது.
ஒருபோதும் healthy நிலைக்கு வராத check-ஐ பிழைத்திருத்துதல்
அதே container-ல், சரியான command-ஐ நீங்களே இயக்கி, exit code-ஐப் பார்க்கவும்:
docker compose exec api curl -fsS http://localhost:8080/healthz; echo "exit=$?"Container இன்னும் unhealthy என்று தெரிவிக்கும் நிலையில் exit=0 இங்கே இருந்தால், நீங்கள் இப்போது type செய்த configuration-இலிருந்து உங்கள் compose test வேறுபடுகிறது. பொதுவாக shell syntax தேவைப்பட்ட இடத்தில் CMD பயன்படுத்தப்பட்டிருப்பதே காரணம்.
மீதமுள்ள பெரும்பாலான பிரச்சினைகளுக்கு இரண்டு தவறுகளே காரணம். முதலாவது தவறு port தொடர்பானது. Healthcheck, container-ன் உள்ளேயே இயங்குவதால், அது container port-ஐ மட்டுமே பயன்படுத்த வேண்டும்; வெளியிடப்பட்ட host port-ஐ ஒருபோதும் பயன்படுத்தக்கூடாது. ports: - "8080:3000" பயன்படுத்தும்போது application, 3000-ல் listen செய்கிறது. அப்போது http://localhost:8080-ஐச் சரிபார்க்கும் check என்றென்றும் தோல்வியடையும்; ஆனால் site browser-ல் சரியாக இயங்கும். இரண்டாவது தவறு host தொடர்பானது. Check-ன் உள்ளே localhost என்பது அதே container-ஐக் குறிக்கும். தன்னையே சரிபார்க்க இது சரியானது; அடுத்த service-ஐச் சரிபார்க்க இது தவறானது. அதற்கு db போன்ற service name-ஐப் பயன்படுத்த வேண்டும்.
இறுதியாக, தனியாகக் குறிப்பிட வேண்டிய ஒரு நிலை உள்ளது: Healthcheck வெற்றி பெறும், ஆனால் users-க்கு errors தெரியும். Endpoint, உண்மையான எந்தச் செயலையும் செய்யாமல் static 200-ஐத் திருப்பி அனுப்பும்போது இது நிகழ்கிறது. Database-ஐ query செய்யாத readiness endpoint, database செயலிழந்துவிட்டதைத் தெரிவிக்க முடியாது. குறைந்த செலவில் இயங்கும் ஒரு உண்மையான query-ஐ அது இயக்குமாறு அமைக்கவும்.
FAQ
depends_on database healthy எனக் காட்டினாலும், என் app ஏன் இன்னும் connect ஆகவில்லை?
ஏனெனில் condition: service_healthy என்பது stack தொடங்கும் நேரத்தில் ஒருமுறை மட்டுமே மதிப்பிடப்படுகிறது. அதன் பிறகு அது எதையும் கண்காணிக்காது. பின்னர் database container restart ஆனால், அந்த condition-ஐ மீண்டும் பூர்த்தி செய்ய Compose உங்கள் application-ஐ restart செய்யாது. எனவே உங்கள் application code-ல் சொந்த reconnect மற்றும் retry logic இருக்க வேண்டும். docker start அல்லது docker compose up --no-deps மூலம் single container-ஐ தொடங்கும்போதும் இந்த condition எந்தச் செயலையும் செய்யாது.
image ஏற்கனவே healthcheck வரையறுத்திருந்தால், எனக்கு healthcheck தேவையா?
பொதுவாக தேவையில்லை. அதை override செய்வது பல நேரங்களில் பின்னடைவை ஏற்படுத்தும், ஏனெனில் அந்த software-க்கு readiness என்பதன் பொருள் என்ன என்பதை image maintainer நன்றாக அறிந்திருப்பார். Image-ன் check உங்கள் setup-க்கு தவறாக இருக்கும்போது மட்டும் சொந்த healthcheck-ஐச் சேர்க்கவும். உதாரணமாக, நீங்கள் மாற்றிய port-ஐ அது probe செய்யும்போது இது தேவைப்படலாம். Image healthcheck-ஐ முடக்க, service-ல் test: ["NONE"] அல்லது disable: true அமைக்கவும்.
healthcheck-க்கு curl அல்லது wget பயன்படுத்த வேண்டுமா?
Image-ல் ஏற்கனவே உள்ளதைப் பயன்படுத்தவும். அதை நம்புவதற்கு முன் docker compose exec <service> curl --version மூலம் அது உள்ளதா என்பதை உறுதிப்படுத்தவும். Debian அடிப்படையிலான பல images-ல் இவ்விரண்டும் இருப்பதில்லை. Alpine அடிப்படையிலான images-ல் BusyBox wget இருக்கும். Software தன்னுடைய client-ஐ வழங்கும்போது, healthcheck இயக்குவதற்காக மட்டும் image-ல் package சேர்க்க வேண்டாம். இதற்கு pg_isready அல்லது redis-cli போன்ற client-களைப் பயன்படுத்தலாம்.
unhealthy container தானாக restart ஆகுமா?
ஒரே host-ல் Docker Engine இதைச் செய்யாது. Restart policies, health state-க்கு அல்ல, process வெளியேறுவதற்கு பதிலளிக்கின்றன. எனவே வேறு ஏதாவது செயல்படும் வரை unhealthy container இயங்கிக்கொண்டே இருக்கும்; அதன் பிரச்சினையும் தொடரும். Failure கண்டறியப்பட்டால் process வெளியேறும் வகையில் அமைக்கவும். அல்லது state குறித்து alert அனுப்பும் external monitor-ஐ இயக்கவும்.
start_period எவ்வளவு நேரமாக இருக்க வேண்டும்?
நீங்கள் அளவிட்ட slowest legitimate first start-க்கு போதுமான நேரத்தையும், அதற்கு மேலான ஒரு margin-ஐயும் வழங்கும் அளவாக இருக்க வேண்டும். Empty volume-ஐப் பயன்படுத்தி docker compose up மூலம் இதன் நேரத்தை அளவிடவும். ஏனெனில் database-ன் first start, அதற்குப் பிறகான ஒவ்வொரு start-ஐவிடவும் மிகவும் மெதுவாக இருக்கும். மிகவும் நீண்ட start period முதல் unhealthy verdict-ஐ மட்டும் தாமதப்படுத்தும். Retries எண்ணிக்கை மிகவும் அதிகமாக இருந்தால், container-ன் முழு lifetime-க்கும் check பலவீனமாகிவிடும். இதுவே மோசமான failure ஆகும்.