Аннотация: Рассказываю, как наша команда реализовала мониторинг состояния шины и аналитику запросов к ней через обратный прокси. Пришлось повозиться, ведь Zabbix из коробки не очень успешно с этим взаимодействует.
В сложных интеграционных системах мониторинг является неотъемлемым инструментом как для инженера, так и для бизнеса. Zabbix показал себя надёжной и гибкой системой, которая позволяет строить понятные дашборды. Но я, как тимлид команды поддержки, столкнулся с тем, что шину Red Hat JBoss Fuse так просто к Zabbix не подключить. Шина работает на JVM, а значит, нужен мониторинг по JMX.
1. Что происходит на шине
Первое, что я сделал — включил в параметры запуска шины (в setenv) работу JMX:
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8921
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
Таким способом мы давно и надёжно наблюдаем стандартные JMX-метрики на всех поддерживаемых шинах. Отмечу, что в продакшн-средах я рекомендую включить аутентификацию (-Dcom.sun.management.jmxremote.authenticate=true) и TLS, используя внутренние CA.
Итак, ура! Через JConsole мы видим нашу шину и запущенные в ней модули (бандлы/бины). Теперь черёд за Zabbix'ом. Создаём кастомную метрику для мониторинга состояния бандла. Она отлично работает... пока модуль жив. Но если его остановить или перезапустить всю шину, Zabbix перестаёт получать данные и сыплет ошибками. Поэтому пришлось мониторить не состояние, а существование бандла, добавив предобработку.
Пример ключа:
jmx.get["beans","org.apache.camel:context=adapter.nav-camel.context-ru.rsins.esb.adapter.nav,type=components,name="properties""]
и предобработки:
return value.search(/properties/) > 0 ? 1 : 0
К сожалению, все модули уникальны, и применить единый шаблон не получилось. Около 15 кастомных метрик пришлось создать вручную. Зато теперь мы видим, если вдруг какой-то модуль «захотел передохнуть». По правде сказать, такого никогда не случалось. Зато когда инженер производит вынос или перезапуск шины, алерты приходят мгновенно — и это бальзам на душу заказчика.
2. Что ты делал 6 недель назад в этот самый час?
Маршрутизация к сервисам шины происходит через единую точку входа — Nginx. Удобно для пользователей. И тут заказчик ставит задачу: он хочет видеть количество обращений к каждому сервису, но не просто так, а в сравнении со средним значением. И это ещё не всё: нужно, чтобы каждый день и час недели сопоставлялся с таким же днём и часом за прошедшие 6 недель. Чего?!! Жизнь к такому не готовила. Или да?
Задача декомпозируется на две:
Получение количества обращений к каждому
locationв Nginx.Получение недельной статистики.
Для первой задачи используем классическое решение — создаём кастомный лог Nginx:
log_format conn 'realip_remote_port $uri';
access_log /var/log/nginx/conn.log conn;
Пишем bash-скрипт, который парсит лог и сопоставляет запросы к location с текущими открытыми соединениями:
#!/bin/bash
if [[ ($1) ]]; then
a_conn=`ss -npt | grep nginx | awk '{print $5}' | sort`
for u_conn in ${a_conn[@]}; do
t_conn+=(`printf "%s\n" | grep $u_conn /var/log/nginx/conn.log | tail -n1 | awk '{print $2}' | sort`)
done
fi
s=0
for item in ${t_conn[*]}
do
if [ "$item" == "$1" ]; then
let s++
fi
done
echo $s
unset s
exit 0
Даём Zabbix'у право запускать этот скрипт по расписанию. Вуаля! Красивые графики, радующие глаз заказчика, готовы.

Конечно, такое решение имеет ограничения по производительности и создаёт нагрузку на диск, но как быстрый win для малонагруженной системы — годится.
Для второй задачи вспоминаем школьную математику и создаём кастомные вычисляемые метрики. Штатных таких нет.
Вот пример формулы для усреднённого исторического значения:

По факту это среднее арифметическое исторических значений за тот же час недели в прошлом.
Изюминка — триггер, срабатывающий, когда текущее значение отличается от среднего на заданный процент. Выражение получается тяжеловесным и трудным для восприятия, так как сочетает усреднение, расчёт процента и сравнение:

Разумеется, для облегчения жизни будущих поколений мы задокументировали логику каждой такой метрики и триггера в Confluence.
Заключение
-
Работающее решение лучше идеального. Не скажу, что это изящная реализация, но иногда приходится отходить от красивых архитектур в пользу работающих. Главное — изолировать такую сложность, чтобы она не усложняла поддержку системы в будущем. Кстати, дашборд выглядит вполне приятно:

«Пилим слона». Задачи от заказчика иногда звучат пугающе. Важно с холодной головой разбивать даже самые немыслимые формулировки на простые технические шаги.
Взгляд в будущее. Идеальным следующим шагом было бы вынесение логики парсинга и агрегации в отдельный лёгкий микросервис, который отдавал бы метрики в Zabbix по API.
P.S. Ещё один интересный кейс интеграции
Пока писал статью, вспомнил ещё одну классическую, но не самую примитивную связку, которая у нас работает: Zabbix → PowerShell → MSSQL → шина.
Есть у нас один сервис отправки чеков в кассу. И отправляются они через очередь в БД. Размер данной очереди — это важная бизнес-метрика, которую необходимо всегда иметь на виду. База у заказчика MSSQL, а свежий Zabbix подружился пока только с PosgreSQL. Поэтому для заглядывания в базы других производителей рекомендуются скрипты. Что мы и реализовали на PowerShell, т. к. именно у этого заказчика шина вертится на MS Windows server.
Данный скрипт работает предельно просто:
1. Выполняет SQL-запросы к базе
2. Парсит результат и отдаёт в Zabbix только цифру — количество чеков.
Дальше Zabbix уже отслеживает меняются ли значения, или зависли и, в случае необходимости рассылает алерты.
Пример PowerShell-скрипта, который Zabbix запускает по расписанию:
# Мониторинг очереди платежей в ESB через MSSQL
# Zabbix вызывает этот скрипт для проверки количества "зависших" платежей
# Параметры подключения
$dataSource = "sql-server-host,1433"
$database = "ESB_Monitoring_DB"
$sql = @"
SELECT COUNT(*) as pending_count
FROM esb_event_log WITH (NOLOCK)
WHERE object_type = 'PAYMENT'
AND stream IS NOT NULL
AND status IN ('PROCESSED', 'PENDING')
"@
# Подключение через OLEDB
$connectionString = "Provider=sqloledb; Data Source=$dataSource; Initial Catalog=$database; User Id=monitoring_user; Password=********;"
$connection = New-Object System.Data.OleDb.OleDbConnection $connectionString
$command = New-Object System.Data.OleDb.OleDbCommand $sql, $connection
try {
$connection.Open()
$result = $command.ExecuteScalar() # Получаем одно значение COUNT
Write-Output $result
} finally {
if ($connection.State -eq 'Open') { $connection.Close() }
}
В Zabbix это реализовано, как внешняя проверка - (system.run[]) вызывает этот скрипт.

Плюшки для бизнеса: теперь видно не только «жива ли шина», но и «здорова ли она» с точки зрения бизнес-процессов. Если в очереди накапливается 100+ чеков без движения в течении 15 минут — это повод поинженерить до того, как пользователи начнут жаловаться.
А вам приходилось решать подобные задачи? Какие подходы вы использовали для мониторинга нестандартных систем или сложной аналитики нагрузки? Буду рад обсудить интересные кейсы в комментариях.