Zabbix 2.4 и Docker API: авто-обнаружение контейнеров вместо ручного прописывания хостов
Docker сломал классическую модель мониторинга: хосты живут минуты, IP меняется. Настроили LLD в Zabbix 2.4 через Docker API - новый контейнер сам появляется на дашборде.
Zabbix 2.4 получил улучшенное Low-Level Discovery и поддержку мониторинга через Docker API
Классическая схема мониторинга в Zabbix работала десятилетиями: добавил хост вручную, прописал IP, назначил шаблон - готово. Этот подход отлично работает на стабильной инфраструктуре. Проблема в том, что Docker эту стабильность отменяет.
На одном из наших managed-проектов к этому лету появилось несколько десятков контейнеров. Не оркестрация уровня Kubernetes - просто несколько хостов, на каждом по десятку-полтора контейнеров, Docker Compose в качестве управления. Контейнеры пересоздаются при каждом деплое: новый IP из внутреннего диапазона, новый container ID, а старого хоста в Zabbix уже нет. Мониторинг дырявый - и дыры появляются именно тогда, когда что-то идёт не так.
Откуда берётся проблема
У контейнеров нет постоянного IP. Даже если Docker-сеть настроена с фиксированными адресами, при пересоздании контейнера адрес может смениться - особенно если кто-то делал docker-compose down && docker-compose up в произвольном порядке. Добавлять каждый контейнер как отдельный хост вручную - это работа на час в день, и без гарантий что ничего не пропустил.
Второй момент: контейнеры появляются и исчезают по логике приложения. Контейнер для одноразовой задачи, который живёт две минуты, в Zabbix-модели выглядит как упавший хост. Начинают сыпаться алерты о недоступности, команда привыкает их игнорировать - и пропускает настоящую проблему.
Zabbix LLD и Docker API
В Zabbix 2.4 улучшили механизм Low-Level Discovery - возможность автоматически находить объекты для мониторинга по правилу и создавать под них прототипы элементов данных и триггеров. Раньше LLD использовали в основном для сетевых интерфейсов и разделов диска. Но ничего не мешает написать внешний скрипт, который возвращает JSON с нужными объектами - в нашем случае с контейнерами.
Docker API доступен через Unix-сокет /var/run/docker.sock или по HTTP если включён TCP-порт. Запрос к /containers/json возвращает список запущенных контейнеров со всеми метаданными: имя, ID, образ, сеть, порты, статус. Это именно то что нужно для LLD.
Схема получилась такая:
Zabbix-агент на Docker-хосте
-> внешний скрипт discovery.py
-> Docker API (/var/run/docker.sock)
-> JSON с макросами {#CONTAINER_NAME}, {#CONTAINER_ID}, {#IMAGE}
-> LLD создаёт элементы данных и триггеры по прототипу
Скрипт discovery опрашивает Docker API и отдаёт Zabbix-агенту список контейнеров в формате LLD. Агент передаёт это Zabbix-серверу, сервер создаёт элементы по прототипам. Новый контейнер появился - скрипт его нашёл, Zabbix создал айтемы. Контейнер умер и не должен мониториться - ставим keep_lost_resources на разумное время (у нас 30 минут) и лишнего шума нет.
Что именно мониторим
По каждому контейнеру через тот же Docker API снимаем:
- CPU -
docker statsотдаёт процент от квоты, если она задана, или от всего хоста. - Память -
memory_stats.usageиmemory_stats.limit. Если лимит не выставлен,limitравен полной памяти хоста - это нормально, смотрим на usage. - Статус -
docker inspect <id>возвращаетState.StatusиState.Running. Триггер наRunning = falseс учётом типа контейнера. - Сетевой трафик -
networks.<net>.rx_bytesиtx_bytesиз stats API. Полезно для контейнеров с активным I/O.
Отдельно с самого Docker-демона снимаем количество запущенных контейнеров, общее число образов и размер занятого места. Это уже уровень хоста, а не отдельных контейнеров.
Где споткнулись
Docker socket и права. Zabbix-агент работает от пользователя zabbix, у которого по умолчанию нет доступа к /var/run/docker.sock. Добавление в группу docker решает проблему, но это расширение привилегий - агент получает фактически root-доступ к Docker. Мы знаем об этом и на данный момент принимаем как компромисс на доверенных хостах клиента.
Ephemeral контейнеры. Задачи, которые живут меньше минуты, Zabbix успевает обнаружить и тут же потерять - появляются ложные алерты пока не истечёт keep_lost_resources. Решили на уровне именования: такие контейнеры в Compose называются с префиксом task_, в скрипте discovery фильтруем их по имени и не включаем в LLD.
Контейнеры без имени. Если контейнер запущен через docker run без --name, Docker даёт ему случайное имя типа jovial_wozniak. Эти имена бесполезны как идентификаторы в дашборде. Договорились с клиентом: все контейнеры в Compose-файлах явно именуются, запуск через голый docker run в продакшне запрещён административно.
Что в итоге
Мониторинг стал работать сам. Новый контейнер появляется на дашборде через интервал discovery - у нас 5 минут. Алертов о несуществующих хостах нет. Команда клиента видит состояние всего парка контейнеров без того чтобы кто-то вручную добавлял их после каждого деплоя.
Решение не идеальное - скрипты внешние, их надо поддерживать, Docker API между версиями меняется. Но оно работает прямо сейчас с Zabbix 2.4 и Docker 1.7, который стоит у клиента. Когда появится готовый шаблон от сообщества с нормальной поддержкой - перейдём, но ждать не стали.
Параллельно думаем о том же подходе для мониторинга самих образов - отслеживать что именно запущено, какой тег, чтобы видеть несоответствия между тем что задеплоено на разных хостах. Это уже следующий шаг.