Zabbix Prometheus
Использование экспортеров Prometheus в Zabbix
Начиная с версии 4.2 Zabbix поддерживает работу с экспортерами Prometheus
Строго говоря это обычные HTTP метрики однако работать с ними стало более-менее удобно.
Эта заметка появилась в связи с недостатком документации
Замечание
Возможно что снимать метрики JMX в условиях интернет можно более эффективно используя нативные средства Zabbix, такие как Zabbix Proxy но этак гипотеза мной не проверялась,
Постановка задачи
- Существует приложение на Java с включенным экспортом JMX-метрик (как включить экспорт см тут: !!!! )
- Требуется снимать метрики JMX в Zabbix
- Метрики динамические (например время чтения из keyspace, имена которых заранее неизвестны), что требует Autodiscovery
- Доступ к интерфейсу JMX приложения осуществляется через интернет (задержки до 100мс)
Вероятно из-за задержек при значительном (~1000) числе метрик часть данных начинала теряться, причем заранее предсказать какая именно метрика будет потеряна нельзя. Все попытки решить проблему настройками Zabbix Java Gateway (увеличение числа потоков/Heap Size/Разные версии Gateway) к успеху не привели
Суть решения в том что вместо того что б делать медленный запрос со 100мс задержкой на каждый объект JMX, локальный экспортер делает те же запросы через localhost c ~0 RTT и отдает в ответ по HTTP все метрик в одном запросе.
Дальнейший разбор метрик происходит уже на стороне Zabbix
Включение JMX на стороне приложения
Это одна строка - разбита для удобства
JAVA_OPTIONS=" -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.net.preferIPv4Stack=true -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.port=8004 -Dcom.sun.management.jmxremote.rmi.port=8004 -Djava.rmi.server.hostname=ec2-XX-XX-XX-XX.eu-west-1.compute.amazonaws.com" "
Важно:
-Djava.rmi.server.hostname
должно сожержать правильное имя или адрес. Именно туда будет конектится JMX клиент за метриками
Prometheus Exporter
Введение в Prometheus Exporter
Prometheus использует отдельные процессы-экспортеры для сбора данных которые запускаются на хостах, и отдают данные по запросу (pull модель) по протоколу http (подробное описание Prometheus выходит за рамки этой заметки)
Для решения задачи предпологается:
- на каждом хосте с JVM запустить JMX Exporter (https://github.com/prometheus/jmx_exporter)
- Экспортер может работать как standalone HTTP сервер, те опрашивать приложение и отдавать метрики по HTTP
- Может встраиваться как агент (этот вариант я не могу использовать так как встраивание на-лету доступно только с 9-й Java а перезапуск приложений для меня недопустим)
- Со стороны Zabbix настраивается сбор метрик по HTTP
Настройка Prometheus JMX Exporter
Сборка
mvn package
или скачать готовый jar file Для запуска standalone server потребуется (с точностью до версии):
jmx_prometheus_httpserver-0.11.1-SNAPSHOT-jar-with-dependencies.jar
Конфигурация
systemd unit file
Потенциально может быть запущено несколько экземпляров экспортера, потому я подготовил юнит с поддержкой экземпляров:
/etc/systemd/system/prometheus-exporter@.service
[Unit] Description=Prometeus Exporter: %i After=network.target [Service] PIDFile=/var/run/prometheus-exporter.%i.pid Type=simple EnvironmentFile=/etc/prometheus-exporter/prometheus-exporter EnvironmentFile=/etc/prometheus-exporter/%i/env ExecStart=/usr/bin/java \ -jar ${JAR_FILE} \ ${BIND_ADDR}:${BIND_PORT} \ /etc/prometheus-exporter/%i/config.yaml SuccessExitStatus=0 143 [Install] WantedBy=multi-user.target
- /etc/prometheus-exporter/prometheus-exporter Переменные общие для всех экземпляров
- /etc/prometheus-exporter/%i/env Переменные специфичные для экземпляра
- %i - instance name , например в сервисе prometheus-exporter@localhost-cassandra %i будет заменено на localhost-cassandra
- Подробнее о других подстановках: https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Specifiers
Переменные окружения общие для всех инстансов
/etc/prometheus-exporter/prometheus-exporter
JAVA="/usr/bin/java" JAR_FILE="/usr/share/jmx_exporter/jmx_prometheus_httpserver-0.11.1-SNAPSHOT-jar-with-dependencies.jar"
Пояснений не требует
Переменные окружения специфичные для инстанса
Пример для сервиса /cassandra-localhost /etc/prometheus-exporter/cassandra-localhost/env
BIND_ADDR="0.0.0.0" BIND_PORT="65001"
BIND_ADDR / BIND_PORT - адрес и порт на котором запускать HTTP сервер с метриками
Конфигурация JMX Exporter
--- hostPort: localhost:7199 username: password: #lowercaseOutputLabelNames: true #lowercaseOutputName: true whitelistObjectNames: - "java.lang:type=GarbageCollector,*" - "java.lang:type=MemoryPool,*" - "java.lang:type=Memory,*" - "java.lang:type=ClassLoading,*" - "java.lang:type=Compilation,*" - "java.lang:type=MemoryManager,*" - "java.lang:type=OperatingSystem,*" - "java.lang:type=Runtime,*" - "java.lang:type=Threading,*" - "org.apache.cassandra.metrics:type=*,scope=*,*" # ColumnFamily is an alias for Table metrics blacklistObjectNames: - "org.apache.cassandra.metrics:type=ColumnFamily,*" - "org.apache.cassandra.metrics:type=Table,keyspace=system_auth,*" - "org.apache.cassandra.metrics:type=Table,keyspace=system,*" - "org.apache.cassandra.metrics:type=Table,keyspace=*,scope=*,name=BloomFilter,*" rules: - pattern: "java.lang<type=GarbageCollector, name=(.+)><>(.+):" name: java.lang_GC type: UNTYPED labels: "gc_name": "$1" "metric_name": "$2" ... skipped...
Конфигурационный файл более-менее подробно описан на странице prometheus-exporter, повторю кратко
- hostPort: localhost:7199 Адрес и порт приложения с JMX. В общем случае JMX Exporter может быть запущен и на другом хосте, особенно в случае локальной сети или одной zone для клаудов, когда RTT между хостами невелико. В примере указан localhost - мониторится приложение запущенное на том же хосте что и exporter на порту 7199 - Cassandra
- username / password Логин/Пароль если JMX настроен с авторизацией, в примере не используется
- whitelistObjectNames: Белый список объектов
- "java.lang:type=GarbageCollector,*" Пример объекта который нужно мониторить - метрики сборщика мусора JVM
- "org.apache.cassandra.metrics:type=*,scope=*,*" Еще один объект для мониторинга
- blacklistObjectNames: Черный список (перекрывает белый список если нужно добавить исключение)
- "org.apache.cassandra.metrics:type=ColumnFamily,*"
В каком виде отдать метрики задает секция rules
- rules: Начало секции
- pattern: "java.lang<type=GarbageCollector, name=(.+)><>(.+):" Паттерн определяет какие части метрики использовать - в примере первое выражение (.+) определит имя коллектора а второе - имя метрики (или скорее тип) например CollectionCount или CollectionTime
- name: java.lang_GC Имя метрики как она будет видна в http- ответе
- type: UNTYPED Тип метрики (уточнить но скорее всего не важен для zabbix)
- labels: Метки которые будут установлены на метрику
- "gc_name": "$1" $1 соответствует первому (.+)
- "metric_name": "$2" $1 соответствует второму (.+)
Вывод http выглядит так
curl 127.0.0.1:65001 2>/dev/null | grep -v '#' | grep java_lang_GC java_lang_GC{gc_name="G1 Young Generation",metric_name="CollectionCount",} 6091.0 java_lang_GC{gc_name="G1 Young Generation",metric_name="CollectionTime",} 1360730.0 java_lang_GC{gc_name="G1 Young Generation",metric_name="Valid",} 1.0 java_lang_GC{gc_name="G1 Old Generation",metric_name="CollectionCount",} 0.0 java_lang_GC{gc_name="G1 Old Generation",metric_name="CollectionTime",} 0.0 java_lang_GC{gc_name="G1 Old Generation",metric_name="Valid",} 1.0
Полный конфиг приведен в конце статьи
Zabbix
В версии 4.2 появилась работа с Prometheus Exporter
Добавить создание всех объектов через Zabbix API - конфигурирование руками подходит только для небольших установок
Предварительная конфигурация
В текущих версиях нет возможности указать дополнительные интерфейсы вроде JMX и ссылаться на них в темплейте, приходится использовать Host Macros
Для того что бы добавить макросы к хосту
1 - Конфигурация 2 - Раздел Hosts, выбрать нужный хост 3 - Раздел макросы
Макросы которые я использую
{$PROMETEUS_EXPORTER_ADDR_0}
{$PROMETEUS_EXPORTER_PORT_0}
Обратить внимание на формат именования макросов хостов - это "{$ИМЯ_МАКРОСА}" в отличие от {#ИМЯ_МАКРОСА} в правилах автообнаружения.
Индекс _0 добавлен для поддержки будущих экспортеров, если такие будут
Создание шаблона
Все элементы добавляются к шаблону, подробно о шаблонах написано в документации Zabbix
Simple Item
Первый шаг - получить все данные
На этом простом элементе будут базироваться остальные элементы
По сути это обычный элемент данных HTTP
Единственное отличие - то что адрес для подключения читается из макроса хоста
{$PROMETEUS_EXPORTER_ADDR_0}:{$PROMETEUS_EXPORTER_PORT_0}
Остальные настройки (которые не попали на скриншот) оставлены по-умолчанию
Данные при нормальной работе выглядят примерно так (начало, показаны не все метрики)
# HELP jmx_config_reload_success_total Number of times configuration have successfully been reloaded. # TYPE jmx_config_reload_success_total counter jmx_config_reload_success_total 77.0 # HELP cassandra_Table_ReadRepairRequests_count Attribute exposed for management (org.apache.cassandra.metrics<type=Table, keyspace=KEYSPACE_NAME, scope=SCOPE_NAME, name=ReadRepairRequests><>Count) # TYPE cassandra_Table_ReadRepairRequests_count untyped cassandra_Table_ReadRepairRequests_count{Table="TABLE_NAME",keyspace="KEYSPACE_NAME",} 0.0 cassandra_Table_ReadRepairRequests_count{Table="TABLE_NAME",keyspace="KEYSPACE_NAME",} 13.0
Discovery Rule
Все дальнейшие действия производятся с данными из базовой метрики, в том числе и процедура дискавери
Создаем Discovery Rule
Правило базируется на уже существуюшум элементе данных
Processing
В разделе Processing указать правило - какие именно метрики собирать
Тут важно обратить внимание на 2 момента
- Указать тип - преобразование в JSON
- Правильно указать способ преобразования
Например полученные данные выглядят так:
curl 127.0.0.1:65001 2>/dev/null | grep -v '#' | grep java_lang_GC
java_lang_GC{gc_name="G1 Young Generation",metric_name="CollectionCount",} 6091.0 java_lang_GC{gc_name="G1 Young Generation",metric_name="CollectionTime",} 1360730.0 java_lang_GC{gc_name="G1 Young Generation",metric_name="Valid",} 1.0 java_lang_GC{gc_name="G1 Old Generation",metric_name="CollectionCount",} 0.0 java_lang_GC{gc_name="G1 Old Generation",metric_name="CollectionTime",} 0.0 java_lang_GC{gc_name="G1 Old Generation",metric_name="Valid",} 1.0
Правило процессинга
java_lang_GC{metric_name=~".*", gc_name=~".*"}
говорит о том что надо выделить метки (labels - они определяются в конфигурации экспортера)
Пример ответа можно посмотреть здесь:
https://www.zabbix.com/documentation/4.2/manual/discovery/low_level_discovery/prometheus?s[]=prometheus
но для версии 4.2 тест сломан (баг заведен) и реально посмотреть не получится
По сути будет заполнена структура labels, для примера примерно так:
"labels": { "metric_name="CollectionCount", "gc_name": "G1 Young Generation" },
Макросы
Данные из этой структуры можно извлечь в макросы:
$.labels['metric_name'] --> {#METRIC_NAME} $.labels['gc_name'] ---> {#GC_NAME}
{#METRIC}=$['name']
Названия макросов произвольные (как и названия меток) - важно только что б они были одинаковы там где они задаются и там где к ним обращаются
Item Prototype
Прототип элемента данных создается с использованием макросов определенных ранее в правиле обнаружения
В моем примере 2 прототипа - снимаются "голые" данные и их изменение (Rate).
Замечу что обычно именно изменение важно для JMX метрик - например не "число ошибок со старта приложения" а "число ошибок за минуту" те что происходит именно сейчас.
Создание прототипа
Важные моменты при создании прототипа:
- Это зависимый элемент и базируется на основном
- Имя составное и собирается из значений макросов определенных выше, число макросов может быть любым
GC Rate {#GC_NAME} {#METRIC_NAME}
- Аналогично с именем ключа (пробелы недопустимы)
GC_Rate[{#GC_NAME}, {#METRIC_NAME}]
Процессинг
Для извлечения данных из основного элемента используется простое правило (аналогично используется например в grafana):
ВНИМАНИЕ НА СКРИНШОТЕ КАК ТУТ ТАК И В ДОКУМЕНТАЦИИ ЗАББИКСА ПРАВИЛО ОБРЕЗАНО!!!
java_lang_GC{gc_name="{#GC_NAME}", metric_name="{#METRIC_NAME}"}
По сути оно означает "взять элемент с именем java_lang_GC и значениями меток которые совпадают со значениями макроса"
Для каждого item эти значения будут разные - будет создано столько элементов данных сколько есть разных меток
Второе правило процессинга нужно для того что бы получить изменение данных вместо постоянно увеличивающихся счетчиков. Нужно оно или нет зависит от конкретной метрики
Дополнительно преобразование на Java Script
Для случаев когда метрика слишком большая и не влазит в Zabbix
java_lang_Threading_[CurrentThreadUserTime]" became not supported: Value 1174320000000.000000 is too small or too large.
Можно применить преобразование только к большим значениям используя кастомный делитель:
if (value > 1000000) { value = value / 10000000; }; return value
Полные конфиги JMX Exporter
Cassandra
--- hostPort: localhost:7199 username: password: #lowercaseOutputLabelNames: true #lowercaseOutputName: true whitelistObjectNames: - "java.lang:type=GarbageCollector,*" - "java.lang:type=MemoryPool,*" - "java.lang:type=Memory,*" - "java.lang:type=ClassLoading,*" - "java.lang:type=Compilation,*" - "java.lang:type=MemoryManager,*" - "java.lang:type=OperatingSystem,*" - "java.lang:type=Runtime,*" - "java.lang:type=Threading,*" - "org.apache.cassandra.metrics:type=*,scope=*,*" # ColumnFamily is an alias for Table metrics blacklistObjectNames: - "org.apache.cassandra.metrics:type=ColumnFamily,*" - "org.apache.cassandra.metrics:type=Table,keyspace=system_auth,*" - "org.apache.cassandra.metrics:type=Table,keyspace=system,*" - "org.apache.cassandra.metrics:type=Table,keyspace=*,scope=*,name=BloomFilter,*" rules: - pattern: "java.lang<type=GarbageCollector, name=(.+)><>(.+):" name: java.lang_GC type: UNTYPED labels: "gc_name": "$1" "metric_name": "$2" - pattern: "java.lang<type=MemoryPool, name=(.+)><(.*)>(.*) " name: java_lang_MemoryPool type: UNTYPED labels: "name": "$1" "usage": "$2" "option": "$3" - pattern: "java.lang<type=Memory><(.*)>(.*):" name: java_lang_Memory type: UNTYPED labels: "memory_type": "$1" "metric_name": "$2" - pattern: "java.lang<type=ClassLoading><>(.*):" name: java_lang_ClassLoading type: UNTYPED labels: "metric_name": "$1" - pattern: "java.lang<type=Compilation><>(.*):" name: java_lang_Compilation type: UNTYPED labels: "metric_name": "$1" - pattern: "java.lang<type=MemoryManager, name=(.*)><>(.*):" name: java_lang_MemoryManager type: UNTYPED labels: "metric_name": "$1" "option": "$2" - pattern: "java.lang<type=OperatingSystem><>(.*):" name: java_lang_OperatingSystem type: UNTYPED labels: "metric_name": "$1" - pattern: "java.lang<type=Runtime><>(.*):" name: java_lang_Runtime type: UNTYPED labels: "metric_name": "$1" - pattern: "java.lang<type=Threading><>(.*):" name: java_lang_Threading type: UNTYPED labels: "metric_name": "$1" - pattern: org.apache.cassandra.metrics<type=ThreadPools, path=(.*), scope=(.*), name=(.*)><>Value name: cassandra_ThreadPools type: UNTYPED labels: "path": "$1" "scope": "$2" "name": "$3" - pattern: org.apache.cassandra.metrics<type=(\S*)(?:, ((?!scope)\S*)=(\S*))?(?:, scope=(\S*))?, name=ReadLatency><>OneMinuteRate name: cassandra_$1_ReadLatency_OneMinuteRate type: UNTYPED labels: "$1": "$4" "$2": "$3" - pattern: org.apache.cassandra.metrics<type=(\S*)(?:, ((?!scope)\S*)=(\S*))?(?:, scope=(\S*))?, name=WriteLatency><>OneMinuteRate name: cassandra_$1_WriteLatency_OneMinuteRate type: UNTYPED labels: "$1": "$4" "$2": "$3" # # Emulate Prometheus 'Summary' metrics for the exported 'Histogram's. # TotalLatency is the sum of all latencies since server start # - pattern: org.apache.cassandra.metrics<type=(\S*)(?:, ((?!scope)\S*)=(\S*))?(?:, scope=(\S*))?, name=(.+)?(?:Total)(Latency)><>Count name: cassandra_$1_$5$6_seconds_sum type: UNTYPED labels: "$1": "$4" "$2": "$3" # Convert microseconds to seconds valueFactor: 0.000001 - pattern: org.apache.cassandra.metrics<type=(\S*)(?:, ((?!scope)\S*)=(\S*))?(?:, scope=(\S*))?, name=((?:.+)?(?:Latency))><>Count name: cassandra_$1_$5_seconds_count type: UNTYPED labels: "$1": "$4" "$2": "$3" - pattern: org.apache.cassandra.metrics<type=(\S*)(?:, ((?!scope)\S*)=(\S*))?(?:, scope=(\S*))?, name=(.+)><>Count name: cassandra_$1_$5_count type: UNTYPED labels: "$1": "$4" "$2": "$3"
Janus Graph
--- hostPort: localhost:8004 username: password: rules: - pattern: "java.lang<type=GarbageCollector, name=(.+)><>(.+):" name: java.lang_GC type: UNTYPED labels: "gc_name": "$1" "metric_name": "$2" - pattern: "java.lang<type=MemoryPool, name=(.+)><(.*)>(.*) " name: java_lang_MemoryPool type: UNTYPED labels: "name": "$1" "usage": "$2" "option": "$3" - pattern: "java.lang<type=Memory><(.*)>(.*):" name: java_lang_Memory type: UNTYPED labels: "memory_type": "$1" "metric_name": "$2" - pattern: "java.lang<type=ClassLoading><>(.*):" name: java_lang_ClassLoading type: UNTYPED labels: "metric_name": "$1" - pattern: "java.lang<type=Compilation><>(.*):" name: java_lang_Compilation type: UNTYPED labels: "metric_name": "$1" - pattern: "java.lang<type=MemoryManager, name=(.*)><>(.*):" name: java_lang_MemoryManager type: UNTYPED labels: "metric_name": "$1" "option": "$2" - pattern: "java.lang<type=OperatingSystem><>(.*):" name: java_lang_OperatingSystem type: UNTYPED labels: "metric_name": "$1" - pattern: "java.lang<type=Runtime><>(.*):" name: java_lang_Runtime type: UNTYPED labels: "metric_name": "$1" - pattern: "java.lang<type=Threading><>(.*):" name: java_lang_Threading type: UNTYPED labels: "metric_name": "$1" - pattern: "JanusGraph Cluster-metrics<name=(.*)><>Count" name: "JanusGraph_Count" type: UNTYPED labels: "metric": "$1" - pattern: "JanusGraph Cluster-metrics<name=(.*)><>Value" name: "JanusGraph_Value" type: UNTYPED labels: "metric": "$1" - pattern: "org.apache.tinkerpop.gremlin.server.GremlinServer.errors><>Count" name: "GremlinServer_errors_Count" type: UNTYPED # - pattern: "org.apache.tinkerpop.gremlin.server.GremlinServer.op.eval><>Count" name: "GremlinServer_op.eval_Count" type: UNTYPED