Всем привет! В данной статье расскажу про проект logporter, который для меня полностью заменил решение от Google для централизованного и полнофункционального мониторинга контейнеров Docker.

Экспортер написан на Go и совмещает функции сборщика метрик, логов, проверки обновлений образов и встроенной панели мониторинга в одном легковесном образе.

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

Экспортер cAdvisor давно стал стандартом де-факто, когда требуется отслеживать производительность контейнеров на базе стека мониторинга Prometheus. Но у него есть одна очевидная и известная проблема - высокое потребление процессорного времени. Причина даже не в его встроенном веб-интерфейсе, а в архитектурном подходе и количестве получаемых метрик, не все из которых нужны на практике. Сам экспортер работает на более низком уровне, он лезет напрямую в ядро и парсит абсолютно все из /sys/fs/cgroup (включая детальные счетчики по каждому процессору и дисковому прерыванию), из-за чего при каждом цикле сбора метрик (скрейпинге) операционная система выполняет огромное количество тяжелых системных вызовов.

Вторая беда после процессора - это потребление памяти. Можно заметить, что после запуска cAdvisor потребляет всего 50 МБ, а через пару недель потребление может вырасти до 1-2 ГБ и закончиться падением по OOM (Out of Memory). Это потому, что в него встроен собственный движок хранения временных рядов (time-series). По умолчанию он хранит историю метрик за последние несколько минут прямо в ОЗУ (нужно для работы встроенного интерфейса, чтобы рисовать там графики), но проблема в другом - этот кеш очищается с задержкой. Если в вашей среде контейнеры часто создаются заново, то cAdvisor будет выделять память под каждый новый контейнер, но при этом вовремя не освобождать структуры данных уже мертвых инстансов. В итоге эти метаданные накапливаются в памяти как снежный ком.

Потребление ресурсов у cAdvisor может значительно превышать даже нагрузку монолитных систем (где есть и интерфейс, и БД, например, Prometheus), что для экспортера точно недопустимо. Конечно, cAdvisor можно протюнить с помощью аргументов командной строки согласно документации, но даже если выкрутить все метрики на минимум (оставив только базу из cpu,memory,diskIO,network), потребление ресурсов все равно выше, чем у logPorter (с учетом включенного сборщика логов со всех контейнеров).

Небольшой график сравнения производительности в стоковом состоянии (с 10:00 до 12:00) и после экспериментов с настройками.

Сравнение производительности cAdvisor и logPorter.
Сравнение производительности cAdvisor и logPorter.

Экспортер logPorter работает куда проще и прозрачнее, все метрики он собирает напрямую из сокета Docker используя официальный Go SDK, а также поддерживает удаленное tcp-подключение к сокету через стандартную переменную окружения DOCKER_HOST. Все получаемые метрики кешируются на стороне сервиса в течение заданного интервала времени, который определяется переменной окружения DOCKER_METRICS_CACHE, за счет чего минимизируется нагрузка при повторных вызовах вне интервала скрепинга (например, при использовании встроенного Dashboard).

Настройка

Развернуть полнофункциональный стек мониторинга возможно буквально за минуту. Сначала скопируйте репозиторий, после чего запустите группу контейнеров одной командой, используя docker compose:

git clone https://github.com/Lifailon/logporter
cd logporter
docker-compose up -d --pull always

Стек включает в себя Prometheus с подключенным экспортером (через конфигурацию в scrape_configs) и настроенными оповещениями (встроенный файл alert rules), сервер Loki, на который нацелена отправка логов через переменные окружения экспортера, а также Grafana с предварительно настроенными источниками данных (Prometheus и Loki) и добавленными панелями мониторинга для метрик и логов.

Панель мониторинга опубликована на Grafana Labs и поддерживает фильтрацию по именам контейнеров и лейблам compose:

Базовые метрики экспортера на панели мониторига Grafana.
Базовые метрики экспортера на панели мониторига Grafana.
Storage-метрики.
Storage-метрики.

