Zabbix Prometheus

Материал из naname.com.ua
Перейти к навигацииПерейти к поиску


Использование экспортеров 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 Переменные специфичные для экземпляра


Переменные окружения общие для всех инстансов

/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

Z-001 Screen Shot .png

Для того что бы добавить макросы к хосту

1 - Конфигурация 2 - Раздел Hosts, выбрать нужный хост 3 - Раздел макросы

Макросы которые я использую

{$PROMETEUS_EXPORTER_ADDR_0}
{$PROMETEUS_EXPORTER_PORT_0}

Обратить внимание на формат именования макросов хостов - это "{$ИМЯ_МАКРОСА}" в отличие от {#ИМЯ_МАКРОСА} в правилах автообнаружения.
Индекс _0 добавлен для поддержки будущих экспортеров, если такие будут

Создание шаблона

Все элементы добавляются к шаблону, подробно о шаблонах написано в документации Zabbix

Simple Item

Первый шаг - получить все данные
На этом простом элементе будут базироваться остальные элементы
По сути это обычный элемент данных HTTP

Z-002.png

Единственное отличие - то что адрес для подключения читается из макроса хоста

{$PROMETEUS_EXPORTER_ADDR_0}:{$PROMETEUS_EXPORTER_PORT_0}

Z-003.png
Остальные настройки (которые не попали на скриншот) оставлены по-умолчанию

Данные при нормальной работе выглядят примерно так (начало, показаны не все метрики)

# 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


Z-004.png

Discovery Rule

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


Создаем Discovery Rule


Правило базируется на уже существуюшум элементе данных
Z-005.png

Processing

В разделе Processing указать правило - какие именно метрики собирать
Z-006.png
Тут важно обратить внимание на 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"
         }, 

Макросы

Данные из этой структуры можно извлечь в макросы:
Z-007.png

$.labels['metric_name'] --> {#METRIC_NAME}
$.labels['gc_name']    ---> {#GC_NAME}
{#METRIC}=$['name']

Названия макросов произвольные (как и названия меток) - важно только что б они были одинаковы там где они задаются и там где к ним обращаются

Item Prototype

Прототип элемента данных создается с использованием макросов определенных ранее в правиле обнаружения


В моем примере 2 прототипа - снимаются "голые" данные и их изменение (Rate).
Замечу что обычно именно изменение важно для JMX метрик - например не "число ошибок со старта приложения" а "число ошибок за минуту" те что происходит именно сейчас.

Создание прототипа


Z-101.png
Важные моменты при создании прототипа:

  • Это зависимый элемент и базируется на основном
  • Имя составное и собирается из значений макросов определенных выше, число макросов может быть любым
GC Rate {#GC_NAME} {#METRIC_NAME}
  • Аналогично с именем ключа (пробелы недопустимы)
GC_Rate[{#GC_NAME}, {#METRIC_NAME}]


Z-102.png

Процессинг

Для извлечения данных из основного элемента используется простое правило (аналогично используется например в grafana):
ВНИМАНИЕ НА СКРИНШОТЕ КАК ТУТ ТАК И В ДОКУМЕНТАЦИИ ЗАББИКСА ПРАВИЛО ОБРЕЗАНО!!!

java_lang_GC{gc_name="{#GC_NAME}", metric_name="{#METRIC_NAME}"}

По сути оно означает "взять элемент с именем java_lang_GC и значениями меток которые совпадают со значениями макроса"
Для каждого item эти значения будут разные - будет создано столько элементов данных сколько есть разных меток
Z-103.png

Второе правило процессинга нужно для того что бы получить изменение данных вместо постоянно увеличивающихся счетчиков. Нужно оно или нет зависит от конкретной метрики

Дополнительно преобразование на 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

Z-201.png


Полные конфиги 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