توی این درس یاد میگیری چرا «کانتینر در حال اجرا است» یعنی هنوز «سرویس آماده است» نیست، و چطور با HEALTHCHECK (بررسی سلامت) این فاصله را اندازه بگیری. وضعیتهای starting، healthy و unhealthy را با docker inspect --format '{{.State.Health.Status}}' میخوانی، با depends_on همراه condition: service_healthy میگویی هر سرویس فقط وقتی شروع شود که وابستگیاش واقعاً آماده است، و با restart policy رفتار پس از خطا را تنظیم میکنی. در تمرین اصلی اپ را طوری مینویسی که منتظر آماده شدن دیتابیس بماند.
مسئله: «روشن» با «آماده» فرق دارد
Section titled “مسئله: «روشن» با «آماده» فرق دارد”دیتابیس یک کانتینر است. داکر وقتی پروسهی اصلیاش شروع شد میگوید Up؛ ولی دیتابیس ممکن است ده ثانیه دیگر مشغول ساختن فایلها و بازیابی تراکنشها باشد و هنوز به هیچ اتصالی جواب ندهد. اگر اپ در همان لحظه بالا بیاید و وصل شود، با «Connection refused» میمیرد.
تشبیه: پرسیدن «نفس میکشی؟»
Section titled “تشبیه: پرسیدن «نفس میکشی؟»”کانتینر مثل آدمی است که چشمش باز است (پروسه روشن است). HEALTHCHECK مثل این است که هر چند ثانیه یک پرستار بیاید و بپرسد «میتوانی جواب بدهی؟». اگر چند بار پشت سر هم جواب نداد، برچسب «ناسالم» میخورد. دیگران (مثل اپ شما) میتوانند منتظر برچسب «سالم» بمانند.
سه وضعیت سلامت
Section titled “سه وضعیت سلامت”دستور سلامت (test) داخل خود کانتینر اجرا میشود. کد خروج ۰ یعنی سالم، ۱ یعنی ناسالم.
مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: healthcheck روی docker run
Section titled “مثال ۱: healthcheck روی docker run”docker run -d --name lx-hc1 \ --health-cmd 'wget -qO /dev/null http://localhost/ || exit 1' \ --health-interval 2s --health-timeout 2s --health-retries 3 \ nginx:alpine >/dev/nullecho "فوراً: $(docker inspect --format '{{.State.Health.Status}}' lx-hc1)"sleep 6echo "بعد از ۶ث: $(docker inspect --format '{{.State.Health.Status}}' lx-hc1)"docker ps --filter name=lx-hc1 --format '{{.Names}}: {{.Status}}'فوراً: startingبعد از ۶ث: healthylx-hc1: Up 6 seconds (healthy)بلافاصله starting است (هنوز اولین بررسی انجام نشده)، بعد از چند ثانیه healthy. در docker ps هم داخل ستون Status مینویسد (healthy). دستور سلامت wget -qO /dev/null http://localhost/ است: اگر nginx جواب بدهد exit 0، وگرنه exit 1.
کد خروج کدام یعنی «سالم»؟
۰ سالم، ۱ ناسالم (۲ رزرو است).
مثال ۲: unhealthy شدن و بازیابی
Section titled “مثال ۲: unhealthy شدن و بازیابی”docker run -d --name lx-hc2 \ --health-cmd 'test -f /ready' --health-interval 1s --health-retries 2 \ alpine sleep 300 >/dev/nullsleep 5echo "وضعیت سلامت: $(docker inspect --format '{{.State.Health.Status}}' lx-hc2)"echo "وضعیت کانتینر: $(docker inspect --format '{{.State.Status}}' lx-hc2)"docker exec lx-hc2 touch /readysleep 3echo "بعد از ساخت /ready: $(docker inspect --format '{{.State.Health.Status}}' lx-hc2)"وضعیت سلامت: unhealthyوضعیت کانتینر: runningبعد از ساخت /ready: healthyفایل /ready نبود، پس دو بار شکست خورد و unhealthy شد. اما کانتینر هنوز running است: داکر خودش کانتینر ناسالم را نمیکشد و ریستارت نمیکند (فقط برچسب میزند). وقتی فایل ساخته شد، با یک موفقیت دوباره healthy شد.
مثال ۳: لاگ بررسیها
Section titled “مثال ۳: لاگ بررسیها”docker inspect --format '{{range .State.Health.Log}}exit={{.ExitCode}} {{end}}' lx-hc2 | cut -c1-80docker inspect --format '{{json .Config.Healthcheck}}' lx-hc2exit=1 exit=1 exit=1 exit=0 exit=0{"Test":["CMD-SHELL","test -f /ready"],"Interval":1000000000,"Retries":2}آخرین چند نتیجهی بررسی (کد خروج هر کدام) در State.Health.Log است؛ برای عیبیابی «چرا ناسالم است؟» همین را (همراه Output) ببین. و تنظیمات healthcheck در Config.Healthcheck (زمانها به نانوثانیه: 1000000000 = ۱ ثانیه).
مثال ۴: HEALTHCHECK داخل Dockerfile
Section titled “مثال ۴: HEALTHCHECK داخل Dockerfile”FROM alpineHEALTHCHECK --interval=2s --timeout=2s --retries=2 --start-period=1s \ CMD test -f /ready || exit 1CMD ["sleep", "300"]docker build -q -t lx-hcimg hcimg >/dev/nulldocker run -d --name lx-hc3 lx-hcimg >/dev/nullsleep 6echo "اپ داخل image: $(docker inspect --format '{{.State.Health.Status}}' lx-hc3)"docker run -d --name lx-hc4 --no-healthcheck lx-hcimg >/dev/nullsleep 1echo "با --no-healthcheck: $(docker inspect --format '{{.State.Health}}' lx-hc4)"اپ داخل image: unhealthyبا --no-healthcheck: <nil>وقتی HEALTHCHECK را در Dockerfile بنویسی، هر کانتینری از آن image با آن بررسی بالا میآید. میتوانی موقع اجرا با --health-cmd عوضش کنی یا با --no-healthcheck خاموشش کنی (کانتینر چهارم هیچ وضعیت سلامتی ندارد: <nil>).
گزینه در HEALTHCHECK |
پیشفرض | معنی |
|---|---|---|
--interval |
۳۰s | فاصلهی بین بررسیها |
--timeout |
۳۰s | بیشترین زمان یک بررسی؛ بیشتر شد = شکست |
--retries |
۳ | چند شکست پشتسرهم تا unhealthy |
--start-period |
۰s | مهلت راهاندازی؛ شکستها در آن شمرده نمیشوند |
مثال ۵: بدون شرط، اپ زودتر از دیتابیس شروع میشود
Section titled “مثال ۵: بدون شرط، اپ زودتر از دیتابیس شروع میشود”دیتابیس ما یک Redis است که عمداً ۶ ثانیه دیر بالا میآید (مثل یک دیتابیس سنگین). اپ ما فقط به آن ping میزند.
name: lxhcn
services: db: image: redis:alpine command: sh -c "sleep 6; exec redis-server" app: image: redis:alpine depends_on: - db command: sh -c 'redis-cli -h db ping'depends_on: [db] (شکل ساده) فقط میگوید «کانتینر db را زودتر شروع کن»، نه اینکه صبر کن آماده شود:
cd hcndocker compose up -d >/dev/null 2>&1sleep 2docker compose logs app 2>&1 | sed -E 's/^[^|]+\| //'docker compose down -v >/dev/null 2>&1Could not connect to Redis at db:6379: Connection refusedاپ رسید، دیتابیس هنوز جواب نمیداد (Connection refused) و اپ شکست خورد. همان مسابقهی زمانی مسئلهی بالا.
مثال ۶: condition: service_healthy
Section titled “مثال ۶: condition: service_healthy”name: lxhc
services: db: image: redis:alpine command: sh -c "sleep 6; exec redis-server" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 2s timeout: 2s retries: 5 start_period: 3s app: image: redis:alpine depends_on: db: condition: service_healthy command: sh -c 'redis-cli -h db ping'حالا دیتابیس healthcheck دارد و اپ منتظر healthy شدن آن میماند:
cd hct0=$(date +%s)docker compose up -d >/dev/null 2>&1t1=$(date +%s)[ $((t1 - t0)) -ge 5 ] && echo "up صبر کرد تا db سالم شود (بیش از ۵ ثانیه)"docker inspect --format 'db: {{.State.Health.Status}}' lxhc-db-1sleep 1docker compose logs app 2>&1 | sed -E 's/^[^|]+\| //'docker compose down -v >/dev/null 2>&1up صبر کرد تا db سالم شود (بیش از ۵ ثانیه)db: healthyPONGup تا سالم شدن دیتابیس صبر کرد، بعد اپ شروع شد و PONG گرفت. بدون هیچ sleep و حدسزدن. بخش condition سه مقدار دارد:
| condition | یعنی |
|---|---|
service_started |
(پیشفرض) کانتینر شروع شده باشد |
service_healthy |
healthcheck آن سرویس healthy باشد |
service_completed_successfully |
سرویس تمام شده و با کد ۰ خارج شده باشد (مثلاً migration) |
depends_on: [db] (شکل ساده) چه تضمینی میدهد؟
برای آماده بودن، condition: service_healthy + healthcheck لازم است.
مثال ۷: الگوی جایگزین، restart policy و تلاش دوباره
Section titled “مثال ۷: الگوی جایگزین، restart policy و تلاش دوباره”بعضی اپها باید خودشان با خطای موقت کنار بیایند (چون دیتابیس در عمل هم ممکن است وسط کار برود). یک راه ساده: restart: on-failure تا وقتی اپ با خطا تمام میشود دوباره امتحانش کند.
name: lxhcr
services: db: image: redis:alpine command: sh -c "sleep 6; exec redis-server" app: image: redis:alpine restart: on-failure:10 command: sh -c 'redis-cli -h db ping | grep -q PONG || exit 1; echo "اتصال برقرار شد"'cd hcrdocker compose up -d >/dev/null 2>&1for i in $(seq 40); do s=$(docker inspect --format '{{.State.Status}}' lxhcr-app-1) [ "$s" = exited ] && break sleep 1donen=$(docker inspect --format '{{.RestartCount}}' lxhcr-app-1)[ "$n" -ge 1 ] && echo "اپ چند بار دوباره تلاش کرد (RestartCount >= 1)"docker inspect --format 'کد خروج نهایی: {{.State.ExitCode}}' lxhcr-app-1docker compose logs app 2>&1 | sed -E 's/^[^|]+\| //' | tail -1docker compose down -v >/dev/null 2>&1اپ چند بار دوباره تلاش کرد (RestartCount >= 1)کد خروج نهایی: 0اتصال برقرار شداپ چند بار شکست خورد و دوباره بالا آمد، تا دیتابیس آماده شد و در تلاش بعدی موفق (کد خروج ۰) شد؛ دیگر ریستارت نشد. on-failure:10 یعنی حداکثر ۱۰ بار تلاش. راه امنتر ترکیب هر دو است: service_healthy برای راهاندازی، و restart برای خرابیهای وسط کار.
پشت پرده
Section titled “پشت پرده”وقتی healthcheck تعریف شده، dockerd برای هر کانتینر یک تایمر نگه میدارد. هر interval یک بار دستور test را با همان کاری که docker exec میکند داخل کانتینر اجرا میکند و نتیجه (کد خروج و ۴ کیلوبایت آخر خروجی) را در State.Health.Log میگذارد (حداکثر ۵ ورودی آخر). شمارندهی FailingStreak شکستهای متوالی را میشمارد؛ وقتی به retries رسید وضعیت unhealthy میشود.
دو نکتهی مهم:
- داکر خودش کانتینر
unhealthyرا ریستارت نمیکند (این کار Swarm و ابزارهای بالاتر است).restartpolicy فقط به خروج پروسه واکنش نشان میدهد نه به برچسب سلامت. اگر میخواهی ناسالم بمیرد، خود اسکریپتtestیا اپ بایدexitکند. depends_onباservice_healthyفقط در زمانdocker compose upاعمال میشود. اگر بعداً دیتابیس بیفتد، Compose اپ را نمیبندد؛ اپ باید خودش دوبارهاتصال (reconnect) بزند.
دستور سلامت را ساده و سریع نگه دار (مثلاً redis-cli ping، pg_isready، wget به یک مسیر /health)، چون هر interval اجرا میشود. بررسی سنگین کانتینر را کند میکند.
جدول مرجع: تنظیمات healthcheck در Compose
Section titled “جدول مرجع: تنظیمات healthcheck در Compose”| کلید | معنی |
|---|---|
test |
["CMD", "cmd", "arg"] یا ["CMD-SHELL", "cmd با شل"] یا ["NONE"] |
interval |
فاصلهی بررسیها |
timeout |
بیشترین زمان یک بررسی |
retries |
شکستهای پشتسرهم تا unhealthy |
start_period |
مهلت راهاندازی |
disable: true |
خاموش کردن healthcheck ارثی از image |
| restart | معنی |
|---|---|
no |
دوباره شروع نشود (پیشفرض) |
on-failure[:N] |
فقط با کد خروج غیرصفر، حداکثر N بار |
always |
همیشه (حتی بعد از ریستارت داکر) |
unless-stopped |
همیشه، مگر دستی متوقف شده باشد |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) فرض کردن اینکه depends_on ساده کافی است
Section titled “۱) فرض کردن اینکه depends_on ساده کافی است”مثال ۵: اپ زودتر شروع شد و شکست خورد. راهحل: condition: service_healthy و healthcheck روی وابستگی.
۲) condition: service_healthy بدون healthcheck
Section titled “۲) condition: service_healthy بدون healthcheck”mkdir -p m2 && cat > m2/compose.yaml <<'LXEOF'services: db: image: alpine command: sleep 30 app: image: alpine depends_on: db: condition: service_healthyLXEOFcd m2 && docker compose up -d 2>&1 | grep "no healthcheck"docker compose down >/dev/null 2>&1dependency failed to start: container m2-db-1 has no healthcheck configuredسرویسی که healthcheck ندارد هرگز healthy نمیشود، پس Compose خطا میدهد. راهحل: برای db یک healthcheck تعریف کن.
۳) دستور سلامتی که در image نیست
Section titled “۳) دستور سلامتی که در image نیست”docker run -d --name lx-hc5 --health-cmd 'pg_isready' --health-interval 1s --health-retries 1 nginx:alpine >/dev/nullsleep 4docker inspect --format '{{.State.Health.Status}}: {{(index .State.Health.Log 0).Output}}' lx-hc5 | head -2docker rm -f lx-hc5 >/dev/nullunhealthy: /bin/sh: pg_isready: not foundnginx کاملاً سالم است، ولی ابزار pg_isready در این image وجود ندارد، پس بررسی همیشه شکست میخورد و سرویس سالم هم «ناسالم» میشود. همین اتفاق با curl در imageهای سبکی که curl ندارند میافتد. راهحل: ابزاری را بنویس که در همان image هست (مثل wget در busybox، redis-cli در Redis، pg_isready در Postgres)، و خروجی State.Health.Log را بخوان.
۴) start_period کم برای سرویس کند
Section titled “۴) start_period کم برای سرویس کند”دیتابیسی که ۳۰ ثانیه برای راهاندازی لازم دارد، با retries: 3 و interval: 5s بعد از ۱۵ ثانیه unhealthy اعلام میشود، حتی اگر نیمهراه راهاندازی باشد. راهحل: start_period را به اندازهی راهاندازی بگذار.
۵) انتظار اینکه unhealthy کانتینر را ریستارت کند
Section titled “۵) انتظار اینکه unhealthy کانتینر را ریستارت کند”مثال ۲: کانتینر ناسالم هنوز running بود. راهحل: ریستارت را با خروج پروسه یا ابزار هماهنگی (Swarm، Kubernetes) انجام بده؛ در Compose خالص، فقط برچسب است و service_healthy.
یک کانتینر redis:alpine با healthcheck (redis-cli ping) اجرا کن و نشان بده بعد از چند ثانیه healthy میشود.
دیدن جواب
docker run -d --name lx-e1 --health-cmd 'redis-cli ping' --health-interval 1s redis:alpine >/dev/nullsleep 4docker inspect --format 'وضعیت: {{.State.Health.Status}}' lx-e1docker rm -f lx-e1 >/dev/nullوضعیت: healthyتمرین اصلی: اپ را طوری تنظیم کن که منتظر آماده شدن دیتابیس بماند. یک db (redis که ۵ ثانیه دیر بالا میآید و healthcheck دارد) و یک app با condition: service_healthy بنویس، با docker compose up -d --wait اجرا کن و با docker compose ps نشان بده db در حالت healthy است.
دیدن جواب
mkdir -p e2 && cat > e2/compose.yaml <<'LXEOF'name: lxe2services: db: image: redis:alpine command: sh -c "sleep 5; exec redis-server" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 1s retries: 10 app: image: redis:alpine depends_on: db: condition: service_healthy command: sh -c 'redis-cli -h db ping && sleep 300'LXEOFcd e2docker compose up -d --wait 2>&1 | grep -E "Healthy|Started" | sed -E 's/^ +//' | sort -udocker compose ps --format '{{.Service}}: {{.State}} {{.Health}}' | sortdocker compose down -v >/dev/null 2>&1Container lxe2-app-1 HealthyContainer lxe2-app-1 StartedContainer lxe2-db-1 HealthyContainer lxe2-db-1 Startedapp: runningdb: running healthy--wait دستور را تا آماده شدن همهی سرویسها نگه میدارد؛ برای اسکریپتها و CI عالی است.
یک image با HEALTHCHECK بساز که تا وقتی فایل /app/ready وجود ندارد ناسالم باشد. کانتینر را اجرا کن، نشان بده ناسالم است، فایل را بساز و نشان بده سالم شد؛ سپس همان image را با --no-healthcheck اجرا کن و نشان بده وضعیت سلامتی ندارد.
دیدن جواب
mkdir -p e3 && cat > e3/Dockerfile <<'LXEOF'FROM alpineRUN mkdir /appHEALTHCHECK --interval=1s --retries=2 CMD test -f /app/readyCMD ["sleep", "300"]LXEOFdocker build -q -t lx-hce3 e3 >/dev/nulldocker run -d --name lx-e3a lx-hce3 >/dev/nullsleep 4echo "ابتدا: $(docker inspect --format '{{.State.Health.Status}}' lx-e3a)"docker exec lx-e3a touch /app/ready; sleep 3echo "بعد: $(docker inspect --format '{{.State.Health.Status}}' lx-e3a)"docker run -d --name lx-e3b --no-healthcheck lx-hce3 >/dev/nullecho "بدون: $(docker inspect --format '{{.State.Health}}' lx-e3b)"docker rm -f lx-e3a lx-e3b >/dev/null; docker rmi lx-hce3 >/dev/nullابتدا: unhealthyبعد: healthyبدون: <nil>آزمونک
Section titled “آزمونک”تفاوت «کانتینر در حال اجرا» و «سرویس آماده» چیست؟
برای همین healthcheck و service_healthy لازم است.
کد خروج دستور healthcheck برای «ناسالم» چیست؟
۰ سالم، ۱ ناسالم.
depends_on: [db] چه میکند؟
برای آماده بودن condition: service_healthy.
کانتینری unhealthy شد؛ داکر چه میکند؟
ریستارت با ابزارهای هماهنگی یا خروج پروسه است.
start_period برای چیست؟
برای سرویسهای کندشروع.
condition: service_completed_successfully برای چیست؟
قبل از شروع اپ، migration باید تمام شده باشد.
جمعبندی
Section titled “جمعبندی”- «در حال اجرا» ≠ «آماده».
HEALTHCHECK(یا--health-cmd/healthcheck:در Compose) آماده بودن را میسنجد. - وضعیتها:
starting←healthy/unhealthy؛ باdocker inspect --format '{{.State.Health.Status}}'میخوانی؛ لاگ درState.Health.Log. depends_onساده فقط ترتیب شروع است؛ برای آماده بودنcondition: service_healthyو برای کارهای تکبارservice_completed_successfully.docker compose up -d --waitتا آماده شدن سرویسها صبر میکند.- داکر کانتینر ناسالم را خودش ریستارت نمیکند؛
restartفقط به خروج پروسه واکنش دارد. اپ باید reconnect هم بلد باشد.
| دستور | کاری که میکند |
|---|---|
docker inspect --format '{{.State.Health.Status}}' C | وضعیت سلامت |
docker run --health-cmd CMD --health-interval 5s IMG | healthcheck موقع run |
docker run --no-healthcheck IMG | خاموش کردن healthcheck image |
HEALTHCHECK --interval=5s CMD cmd || exit 1 | در Dockerfile |
healthcheck: {test: [CMD, redis-cli, ping]} | در Compose |
depends_on: {db: {condition: service_healthy}} | منتظر سالم شدن db |
docker compose up -d --wait | صبر تا آماده شدن سرویسها |
restart: on-failure:5 | حداکثر ۵ تلاش دوباره |