Привет, Хабр!

У нас больше 40 сервисов, развернутых в docker-compose, у многих есть API, база данных, кэш, брокер сообщений, у одного только Posthog больше 20 контейнеров с воркерами, плагинами, ClickHouse, Redis и сопутствующей обвязкой.

Со временем появился простой запрос: хотим понимать, что вообще происходит с контейнерами на хосте, видеть всю информацию централизованно в Grafana и узнавать о проблемах через алерты, а не от юзеров. Понятно, что это не панацея, но большинство проблем все же решает.

В Docker для этого реализован healthcheck, если прописать docker ps, то будет видно статус контейнера - healthy / unhealthy, можно локально быстро понять жив он или нет. Но как только мы хотим превратить это в нормальный продовый мониторинг (Prometheus/Grafana/Alertmanager) - выясняется неприятное: Docker healthcheck не становятся метриками из коробки.

Разумеется, первым делом мы пошли смотреть в сторону cAdvisor, как главную тулзу в области мониторинга docker-контейнеров, но к нашему разочарованию он исторически не отдавал метрику healthcheck-статуса контейнеров (в issue это прямо сформулировано как “no metrics are available for healthcheck status of a container”).

Мы походили по рынку, посмотрели что уже реализовано, нашли пару проектов, которые либо уже лет 5 или больше не обновляются, либо сделаны, но нет понимания - будет ли это поддерживаться дальше или нет, да и реализованы весьма просто: многие решения инспектят контейнеры на каждый scrape Prometheus, что может быть шумно и нестабильно под нагрузкой.

В итоге мы пришли к довольно очевидному выводу: проще написать свой небольшой экспортер, который будет вести себя предсказуемо и закроет наши реальные требования.

Кстати, о требованиях, список был очень практичный и лаконичный:

  • Понимать реальное состояние сервисов, а не контейнер запущен;

  • Не забивать Docker API частыми запросами на каждом скрейпе Prometheus;

  • Иметь удобные для docker-compose (проект/сервис), чтобы не гадать по random-именам контейнеров;

  • Нормально жить в проде: systemd, отдельный пользователь, минимальные права, простой апгрейд/роллбек, либо docker исполнение;

  • Возможность фильтрации: на shared-хостах мониторить не все подряд, а только помеченные контейнеры.

Самое важное решение в проекте: разделить сбор данных и отдачу метрик, а главной идеей было следующее: не делать docker inspect на каждый scrape, а работать со снапшотом. В этом случае Prometheus может скрейпать хоть каждые 5 секунд - экспортер не начинает пересчитывать всё на каждый запрос.

Как все работает:

  • В фоне крутится цикл обновления: раз в N секунд он опрашивает Docker Engine, собирает состояния контейнеров и формирует снапшот.

  • Эндпоинт /metrics не ходит в Docker вообще - он просто отдаёт последний готовый снапшот. Поэтому /metrics становится быстрым и стабильным по времени ответа.

  • Частоту обновления можно настраивать (REFRESH_INTERVAL_SECONDS).

  • Чтобы не устроить “inspect-шторм” на хосте с сотней контейнеров, есть лимит параллелизма (MAX_CONCURRENCY).

Что именно мы считаем здоровьем контейнера

Healthcheck в Docker - штука полезная, но в реальности есть несколько состояний, которые хочется различать:

  • контейнер healthy;

  • контейнер unhealthy;

  • контейнер running, но healthcheck не задан (это тоже важная информация);

  • контейнер не запущен (а это уже критично для нас);

  • контейнер в странном/неизвестном состоянии (ошибка инспекта, гонки, проблемы с Docker API).

Есть еще отдельная мелочь, которая сильно помогает не спамить ложными алертами: экспортер пропускает одноразовые контейнеры, которые отработали и вышли с кодом 0 (типа миграций/джобов).

Метрики:

Основная метрика: docker_container_health_status{...} <value>

Пример (с лейблами compose_project и compose_service) можно посмотреть тут

Также мы сделали простые статусы:

  • 2 - healthy

  • 1 - running, но healthcheck не настроен

  • 0 - unhealthy

  • -1 - failed / unknown

  • -2 - critical (not running)

