Skip to main content

Як встановити health check для контейнера?

Container healthcheck це як Docker (і Compose, Swarm, K8s) розрізняють «процес up» і «застосунок реально працює». Без healthcheck єдиний сигнал це «чи живий PID 1?», що пропускає кожен цікавий failure mode.

Теорія

TL;DR

  • Healthcheck це команда, що Docker періодично запускає всередині container. Exit 0 = healthy; non-zero = unhealthy.
  • Три стани: starting (ще у start_period), healthy, unhealthy (retries разів підряд провалено).
  • Постав через HEALTHCHECK у Dockerfile, --health-cmd на docker run або healthcheck: у Compose.
  • Використовується у docker ps, у Compose depends_on: service_healthy, у Swarm для рішень про заміну реплік.
  • Поширена команда: curl -f http://localhost:<port>/health. Тонкий момент: команда має існувати всередині container.

Швидкий приклад

dockerfile
FROM node:22-alpine WORKDIR /app COPY . . RUN npm ci --omit=dev EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1 CMD ["node", "server.js"]
bash
$ docker run -d --name api myapp $ docker ps CONTAINER ID IMAGE STATUS NAMES a3f9d2b8c1e4 myapp Up 30 seconds (healthy) api # Status тепер включає (healthy) / (unhealthy) / (starting)

Через ~30 секунд перша перевірка крутиться. Якщо успішно, статус стає (healthy).

Чотири флаги, що важать

--interval=DURATION # як часто крутити перевірку (дефолт 30s) --timeout=DURATION # макс час, що перевірка має для повернення (дефолт 30s) --retries=N # скільки невдач до unhealthy (дефолт 3) --start-period=DURATION # grace при старті; невдачі тут не рахуються (дефолт 0s)

Для типового веб-сервісу: --interval=30s --timeout=3s --retries=3 --start-period=10s нормально. Застосунки, що беруть 30+ секунд на прогрів (JVM, великі Python ML-сервіси), потребують довшого --start-period (60-120с).

Compose-синтаксис

yaml
services: api: image: myapp healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 3s retries: 3 start_period: 10s web: image: nginx depends_on: api: condition: service_healthy # чекати, поки api healthy перед стартом

depends_on: condition: service_healthy це killer-feature, чекає, поки healthcheck deps пройде, перед стартом dependent. Набагато надійніше за просту list-форму.

Три форми test-команди

yaml
# Форма 1: CMD (краще, без shell) test: ["CMD", "curl", "-f", "http://localhost:3000/health"] # Форма 2: CMD-SHELL (з shell, дозволяє && || env-розгортання) test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"] # Форма 3: NONE (вимкнути успадкований healthcheck з base-image) test: ["NONE"]

Форма CMD швидша (без shell-процесу). CMD-SHELL потрібна для env-розгортання або shell-логіки.

Поширений провал: команди немає у container

Найпоширеніший healthcheck-баг:

dockerfile
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1

але image це alpine без встановленого curl. Healthcheck завжди падає.

Рішення:

  • RUN apk add --no-cache curl (або для Alpine часто wget уже є: wget -q --spider URL)
  • Для Node-застосунків: node -e "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode===200?0:1))" (без додаткового пакета)
  • Distroless image часто потребують вбудованого бінаря: включи /healthcheck-бінар у Dockerfile, що виходить 0/1

Inspecting health

bash
# Поточний статус у ps docker ps --format 'table {{.Names}}\t{{.Status}}' # Повні деталі health docker inspect api --format '{{json .State.Health}}' | jq # { # "Status": "healthy", # "FailingStreak": 0, # "Log": [ # { "Start": "...", "End": "...", "ExitCode": 0, "Output": "..." }, # ... # ] # } # Live watch 'docker ps --format "table {{.Names}}\t{{.Status}}"'

Масив Log тримає останні 5 healthcheck-результатів, неоціненно для дебагу «чому unhealthy?».

Типові помилки

Без start_period, healthcheck падає під час повільного старту

yaml
# НЕПРАВИЛЬНО: застосунок потребує 60с на прогрів; перші 3 fail роблять unhealthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 5s retries: 3 # Через 15с container unhealthy і може рестартуватися # ПРАВИЛЬНО: start_period дає grace-вікно healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s start_period: 60s # невдачі під час цього вікна не рахуються

Healthcheck, що залежить від залежності

yaml
# НЕПРАВИЛЬНО: api-healthcheck пингує db; якщо db коротко down, api стає unhealthy test: ["CMD", "sh", "-c", "curl -f http://localhost:3000/health && pg_isready -h db"]

