healthcheck، restart سیاست و لاگ
Healthchecks, restart policies, and logs
container در فهرست نوشتهٔ Up دارد، اما درخواستها جواب نمیگیرند. بعد همان برنامه از کار میافتد و دوباره بالا میآید، بیآنکه بفهمیم چرا. سه پرسش جدا داریم: فرایند زنده است؟ برنامه آمادهٔ کار است؟ و پیش از خرابی چه گفته؟
A container shows Up, but requests fail. Later the process exits and comes back without telling us why. These are three separate questions: is the process alive, is the application ready, and what did it say before failing?
Up است؛ اما آیا واقعاً میشود از برنامه استفاده کرد؟It says Up—so why does the page fail?
در فصل ۳ یاد گرفتیم Up بودن یعنی فرایند اصلی هنوز زنده است. حالا حالت آزاردهندهتری را تصور کن: container بالا است، پورت هم درست منتشر شده، اما برنامه هنوز در حال آمادهشدن است یا دیگر نمیتواند درخواستها را جواب بدهد. اگر فقط به Up نگاه کنیم، هر دو حالت را سالم میبینیم.
Chapter 3 showed a container marked Up that was still unreachable from a browser. We learned that Up describes container/process state, not the success of a real request. Now consider a harder case: the port is correct and the process is alive, but the application needs time to initialize—or it has stopped serving HTTP altogether.
پس یک سؤال تازه داریم: «برنامه فقط زنده است یا واقعاً آمادهٔ کار هم هست؟» Docker جواب این سؤال را از روی حدس نمیسازد. ما باید یک آزمون تعریف کنیم و نتیجهٔ آن را جدا از وضعیت فرایند بخوانیم.
If Docker says a container is running, does that mean the application is healthy? Not necessarily. So far we only know that its main process has not exited. To answer “can this application do the work we expect?”, we need a separate test.
آزمون سلامت از داخل container اجرا میشود و نتیجهاش به وضعیت معمول Running اضافه میشود؛ جای آن را نمیگیرد.
A health test runs inside the container and adds a health status alongside ordinary Running state; it does not replace it.
اسم این لایه health است. Docker از روی پورت یا زندهبودن PID حدس نمیزند برنامه چهقدر سالم است؛ ما آزمونی تعریف میکنیم، Docker آن را در container اجرا میکند و نتیجه را بهعنوان نشانهای مستقل نگه میدارد. بعد میتوانیم از همان نشانه برای فهم آمادگی dependency در Compose هم استفاده کنیم.
That extra layer is health. Docker cannot infer application correctness from an open port or live PID; we define a test, Docker runs it inside the container, and the result is recorded as a separate signal. Compose can then use that signal to reason about dependency readiness.
سه سؤال جدا داریم: زنده است؟ سالم است؟ چه اتفاقی افتاده؟State, health, and logs answer different questions
قبل از رفتن سراغ گزینهها، این سه سؤال را قاطی نکن. وضعیت container میگوید فرایند اصلی زنده است یا تمام شده. healthcheck نتیجهٔ آزمونی را میگوید که خودمان تعریف کردهایم. لاگ هم حرفهای خود برنامه را نگه میدارد. ممکن است یکی خوب باشد و دیگری نه؛ این تناقض نیست.
State tells us what the main process is doing. A healthcheck tells us whether our chosen test passed. Logs tell us what the application wrote to its standard output or error stream. None substitutes for the other two.
| نشانه / Signal | چه میپرسد؟ / Question | نمونه / Example | چه چیزی را ثابت نمیکند؟ / Does not prove |
|---|---|---|---|
| State | فرایند اصلی زنده است؟ | docker ps -a | برنامه به درخواستها جواب میدهد |
| Health | آزمون تنظیمشده موفق شده؟ | docker inspect، docker ps | همهٔ مسیرهای کاربر یا dependencyها سالماند |
| Logs | برنامه چه رویداد یا خطایی گزارش کرده؟ | docker logs | هیچ دادهای گم یا ثبتنشده نمانده |
مثلاً Running + unhealthy یعنی فرایند هنوز زنده است اما آزمون سلامت شکست میخورد. یک container میتواند Exited باشد ولی لاگ خیلی خوبی برای پیدا کردن علت باقی گذاشته باشد. و Restarting یعنی Docker دوباره تلاش میکند؛ نه اینکه علت مشکل خودبهخود حل شده باشد.
State shows process liveness, health reports only the configured test, and logs contain the app’s own reports; none alone proves complete service health or capture of every event.
هر سه مسیر یک container را مشاهده میکنند، اما هرکدام نوع دیگری از مدرک را برمیگردانند.
All three channels observe one container, but each returns a different kind of evidence.
این وضعیتها تناقض ندارند: Running + unhealthy یعنی فرایند هست اما آزمون سلامت شکست خورده؛ Exited همراه لاگ مفید یعنی فرایند تمام شده ولی ردّی برای تشخیص داریم؛ و Restarting همراه خطای تکراری یعنی سیاست دوباره تلاش میکند، نه اینکه علت را درمان کرده باشد.
These combinations are not contradictions: Running + unhealthy means the process exists while its probe fails; Exited with useful logs means the process ended but left diagnostic evidence; and Restarting with repeated errors means a policy is retrying, not curing the cause.
HEALTHCHECK یعنی «این آزمون را اجرا کن و نتیجهاش را نگه دار»What does HEALTHCHECK do?
فرض کن وبسرور هنوز یک فرایند زنده دارد، اما دیگر HTTP جواب نمیدهد. از دید lifecycle، container هنوز Running است. HEALTHCHECK یک آزمون جدا تعریف میکند که داخل همان container اجرا میشود تا چیزی نزدیکتر به «آماده و قابلاستفاده بودن» را بسنجیم.
Imagine a web server with a live process that no longer answers HTTP requests. From outside, Docker sees only that the process is alive. HEALTHCHECK tells an image or service which command to run inside the container to get a useful signal about health or readiness.
در Dockerfile فقط یک دستور HEALTHCHECK مؤثر است؛ اگر چند بار بیاید، آخری جای قبلی را میگیرد. دو فرم اصلی دارد: اجرای فرمان برای سنجش، یا خاموشکردن healthcheck بهارثرسیده از image پایه.
Only one HEALTHCHECK instruction takes effect in a Dockerfile; if several appear, the last one wins. Its two main forms run a test command or disable a healthcheck inherited from a base image.
HEALTHCHECK --interval=30s --timeout=3s \ --start-period=20s --start-interval=2s --retries=3 \ CMD ["curl", "-f", "http://127.0.0.1:8080/health"]
نمونه فقط وقتی کار میکند که image واقعاً curl داشته باشد و برنامه روی همان پورت مسیر سلامت را پاسخ دهد. Docker فرمان را داخل container اجرا میکند. فرم آرایهای/exec از shell استفاده نمیکند؛ اگر به pipe یا شرط shell نیاز داری، فرم shell را آگاهانه انتخاب کن و مطمئن شو shell هم در image هست.
This example works only if the image contains curl and the application serves that endpoint on the stated port. Docker runs the command inside the container. Exec/array form does not invoke a shell; if you need a pipe or shell condition, choose shell form deliberately and ensure the image contains a shell.
| گزینه / Option | نقش / Purpose | نکتهٔ عملی / Practical reading |
|---|---|---|
test / CMD | فرمان آزمون و کد خروج آن | 0 موفق، 1 ناموفق؛ 2 رزرو است |
interval | فاصلهٔ معمول میان اجرای آزمون | check را بیجهت هر ثانیه اجرا نکن |
timeout | حداکثر زمان هر اجرا | اگر طول بکشد همان check شکست میخورد |
retries | شکستهای پیدرپی تا unhealthy | وقفهٔ گذرا را با خرابی پایدار یکی نکن |
start_period | مهلت راهاندازی برای شکستهای آغازین | پس از اولین check موفق، شکستهای بعدی شمرده میشوند |
start_interval | فاصلهٔ check در دورهٔ آغاز | Engine 25.0+؛ Compose 2.20.2+ |
فرمان test کد موفقیت یا شکست را میدهد؛ interval فاصلهٔ اجراها، timeout سقف هر اجرا و retries شمار شکستهای پیدرپی را تعیین میکند. start period به برنامه فرصت آغاز میدهد و start interval آهنگ آزمون را در همان بازه تنظیم میکند؛ گزینهٔ آخر به نسخههای تازه نیاز دارد.
The test command supplies the pass/fail exit code; interval sets cadence, timeout bounds each run, and retries count consecutive failures. Start period gives the app initialization time, while start interval adjusts probe cadence in that window and requires recent versions.
پیشفرض Docker برای interval و timeout هرکدام ۳۰ ثانیه، برای retries سه بار، برای start_period صفر و برای start_interval پنج ثانیه است. در دورهٔ آغاز checkها با start interval اجرا میشوند. شکستها تا قبل از نخستین موفقیت از شمار retries کنار گذاشته میشوند؛ با اولین موفقیت، شکستهای متوالی دوباره حساب میشوند، حتی اگر start period هنوز تمام نشده باشد.
Docker defaults to 30 seconds for both interval and timeout, three retries, a zero start_period, and a five-second start_interval. During startup, probes use the start interval. Failures before the first success do not count toward retries; after the first success, consecutive failures count even if the start period has not elapsed.
آزمون سلامت باید سبک و بیخطر باشد. قرار نیست هر چند ثانیه migration اجرا کند، رکورد بسازد یا کل پایگاهداده را scan کند. معمولاً یک مسیر ساده مثل /health یا ابزار بومی خود سرویس کافی است؛ چیزی که سریع بگوید سرویس حداقل وعدهٔ اصلیاش را انجام میدهد.
Probe a lightweight endpoint such as /health or a service-native command. A check should not create rows, run migrations, or delete files. An expensive probe run frequently can become load of its own.
آیا curl localhost همیشه کافی است؟ نه. اگر فقط میخواهی مطمئن شوی همان وبسرور پاسخ میدهد، شاید خوب باشد. اما اگر مسیر اصلی برنامه یا وابستگی ضروری خراب باشد، یک پاسخ سادهٔ ۲۰۰ ممکن است زیادی خوشبینانه باشد. از آن طرف هم آزمون را آنقدر سنگین نکن که خودش منبع خطا شود.
Is curl localhost enough? Perhaps, if the promise you care about is an HTTP response from that process. Perhaps not, if the main route is broken or a required dependency is unreachable; and if curl is absent, the probe cannot run at all. Test the promise that matters to consumers without turning the check into an expensive sweep of every external service.
همین آزمون را میتوان کنار سرویس Compose هم تعریف کردCompose can define or override a healthcheck
اگر healthcheck باید مخصوص همین پروژه باشد، لازم نیست حتماً آن را داخل Dockerfile بگذاری. Compose میتواند آزمون سرویس را تعریف کند یا مقدار image را تغییر دهد. مهمتر از شکل YAML این است که بدانیم دستور داخل همان container اجرا میشود و خروجیاش فقط دربارهٔ همان آزمون حرف میزند.
If the image is shared, or an environment needs a different test, define the healthcheck next to the service in Compose. test can be a CMD array, a CMD-SHELL array, or a string. A string implies shell execution; a CMD array invokes the executable directly.
services:
web:
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health', timeout=1)"]
interval: 10s
timeout: 2s
retries: 3
start_period: 15s
start_interval: 2s
در نمونهٔ ما از Python استفاده شده تا فرض نکنیم image حتماً curl دارد. 127.0.0.1 از داخل container به خود همان container برمیگردد. اگر مسیر /health همیشه ۲۰۰ بدهد، حتی وقتی قابلیت اصلی خراب است، مشکل از Docker نیست؛ تعریف سلامت ما ضعیف بوده است.
Here Python is the probe, so we do not assume the image contains curl. From inside the container, 127.0.0.1 loops back to that same container. This checks the response status only; if the endpoint always returns 200 while the important feature is broken, the test is too optimistic.
Compose میتواند مقادیر healthcheck مربوط به image را override کند. start_interval در Compose از نسخهٔ 2.20.2 پشتیبانی میشود و سمت Engine هم به Docker Engine 25.0 یا جدیدتر نیاز دارد. اگر نسخهٔ قدیمی پیام «unknown field» داد، نسخه را بررسی کن و تا زمان ارتقا این گزینه را بردار؛ بقیهٔ healthcheck همچنان کار میکند.
Compose can override healthcheck values from the image. Compose supports start_interval from version 2.20.2, and the Engine side requires Docker Engine 25.0 or later. If an older version reports an unknown field, check the version and omit this option until upgraded; the rest of the healthcheck still works.
حالا فرق Running و healthy را با زمان ببینFrom starting to healthy or unhealthy
برنامهٔ آزمایشگاهی شش ثانیه زمان میخواهد تا آماده شود. container تقریباً فوری شروع میشود، پس چند لحظه وضعیت اصلی Running است ولی health هنوز starting مانده. وقتی مسیر سلامت جواب موفق بدهد، همان container بدون عوضشدن lifecycle به healthy میرسد.
Our application needs six seconds to initialize. The container starts first; early probes fail, then the endpoint becomes ready. This makes the difference between “the process started” and “the application is ready” visible.
health یک وضعیت مستقل است؛ شکست آزمون بهتنهایی فرایند اصلی را متوقف نمیکند.
Health is a separate state; a failed probe alone does not stop the main process.
docker run -d --name slow-ready \
-p 18080:8080 \
--health-cmd='python -c "import urllib.request; urllib.request.urlopen(\"http://127.0.0.1:8080/health\", timeout=1)"' \
--health-interval=2s --health-timeout=1s --health-retries=3 \
--health-start-period=8s --health-start-interval=1s \
codename-lab/slow-ready:dev
docker ps --filter name=slow-ready
docker inspect --format '{{json .State.Health}}' slow-ready
نام image در این نمایش به image کوچکی اشاره میکند که برنامهٔ آزمایشگاه میسازد؛ پیش از اجرا باید آن را build کرده باشی. پیش از پایان تأخیر، docker ps معمولاً Up ... (health: starting) نشان میدهد؛ پس از پاسخ موفق همان container به (healthy) میرسد. وضعیت اصلی در هر دو لحظه Running است.
The image name refers to the small lab image built for this experiment; build it before running the command. Before initialization ends, docker ps typically shows Up ... (health: starting); after a successful response, the same container becomes (healthy). Its ordinary state is Running at both points.
خروجی کامل inspect فیلدهایی مثل Status، FailingStreak و تاریخچهٔ محدود اجرای آزمون را دارد؛ هر مدخل میتواند زمان، ExitCode و خروجی کوتاه را ثبت کند. کد صفر یعنی موفق، یک یعنی ناموفق و دو رزرو است. متن stdout/stderr آزمون را برای عیبیابی کوتاه و بیحساسیت نگه دار.
The full inspect result includes fields such as Status, FailingStreak, and a bounded probe history; each record can include time, ExitCode, and short output. Zero means success, one means failure, and two is reserved. Keep probe stdout/stderr short and free of sensitive data.
docker ps --filter name=slow-ready
docker inspect --format 'state={{.State.Status}} health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}} failing={{if .State.Health}}{{.State.Health.FailingStreak}}{{else}}0{{end}}' slow-ready
docker inspect --format '{{json .State.Health.Log}}' slow-ready
اگر بخش Health اصلاً وجود ندارد، اول احتمال بده آزمونی تعریف نشده. اگر container Running است ولی unhealthy شده، قبل از restart سراغ خود آزمون برو: آیا ابزارش وجود دارد؟ پورت درست است؟ مسیر جواب میدهد؟ خروجی آخر چه میگوید؟
If Health is absent, a probe may not be configured. If the container remains Running but unhealthy, inspect the check, endpoint, binary, and error output before restarting blindly.
آزمون خوب باید چیزی را بسنجد که واقعاً برای مصرفکننده مهم استA probe should test the promise we expect from the service
یک درخواست موفق به /health نشانهٔ خوبی است، اما باید بدانی چه چیزی را ثابت میکند. شاید فقط وبسرور پاسخ میدهد، درحالیکه پایگاهداده قطع است. از آن طرف، اگر healthcheck به چند سرویس بیرونی وابسته باشد، یک قطعی کوتاه شبکه میتواند برنامهٔ سالم را unhealthy نشان دهد. تعریف سلامت باید بهاندازهٔ نیاز واقعی دقیق باشد.
A successful curl -f http://localhost/ is useful but limited evidence: the process returned a successful status. The main route may still fail, or the database may be unreachable. Conversely, a check that depends directly on an external service can report a brief network interruption as failure of the application itself. The probe reveals our definition of “ready.”
| آزمون / Test | چه چیزی را نشان میدهد؟ / Evidence | چه چیزی را نه؟ / Not proof of |
|---|---|---|
HTTP محلی /health | web process پاسخ قابلقبول میدهد | همهٔ قابلیتها یا dependencyها کار میکنند |
pg_isready | PostgreSQL برای اتصال آماده پاسخ میدهد | جدول، مجوز یا query برنامه درست است |
| query کامل یا مسیر سنگین | ممکن است مسیر بیشتری را پوشش دهد | هزینه یا اثر جانبی آن توجیه شده |
مسیر HTTP محلی پاسخ web را ثابت میکند، نه سلامت همهٔ قابلیتها. pg_isready میگوید PostgreSQL اتصال میپذیرد، نه اینکه schema و پرسوجو برنامه درستاند. آزمون کاملتر را فقط وقتی انتخاب کن که هزینه و اثر جانبیاش توجیه دارد.
A local HTTP route proves that web responds, not that every feature works. pg_isready signals that PostgreSQL accepts connections, not that the app schema or queries are correct. Use a broader probe only when its cost and side effects are justified.
پاسخ سلامت را کوتاه و بیحساسیت نگه دار؛ خروجی آزمون ممکن است در metadata container ثبت شود. توکن، رمز، اطلاعات کاربر یا پیکربندی محرمانه را در آن ننویس. مسیر هم نباید با هر درخواست دادهای را تغییر دهد.
Keep health output short and non-sensitive; probe output may be recorded in container metadata. Do not include tokens, passwords, user data, or confidential configuration. The endpoint must not mutate data on each request.
حالا محدودیت depends_on فصل ۸ معنای کاملتری پیدا میکندThis completes Chapter 8’s depends_on discussion
در فصل ۸ گفتیم شروعشدن PostgreSQL با آمادهبودنش یکی نیست. حالا یک راه برای بیان «آماده» داریم. اگر سرویس وابسته healthcheck معناداری داشته باشد، Compose میتواند شروع مصرفکننده را تا healthy شدن آن عقب بیندازد. این فقط گیت شروع است؛ بعد از بالا آمدن، مدیریت قطعووصلی هنوز وظیفهٔ خود برنامه است.
Chapter 8 showed that short-form syntax orders startup but does not guarantee PostgreSQL is ready for queries. If the dependency has a healthcheck, Compose can wait for it before starting the consumer. This is a startup barrier, not a permanent dependency manager or a guarantee about future requests.
services:
web:
build: ./web
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: lab-only
POSTGRES_DB: app
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
Compose سرویس db را شروع میکند و تا وقتی آزمون آن healthy نشده، web را آغاز نمیکند. pg_isready در image رسمی PostgreSQL هست؛ وجودش را در imageهای دیگر فرض نکن. اگر web بعداً ازکارافتادن کند یا پایگاهداده پس از startup از دسترس خارج شود، این شرط بهتنهایی retry یا repair انجام نمیدهد؛ برنامه همچنان باید خطای اتصال را مدیریت کند.
Compose starts db and holds web until its probe reports healthy. pg_isready is available in the official PostgreSQL image; do not assume other images contain it. If web later crashes or the database becomes unavailable after startup, this condition does not retry or repair the dependency; the application still needs connection-error handling.
اگر healthcheck اشتباه باشد یا مسیر بیاهمیتی را بسنجد، Compose هم با همان تعریف تصمیم میگیرد. service_healthy قدرتی فراتر از آزمون تو ندارد.
If the healthcheck is wrong or probes an irrelevant route, Compose makes its decision from that same flawed definition. service_healthy is no stronger than your probe.
وقتی healthcheck شکست میخورد، مرز خراب را پیدا کنWhen a healthcheck fails, find the broken boundary
unhealthy فقط میگوید آزمون موفق نشده؛ هنوز نمیگوید خود برنامه خراب است. ممکن است ابزار در image نباشد، port یا مسیر غلط باشد، timeout کم باشد، مسیر واقعاً خطا دهد یا syntax شل در فرم exec نوشته شده باشد. خروجی آزمون این فرضها را از هم جدا میکند.
Unhealthy says the probe did not pass; it does not yet say the application itself is broken. The executable may be missing, the port or path may be wrong, timeout too short, the endpoint genuinely failing, or shell syntax placed in exec form. Probe output helps distinguish these hypotheses.
| نشانه / Symptom | مرز محتمل / Likely boundary | بررسی بعدی / Next check |
|---|---|---|
curl: not found | ابزار در image نیست | از ابزار موجود استفاده کن یا check سبک بساز |
| connection refused به localhost | port اشتباه یا server هنوز گوش نمیدهد | port داخلی را با log startup تطبیق بده |
| همیشه healthy با endpoint خراب | test همیشه exit صفر میدهد | پاسخ خطا بساز و check را عمداً fail کن |
| healthy ولی درخواست کاربر شکست میخورد | health endpoint وعدهٔ کافی را نمیسنجد | مسیر مهم را جدا امتحان و دامنهٔ check را بازبینی کن |
unknown field start_interval | Compose یا Engine قدیمی است | نسخه را بررسی و گزینهٔ تازه را بردار یا ارتقا بده |
ابزار گمشده یعنی آزمون هنوز مسیر را نسنجیده؛ connection refused بیشتر به port یا زمان شروع اشاره میکند. آزمون همیشه سبز یا مسیر کمعمق اطمینان کاذب میدهد. پیام unknown field را با بررسی نسخهٔ Compose/Engine پیگیری کن.
A missing executable means the endpoint has not yet been tested; connection refusal points toward the port or startup timing. An always-green or shallow probe creates false confidence. Investigate an unknown field by checking Compose and Engine versions.
آزمونی که همیشه موفق است از نبودن آزمون بدتر است، چون اطمینان کاذب میدهد. یک پاسخ HTTP ناموفق یا binary اشتباه ایجاد کن و مطمئن شو container به unhealthy میرسد. از آن طرف interval کوتاه و retries کم ممکن است وقفهٔ گذرا را خرابی پایدار جا بزند. زمان تشخیص را با هزینهٔ آزمون و نوسان طبیعی برنامه موازنه کن.
A test that always passes can be worse than no test because it creates false confidence. Produce an HTTP failure or wrong executable deliberately and confirm the container becomes unhealthy. Conversely, a short interval and few retries can mistake a transient pause for sustained failure. Balance detection time against probe cost and normal application variance.
اگر خود فرایند تمام شد، Docker دوباره تلاش کند یا نه؟What happens when the process exits?
تا اینجا دربارهٔ فرایندی حرف میزدیم که زنده بود ولی شاید سالم نبود. حالا فرض کن خود فرایند خارج میشود. اینجاست که restart policy وارد میشود: به Docker میگوید بعد از پایان فرایند چه کند. اما حتی بهترین سیاست restart هم جواب سؤال «چرا برنامه خارج شد؟» را نمیدهد.
Now the problem is different: the web process exits with a non-zero code. The cause might be transient—or a bug that repeats on every start. A restart policy tells Docker what to do after exit; it does not explain why the process stopped.
| Policy | رفتار / Behavior | مرز / Boundary |
|---|---|---|
no | خودکار شروع نمیکند؛ پیشفرض | برای سرویس دائمی باید تصمیم دیگری بگیری |
on-failure[:N] | فقط پس از exit ناموفق؛ retry را میتوان محدود کرد | exit صفر یا restart شدن daemon را مثل always پوشش نمیدهد |
always | پس از توقف دوباره اجرا میکند | توقف دستی تا start دستی یا restart شدن daemon حفظ میشود |
unless-stopped | شبیه always، جز containerی که عمداً متوقف شده | علت crash یا سلامت پس از start را تشخیص نمیدهد |
no container را خودکار راه نمیاندازد؛ on-خطا فقط exit غیرصفر را retry میکند و مانند always به daemon restart واکنش ندارد. always پس از توقف دوباره شروع میکند مگر توقف دستی تا start بعدی یا restart daemon. unless-stopped توقف عمدی را حفظ میکند. هیچکدام علت را پیدا نمیکنند.
The no policy never restarts automatically. on-failure retries non-zero exits and does not react to daemon restart like always. always restarts after stopping unless a manual stop remains in force until a later start or daemon restart. unless-stopped preserves an intentional stop. None diagnoses the cause.
در docker run فلگ --restart سیاست را تعیین میکند؛ در Compose کلید restart زیر service میآید. در Compose مقدار no را رشته بنویس تا با Boolean اشتباه نشود:
For docker run, --restart selects the policy; in Compose, put restart under the service. Quote no in Compose so it is not parsed as a Boolean:
docker run -d --name worker --restart on-failure:3 example/worker:dev
services:
web:
image: example/web:dev
restart: unless-stopped
برای جلوگیری از حلقهای که از همان لحظهٔ اول بیوقفه میچرخد، Docker سیاست را پس از آن فعال میکند که container دستکم ۱۰ ثانیه با موفقیت اجرا شده باشد. پس processی که فوراً خارج میشود ممکن است در آزمایش ساده restart نشود؛ برای دیدن loop، فرایند را بیش از ده ثانیه زنده نگه دار.
To avoid an immediate endless loop, Docker activates the policy only after the container has run successfully for at least ten seconds. A process that exits immediately may therefore not restart in a simple experiment; keep it alive longer than ten seconds to demonstrate a loop.
تعداد restart را کنار exit code و لاگ بخوان. زیادشدن تعداد فقط میگوید تلاشهای تازه ساخته شدهاند؛ علت را نمیگوید. یک نکتهٔ مهم: unhealthy شدن بهتنهایی سیاست restart را فعال نمیکند؛ restart به پایان فرایند واکنش دارد، نه صرفاً شکست healthcheck.
Read restart count alongside exit code and logs. A high count does not reveal the cause. Important: becoming unhealthy alone does not trigger the restart policy; it responds to process exit, not failed health status.
docker ps -a --filter name=worker
docker inspect --format 'state={{.State.Status}} exit={{.State.ExitCode}} restarts={{.RestartCount}}' worker
سیاست restart میتواند از بعضی خطاهای گذرا عبور کند، اما درمان ریشهای نیست. اگر تنظیمات غلط است یا برنامه هر بار همان bug را تکرار میکند، restart فقط همان شکست را دوباره اجرا میکند.
A restart policy can recover from some failures; it cannot diagnose or repair the root cause. If the image is wrong, configuration is incomplete, or the process repeats the same bug, a restart only repeats the failure.
حلقهٔ restart را ببین؛ بعد دلیلش را پیدا کنObserve a restart loop, then find its cause
یک فرایند آزمایشی را طوری تنظیم کن که ۱۲ ثانیه زنده بماند و بعد با کد ۲۳ خارج شود. هر بار شروعش را روی stdout مینویسد. این تأخیر از شرط فعالشدن سیاست عبور میکند و log چرخه را قابلدیدن نگه میدارد.
Configure a disposable process to stay alive for twelve seconds and then exit with code 23. It writes each start to stdout. The delay crosses the policy activation guard and makes the cycle visible in logs.
docker run -d --name crash-demo --restart on-failure:3 alpine:3.21 sh -c "echo attempt-started; sleep 12; echo deliberate-exit-23; exit 23"
docker ps -a --filter name=crash-demo
docker inspect --format 'state={{.State.Status}} exit={{.State.ExitCode}} restarts={{.RestartCount}}' crash-demo
docker logs --timestamps --tail=20 crash-demo
لحظهٔ دیدن خروجی مهم است؛ وضعیت شاید میان Restarting و Up جابهجا شود. انتظار داریم پیام شروع چند بار دیده شود و restart تعداد بالا برود؛ سه retry تعداد تلاشها را محدود میکند. این یعنی سیاست عمل میکند، نه اینکه سرویس بازیابی شده باشد.
Sampling time matters; state may alternate between Restarting and Up. Expect the start message to appear repeatedly and restart count to rise; three retries bound the attempts. This proves the policy is acting, not that the service recovered.
بعد exit code و log تلاشها را مقایسه کن و تنظیمات یا dependency را بررسی کن. اگر container را زود حذف کنی راه سادهٔ خواندن log از دست میرود. مدرک را پیش از پاکسازی جمع کن؛ container آزمایشی را آخر کار حذف خواهیم کرد.
Compare exit codes and logs across attempts, then inspect configuration or dependencies. If you remove the container too early, the easy path to its logs is gone. Collect evidence before cleanup; we will remove the disposable container at the end.
فلش بازگشت تلاش دیگری را نشان میدهد؛ علت ازکارافتادن را تغییر نمیدهد.
The loop arrow means another attempt; it does not change the cause of the crash.
بعد از اینکه وضعیت و log لازم را جمع کردی، container آزمایشی را حذف کن؛ پیش از آن حذفش نکن:
After collecting the state and logs you need, remove the disposable container. Do not remove it before then:
docker rm -f crash-demo
وقتی چیزی خراب شد، لاگ همان چیزی است که برنامه خودش گفتهApplications normally speak through stdout and stderr
در الگوی معمول container، برنامه پیامهای عادی را روی stdout و خطاها را روی stderr مینویسد و Docker آنها را به درایور لاگ میدهد. برای همین docker logs میتواند بدون ورود به فایلسیستم حرفهای برنامه را نشان بدهد؛ به شرط اینکه برنامه واقعاً همانجا بنویسد.
In the usual container model, the foreground application writes ordinary output to stdout and errors to stderr. Docker passes these streams to a logging driver. That is why docker logs can read history without entering the filesystem—provided the application writes to those streams.
اگر برنامه همهچیز را فقط در فایل داخلی خودش بنویسد، انتظار نداشته باش docker logs جادویی آن فایل را بخواند. برای سرویسهای containerمحور، پیامهای مهم عملیاتی را کوتاه و روشن روی stdout/stderr بده و رمز یا اطلاعات حساس را وارد لاگ نکن.
A private file such as /var/log/app.log is not automatically connected to docker logs. A container-oriented app should send concise, structured operational events to stdout/stderr: startup, readiness, important work, actionable errors, and shutdown. Do not log passwords, tokens, or personal data.
docker logs web docker logs --tail=30 web docker logs --follow --tail=10 web docker logs --timestamps --since=10m web docker logs --since=2026-09-24T10:00:00Z --until=2026-09-24T10:05:00Z web
برای پیامهای تازه --follow را بهکار ببر؛ با --tail ترکیبش کن تا همهٔ گذشته چاپ نشود. --timestamps زمان هر خط را اضافه میکند و --since/--until بازه میسازند؛ مقدار نسبی مثل 10m هم پذیرفته میشود. زمان مطلق را با منطقهٔ زمانی روشن بنویس.
Use --follow to stream new messages and combine it with --tail to avoid printing all history. --timestamps adds a timestamp to each line, while --since/--until bound a time window; relative values such as 10m also work. Include a clear timezone with absolute times.
با Compose معمولاً چند service را یکجا میخوانی. نام service را بده تا خروجی متمرکز بماند؛ برای دید کلی همه را دنبال کن و بعد روی مورد مشکوک زوم کن.
With Compose you often inspect several services together. Name a service to keep output focused; follow all services for an overview, then narrow in on the suspect.
docker compose logs --tail=20 web db docker compose logs --follow --tail=10 docker compose logs --timestamps --since=5m web
خروجی Compose معمولاً با نام service و container پیشوند میگیرد. ترتیب خطهای چند فرایند بهتنهایی ترتیب دقیق رویدادها را ثابت نمیکند؛ اگر ترتیب مهم است، زمان و شناسهٔ درخواست یا کار را هم در log برنامه بگذار.
Compose output is typically prefixed with service and container names. Interleaved lines alone do not prove exact event order; if ordering matters, include timestamps and a request or job identifier in application logs.
logging درایور مرز خروجی برنامه و نگهداری آن استA logging driver separates app output from log storage
برنامه stdout/stderr تولید میکند؛ logging درایور تعیین میکند Docker آن stream را چطور جمعآوری، نگهداری یا ارسال کند. درایور پیشفرض معمولاً json-file است و بهطور پیشفرض rotation ندارد؛ خروجی زیاد میتواند دیسک میزبان را پر کند. درایور local بهطور پیشفرض rotation دارد، اما تغییر تنظیم daemon روی containerهای از قبل ساختهشده خودکار اثر نمیگذارد.
The app produces stdout/stderr; the logging driver determines how Docker collects, stores, or forwards those streams. The default is commonly json-file, which does not rotate by default; high-volume output can fill host disk. The local driver rotates by default, but daemon setting changes do not automatically affect existing containers.
| لایه / Layer | مسئولیت / Responsibility | نشانهٔ خرابی / Failure clue |
|---|---|---|
| برنامه | رویداد مفید را به stdout/stderr بفرستد | docker logs خالی ولی فایل داخلی پر است |
| Docker logging driver | دریافت، buffer، چرخش یا ارسال خروجی | driver یا مقصد بیرونی در دسترس نیست |
| مصرفکنندهٔ log | خواندن محلی یا سامانهٔ بیرونی | driver انتخابی خواندن محلی را نمیدهد |
برنامه باید رویداد را روی stdout/stderr بنویسد؛ Docker فایل دلخواه داخلی را خودکار جمع نمیکند. logging درایور خروجی را میگیرد، میچرخاند، نگه میدارد یا forward میکند. مصرفکننده بعداً بسته به قابلیت همان درایور محلی یا بیرونی آن را میخواند.
The application must write events to stdout/stderr; Docker does not automatically collect an arbitrary internal file. The logging driver receives, rotates, stores, or forwards output. A consumer then reads it locally or externally, depending on that driver’s capabilities.
رفتار docker logs را برای همهٔ driverها یکسان فرض نکن. بعضی driverها log را بیرون Docker میفرستند و خواندن محلی را پشتیبانی نمیکنند؛ dual logging هم ممکن است فعال نباشد. اگر log پیدا نشد، پیش از نتیجهگیری دربارهٔ برنامه نوع درایور و مقصد را بررسی کن.
Do not assume docker logs behaves identically with every driver. Some drivers forward logs and do not support local reads; dual logging may not be enabled. If logs appear missing, inspect the driver and destination before concluding that the application produced nothing.
docker info --format '{{.LoggingDriver}}'
docker inspect --format '{{.HostConfig.LogConfig.Type}}' web
برای برنامهٔ پرحرف، rotation و سقف نگهداری را در سیاست daemon یا سرویس طراحی کن. تنظیم daemon معمولاً containerهای بعدی را تحت تأثیر میگذارد و برای container موجود شاید recreate لازم شود. پاککردن container هم جای نگهداری log را نمیگیرد.
For a chatty application, plan rotation and retention at the daemon or service level. A daemon setting generally affects newly created containers; an existing one may need recreation. Removing a container is not a log-retention strategy.
سه نشانه را کنار هم بگذار: شروع شد، آماده شد، افتاد، دوباره برگشتCombine the signals: ready, crashed, recovered
حالا داستان کامل میشود. سرویس start میشود و وضعیت میگوید Running؛ health هنوز starting است. چند ثانیه بعد آزمون موفق میشود و health به healthy میرسد. بعد فرایند عمداً خارج میشود، سیاست restart تلاش تازهای میسازد، لاگ سرنخ خروج را نگه میدارد و healthcheck نشان میدهد اجرای تازه فقط Running نیست، دوباره آماده هم شده است.
Web starts; state says Running while health is still starting. A probe succeeds and health becomes healthy. Later the process exits; the restart policy creates another attempt. Logs preserve the exit clue, and the healthcheck tells us whether the new attempt is actually ready.
restart تلاش میسازد؛ log سرنخ میدهد؛ health آمادگی اجرای تازه را میسنجد. این سه کار جای هم را نمیگیرند.
Restart creates an attempt; logs provide clues; health checks readiness of the new attempt. These jobs are not interchangeable.
برای عیبیابی همین ترتیب را نگه دار: اول وضعیت و exit code، بعد health، بعد لاگ همان بازه. پس از اصلاح هم همان سه سؤال را دوباره بپرس. دیدن Running بعد از restart بهتنهایی هنوز بازیابی را ثابت نمیکند.
Keep the diagnostic order: inspect state to learn whether the process exists, inspect health for the probe result, then read logs from the same window for a likely cause. After a repair, check again: is state stable, has health returned, and do logs show a new error? Seeing Running after restart is not proof of recovery.
از روی نشانه، سؤال بعدی را انتخاب کن؛ نه اینکه همهچیز را restart کنیChoose the next test from the symptom, not a random restart
یک نشانه بهتنهایی تشخیص نهایی نیست. اگر unhealthy است، آزمون سلامت را بررسی کن. اگر Restarting است، exit و لاگ تلاشهای تکراری را ببین. اگر لاگ خالی است، اول بفهم برنامه کجا مینویسد و درایور لاگ چیست. هر بار فقط یک فرض را امتحان کن تا علت بین چند تغییر گم نشود.
One symptom rarely proves a cause. Use the table to choose the next question; test one hypothesis with one focused check before changing several settings at once.
| مشاهده / Observation | برداشت اولیه / First interpretation | بررسی بعدی / Next check |
|---|---|---|
| Running، ولی unhealthy | فرایند هست، check شکست میخورد | .State.Health.Log، binary، port و endpoint |
| Restarting با log تکراری | فرایند خارج و دوباره ساخته میشود | exit code، آخرین log، config و dependency |
| Exited با log مفید | فرایند تمام شده اما container موجود است | inspect و log را پیش از حذف نگه دار |
docker logs خالی | برنامه log نکرده یا driver محلی نمیدهد | stdout/stderr، driver و مقصد log |
| container از قبل remove شده | هویت و log محلی در دسترس نیست | نگهداری بیرونی یا بازتولید کنترلشده |
| web زودتر از database آمده | فقط short depends_on بوده | healthcheck واقعی و شرط service_healthy |
Running و unhealthy یعنی فرایند زنده و آزمون ناموفق است؛ Restarting همراه log مشابه یعنی چرخهٔ exit و تلاش دوباره را بررسی کن. log خالی ممکن است از برنامه یا درایور باشد، و container حذفشده مدرک محلیاش را از دست داده. short depends_on هم بهتنهایی readiness را تضمین نمیکند.
Running plus unhealthy means the process lives while its probe fails; Restarting with repeated logs calls for inspecting the exit/retry cycle. Empty logs may come from the app or driver, and a removed container loses local evidence. Short depends_on alone does not guarantee readiness.
اگر healthcheck همیشه سبز میماند، خود آزمون را با شکست کنترلشده امتحان کن؛ این از نگاهکردن دوباره به وضعیت سبز مفیدتر است. اگر restart تعداد بالا میرود، logها را در یک بازهٔ زمانی بخوان و نقطهٔ مشترک تلاشها را پیدا کن.
If health stays green no matter what, test the probe with a controlled failure; that is more useful than staring at the status again. If restart count rises, read logs in a bounded time window and find what the attempts have in common.
۱۸ تمرین: اول بگو چه چیزی را میدانی، بعد دنبال چیزی برو که هنوز ثابت نشده18 exercises: from predicting state to diagnosing cause
در این تمرینها جواب خوب معمولاً یک دستور نیست؛ یک ترتیب فکر است. اول مشخص کن الان چه چیزی قطعی است: فرایند زنده است؟ health چه میگوید؟ لاگ چه سرنخی دارد؟ بعد فقط همان بررسیای را انتخاب کن که یکی از سؤالهای باز را جواب میدهد.
For each scenario, state what the evidence tells you and what is not yet proven. Open the solution after making a prediction or choosing a diagnostic command.
۱. Up اما درخواست ناموفق1. Up, but requests fail
container در docker ps برابر Up است، اما مسیر از مرورگر timeout میشود. این وضعیت چه چیزی را ثابت میکند و دو سؤال بعدیات چیست؟
A container is Up in docker ps, but the browser request times out. What has this status proved, and what are two useful next questions?
فرایند زنده است؛ پاسخگویی ثابت نشدهThe process is alive; responsiveness is unproven
Up میگوید container در وضعیت اجراست، نه اینکه مسیر سالم است. اول port mapping و network را بررسی کن، بعد مسیر HTTP و log برنامه را بسنج. healthcheck هم فقط وقتی نشانهای میدهد که آزمون معنادار تعریف شده باشد.
Up means the container is running, not that its endpoint is healthy. Check port mapping and networking, then test HTTP and inspect app logs. A healthcheck adds evidence only when its probe is meaningful.
۲. وضعیت با health یکی نیست2. State is not health
خروجی state=running health=unhealthy است. آیا با restart کردن Docker میتواند آن را سالم اعلام کند؟
Output says state=running health=unhealthy. Can restarting make Docker declare it healthy?
اول نتیجه و خروجی آزمون را بخوانRead the probe result first
فرایند هنوز زنده است اما آزمون سلامت شکست خورده. restart شاید یکبار فرایند را از نو بسازد؛ اگر مسیر، port یا binary آزمون غلط باشد همان نتیجه برمیگردد. .State.Health.Log را با inspect بخوان و از روی آن فرض بعدی را انتخاب کن.
The process is alive, but its probe failed. A restart may recreate it once; a wrong endpoint, port, or probe binary will produce the same result. Read .State.Health.Log with inspect and use it to choose the next hypothesis.
۳. خروجی آزمون برابر یک3. The probe exits with one
در آخرین health record مقدار ExitCode: 1 است. چه نتیجهای مجاز است و چه نتیجهای زودهنگام؟
The latest health record has ExitCode: 1. What conclusion is justified, and what is premature?
فرمان آزمون شکست خورده؛ علت هنوز معلوم نیستThe probe failed; the cause is still unknown
میدانیم همان اجرای آزمون ناموفق بوده؛ معلوم نیست برنامه خراب است یا فرمان، مسیر، port، permission یا dependency اشتباه است. خروجی همان مدخل را کنار log برنامه بخوان. exit code آزمون را با exit code فرایند اصلی اشتباه نگیر.
We know that probe invocation failed; we do not know whether the app, command, path, port, permissions, or dependency is wrong. Read the record output alongside app logs. Do not confuse the probe exit code with the main process exit code.
۴. curl در image نیست4. curl is missing from the image
image کوچک با curl -f http://localhost/health بررسی میشود و unhealthy است. خروجی curl: not found میگوید. چه چیزی را هنوز نسنجیدهایم؟
A slim image is probed with curl -f http://localhost/health and is unhealthy. Output says curl: not found. What have we not tested yet?
مسیر را هنوز اصلاً آزمون نکردهایمThe endpoint has not been probed at all
executable آزمون در image نیست؛ این مدرک خرابی مسیر نیست. از ابزاری که وجود دارد استفاده کن یا ابزار کوچک و موجهی اضافه کن. نصب ابزار بزرگ فقط برای آزمون، حجم و سطح حمله را زیاد میکند.
The probe executable is absent; this is not evidence that the endpoint is broken. Use an existing tool or add a small justified one. Installing a large utility solely for the check increases image size and attack surface.
۵. localhost در کدام container است؟5. Which container owns localhost?
web روی port داخلی 8080 گوش میدهد، اما healthcheck در پایگاهداده به localhost:8080 میزند و connection refused میگیرد. چرا؟
Web listens on internal port 8080, but a healthcheck in the database calls localhost:8080 and gets connection refused. Why?
آزمون داخل خود پایگاهداده اجرا میشودThe probe runs inside the database container
healthcheck داخل همان container اجرا میشود که health آن را میسنجد؛ localhost به پایگاهداده برمیگردد، نه web. برای پایگاهداده از آزمون خودش مثل pg_isready استفاده کن.
The healthcheck runs inside the container it measures, so localhost points to the database, not web. Use a database-native probe such as pg_isready.
۶. interval یکثانیهای6. A one-second interval
سرویسی interval=1s و retries=1 دارد. چه خطرهایی میبینی و چه دادهای برای تنظیم بهتر جمع میکنی؟
A service uses interval=1s and retries=1. What risks do you see, and what data would guide better settings?
نوسان گذرا خیلی زود unhealthy میشودA transient pause may trigger unhealthy too quickly
بار لحظهای یا مکث کوتاه سریعاً وضعیت را قرمز میکند و خود آزمون هم بار اضافه دارد. زمان startup و پاسخ عادی و تحمل مصرفکننده را اندازه بگیر؛ بعد interval، timeout و retries را با آنها موازنه کن.
A short pause or load spike can mark the service unhealthy quickly, while probes add load. Measure normal startup and response times and consumer tolerance, then balance interval, timeout, and retries against them.
۷. اولین موفقیت در start period7. The first success during start period
آزمون در ثانیهٔ ۲ fail، در ۵ موفق و در ۷ دوباره fail میشود. با start_period: 10s و retries: 2 آیا شکست ثانیهٔ ۷ نادیده است؟
The probe fails at second 2, succeeds at 5, then fails at 7. With start_period: 10s and retries: 2, is the second-7 failure ignored?
نه؛ پس از نخستین موفقیت شکستها دوباره شمرده میشوندNo; failures count again after the first success
شکستهای پیش از اولین آزمون موفق در مهلت startup کنار گذاشته میشوند؛ بعد از موفقیت، شکستهای بعدی به retries میروند، حتی اگر start period تمام نشده باشد. در ثانیهٔ ۷ شکست شمرده میشود.
Failures before the first successful probe are ignored during startup; after success, later failures count toward retries even before the start period ends. The second-7 failure counts.
۸. dependency هنوز آماده نیست8. The dependency is not ready yet
Compose فقط web: depends_on: db دارد. نخستین پرسوجو هنگام initialization پایگاه داده شکست میخورد. مدل را چطور اصلاح میکنی؟
Compose only has web: depends_on: db. The first query fails during database initialization. How do you improve the model?
healthcheck و شرط service_healthyA healthcheck and service_healthy condition
برای db آزمون readiness معنادار بگذار و در web بنویس condition: service_healthy. short syntax ترتیب شروع را میدهد؛ شرط تازه startup را عقب میاندازد تا آزمون موفق شود. این جای retry برنامه در اتصالهای بعدی را نمیگیرد.
Give db a meaningful readiness probe and set condition: service_healthy for web. Short syntax only orders startup; the condition waits for a successful check. It does not replace app retries for later connections.
۹. آزمون همیشه موفق9. A check that always succeeds
healthcheck با curl ... || true حتی با پاسخ HTTP 500 سبز میماند. کدام قسمت شکست را پنهان کرده؟
A healthcheck using curl ... || true stays green despite HTTP 500. Which part hides the failure?
true exit وضعیت نهایی را صفر میکندtrue makes the final status successful
وقتی curl fail میشود، shell بهخاطر || true فرمان موفق اجرا میکند؛ healthcheck فقط وضعیت نهایی را میبیند. عبارت را بردار و پاسخ خراب را عمداً امتحان کن تا آزمون واقعاً fail شود.
When curl fails, the shell runs a successful command because of || true; the healthcheck sees only the final status. Remove it and deliberately test a failing response.
۱۰. exit code دو10. Exit code two
ابزار healthcheck برای ورودی نامعتبر exit code دو میدهد. آیا این کد را برای معنی ویژهٔ خودت بهکار ببر؟
A healthcheck tool exits with code two on invalid input. Should you use that code for a custom meaning?
نه؛ دو برای Docker رزرو استNo; Docker reserves code two
قرارداد healthcheck صفر را موفق و یک را ناموفق میداند؛ دو رزرو شده است. wrapper را طوری بنویس که خطاهای ورودی را به یک تبدیل کند و پیام کوتاه بدهد.
The healthcheck contract uses zero for success and one for failure; two is reserved. Make a wrapper map invalid input to one and emit a short message.
۱۱. سیاست برای کار دستهای11. A policy for a batch job
یک worker با موفقیت exit صفر میکند؛ شکست موقت را حداکثر دو بار باید retry کند. کدام سیاست مناسب است و چه خطر جانبی را میسنجی؟
A worker exits zero on success and should retry transient failures at most twice. Which policy fits, and what side effect must you consider?
on-failure:2، اگر تکرار امن باشدon-failure:2, if repetition is safe
این سیاست فقط خروج ناموفق را تا سقف تعیینشده retry میکند؛ خروج صفر پایان طبیعی است. مطمئن شو retry عملیات غیرتکرارپذیر را دوباره ثبت یا side effect ناخواسته ایجاد نمیکند.
This policy retries non-zero exits up to the limit; zero remains normal completion. Ensure a retry cannot duplicate a non-idempotent operation or cause an unwanted side effect.
۱۲. توقف دستی پس از reboot12. A manual stop across reboot
اپراتور container را دستی متوقف کرده و میزبان reboot میشود. کدام سیاست برای حفظ این توقف طراحی شده؟
An operator manually stops a container and the host reboots. Which policy is designed to preserve that stop?
unless-stoppedunless-stopped
این سیاست توقف صریح را پس از restart شدن daemon حفظ میکند؛ رفتار را مستند کن تا توقف عمدی با ازکارافتادن اشتباه نشود. always در این مرز فرق دارد.
This policy honors an explicit stop across daemon restart. Document that behavior so an intentional stop is not mistaken for a crash; always differs at this boundary.
۱۳. unhealthy شد اما restart نشد13. Unhealthy, but not restarted
healthcheck چند بار fail شده و container unhealthy است، اما restart تعداد ثابت مانده. آیا این اشکال است؟
The healthcheck failed repeatedly and the container is unhealthy, but restart count is unchanged. Is this a bug?
نه؛ فرایند خارج نشده استNo; the process has not exited
health نتیجهٔ آزمون را نگه میدارد، اما restart سیاست به خروج فرایند واکنش دارد. اگر فرایند زنده است، restart تعداد لزوماً تغییر نمیکند. واکنش به unhealthy نیازمند سازوکار بالاتری است که بیرون از دامنهٔ این فصل است.
Health records the probe result, while restart policy responds to process exit. If it remains alive, the restart count need not change. Acting on unhealthy requires a higher-level mechanism outside this chapter’s scope.
۱۴. علت تکراری در ازکارافتادن loop14. A repeated cause in a crash loop
وضعیت گاهی Restarting است، restart تعداد بالا میرود و هر تلاش log میکند missing DB_HOST. آیا retry بیشتر درمان است؟
State intermittently shows Restarting, restart count rises, and every attempt logs missing DB_HOST. Will more retries fix it?
نه؛ پیکربندی هر بار ناقص استNo; configuration is missing on every attempt
log علت تکرارشونده را نشان میدهد؛ سیاست فقط فرایند را دوباره اجرا میکند. ورودی Compose را اصلاح کن و container را با تنظیمات تازه recreate کن. شمار restartها تأیید میکند حلقه متوقف شده، نه اینکه علت خودبهخود اصلاح شده باشد.
Logs show the repeated cause; the policy only reruns the process. Fix Compose input and recreate with the corrected config. Restart count can confirm the loop stopped, not that the cause fixed itself.
۱۵. چرا docker logs خالی است؟15. Why are docker logs empty?
برنامه میگوید log را در فایل داخلی مینویسد، اما docker logs web خالی است. دو احتمال مستقل را نام ببر.
The app says it writes logs to an internal file, but docker logs web is empty. Name two independent possibilities.
مسیر خروجی برنامه یا logging درایورApp output path or logging driver
برنامه شاید فقط فایل داخلی را مینویسد نه stdout/stderr؛ یا درایور خواندن محلی را پشتیبانی نمیکند و log را جای دیگری میفرستد. اول مسیر خروجی برنامه را روشن کن، بعد درایور و مقصد را بررسی کن.
The app may write only to an internal file instead of stdout/stderr, or the driver may not support local reads and may forward elsewhere. Confirm the app output path, then inspect the driver and destination.
۱۶. log را بعد از rm میخواهی16. You need logs after rm
container حذف شده و میخواهی خروجیاش را با docker logs بخوانی. پیش از rm چه باید میکردی؟
The container is removed and you want its output with docker logs. What should you have done before rm?
مدرک را ذخیره یا به مقصد پایدار بفرستCapture or forward the evidence durably
پیش از حذف log و inspect لازم را جمع میکردی یا logging درایور/سامانهٔ بیرونی با نگهداری مناسب داشتی. پس از remove شاید lookup محلی ممکن نباشد؛ مقصد بیرونی میتواند مستقل باقی بماند. نگهداری را به container حذفشده نسپار.
Before removal, capture relevant logs and inspect data or use a logging driver/external system with suitable retention. Local lookup may no longer work after removal, though an external sink may retain records. Do not rely on a removed container for retention.
۱۷. healthcheck ارزان طراحی کن17. Design a cheap healthcheck
API برای health هر بار همهٔ جدولها را scan و یک ردیف آزمایشی insert میکند. چه تغییری میدهی؟
An API scans every table and inserts a test row for each health probe. What would you change?
آزمون محدود و بدون mutationA focused, read-only probe
اگر فقط پاسخگویی فرایند مهم است، مسیر سبک را بسنج. اگر آمادگی پایگاهداده هم بخشی از قرارداد است، از آزمون کوتاه و read-only یا ابزار بومی استفاده کن، نه scan و insert پرتکرار. آزمون نباید داده بسازد یا dependency را زیر بار ببرد.
If process responsiveness is the promise, probe a lightweight endpoint. If database readiness is part of the contract, use a short read-only check or native tool rather than repeated scans and inserts. A probe should not mutate data or overload a dependency.
۱۸. سه مدرک در گزارش رخداد18. Three signals in an incident note
container چند بار ازکارافتادن کرده و کاربر فقط میگوید «سرویس بد است». چه سه مدرکی را با زمان جمع میکنی و چه نتیجهای را از آنها نمیگیری؟
A container has crashed several times and the user only says “the service is bad.” Which three time-bounded pieces of evidence do you collect, and what do you avoid concluding?
وضعیت، health و log؛ با تفسیر محدودState, health, and logs—with bounded conclusions
وضعیت، exit code و restart تعداد؛ وضعیت و خروجی اخیر آزمون؛ و logهای پنجرهٔ ازکارافتادن را جمع کن. وضعیت بهتنهایی کیفیت پاسخ را ثابت نمیکند، health فقط آزمون تعریفشده را میسنجد و log ممکن است ناقص باشد. اینها نقطهٔ شروع عیبیابیاند، نه حکم نهایی.
Collect state, exit code, and restart count; health and recent probe output; and logs from the crash window. State alone does not prove response quality, health measures only the configured probe, and logs can be incomplete. These are diagnostic starting points, not a final verdict.
آزمایشگاه: یک سرویس را سالم، ناسالم و ازکارافتاده ببین و فرقشان را با چشم خودت ثابت کنLab: one process, three signals, one verified recovery
یک وب کوچک میسازیم که شش ثانیه برای آمادهشدن وقت میخواهد. بعد دو خرابی جدا ایجاد میکنیم: اول فقط health endpoint را خراب میکنیم درحالیکه فرایند و صفحهٔ اصلی هنوز کار میکنند؛ بعد خود فرایند را از کار میاندازیم. هدف این است که با وضعیت، health و لاگ سه داستان متفاوت را از هم جدا کنی.
We will run a tiny web service that takes six seconds to become ready, exposes a health endpoint, and logs important events. Then we break one thing at a time: first health while the process stays up, then the process itself. The goal is not just a green Docker display; state, health, and logs must each provide evidence of recovery.
۱. پوشهٔ آزمایشی بساز و برنامه را بخوان1. Create a disposable folder and read the app
تمرین را در پوشهای تازه انجام بده تا هیچ منبعی از فصلهای قبل دست نخورد. این سرویس فقط برای آزمایش است؛ داده یا رمز واقعی واردش نکن. در پایان هم قبل از پاککردن container، لاگ و تعداد restart را نگه دار تا مدرک عیبیابی از بین نرود.
Start in a fresh folder so projects and volumes from earlier chapters remain untouched. Create the files there. This server is lab-only; do not use real data or credentials.
New-Item -ItemType Directory -Path .\health-lab Set-Location .\health-lab
در همان پوشه فایل app.py را بساز. زمان آغاز، آمادگی را تعیین میکند؛ وجود health.fail فقط مسیر سلامت را ناموفق میکند. مسیر /_crash پاسخ میدهد و کمی بعد فرایند اصلی را با exit code آزمایشی ۲۳ خارج میکند.
Create app.py in the same folder. Elapsed time controls readiness; the presence of health.fail makes only the health endpoint fail. The /_crash route responds and shortly afterward exits the main process with disposable code 23.
import json
import os
import threading
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
STARTED = time.monotonic()
READY_AFTER = 6
reported_ready = False
def log(event, **fields):
print(json.dumps({"event": event, "time": round(time.time(), 3), **fields}), flush=True)
class Handler(BaseHTTPRequestHandler):
def reply(self, status, body):
self.send_response(status)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(body.encode())
def do_GET(self):
global reported_ready
elapsed = time.monotonic() - STARTED
if self.path == "/health":
if elapsed < READY_AFTER:
return self.reply(503, "starting")
if os.path.exists("/app/health.fail"):
return self.reply(503, "lab-controlled health failure")
if not reported_ready:
reported_ready = True
log("ready", elapsed=round(elapsed, 2))
return self.reply(200, "healthy")
if self.path == "/_crash":
log("deliberate_process_failure", exit_code=23)
self.reply(202, "crashing after this response")
threading.Timer(0.25, lambda: os._exit(23)).start()
return
if elapsed < READY_AFTER:
log("request_before_ready", path=self.path, elapsed=round(elapsed, 2))
return self.reply(503, "still starting")
log("request_served", path=self.path)
self.reply(200, "web is ready")
def log_message(self, *_):
pass
log("process_started", ready_after_seconds=READY_AFTER)
ThreadingHTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
رویدادهای شروع، آمادگی، درخواست زودهنگام، درخواست موفق و ازکارافتادن عمدی log میشوند. probeهای عادی را ثبت نمیکنیم تا خروجی با پیام تکراری شلوغ نشود. اگر کد را تغییر دادی، python -m py_compile app.py را اجرا کن؛ نبود خروجی فقط نحو را تأیید میکند، نه رفتار server را.
Startup, readiness, early requests, successful requests, and the deliberate crash are logged. Routine probes are omitted to keep output readable. If you edit the code, run python -m py_compile app.py; no output confirms syntax only, not server behavior.
۲. Compose را با healthcheck و restart سیاست بساز2. Add a healthcheck and restart policy in Compose
فایل compose.yaml را کنار برنامه بساز. پوشه read-only mount شده، پس container نمیتواند خودش نشانگر را بسازد یا پاک کند؛ تغییر را از میزبان کنترل میکنیم.
Create compose.yaml beside the app. The folder is mounted read-only, so the container cannot create or remove the marker; we control that change from the host.
services:
web:
image: python:3.13-alpine
command: ["python", "-u", "/app/app.py"]
ports:
- "8090:8080"
volumes:
- .:/app:ro
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health', timeout=1)"]
interval: 2s
timeout: 1s
retries: 3
start_period: 8s
start_interval: 1s
restart: unless-stopped
این تنظیم برای start_interval به Compose 2.20.2+ و Engine 25.0+ نیاز دارد. نسخه را با docker compose version بررسی کن؛ اگر قدیمی است خط start_interval را بردار، interval معمولی همچنان کار میکند. Python داخل image ابزار آزمون هم هست، پس به وجود curl وابسته نیستیم.
This configuration needs Compose 2.20.2+ and Engine 25.0+ for start_interval. Check with docker compose version; on an older version remove that line, and the ordinary interval still works. Python is already present in the image, so the probe does not depend on curl.
۳. baseline را ببین: starting بعد healthy3. Observe the baseline: starting, then healthy
پروژه را با نام یکتای health-lab بالا بیاور تا منابعش از تمرینهای دیگر جدا بمانند. همزمان وضعیت و health را بخوان.
Start the project with the unique name health-lab so its resources stay separate from other exercises. Read state and health as it starts.
docker compose -p health-lab up -d
docker compose -p health-lab ps
$cid = docker compose -p health-lab ps -q web
docker inspect --format 'state={{.State.Status}} health={{.State.Health.Status}}' $cid
Start-Sleep -Seconds 3
docker compose -p health-lab ps
Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8090/
بار نخست باید Running و starting ببینی؛ چند ثانیه بعد healthy و پاسخ صفحه web is ready. وضعیت اصلی در هر دو لحظه Running است. اگر دیر نمونه گرفتی و فقط healthy دیدی، سرویس را restart کن و بلافاصله مشاهده را تکرار کن.
The first sample should show Running and starting; a few seconds later, healthy and a page response of web is ready. Ordinary state is Running at both times. If you sampled too late and saw only healthy, restart the service and sample immediately.
۴. health را خراب کن، نه فرایند را4. Break health, not the process
نشانگر را روی میزبان بساز؛ container فقط آن را میخواند. چند interval صبر کن، هر دو وضعیت را ببین و مسیر معمول را جدا امتحان کن.
Create the marker on the host; the container only reads it. Wait several intervals, inspect both statuses, and test the normal route separately.
New-Item -ItemType File -Path .\health.fail
Start-Sleep -Seconds 8
docker compose -p health-lab ps
$cid = docker compose -p health-lab ps -q web
docker inspect --format 'state={{.State.Status}} health={{.State.Health.Status}} failing={{.State.Health.FailingStreak}}' $cid
docker inspect --format '{{json .State.Health.Log}}' $cid
Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8090/
انتظار داریم وضعیت هنوز Running باشد، health برابر unhealthy شود و مسیر عادی همچنان موفق جواب دهد. نشانگر فقط health مسیر را خراب کرده، نه فرایند یا صفحه. اگر هر دو درخواست شکست خوردند، مسیر برنامه یا port را جدا بررسی کن.
We expect state to remain Running, health to become unhealthy, and the normal route to keep succeeding. The marker broke only the health endpoint, not the process or page. If both requests fail, inspect the app or port separately.
۵. نشانگر را بردار و سلامت را دوباره ثابت کن5. Remove the marker and prove health again
فایل را از میزبان پاک کن، نه container را restart. آزمون بعدی باید موفق شود و health از unhealthy به healthy برگردد، درحالیکه همان فرایند هنوز زنده است.
Remove the host file without restarting the container. A later probe should pass and health should move from unhealthy back to healthy while the same process remains alive.
Remove-Item -LiteralPath .\health.fail Start-Sleep -Seconds 4 docker compose -p health-lab ps Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8090/
این بازیابی سلامت است: علت خطای مسیر برداشته شد و آزمون آن را تأیید کرد. حالا logهای همان service را بخوان و event آمادگی و درخواست موفق را پیدا کن:
This is health recovery: the endpoint failure was removed and the probe confirmed it. Read the service logs and find readiness and successful-request events:
docker compose -p health-lab logs --timestamps --since=10m --tail=30 web
۶. فرایند را خارج کن و بازیابی را سهگانه بسنج6. Exit the process and verify recovery through all three signals
حالا مسیر ازکارافتادن را صدا بزن. PowerShell ممکن است همزمان با قطع اتصال خطا نشان دهد؛ این آزمایش عمداً فرایند را پس از پاسخ خارج میکند. چند ثانیه بعد وضعیت، restart تعداد و log را ببین.
Call the crash route. PowerShell may report a connection interruption because the experiment exits the process just after the response. A few seconds later inspect state, restart count, and logs.
try { Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8090/_crash } catch { $_.Exception.Message }
Start-Sleep -Seconds 3
docker compose -p health-lab ps
$cid = docker compose -p health-lab ps -q web
docker inspect --format 'state={{.State.Status}} exit={{.State.ExitCode}} restarts={{.RestartCount}} health={{.State.Health.Status}}' $cid
docker compose -p health-lab logs --timestamps --tail=40 web
چون container از قبل بیش از ده ثانیه اجرا شده، unless-stopped باید فرایند تازهای بسازد. restart تعداد باید افزایش یابد؛ هنگام نمونهگیری health ممکن است دوباره starting باشد چون فرایند تازه هم شش ثانیه زمان لازم دارد. log باید ازکارافتادن و شروع بعدی را نشان دهد. چند ثانیه صبر کن و healthy و پاسخ صفحه را دوباره تأیید کن؛ اینها با هم مدرک بازیابی هستند.
Because the container has already run for more than ten seconds, unless-stopped should start a new process. Restart count should rise; at sampling time health may be starting again because the new process also needs six seconds. Logs should show the crash and next startup. Wait a little, then confirm healthy and a successful page response; together these are evidence of recovery.
۷. مدرک را نگه دار و بعد پاکسازی کن7. Preserve evidence, then clean up
پیش از حذف، یادداشت کن: Up با starting چه تفاوتی داشت؛ هنگام unhealthy آیا فرایند هنوز Running بود؛ و restart را کدام مدرکها نشان دادند؟ بعد منابع Compose را جمع کن. volume نساختهایم و پاکسازی به پوشههای دیگر دست نمیزند.
Before cleanup, note how Up differed from starting, whether the process stayed Running while unhealthy, and which evidence showed the restart. Then remove this Compose project. We created no volume, and cleanup does not touch other folders.
docker compose -p health-lab down
if (Test-Path -LiteralPath .\health.fail) { Remove-Item -LiteralPath .\health.fail }
Set-Location ..
container را پیش از جمعآوری log و restart تعداد حذف نکن. فایلهای app.py و compose.yaml آزمایشی را میتوانی نگه داری؛ اگر خواستی پوشه را پاک کنی، اول مسیر دقیق را بررسی کن. در محیط واقعی برای نگهداری بلندمدت باید logging مقصد و retention جداگانه داشته باشی.
Do not remove the container before collecting logs and restart count. You may keep the lab’s app.py and compose.yaml; if you delete the folder, verify its exact path first. In a real environment, durable retention requires a separately configured logging destination and policy.
اگر فقط یک چیز از این فصل یادت بماند: Running جواب همهٔ سؤالها نیستKeep three observations for three questions
وضعیت میگوید فرایند اصلی زنده است یا نه؛ healthcheck نتیجهٔ آزمونی را میگوید که خودمان تعریف کردهایم؛ و لاگ روایت برنامه از اتفاقات است. ممکن است فرایند زنده ولی unhealthy باشد، یا container خارج شده باشد و لاگ خوبی برای تشخیص باقی گذاشته باشد.
State told us whether the process existed or exited; healthcheck told us whether the chosen test passed; logs showed what the application reported. A live process can be unhealthy, an exited container can retain useful logs, and a restart loop can briefly show Running without fixing anything.
| سؤال / Question | مدرک نخست / First evidence | محدودیت / Limitation |
|---|---|---|
| فرایند زنده است؟ | state، exit code | زندهبودن یعنی پاسخگویی نیست |
| آزمون سلامت میگذرد؟ | health و خروجی probe | فقط وعدهٔ تعریفشده را میسنجد |
| چه اتفاقی افتاد؟ | logهای زماندار | ممکن است ناقص یا بیرون از driver محلی باشند |
برای یک رخداد واقعی هر سه را کنار زمان بخوان: وضعیت و exit code، نتیجهٔ healthcheck و لاگ همان بازه. هیچکدام بهتنهایی حکم نهایی نیستند، اما کنار هم خیلی سریعتر نشان میدهند سؤال بعدی را باید کجا بپرسی.
Use state and exit code for process liveness, health and probe output for the configured test, and timestamped logs to reconstruct events. Every signal has limits and none is a verdict by itself.
این فصل metrics، dashboard، log مرکزی یا orchestration نساخت؛ فقط مشاهدهٔ یک container را دقیقتر کرد. فصل بعد image را در registry منتشر میکند: وقتی image از ماشین تو بیرون میرود، باید بدانی کدام نسخه را push و pull کردهای و نشانهٔ هویت آن چیست.
This chapter did not build metrics, dashboards, centralized logging, or orchestration; it sharpened how to observe one container. Next we publish images to a registry. Once an image leaves your machine, you need to know which version was pushed and pulled and what identifies it.
مرجع سریعQuick reference
docker ps -aوضعیت را ببین؛ Up بهمعنی healthy نیستread state; Up does not mean healthydocker inspect --format '{{json .State.Health}}' NAMEوضعیت health و اجرای آزمون را بخوانread health status and probe recordscondition: service_healthyبرای آمادگی startup dependency در Compose صبر کنwait for dependency readiness during Compose startuprestart: unless-stoppedرفتار exit را مشخص کن؛ علت را تعمیر نمیکندchoose exit behavior; it does not fix the cause{{.RestartCount}}تعداد restart را کنار exit و log بخوانread restart count alongside exit state and logsdocker logs -f --tail=20 NAMEخطوط تازه را دنبال کن، نه همهٔ گذشته راfollow new output without dumping all historydocker compose logs --since=5m web dbبازهای محدود از چند service را مقایسه کنcompare a bounded window across servicesdocker inspect --format '{{.HostConfig.LogConfig.Type}}' NAMEوقتی log محلی نیست درایور را بررسی کنinspect the driver when local logs are missingبرای رفتار جاری، مرجع رسمی Dockerfile HEALTHCHECK، healthcheck سرویس Compose، ترتیب شروع و service_healthy، restart policyها، گزینههای docker logs، خواندن log container و logging درایور و rotation را ببین. پشتیبانی start_interval به نسخهٔ Compose و Engine وابسته است.
For current behavior, consult the official references for Dockerfile HEALTHCHECK, Compose service healthchecks, startup order and service_healthy, restart policies, docker logs options, reading container logs, and logging drivers and rotation. start_interval support depends on Compose and Engine versions.