Ви це бачили: контейнер «Up», PID живий, панелі моніторингу зелені. Тим часом користувачі отримують 502,
черга росте як на дріжджах, а ваш on‑call вивчає нові лайливі слова трьома мовами.
Docker‑healthchecks призначені щоб запобігати саме цьому виду самообману. Надто часто їх пишуть як перший скрипт
стурбованого інтерна: «а процес запущений?» Це не healthcheck. Це пульсова перевірка пацієнта, який
може вже бути клінічно непридатним.
Визначте «здоровий» так, як має бути
«Здоровий» — це не «запущений». Контейнер може бути запущеним, але абсолютно марним: завис на м’ютексі, застряг на DNS,
вичерпав файлові дескриптори, не може дістатися до бази даних, повертає лише помилки або тихо втрачає повідомлення.
Ваше завдання — обрати визначення стану, яке відповідає успіху, видимому користувачу.
Думайте шарами:
- Стан процесу: бінарник живий, не в циклі перезапусків, не OOM‑killed.
- Стан сервісу: він може приймати запити і повертати коректні відповіді в межах допустимого (затримка, коди статусу, цілісність payload).
- Стан залежностей: він може дістатися до необхідних ресурсів (БД, кеш, брокер, DNS) і деградувати свідомо, якщо не може.
- Стан ємності: це не «живий, але насичений» (вичерпано пул потоків, черга заповнена, диск забитий).
Для Docker healthchecks найбільш корисне одне питання: Чи вважати цей контейнер придатним до обслуговування?
Це питання готовності. У Docker немає окремого readiness probe, як у Kubernetes, тому ви або:
- Використовуєте Docker‑healthchecks як «readiness» і не робите їх настільки агресивними, щоб контейнер вбивався через транзієнтні помилки.
- Або трактуєте їх як «liveness», але тоді приймаєте, що пропустите випадки «живий, але зламаний».
Моя одностороння думка: у стеку лише з Docker (Compose, Swarm, plain docker run) нехай healthchecks поводяться як readiness:
«чи можу я зараз виконати мінімально корисну роботу?» Це ловить ті збої, які хвилюють користувачів.
Цікаві факти та трохи історії
- Факт 1: Docker‑healthchecks з’явилися в епосі Docker 1.12, переважно щоб підтримати поведінку оркестрації без зовнішніх інструментів.
- Факт 2: Статус здоров’я зберігається на рівні контейнера і видимий через
docker inspect; це не перша черга метрик, якщо ви його не експортуєте. - Факт 3: Compose спочатку не блокував
depends_onзалежно від здоров’я; сучасний Compose може, але люди все ще вважають, що так було завжди. - Факт 4: Swarm використовує статус здоров’я для рішень планування сервісів, але «healthy» не завжди означає «в лоадбалансі» в кожній конфігурації.
- Факт 5: Healthcheck виконується всередині неймспейсів контейнера, тому він бачить контейнерний DNS, маршрутизацію і файлову систему — і це як плюс, так і мінус.
- Факт 6: Команда healthcheck виконується з
/bin/sh -c, коли ви використовуєтеCMD-SHELL; помилки з лапками та несподіванки з PATH — звичне явище. - Факт 7: Має значення код виходу, а не stdout. Люди люблять гарний вивід; Docker любить
0і «не 0». - Факт 8: Ранні «health endpoints» у веб‑додатках стали популярні, бо балансувальникам потрібен був простий yes/no сигнал; вони ніколи не заміняли повноцінний моніторинг.
- Факт 9: Перевірка, що займає забагато часу, ефективно стає самосаботажем для вашого контейнера, якщо вона накопичується або споживає дефіцитні ресурси.
Як Docker фактично оцінює healthchecks
Docker‑healthchecks — це невелика машина станів: починають зі starting, потім переходять у healthy, якщо перевірки проходять,
і в unhealthy після налаштованої кількості невдач. Це просто, що одночасно й привабливо, й небезпечно.
Що насправді роблять налаштування
- interval: як часто запускати перевірку.
- timeout: як довго чекати, перш ніж вважати перевірку невдалою.
- retries: скільки послідовних невдач потрібно, щоб позначити unhealthy.
- start_period: період поблажки, коли невдачі не рахуються (але перевірки все одно виконуються).
Healthcheck має бути:
- Дешевим (мілісекунди—десятки мілісекунд у нормі).
- Обмеженим (жорсткі тайм‑аути; без зависань).
- Репрезентативним (перевіряє те, що справді потрібно користувачам).
- Важко підробляти (не просто перевіряє існування файлу).
Один оперативний цитат, вартий наклеїти на стікер:
«Надія — це не стратегія.»
— Gene Kranz (часто цитують у контексті надійності інженерії).
Принципи проєктування, які ловлять реальні збої
1) Пробуйте критичний шлях, а не щасливий
Якщо ваш сервіс існує, щоб обслуговувати HTTP, підкріплений базою даних, то критичний шлях — «прийняти запит → виконати легкий DB‑запит → повернути».
Ваш healthcheck має пробувати цей шлях з мінімальними витратами.
Уникайте перевірки, яка лише зачіпає in‑memory endpoint і ніколи не торкається залежностей. Так ви отримаєте зелені healthchecks,
поки база даних горить.
2) Віддавайте перевагу дешевим синтетичним транзакціям над повними «self tests»
Хороша перевірка — це крихітна транзакція з обмеженим бюджетом: один легкий запит, один ping, один невеликий GET, який зачіпає маршрутизацію, автентифікацію й серіалізацію.
Погана перевірка — це повний набір інтеграційних тестів всередині продакшн‑контейнера.
3) Вирішіть, які помилки мають викликати перезапуск, а які — лише виключення з трафіку
Docker‑healthchecks самі по собі не перезапускають контейнери, якщо ви не поєднаєте їх з політиками чи поведінкою оркестратора. Навіть тоді,
перезапуск при кожній проблемі залежності — класичний шлях перетворити «транзієнтний збій БД» у «повний простій».
Якщо залежність впала, часто краще лишатися в онлайні й видавати деградовані відповіді або хоча б тримати процес «теплим» під час очікування.
Healthchecks можуть вивести вас з ротації (або не дозволити додати зарано), але не обов’язково мають вбивати процес.
4) Швидко фіксуйте виснаження ресурсів
«Живий, але насичений» — один із найвитратніших режимів відмови, бо виглядає як частковий успіх. Ваша перевірка має ловити:
забиті пули потоків, повні черги, заповнений диск або неможливість створити файли/сокети.
Гарний трюк — перевірити, що ви можете зробити невелике виділення або відкрити сокет, не перетворюючи healthcheck на бенчмарк.
5) Зробіть перевірку детермінованою та локальною, але не бездумною
Вона має виконуватись швидко й передбачувано. Це означає:
- Використовуйте фіксовані тайм‑аути (
curl --max-time,timeout). - Мінімізуйте мережеві хопи.
- Уникайте викликів сторонніх API з healthchecks. Якщо викликаєте — ви віддаєте свою доступність їхнім лімітам.
Жарт №1: Healthcheck, який викликає зовнішній API — це як питати сусіда, чи горить ваш дім, — текстовим повідомленням — під час торнадо.
6) Повертайте змістовні коди виходу і логуйтесь достатньо
Docker зберігає останні кілька виводів healthcheck. Скористайтеся цим. Друкуйте однорядковий контекст при невдачі: яка залежність впала, який тайм‑аут, який статус.
Не друкуйте мегабайти — це не бортовий регістратор польоту.
Шаблони healthcheck, що працюють у проді
Шаблон A: HTTP‑endpoint з перевіркою залежностей
Побудуйте /healthz endpoint, який перевіряє:
- додаток може приймати запити
- пул з’єднань DB може позичити з’єднання і виконати
SELECT 1(або еквівалент) - з’єднання з кешем/брокером, якщо вони є жорсткими вимогами
Тримайте його дешевим. Якщо потрібна глибока перевірка — винесіть її на інший endpoint (наприклад, /readyz проти /healthz),
але Docker дає вам один важіль, тож, ймовірно, ви оберете «readiness‑подібну» версію.
Шаблон B: Пряма перевірка залежностей з контейнера
Коли не можна змінити додаток, зробіть наступне: пробуйте сервіс зовні, але в неймспейсі контейнера.
Наприклад: перевірте, що локальний HTTP‑порт повертає 200, і що TCP‑порт БД досяжний.
Це менш семантично багате, ніж перевірки на рівні аплікації, але все одно ловить випадки «процес живий, але сокет не слухає».
Шаблон C: Затримка черги / поріг беклогу (обережно)
Для consumer‑ів «здоровий» може означати «я рухаюся вперед». Це може бути:
- збільшення offset повідомлень
- глибина черги нижче порогу
- rate dead‑letter не експоненційно зростає
Небезпека — флапінг: глибина черги може природно стрибати. Використовуйте пороги з повторними спробами й часовими вікнами, а не один зразок.
Шаблон D: Виявлення дедлоків і зависань event loop
Декілька найогидніших інцидентів — дедлоки й зависання: процес живий, порт відкритий, але запити ніколи не завершуються.
Ваш healthcheck має включати строгий бюджет часу відповіді. Повільний «успіх» — це прихована невдача.
Шаблон E: Правильність диска й файлової системи для stateful‑контейнерів
Якщо контейнер пише у volume, потрібно знати, коли файлову систему заповнено, змонтовано readonly або права зламалися після зміни образу.
Перевірте, що можете записати і fsync невеликий файл у потрібний шлях — час від часу, а не кожну секунду.
Шаблон F: Перевірка DNS
Сервіс, який не може вирішити внутрішні імена, відпаде так, ніби всі залежності впали. Простий getent hosts
або nslookup по потрібному імені може заощадити час.
Базові приклади (Dockerfile / Compose)
У Dockerfile використовуйте healthchecks лише якщо образ дійсно відповідає за runtime‑контракт. Якщо здоров’я залежить від деплоймент‑специфічних
залежностей, перевірки на рівні Compose часто краще підходять.
cr0x@server:~$ cat Dockerfile
FROM alpine:3.20
RUN apk add --no-cache curl
HEALTHCHECK --interval=10s --timeout=2s --retries=3 --start-period=20s \
CMD curl -fsS --max-time 1 http://127.0.0.1:8080/healthz || exit 1
cr0x@server:~$ cat compose.yaml
services:
api:
image: example/api:1.9.3
healthcheck:
test: ["CMD-SHELL", "curl -fsS --max-time 1 http://127.0.0.1:8080/healthz || exit 1"]
interval: 10s
timeout: 2s
retries: 3
start_period: 25s
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -h 127.0.0.1 || exit 1"]
interval: 5s
timeout: 3s
retries: 10
start_period: 20s
Зверніть увагу на ухил: API перевіряє себе на localhost (без неоднозначності з мережею), а Postgres використовує свій легкий інструмент для готовності.
Якщо можете використовувати спеціалізовані проби (pg_isready, redis-cli ping), робіть це.
Практичні завдання: команди, виводи, рішення (12+)
Healthchecks ламаються з причин, які рідко є загадкою. Зазвичай це «мережа», «DNS», «тайм‑аути», «права» або «виснаження ресурсів».
Ось конкретні завдання, що допоможуть припинити домисли.
Завдання 1: Переглянути статус здоров’я контейнера та останній вивід перевірки
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Status}}'
NAMES STATUS
api Up 2 hours (unhealthy)
db Up 2 hours (healthy)
Що це означає: Docker вважає, що healthcheck у api постійно не проходить.
Рішення: Негайно перегляньте логи healthcheck; не перезапускайте бездумно.
cr0x@server:~$ docker inspect --format '{{json .State.Health}}' api | jq
{
"Status": "unhealthy",
"FailingStreak": 7,
"Log": [
{
"Start": "2026-02-04T09:44:00.123456789Z",
"End": "2026-02-04T09:44:01.126789012Z",
"ExitCode": 1,
"Output": "curl: (28) Operation timed out after 1000 milliseconds with 0 bytes received\n"
}
]
}
Що це означає: Ваша перевірка тайм‑аутиться, а не повертає non‑200. Це — зависання, проблема бинду або сильна насиченість.
Рішення: Перевірте, чи сервіс слухає і чи localhost‑запити укладаються у бюджет часу.
Завдання 2: Перевірити команду healthcheck точно так, як її виконує Docker
cr0x@server:~$ docker inspect --format '{{json .Config.Healthcheck.Test}}' api
["CMD-SHELL","curl -fsS --max-time 1 http://127.0.0.1:8080/healthz || exit 1"]
Що це означає: Виконується через shell, отже діють shell‑семантики.
Рішення: Запустіть ту саму команду всередині контейнера, щоб підтвердити середовище й інструментарій.
Завдання 3: Запустити пробу вручну всередині контейнера
cr0x@server:~$ docker exec -it api sh -lc 'curl -fsS --max-time 1 http://127.0.0.1:8080/healthz; echo "rc=$?"'
curl: (28) Operation timed out after 1000 milliseconds with 0 bytes received
rc=28
Що це означає: Endpoint не відповідає в межах 1 секунди зсередини контейнера.
Рішення: Визначте, чи не слухає сервіс, чи застряг він, чи занадто повільний. Перейдіть до перевірок сокетів і логів аплікації.
Завдання 4: Перевірити, чи порт слухає (не припускаючи наявності netstat)
cr0x@server:~$ docker exec -it api sh -lc 'ss -lntp | head'
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("api",pid=1,fd=7))
Що це означає: Сервіс слухає на 8080.
Рішення: Якщо він слухає, але healthcheck тайм‑аутиться, підозрюйте зависання аплікації, витрати пулів потоків або блокування на залежностях.
Завдання 5: Заміряти латентність і код статусу з жорстким бюджетом
cr0x@server:~$ docker exec -it api sh -lc 'curl -s -o /dev/null -w "code=%{http_code} time=%{time_total}\n" --max-time 2 http://127.0.0.1:8080/healthz'
code=200 time=1.873
Що це означає: Повертає 200, але повільно — майже 2 секунди.
Рішення: Або обережно підвищіть бюджет healthcheck, або виправляйте повільність. Не «вирішуйте» це підняттям тайм‑аутів до 30 секунд.
Завдання 6: Перевірити логи контейнера навколо вікна невдач
cr0x@server:~$ docker logs --since 10m --tail 200 api
2026-02-04T09:41:12Z WARN db pool exhausted; waiting for connection
2026-02-04T09:41:13Z WARN db query timeout after 1500ms
2026-02-04T09:41:15Z ERROR /healthz dependency=db timeout=1500ms
Що це означає: Health endpoint робить саме те, що треба: повідомляє про проблеми з БД. Сервіс недостатньо здоровий, щоб обслуговувати.
Рішення: Припиніть налаштовувати healthchecks. Полагодьте БД або розмір пулу з’єднань. Дослідіть доступність БД і насиченість.
Завдання 7: Перевірити підключення до БД зсередини контейнера аплікації
cr0x@server:~$ docker exec -it api sh -lc 'nc -vz -w 1 db 5432; echo "rc=$?"'
db (172.20.0.3:5432) open
rc=0
Що це означає: TCP‑з’єднання є; це не базовий мережевий розрив.
Рішення: Дивіться на навантаження БД, аутентифікацію, налаштування пулу, латентність DNS або повільні запити — речі вище, ніж TCP.
Завдання 8: Перевірити час розв’язання DNS (прихований вбивця)
cr0x@server:~$ docker exec -it api sh -lc 'time getent hosts db'
172.20.0.3 db
real 0m0.003s
user 0m0.000s
sys 0m0.002s
Що це означає: DNS/hosts‑lookup тут швидкий.
Рішення: Якщо це повільно (сотні мс), виправте конфіг резолвера, Docker DNS або search domains. Не варто одразу звинувачувати БД.
Завдання 9: Перевірити вичерпання файлових дескрипторів
cr0x@server:~$ docker exec -it api sh -lc 'cat /proc/1/limits | grep -i "open files"'
Max open files 1024 1024 files
Що це означає: Малий ліміт FD. Під навантаженням ви можете дістати це і стати «живим, але марним».
Рішення: Збільшіть ulimit у визначенні сервісу; також перевірте на витоки FD.
Завдання 10: Шукати OOM‑killy та тиск пам’яті
cr0x@server:~$ docker inspect --format 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}' api
OOMKilled=false ExitCode=0
Що це означає: У цьому життєвому циклі контейнера не було OOM‑kill.
Рішення: Якщо true — виправляйте ліміти пам’яті, витоки або налаштування heap у JVM/node. Healthchecks не врятують процес, якого вбиває ядро.
Завдання 11: Підтвердити, що інструмент для healthcheck присутній у образі
cr0x@server:~$ docker exec -it api sh -lc 'command -v curl || echo "curl missing"'
/usr/bin/curl
Що це означає: Бінарник існує у очікуваному місці.
Рішення: Якщо відсутній, не «виправляйте» це хитрощами з busybox. Додайте інструмент або перепишіть перевірку під те, що ви дійсно доставляєте.
Завдання 12: Перевірити, чи healthcheck не жере CPU і не породжує зомбі
cr0x@server:~$ docker exec -it api sh -lc 'ps -o pid,ppid,stat,comm | head -n 10'
PID PPID STAT COMMAND
1 0 S api
42 1 S worker
77 1 S worker
201 1 S curl
Що це означає: Якщо ви бачите зростаючу купу процесів curl, ваш healthcheck може зависати й накопичуватися.
Рішення: Додайте тайм‑аути (--max-time) і переконайтеся, що команда перевірки не блокує назавжди.
Завдання 13: Використати події, щоб зв’язати «unhealthy» з перезапусками і деплойами
cr0x@server:~$ docker events --since 30m --filter container=api
2026-02-04T09:31:00Z container health_status: healthy
2026-02-04T09:40:10Z container health_status: unhealthy
2026-02-04T09:40:11Z container exec_start: curl -fsS --max-time 1 http://127.0.0.1:8080/healthz || exit 1
Що це означає: Воно стало unhealthy у конкретний час. Це ваша точка повороту.
Рішення: Перевірте історію деплоїв, перезавантаження конфігів, вікна технічних робіт БД, ротації сертифікатів, зміни DNS саме тоді.
Завдання 14: Перевірити, чи ви не покладаєтесь на хибну умову в Compose
cr0x@server:~$ docker compose ps
NAME IMAGE COMMAND SERVICE STATUS
stack-api-1 example/api "..." api running (unhealthy)
stack-db-1 postgres:16 "docker-entrypoint..." db running (healthy)
Що це означає: Compose запустив API, і він все ще працює, але позначений як unhealthy.
Рішення: Якщо ви очікували, що Compose затримає запуск API до готовності БД, перевірте, чи підтримуються й коректні ваші умови depends_on.
Завдання 15: Перевірити, чи «unhealthy» впливає на трафік (може і ні)
cr0x@server:~$ docker inspect --format '{{.State.Health.Status}} {{.Name}}' api
unhealthy /api
Що це означає: Docker знає, що контейнер unhealthy, але ваш reverse proxy може й досі надсилати трафік.
Рішення: Підтвердьте, що ваш балансувальник/проксі інтегрується зі статусом здоров’я, або реалізуйте явний upstream health механізм.
Жарт №2: Якщо ваш проксі ігнорує стан контейнера, той рядок «HEALTHCHECK» — по суті декоративна рослина: жива, зелена і нічого не вирішує.
Швидкий план діагностики
Коли дзвонить pager і контейнер «unhealthy», ваше завдання — знайти вузьке місце до того, як ви «залатаєте» його в ще гірший простій.
Ось порядок, який мінімізує час до істини.
Перше: доведіть, що саме ламається
- Перегляньте останній вивід healthcheck (
docker inspect ... .State.Health). Це тайм‑аут, non‑200, помилка DNS, помилка аутентифікації? - Запустіть ту саму команду вручну всередині контейнера. Якщо вона проходить інтерактивно, можуть бути відмінності в середовищі, PATH або в часі.
- Перевірте, чи сервіс слухає на очікуваному порту (
ss -lntp).
Друге: класифікуйте режим відмови
- Тайм‑аут: підозрюйте дедлок, витрачання пулу потоків, паузу GC, блокування по залежностях або проблеми ядра.
- Connection refused: процес не слухає, впав або прив’язаний не до того інтерфейсу.
- DNS failure: проблеми резолвера, неправильна мережа, забагато search domain або відсутнє ім’я сервісу.
- Non‑200: логіка аплікації, відмова залежності, неправильна конфігурація, незавершені міграції.
Третє: перевірте залежності та ресурси
- TCP‑доступність до залежностей (
nc -vz). - Логи аплікації про вичерпання пулів, тайм‑аути, помилки аутентифікації.
- Ліміти FD і поточне використання (
/proc/1/limits,lsofякщо є). - Заповненість диска і стан монтування (всередині контейнера, якщо він пише в volume).
Коли перезапуск допомагає, а коли шкодить
- Перезапуск допомагає, якщо процес завис і ваша система може терпіти втрачену роботу в польоті.
- Перезапуск шкодить, якщо залежність впала і аплікація потребує теплих кешів, міграцій або backoff‑логіки. Ви підсилите навантаження і продовжите відновлення.
Поширені помилки: симптом → причина → виправлення
Помилка 1: Healthcheck — це «ps | grep»
Симптоми: Здоров’я завжди показує healthy до падіння процесу, але користувачі бачать помилки хвилини/години.
Причина: Ви перевіряєте існування, а не функціональність.
Виправлення: Пробуйте реальний інтерфейс сервісу (HTTP/TCP) з жорстким тайм‑аутом; опціонально включіть перевірку залежностей.
Помилка 2: Healthcheck звертається до «статичного OK» endpoint
Симптоми: Здоров’я залишається зеленим під час відмов БД; кількість помилок зростає.
Причина: Endpoint не перевіряє критичні залежності.
Виправлення: Зробіть /healthz включаючим дешеві перевірки залежностей (позичити з’єднання до DB, ping кешу). Тримайте швидким і обмеженим.
Помилка 3: Немає тайм‑аутів, тож перевірки зависають і накопичуються
Симптоми: Багато завислих процесів curl; CPU зростає; контейнер стає нестабільним.
Причина: Команда healthcheck блокує безкінечно; Docker продовжує її планувати.
Виправлення: Використовуйте curl --max-time або timeout; тримайте timeout меншим за Docker‑тайм‑аут.
Помилка 4: Healthcheck занадто суворий і викликає флапінг
Симптоми: Контейнер коливається healthy/unhealthy; відбуваються перезапуски; трафік постійно мігрує.
Причина: Надчутливі пороги; інтервал дуже короткий; retries замалі; start_period замалий.
Виправлення: Збільшіть retries, подовжте interval, додайте start_period. Також виправте джерело латентних сплесків — не лише підганяйте тайм‑аути.
Помилка 5: Healthcheck використовує зовнішні DNS‑імена, не резольвовані в контейнері
Симптоми: curl: (6) Could not resolve host; періодично, залежно від середовища.
Причина: DNS контейнера відрізняється від хоста; відсутня мережа; неправильні search domains.
Виправлення: Використовуйте імена сервісів у Docker‑мережі; перевіряйте з getent hosts і задавайте DNS явно при потребі.
Помилка 6: Успіх healthcheck не впливає на трафік
Симптоми: Контейнер unhealthy, але запити все одно потрапляють на нього; користувачі бачать помилки.
Причина: Проксі/балансувальник не інтегрований зі статусом контейнера.
Виправлення: Налаштуйте проксі використовувати власні upstream‑перевірки або інтегруйтеся з оркестратором; не припускайте, що Docker‑health змінює маршрутизацію сам по собі.
Помилка 7: Healthcheck містить дорогі DB‑запити
Симптоми: Навантаження БД зростає з кількістю реплік; healthchecks падають під навантаженням і погіршують інцидент.
Причина: Healthchecks перетворились на міні‑навантажувальні тести; вони конкурують з продакшн‑трафіком.
Виправлення: Використовуйте дешеві запити з постійним часом виконання; кешуйте результати коротко в аплікації; зменшіть частоту.
Помилка 8: Healthcheck покладається на інструменти, яких немає в мінімальних образах
Симптоми: Healthcheck завжди падає з «command not found».
Причина: Ви використали curl, bash або nc, але відпустили scratch/distroless без них.
Виправлення: Додайте маленький спеціалізований probe‑бінар, використовуйте рідний health endpoint аплікації або включіть мінімальні інструменти навмисно.
Контрольні списки / покроковий план
Покроково: напишіть healthcheck, що ловить реальність
- Визначте контракт, важливий для користувача. «Може обслуговувати HTTP за 500мс і дістатися до БД.» Запишіть це.
- Вирішіть поведінку readiness vs liveness. У Docker‑only за замовчуванням — readiness‑стиль.
- Виберіть метод проби. Віддавайте перевагу endpoint аплікації; інакше локальний TCP/HTTP з жорсткими тайм‑аутами.
- Додайте жорсткі тайм‑аути. Кожна перевірка має закінчуватися швидко, навіть коли система хвора.
- Правильно обробляйте прогрівання. Використовуйте
start_period, щоб уникнути хибних негативів під час міграцій і прогріву кешу. - Тримайте перевірку дешево. Один ping DB, не звітний запит. Один HTTP‑запит, не повний логін‑флоу.
- Зробіть помилки пояснювальними. Однорядковий вивід на кшталт
db timeoutабоhttp 503. - Навантажте health endpoint тестами. Він має залишатися швидким під навантаженням; якщо він перш за все сповільнюється — це ненадійний сигнал.
- Підключіть health до рішень про трафік. Якщо ваш проксі його ігнорує — виправте це або не прикидайтеся, що healthchecks керують маршрутизацією.
- Переглядайте після інцидентів. Кожен простій навчає вас, що ваш healthcheck не виявив.
Контрольний список: не відправляйте в прод, поки не виконано
- Команда healthcheck має жорсткий тайм‑аут, коротший за Docker‑тайм‑аут.
- Healthcheck протестовано всередині запускаємого контейнера (не на вашому ноуті).
- Endpoint перевіряє хоча б одну критичну залежність, якщо сервіс без неї не працює.
- Start period охоплює найгірший час старту в проді (міграції, холодні кеші).
- Невдачі придатні для дій на основі останнього рядка логу healthcheck.
- Ви розумієте, що означає «unhealthy» у вашому деплойменті (перезапуск? виключення з трафіку? нічого?).
Три міні‑історії з практики
Міні‑історія 1: Інцидент через хибне припущення
Середній фінтех запускав стек Docker Compose за реверс‑проксі. Команда додала healthchecks і похвалилася:
API чекав на базу даних через depends_on, тож послідовність старту «вирішена».
Потім рутинне оновлення БД перезавантажило хост БД. API‑контейнери лишилися працювати, продовжували приймати запити й почали
повертати 500. Healthchecks залишалися зеленими, бо /health був статичним «OK». Проксі продовжував спрямовувати трафік
на всі репліки, які перетворились на фабрики помилок.
On‑call припустив, що Docker припинить маршрутувати до unhealthy контейнерів, бо так «по‑людськи» означає слово «health».
Але їхній проксі не читав статус Docker; він дивився лише, чи відкритий upstream TCP‑порт.
Рішення не було драматичним. Вони змінили endpoint, щоб включати дешевий DB‑ping, та налаштували проксі робити власну upstream‑перевірку цього endpoint.
Також задокументували, простими словами, що означає health у їхньому стеку. Важливим було не код — а видалити хибне припущення раніше, ніж воно знищило їхню суботу.
Міні‑історія 2: Оптимізація, що повернулась бумерангом
Медіакорпорація мала великий флот статлес API‑контейнерів. Хтось помітив, що healthchecks генерують «зайвий» DB‑трафік: по простому запиту кожні 5 секунд на контейнер накапичується.
Команда «оптимізувала», змінивши healthcheck на перевірку лише запущеності процесу і відкритого порту.
Через місяць стався інцидент: конфіг палу пулу з’єднань БД регреснув. Під навантаженням API приймав запити, а потім блокувався в очікуванні з’єднання до БД, поки не спливали тайм‑аути клієнтів.
Порт залишався відкритим, процес — живим. Healthchecks були задоволені. Користувачі — ні.
Аутедж тривав довго, бо симптоми були розмиті: CPU не був зашплинений, пам’ять виглядала нормально, логи помилок були шумними, але не визначальними.
«Оптимізація» забрала той автоматичний сигнал, який швидко класифікував би відмову: насичення залежності.
Вони повернули перевірку, чутливу до залежностей, але з розумнішим бюджетом: нижча частота, дешевий DB‑ping і кешування в endpoint, щоб уникнути stampede на БД.
І ще раз підтвердили банальну істину: якщо ви оптимізуєте спостережуваність — потім доведеться платити з відсотками.
Міні‑історія 3: Нудна, але правильна практика, що врятувала день
Провайдер SaaS мав нудну звичку: кожен сервіс мав письмовий «контракт здоров’я» в репозиторії.
Він описував, що перевіряє /healthz, чого не перевіряє, і який бюджет часу має витримати. Також був runbook‑сніпет:
точна команда healthcheck і як відтворити її в контейнері.
Під час ротації сертифікатів підмножина контейнерів почала падати при встановленні TLS до залежності. Healthcheck робив мінімальний виклик залежності й почав повертати 503 з однорядковою причиною: tls handshake failed.
On‑call не довелося гадати, DNS це, маршрути чи код. Вони запустили задокументовану команду в проблемному контейнері, побачили той самий TLS‑ланцюг помилок і порівняли середовища.
Проблему відслухали до застарілого CA‑пакету в базовому образі одного лінійки сервісів. Виправлення — перебудувати з актуальним CA‑пакетом і redeploy.
Ніхто не писав героїчний постмортем, бо нічого героїчного не сталося. Це було передбачувано, швидко класифіковано і швидко виправлено.
Нудна практика — стандартизовані контракти і відтворювані перевірки — не дозволила інциденту перетворитися на легенду.
FAQ
1) Чи має мій Docker‑healthcheck автоматично перезапускати контейнер?
Самі по собі Docker‑healthchecks не перезапускають контейнери. Перезапуски приходять від політик рестарту або оркестраторів. Вирішуйте залежно від режиму помилки:
перезапуск для дедлоків/зависань; уникайте перезапусків циклічно, коли залежності впали.
2) У чому різниця між Docker healthchecks і Kubernetes liveness/readiness?
Kubernetes розділяє «чи перезапустити?» (liveness) та «чи відправляти трафік?» (readiness). Docker дає один статус здоров’я,
тож ви маєте вирішити, що він означає, і забезпечити, щоб шар маршрутизації це поважав.
3) Наскільки суворими мають бути тайм‑аути?
Достатньо суворими, щоб виявляти зависання, але не настільки, щоб нормальний джиттер робив вас unhealthy.
Поширений шаблон — тайм‑аут 1–2 секунди, інтервал 10 секунд, retries 3, з start_period, який відповідає найгіршому старту.
4) Чи мають healthchecks включати DB‑запити?
Якщо сервіс не може працювати без БД — так, використовуйте дешевий запит або ping з жорстким тайм‑аутом. Якщо сервіс може деградуватися,
подумайте про легшу перевірку і виокремлене експортурування статусу залежностей для алертингу.
5) Мій health endpoint створює навантаження. Що робити?
Зробіть роботу константно‑часовою, кешуйте результати на короткий час і зменшіть частоту. Не видаляйте перевірки залежностей повністю; налаштуйте їх.
Переконайтеся, що health endpoints дешево обчислюються (без важкого auth, без гігантської JSON‑серіалізації).
6) Чому healthcheck проходить у docker exec, але падає у статусі Docker?
Поширені причини: інший shell, відмінності PATH, відсутні змінні середовища або таймінг (ваш ручний тест припав на добрий момент).
Порівняйте точну команду з .Config.Healthcheck.Test і запустіть її через sh -lc.
7) Використовувати CMD чи CMD-SHELL для healthchecks?
Віддавайте перевагу CMD (exec‑формі), коли можливо, бо це уникає проблем з лапками в shell. Використовуйте CMD-SHELL, коли потрібні пайпи,
умовні конструкції або кілька перевірок. Якщо використовуєте shell — будьте явними і уважними.
8) Як запобігти флапінгу під час стартових міграцій?
Використовуйте start_period, достатній для покриття найгіршого часу міграцій і холодних стартів. Також робіть health endpoint,
який під час прогріву повертає зрозумілу причину «starting», якщо це можливо.
9) Чи можна перевіряти диск і монтування у healthchecks?
Так, і робіть це для всього, що пише у volume. Перевірте, що шлях записуваний і не заповнений. Робіть це дешево і не дуже часто.
10) Добре, якщо healthcheck викликає інші сервіси?
Тільки якщо ті сервіси є жорсткими вимогами для коректності. Тримайте виклики мінімальними і обмеженими. Уникайте сторонніх сервісів і дорогих «глибоких» перевірок на гарячому шляху.
Наступні кроки, які ви можете зробити цього тижня
Якщо ваші нинішні healthchecks — це «процес запущений», ви не перевіряєте здоров’я — ви перевіряєте, чи горять вогні.
Замініть їх на проби, що відображають мінімальну корисну роботу сервісу: жорсткі тайм‑аути та змістовний вивід помилок.
Практичні наступні кроки:
- Обрати один критичний сервіс і переписати його healthcheck так, щоб він звертався до localhost HTTP з бюджетом 1–2 секунди.
- Оновити health endpoint, щоб він вибірково перевіряв принаймні одну критичну залежність (дешево) і повертав дієве текстове пояснення помилки.
- Додати
start_periodна підставі реального часу старту в проді, а не оптимізму. - Перевірити, що означає «unhealthy» у вашому шарі маршрутизації; зробіть так, щоб це мало значення, або перестаньте прикидатися, що має.
- Після наступного інциденту оновіть контракт здоров’я і runbook, щоб майбутньому вам не довелося знову відкривати ту саму проблему о 3‑й ранку.