Почему так? Потому что:

  • легко алертить простыми порогами;

  • легко строить панели: "значение -2" - красное, "значение 0" - жёлтое, "значение 2" - зелёное.

Ещё один важный момент, который быстро всплывает в реальном продакшене - one-shot контейнеры. В любом docker-compose со временем появляются контейнеры, которые запускаются один раз, делают полезную работу (миграции, инициализация схемы и т.д.), а после корректно завершаются с exit code 0. Формально такие контейнеры находятся в состоянии exited. Но по смыслу - это не ошибка и не проблема, это нормальная часть жизненного цикла сервиса. Если наивно алертить всё, что не running, то такие контейнеры очень быстро превращаются в постоянный источник шума.

Как это выглядит в Grafana

Когда базовая логика была готова, следующим шагом стала визуализация. Мы собрали простой дашборд в Grafana, который показывает состояние контейнеров во времени:

max(docker_container_health_status) by (name)
max(docker_container_health_status) by (name)

На дашборде каждая строка - это сервис , а по оси времени видно, в каком состоянии он находился, такой формат очень быстро даёт ответ на главный вопрос: это разовая проблема или сервис живёт в деградированном состоянии уже какое-то время.

Также хорошо бы сделать саммари по всем контейнерам на хосте и видеть минимальное значение метрик, чтобы сразу понять, есть у сервиса проблемы:

min(min_over_time(docker_container_health_status[$__range]))
min(min_over_time(docker_container_health_status[$__range]))

Еще я бы рекомендовал настроить repeat по инстансам, чтобы самим не собирать под каждый хост панели.

Какие алерты мы строим

После того, как дашборд был собран, нам нужны алерты, чтобы самим глазами не мониторить состояние сервисов, а модель статусов это сильно упрощает.

Самый очевидный и самый важный кейс - контейнер вообще не работает:

docker_container_health_status == -2

Другой случай, контейнер работает, но его статус unhealthy:

docker_container_health_status == 0

Ну и если у нас сам экспортер не обновляет снапшот:

docker_healthcheck_exporter_snapshot_age_seconds > 30

Почему мы решили выложить это в open-source

Изначально Docker Healthcheck Exporter был внутренним инструментом, который мы писали под свои задачи и свою инфраструктуру. Без попыток сделать продукт, просто чтобы мониторинг docker-compose наконец начал отвечать на вопрос "сервис жив или уже нет".

Но по мере использования стало понятно, что эта проблема далеко не уникальна:

  • docker-compose по-прежнему широко используется в продакшене;

  • healthcheck есть почти в каждом сервисе, но редко где он становится полноценной метрикой;

  • готовых решений, которые ведут себя предсказуемо под нагрузкой, по-прежнему немного.

В какой-то момент мы решили, что нет смысла держать этот инструмент только у себя. Он небольшой, понятный по архитектуре и уже решает реальные проблемы. Поэтому мы оформили его, добавили документацию, сборку пакетов и выложили в open-source.

Проект живой: мы используем его сами, постепенно улучшаем и будем рады:

  • идеям;

  • баг-репортам;

  • pull request’ам;

  • и просто обратной связи.

Если у вас много сервисов в docker-compose и вы хотите понимать их реальное состояние - возможно, этот инструмент окажется полезным и вам.

Репозиторий проекта тут.

Комментарии (2)


  1. fujinon
    11.01.2026 21:06

    Мне кажется, основное назначение healthcheck в докере - это управление последовательностью старта контейнеров, когда контейнеры ждут, когда подтвердится healthy статус контейнера, от которого они зависят (напр. БД). То есть это больше внутренний инструмент compose, нежели метрика. Мне бы не пришло в голову это выводить в графану.


    1. bill876
      11.01.2026 21:06

      Если бы это было единственное назначение, докеру бы не было смысла продолжать делать проверки после того, как все сервисы запустились.

      В референсе к докерфайлам тоже явно написано:

      The HEALTHCHECK instruction tells Docker how to test a container to check that it's still working.

      Не "started working" и не "becomes ready".

      К тому же, контейнер можно запустить и без compose.