Запуск и подключение экспортера к Prometheus стандартное, детальное руководство (в том числе описание переменных окружения для настройки и описание метрик) доступно в исходном репозитории.

Обновления образов

На панели мониторинга отображается сводная таблица со списком всех локальных образов и доступных для них обновлений в удаленных реестрах контейнеров (CR - Container Registry). Помимо официального Docker Hub, поддерживается GHCR от GitHub, LSCR от LinuxServer, и многие другие, с помощью библиотеки go-containerregistry.

Таблица со списком образов на панели мониторига Grafana.
Таблица со списком образов на панели мониторига Grafana.

Для анализа определения новых версий используются два подхода:

  • Семантическое сравнение версий (по умолчанию) - через библиотеку semver проверяется используемый локальный тег образа, если он удовлетворяет правилам семантического версионирования, из удаленного реестра запрашивается полный список тегов, из которого отбираются только семантически валидные, после чего текущий тег сравнивается с максимальным (последним по версии) найденным тегом. При наличии более нового тега (например, текущий тег образа 1.5.0, а в удаленном реестре последний 1.6.0) - обновление считается доступным. Если тег не является семантическим (например, latest или nightly), используется второй подход - сравнение digest.

  • Сравнение цифровых отпечатков (хеш-сумма) - используется как запасной вариант, когда у образа не семантический тег или в реестре нет семантических тегов. Через Docker API запрашивается актуальная хеш-сумма (sha256) образа в удаленном реестре и сравнивается с digest локального образа - если они различаются, обновление также считается доступным.

Сборщик логов для отправки в Loki

Очевидно, что для полноценного мониторинга одних только метрик недостаточно, требуется также читать сообщения, которые выводят контейнеризированные приложения в процессе своей работы. Мне очень нравится проект Dozzle, у него современный и легковесный интерфейс, но он не хранит историю сообщений и не строит Timeline. Стек на базе Elastic (ELK) в свою очередь слишком тяжеловесен, поэтому наиболее компромиссным решением является Loki из экосистемы Grafana.

Панель управления для просмотра логов в Grafana.
Панель управления для просмотра логов в Grafana.

При автономном запуске экспортера функционал сбора и отправки логов отключен, сначала его нужно нацелить на существующую инсталляцию Loki с помощью переменных окружения.

Встроенный Dashboard

Экспортер поддерживает встроенную панель мониторинга, которая содержит сводную информацию о состоянии всех контейнеров, образов и томов в табличном формате.

Встроенная панель мониторинга.
Встроенная панель мониторинга.

Интерфейс основан на Go template, который никак не влияет на общую производительность экспортера, поскольку использует кешированные данные для отрисовки шаблона.

Панель управления позволяет просматривать журналы контейнеров в режиме реального времени. Поддерживается несколько режимов фильтрации логов - AND (все ключевые слова должны присутствовать в строке), OR (достаточно любого ключевого слова), Regex (поддержка регулярных выражений) и Context (выводит по одной дополнительной строке до и после найденной, аналогично использованию grep -A 1 -B 1), а также стандартные опции из команды docker logs (выбор количества последних строк, фильтрация по потоку Stdout или Stderr и отображение таймстампов).

Просмотр и фильтрация логов.
Просмотр и фильтрация логов.

Итоги

logPorter не создает ничего принципиально нового, весь функционал по отдельности давно закрыт существующими инструментами. Основная цель проекта в том, что он объединяет возможности нескольких решений в одном легковесном образе. Вместо того, чтобы поддерживать cAdvisor, отдельный сборщик логов и инструмент проверки обновлений, вы получаете один контейнер на основе scratch образа (размером 8 Мбайт), который разворачивается одной командой и закрывает все перечисленные задачи.

Такой набор возможностей делает logPorter удобной точкой входа для мониторинга домашней инфраструктуры, а если вы захотите предложить идеи по доработке или внедрить что-то свое, смело открывайте Issue или Pull Request в репозитории.