Трендовые github проекты в нашем телеграм канале. Подпишись → Свой exporter для Prometheus как инженерный слой наблюдаемости
Prometheus хорошо работает там, где сервисы сами отдают метрики в понятном текстовом формате по HTTP. Для собственного backend это обычно решается библиотекой клиента: добавили счётчики, гистограммы, /metrics, несколько алертов — и система уже видна в Grafana. Сложнее становится с компонентами инфраструктуры, которые живут по своим правилам. Kafka часто завязана на JMX, сетевое оборудование говорит через SNMP, базы данных требуют запросов к внутренним таблицам или собственным API, Redis отдаёт диагностическую информацию через свой протокол.
В такой ситуации exporter становится переводчиком между миром конкретной системы и моделью Prometheus. Он забирает данные из внешнего источника, приводит их к стабильному набору метрик и публикует HTTP-эндпоинт для регулярного scrape. На практике это небольшой сервис, но от его качества зависит вся цепочка observability: дашборды, SLO, алерты и расследование инцидентов.
Go для такой задачи подходит естественно. В языке удобно писать сетевые сервисы, есть зрелые библиотеки для Prometheus, простая модель конкурентности и предсказуемая сборка в один бинарник. Exporter часто запускается рядом с инфраструктурой, в контейнере или как systemd-сервис, поэтому компактный статический артефакт сильно упрощает эксплуатацию.
Что именно делает exporter
Базовый цикл работы выглядит просто. Prometheus приходит на /metrics, exporter в этот момент собирает актуальное состояние целевой системы, преобразует значения и отдаёт текстовый ответ. В ответе каждая метрика имеет имя, тип, описание, значение и набор labels. За этой простотой скрываются важные проектные решения.
Первое решение — какие данные считать сигналом. У любой системы есть десятки или сотни чисел: counters, gauges, latency, размеры очередей, статусы соединений, ошибки, версии, лимиты. Если вытащить всё подряд, дашборды быстро превратятся в свалку, а Prometheus начнёт хранить дорогие time series без понятной пользы. Хороший exporter публикует метрики, которые помогают отвечать на эксплуатационные вопросы: работает ли сервис, растёт ли очередь, есть ли ошибки, приближается ли ресурс к лимиту, нарушается ли SLO.
Второе решение — как назвать метрики. Имена должны быть стабильными, предсказуемыми и понятными без чтения кода. В экосистеме Prometheus обычно используют snake_case, единицы измерения в имени и суффиксы вроде _total, _seconds, _bytes. Если сегодня метрика называется queue_lag, а через месяц превращается в consumer_delay, ломаются запросы PromQL, алерты и панели Grafana. Схему имён стоит проектировать как публичный контракт.
Третье решение — что помещать в labels. Labels дают гибкость: можно разделить метрики по топикам Kafka, базам данных, инстансам, интерфейсам или операциям. При этом каждый label увеличивает кардинальность. Значения, которые меняются слишком часто, опасны: user id, request id, полный путь файла, динамические имена задач. Exporter для инфраструктуры должен особенно внимательно контролировать кардинальность, потому что один неудачный label способен создать миллионы рядов.
Архитектура на Go
Минимальная структура exporter обычно состоит из трёх частей: клиента целевой системы, слоя преобразования и HTTP-сервера. Клиент знает, как получить исходные данные: сходить в REST API, выполнить SQL-запрос, обратиться к JMX-прокси, прочитать SNMP-таблицу или вызвать команду локального агента. Слой преобразования превращает эти данные в метрики Prometheus. HTTP-сервер публикует /metrics и служебные эндпоинты вроде /healthz.
В Go удобно разделить эти обязанности через интерфейсы. Например, collector может зависеть от абстрактного клиента, который возвращает структуру состояния. Тогда сбор метрик тестируется отдельно от реального подключения к Kafka, PostgreSQL или Redis. Это особенно полезно для edge cases: таймауты, частично пустые ответы, ошибки авторизации, недоступные узлы, неожиданные значения.
Для интеграции с Prometheus обычно используют client_golang. В простых случаях достаточно зарегистрировать gauges и counters, обновлять их перед ответом и отдать стандартный handler. Для динамического сбора лучше реализовать собственный collector с методами Describe и Collect. Такой подход позволяет получать свежие данные прямо во время scrape и аккуратно возвращать ошибку через специальные метрики, если источник временно недоступен.
Важный нюанс — таймауты. Prometheus сам имеет scrape_timeout, но exporter не должен ждать до последней миллисекунды. Если целевая система зависла, запрос к /metrics обязан завершиться быстро и предсказуемо. Контексты Go хорошо подходят для этого: на каждый сбор задаётся deadline, сетевые клиенты получают context, а частичные результаты маркируются отдельной метрикой состояния.
Метрики самого exporter
Exporter тоже является сервисом и нуждается в наблюдаемости. Помимо данных целевой системы полезно публиковать собственные метрики: длительность последнего сбора, количество ошибок, timestamp успешного scrape, версию exporter, число обработанных объектов. Это помогает быстро отличить проблему наблюдаемой системы от проблемы инструмента наблюдения.
Например, если в Grafana пропали метрики Kafka, причина может быть в самой Kafka, в сети, в правах доступа, в ошибке JMX или в падении exporter. Метрика exporter_up или scrape_success сразу показывает, смог ли exporter получить данные. Гистограмма длительности сбора показывает, приближается ли он к timeout. Счётчик ошибок с label по типу ошибки помогает увидеть повторяющуюся проблему авторизации или сетевую деградацию.
Служебные метрики лучше держать отдельно по префиксу. Так пользователи exporter смогут строить алерты на здоровье самого инструмента, не смешивая их с бизнесовыми или инфраструктурными сигналами целевой системы.
Как не перегрузить Prometheus
Главная эксплуатационная опасность exporter — неконтролируемый объём рядов. Особенно это заметно на системах с множеством сущностей: Kafka topics и consumer groups, таблицы PostgreSQL, Redis keyspace, сетевые интерфейсы, очереди и партиции. На старте кажется полезным публиковать всё, но через несколько недель Prometheus получает рост диска, медленные запросы и шумные алерты.
Перед добавлением метрики стоит задать три вопроса. Кто будет её смотреть? Какой alert или dashboard она поддерживает? Какая кардинальность получится в худшем случае? Если ответов нет, метрику лучше оставить за feature flag или включать через конфигурацию. Для exporter это нормальная практика: базовый набор метрик безопасен по умолчанию, расширенные наборы включаются осознанно.
Также стоит избегать labels с сырыми именами, если их пространство плохо контролируется. Для Redis опасно превращать ключи в labels. Для SQL — публиковать метрики по каждой пользовательской таблице без лимитов. Для Kafka — бездумно раскрывать комбинации topic, partition, group, member. В каждой системе нужен свой баланс между детализацией и стоимостью хранения.
Конфигурация и эксплуатация
Хороший exporter легко запускать в разных окружениях. Минимум настроек обычно включает адрес целевой системы, credentials, timeout, порт HTTP-сервера, список включённых collectors и параметры TLS. Credentials нельзя писать в аргументы командной строки, если их могут увидеть другие процессы или логи. Лучше использовать переменные окружения, файлы секретов или secret management платформы.
Для контейнерного запуска важны readiness и liveness endpoints. Readiness может проверять доступность конфигурации и базовую возможность подключения, а liveness подтверждать, что процесс жив и HTTP-сервер отвечает. При этом /metrics не стоит превращать в тяжёлую health check: Prometheus должен получать метрики по расписанию, а оркестратор — быстро понимать состояние процесса.
Версионирование метрик желательно фиксировать в changelog. Удаление или переименование метрики является breaking change для пользователей. Если схема меняется, лучше временно поддерживать старое имя, добавить новое и явно описать миграцию PromQL-запросов.
С чего начать реализацию
Практичный первый шаг — написать минимальный exporter с одной-двумя метриками, которые закрывают реальную эксплуатационную боль. Для Kafka это может быть lag consumer group, для PostgreSQL — состояние подключения и размер выбранных баз, для Redis — used memory и connected clients, для сетевого устройства — статус интерфейсов и счётчики ошибок. Такой узкий вертикальный срез сразу проверяет все важные части: подключение, сбор, формат Prometheus, scrape и визуализацию.
Дальше можно добавлять collectors итеративно. Каждый новый набор метрик должен иметь тесты на преобразование данных и пример PromQL. Это дисциплинирует схему имён и помогает понять, ради какого сценария метрика появилась. Документация рядом с кодом не менее важна, чем сам бинарник: оператору нужно знать, что означает значение, какие labels возможны и какие алерты рекомендуется строить.
Собственный Prometheus exporter — небольшой проект по объёму кода, но серьёзная часть платформенной инженерии. Он превращает закрытые или неудобные интерфейсы инфраструктуры в единый язык метрик. Если сразу думать о контрактах, кардинальности, таймаутах и наблюдаемости самого exporter, результат будет полезен не только для красивого дашборда, но и для спокойной поддержки production-систем.