Мікс залежностей у твою liveness-перевірку, і transient db-збій рестартує твій api. Краще: healthcheck перевіряє лише власний readiness container; роби окремий /readiness endpoint, якщо застосунок має gate'ити трафік на основі health залежностей.

Удар у зовнішній URL у healthcheck

dockerfile
HEALTHCHECK CMD curl -f https://api.example.com/health || exit 1

Тепер health твого container залежить від чужого uptime. Не роби.

Вимкнути healthcheck не помітивши

dockerfile
FROM postgres:16 # успадковує healthcheck postgres. Якщо твій застосунок не exit 0 там, отримаєш unhealthy. # Або постав свій: HEALTHCHECK CMD pg_isready -U postgres # Або вимкни успадкований: HEALTHCHECK NONE

Реальне застосування

  • Compose з depends_on: service_healthy: чекати, поки db готова, перед стартом api. Найбільший практичний use.
  • Swarm-оркестрація: unhealthy-репліки вбиваються і замінюються. Healthcheck це сигнал.
  • Reverse proxy-інтеграція: Traefik і nginx-proxy можуть inspect'ити Docker healthcheck-стан, щоб роутити лише до healthy.
  • Monitoring-дашборди: скрейпити вивід docker inspect для health-статусу, alert на unhealthy.

Питання для поглиблення

Q: Яка різниця між Docker healthcheck і Kubernetes liveness/readiness probe?


A: Та сама ідея, інший scope. K8s розділяє liveness (чи я живий?) і readiness (чи я готовий до трафіку?), з різними поведінками (liveness рестартує; readiness прибирає з сервісу). Docker має лише один комбінований healthcheck. K8s не використовує Docker healthcheck, має свій.

Q: Який сигнал отримує unhealthy container?


A: Жодного, бути unhealthy не auto-restart саме по собі. З --restart=on-failure це не допомагає (немає exit code). Зі Swarm або Compose-з-оркестратором оркестратор вирішує замінити unhealthy task. Зі звичайним docker run ти (або твій monitor) дієш.

Q: Чи можна мати кілька healthcheck?


A: Лише один на container. Комбінуй логіку всередині однієї CMD-SHELL команди, якщо треба.

Q: Як вимкнути успадкований healthcheck з base-image?


A: HEALTHCHECK NONE у твоєму Dockerfile або test: ["NONE"] у Compose.

Q: (Senior) Коли healthcheck має робити більше ніж curl /health?


A: Додай розумніший /health-endpoint всередині застосунку, що перевіряє readiness downstream (пул з'єднань DB не вичерпаний, черга не накопичена понад поріг). Тримай саму healthcheck-команду простою, інтелект у endpoint, не у shell. Для сервісів з довгим warmup роби /livez (завжди OK, як тільки процес up) і /readyz (реальний readiness застосунку) окремо, і бери K8s-style підхід у Swarm через два endpoint.

Приклади

Compose-стек з healthcheck-gated стартом

yaml
services: api: build: . depends_on: db: condition: service_healthy healthcheck: test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/health || exit 1"] interval: 10s timeout: 3s retries: 3 start_period: 15s db: image: postgres:16 environment: POSTGRES_PASSWORD: dev healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s retries: 5 start_period: 5s
bash
$ docker compose up -d [+] Running 2/2 ✔ Container db Healthy 1.4s ✔ Container api Healthy 12.3s

Compose чекає, поки db healthy, перед стартом api. Без race condition, без «connection refused» на першому запуску.

Node-застосунок з вбудованим healthcheck (без extra-пакетів)

dockerfile
FROM node:22-alpine WORKDIR /app COPY . . RUN npm ci --omit=dev EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD node -e "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))" CMD ["node", "server.js"]

Curl не потрібен, використовує Node runtime, що уже є. Менший image, без extra-пакета.

Перегляд health-логів

bash
$ docker inspect api --format '{{range .State.Health.Log}}{{.End}}: exit={{.ExitCode}} out={{.Output}}\n{{end}}' 2026-04-30T10:00:00Z: exit=0 out=ok 2026-04-30T10:00:30Z: exit=0 out=ok 2026-04-30T10:01:00Z: exit=1 out=connection refused 2026-04-30T10:01:30Z: exit=1 out=connection refused 2026-04-30T10:02:00Z: exit=0 out=ok

Останні 5 результатів з exit-кодами і stdout. Часто відповідає на «чому є/був unhealthy?» без подальшого розслідування.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Коментарі

Ще немає коментарів