Learning Platform
Глоссарий Troubleshooting
Урок 09.04 · 22 мин
Средний
dockerhealthcheckmonitoringcompose

HEALTHCHECK: проверка живости контейнера

Docker считает контейнер «живым», пока процесс PID 1 не упал. Но процесс может работать и при этом быть полусломан: Postgres запустился, но connections отказывает; HTTP-сервер слушает порт, но 500 на каждый запрос; Airflow worker процесс жив, но heartbeat не отправляется. Для таких случаев существует HEALTHCHECK — инструкция в Dockerfile (или в compose), которая периодически выполняется внутри контейнера и определяет реальное состояние сервиса.

В этом уроке: синтаксис HEALTHCHECK, exit codes, как HEALTHCHECK влияет на compose / depends_on / restart policy.


Синтаксис HEALTHCHECK в Dockerfile

HEALTHCHECK [OPTIONS] CMD command

Параметры:

  • --interval=30s — как часто запускать (default 30s, минимум 1s)
  • --timeout=5s — таймаут на один прогон (если CMD виснет — считается unhealthy)
  • --start-period=30s — initial grace period после запуска контейнера. В это время неудачные healthcheck’и не считаются «unhealthy». Полезно для slow-starting сервисов (Java, Postgres init).
  • --start-interval=5s — интервал во время start-period (default 5s)
  • --retries=3 — сколько подряд неудач до пометки unhealthy (default 3)

CMD может быть в shell или exec form:

# Shell form
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# Exec form
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD ["curl", "-f", "http://localhost:8080/health"]

CMD должен exit с одним из трёх кодов:

  • 0 — healthy
  • 1 — unhealthy
  • 2 — reserved, не использовать (Docker считает это unhealthy, но это deprecated)

Стандартный паттерн || exit 1: если curl упал — exit 1; если успешен — exit 0.


Exit codes и состояния

Контейнер с HEALTHCHECK имеет дополнительный статус. docker ps показывает:

$ docker ps
CONTAINER ID   IMAGE        STATUS                        NAMES
abc123def456   my-app       Up 5 minutes (healthy)        app

Статусы:

  • starting — контейнер запущен, но ещё в start-period; первый healthcheck не отработал
  • healthy — последний healthcheck успешен
  • unhealthyretries неудач подряд
# Получить только healthcheck status
docker inspect --format '{{.State.Health.Status}}' app
# healthy

# Получить лог healthcheck'ов (последние 5)
docker inspect --format '{{json .State.Health.Log}}' app | jq
# [
#   {
#     "Start": "2026-05-15T10:00:00Z",
#     "End": "2026-05-15T10:00:00.123Z",
#     "ExitCode": 0,
#     "Output": "OK"
#   },
#   ...
# ]

Реальный пример: HTTP-сервис

FROM python:3.13-slim

RUN apt-get update && apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD curl -fs http://localhost:8080/health || exit 1

CMD ["python", "server.py"]

Что это значит:

  • start-period=10s: первые 10 секунд после старта healthcheck’и не считаются failure (даже если падают). Это даёт серверу время инициализироваться.
  • interval=30s: каждые 30 секунд проверка.
  • timeout=5s: если /health долго не отвечает (>5s) — считается failure.
  • retries=3: 3 подряд failure → unhealthy.

В Python-приложении нужно реализовать /health endpoint:

# server.py (упрощённо)
from fastapi import FastAPI
import psycopg2

app = FastAPI()

@app.get("/health")
def health():
    try:
        # Лёгкая проверка зависимостей
        conn = psycopg2.connect("...")
        conn.close()
        return {"status": "ok"}
    except Exception as e:
        # FastAPI вернёт 500 по умолчанию через исключение
        raise HTTPException(status_code=503, detail=str(e))

Healthcheck без curl: альтернативы

curl или wget нужно ставить через apt — +5MB к образу. Альтернативы:

Python:

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health', timeout=3)"

Использует встроенный Python для HTTP-проверки. Не нужен curl.

exec через приложение:

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD /app/healthcheck.sh

healthcheck.sh — скрипт, который знает специфику сервиса.

Для DB:

# Postgres
HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=5 \
    CMD pg_isready -U postgres || exit 1
# Redis
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
    CMD redis-cli ping | grep PONG || exit 1

HEALTHCHECK в Compose

Compose может объявить HEALTHCHECK на уровне сервиса (часто override от Dockerfile):

# docker-compose.yml
services:
  app:
    image: my-app:v1
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      start_period: 10s
      retries: 3
  
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

Compose имеет два формата test:

  • ["CMD", "curl", "-f", "http://..."] — exec form (без shell)
  • ["CMD-SHELL", "pg_isready -U postgres"] — shell form (через /bin/sh -c)

Compose overrides Dockerfile HEALTHCHECK для этого сервиса. Если хочешь полностью отключить healthcheck:

services:
  app:
    healthcheck:
      disable: true

depends_on с condition: service_healthy

Самый полезный use case HEALTHCHECK в compose — синхронизация старта сервисов:

services:
  app:
    image: my-app:v1
    depends_on:
      db:
        condition: service_healthy   # ждёт пока db не healthy
      cache:
        condition: service_started   # ждёт только что cache запустился (default)
  
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      start_period: 30s
      retries: 10
  
  cache:
    image: redis:7

condition: service_healthy блокирует старт app пока db не вернёт healthy. Это решает классическую гонку «app стартует, пытается connect к Postgres, который ещё в init» — без healthcheck app падает с ConnectionRefusedError.

depends_on + healthcheck: упорядоченный старт
db: startingdocker compose up: запускает db первым
initializing
db: not readyPostgres делает init (создаёт database, applies init scripts). Healthcheck pg_isready возвращает 1 (not yet).
10-20s
db: healthypg_isready возвращает 0. Compose отмечает db как healthy.
condition met
app: startingApp стартует, может connect к db уверенно.

Restart policy и healthcheck

Liveness и readiness probe в Kubernetes

Docker по дефолту НЕ перезапускает unhealthy контейнеры — только упавшие (exit code != 0). Чтобы был auto-restart по unhealthy, нужно использовать orchestrator (Swarm, Kubernetes) или внешнюю систему мониторинга.

services:
  app:
    image: my-app:v1
    restart: unless-stopped     # auto-restart при exit
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3

restart: unless-stopped рестартит, если процесс упал. Но unhealthy != crashed. Standalone Docker не рестартит unhealthy.

В Kubernetes аналог — liveness probe: периодически вызывает endpoint, при failure k8s kill’ит pod и заменяет его. Это полноценный self-healing.

# k8s pod spec
livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 30
  timeoutSeconds: 5
  failureThreshold: 3

HEALTHCHECK NONE: отключение

Иногда нужно отключить унаследованный HEALTHCHECK из базового образа:

FROM nginx:1.27   # nginx имеет HEALTHCHECK по умолчанию

HEALTHCHECK NONE   # отключаем

Это полезно если параметры базового HEALTHCHECK не подходят (например, нужен другой interval).


Производительность HEALTHCHECK

Healthcheck выполняется внутри контейнера и тратит ресурсы (CPU, memory). На machine c 50+ контейнерами при interval=5s healthcheck’и могут заметно нагружать систему.

Best practices:

  • Использовать realistic interval: 10-30s для большинства сервисов. 1s только для критичных, где нужен super-fast failover.
  • Лёгкий healthcheck: проверять только liveness (процесс живой, базовые deps доступны). НЕ делать тяжёлые операции (SELECT * из базы, full-cache scan).
  • Различать liveness и readiness: liveness — «процесс жив»; readiness — «готов принимать трафик». В k8s это два разных probe.
# Хороший /health (только liveness)
@app.get("/health")
def health():
    return {"status": "ok"}    # просто что-то ответил

# Хороший /ready (readiness, проверяет deps)
@app.get("/ready")
def ready():
    db_ok = check_db_connection_quickly()
    if not db_ok:
        raise HTTPException(503, "db unavailable")
    return {"status": "ready"}

Попробуй сам

Создай контейнер с healthcheck:

mkdir healthcheck-demo && cd healthcheck-demo

cat > server.py <<'EOF'
from http.server import HTTPServer, BaseHTTPRequestHandler
import time, random

start = time.time()

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        # Симуляция slow start: первые 5 секунд /health возвращает 500
        if time.time() - start < 5:
            self.send_response(500); self.end_headers()
            self.wfile.write(b"warming up")
            return
        # После 5 секунд: 80% успехов, 20% failure
        if self.path == "/health":
            if random.random() < 0.8:
                self.send_response(200); self.end_headers()
                self.wfile.write(b"OK")
            else:
                self.send_response(500); self.end_headers()
        else:
            self.send_response(200); self.end_headers()
            self.wfile.write(b"hello")

HTTPServer(("0.0.0.0", 8080), H).serve_forever()
EOF

cat > Dockerfile <<'EOF'
FROM python:3.13-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY server.py .
EXPOSE 8080
HEALTHCHECK --interval=5s --timeout=2s --start-period=10s --retries=3 \
    CMD curl -fs http://localhost:8080/health || exit 1
CMD ["python", "server.py"]
EOF

docker build -t hc-demo .
docker run -d --name hc hc-demo

# Wait и проверяй статус каждые 5 секунд
for i in 1 2 3 4 5 6; do
    sleep 5
    docker inspect --format '{{.State.Health.Status}}' hc
done

# Получи лог healthcheck'ов
docker inspect --format '{{json .State.Health.Log}}' hc | jq

# Cleanup
docker rm -f hc
cd .. && rm -rf healthcheck-demo
docker rmi hc-demo

Проверка знанийKnowledge check
Команда добавила HEALTHCHECK CMD curl -f http://localhost:8080/health в Dockerfile, но в docker compose up контейнер сразу помечается unhealthy. Сервис на самом деле работает (curl с хоста проходит). В чём проблема?
ОтветAnswer
Скорее всего одна из двух причин: (1) curl не установлен в финальном образе. Если HEALTHCHECK использует curl, а в Dockerfile нет apt install curl, то команда падает с 'curl: command not found' -- exit code не 0 -- healthcheck failure. Решение: либо apt install curl в Dockerfile, либо HEALTHCHECK на python -c (urllib.request), либо wget если установлен. (2) start_period слишком короткий: сервис не успевает инициализироваться. По default start_period=0, retries=3, interval=30s -- через ~30s контейнер уже unhealthy если за это время не отдал 200. Решение: HEALTHCHECK --start-period=30s ... -- даст grace period на startup, в это время failures не считаются unhealthy.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Какой статус контейнера показывается, пока start-period ещё не истёк, но healthcheck'и падают?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5