Zabbix — замечательный продукт для администраторов крупных программно-аппаратных комплексов. Он настолько хорош, что может использоваться не только крупным бизнесом, но и средне-малым бизнесом, и даже в pet-проекте. В общем, у меня есть небольшой опыт работы с Zabbix'ом и я смело могу рекомендовать его к использованию.


Правда я не могу сказать, что понимаю "философию Zabbix'а". Несмотря на обширную подробную документацию на русском языке, мне было сложно погружаться в мир Zabbix'а — создавалось ощущение, что мы с разработчиками одни и те же вещи называем разными именами. Возможно потому, что Zabbix создавался админами для админов, а я всё-таки больше разработчик и пользователь.


Тем не менее, для запуска Zabbix'а и для мониторинга основных параметров компьютерных систем (процессор, память и т.п.) навыков обычного linux-пользователя хватает. Есть большое количество плагинов от сторонних разработчиков, расширяющих возможности Zabbix'а. Для моих нужд мне потребовалось настроить мониторинг Redis-сервера. Я немного покопался в коде имеющихся плагинов и на их примере выяснил, что архитектура Zabbix'а позволяет достаточно просто подключать к мониторингу любые параметры информационных систем, которые могут быть выражены в числовом виде.


Под катом — пример Zabbix-плагина с моим пояснением по терминологии Zabbix'а. Кому-то этот пример покажется наивным, ну а кому-то поможет проще освоиться с понятиями. В любом случае, Zabbix достаточно велик для того, чтобы ощупать его с разных сторон.


Базовые понятия


Кратко о некоторых понятиях, которые используются в Zabbix'е: agents, items, triggers, actions, notifications, templates.


Сервер и агенты


С точки зрения пользователя Zabbix делится на две большие части: сервер и агенты. Сервер располагается на одной машине, которая собирает и хранит статистические данные, а агенты — на тех машинах, данные с которых собираются:


Zabbix server and agents


Параметры мониторинга


Любая величина, которая может выражена в числовом или строковом виде, называется в терминологии Zabbix'а — элементом данных (item). Каждый элемент связывается с уникальным ключом (именем). Вот примеры элементов данных:


  • system.cpu.load[percpu,avg1]: 0.1167
  • system.uname: "Linux supru 4.15.0-50-generic #54-Ubuntu SMP Mon May 6 18:46:08 UTC 2019 x86_64"

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


События


При наступлении некоторого события в Zabbix'е срабатывает триггер. Например,


  • {system.cpu.load[percpu,avg1].avg(5m)}>10 — среднее значение параметра за последние 5 минут превысило "10"
  • {system.uname.diff(0)}>0 — текущее значение параметра не равно предыдущему значению

По сути, триггеры — это формулы, в которых переменными выступают параметры мониторинга (текущие и сохранённые), и которые на выходе дают true/false.


Действия и Оповещения


В случае наступления события (срабатывания тригера) сервер может выполнить действие. Например, отправить оповещение по email'у на заданный адрес ("Problem: host is unreachable for 5 minutes"). Также действие может быть выполнено в случае возвращения триггера в исходное состояние ("Resolved: host is unreachable for 5 minutes"). Все события (переключения триггера) логируются на стороне сервера.


Шаблоны


Zabbix даёт возможность как настроить правила мониторинга для отдельного хоста, так и создать шаблон правил (template), который можно применять к различным хостам:


zabbix templates


На примере видно, что шаблон "Template App SSH Service" описывает одно приложение (Applications), один параметр мониторинга (Items), один триггер (Triggers). Также доступны описания для графиков, экранов, правил обнаружения и web-сценариев.


Постановка задачи для плагина


Начальное положение


Сам Zabbix предлагает свой собственный плагин для мониторинга состояния Redis'а, но на моей версии сервера (4.2.8) мне не удалось его задействовать (плагин для версии 4.4 и выше). Также предлагаются решения от третьих лиц (около десятка вариантов под различные версии Zabbix'а, на картинке только первых три):


Redis plugins for Zabbix


Каждый из них обладал своими плюсами-минусами, пришлось заглянуть внутрь, чтобы выбрать. Лучшим, на мой взгляд, оказался плагин Shakeeljaveed/zabbix-redis-userparamaters, состоявший из двух файлов:


  • README.md
  • redis-userparameter.conf

Немножко пришлось поработать "ручками", но зато на его примере стало чуть понятнее, как данные от агента попадают на сервер. По предложению автора Javeed Shakeel состояние Redis'а каждые 2 минуты сбрасывалось кроном в файл /tmp/redismetric:


*/2 * * * * /usr/bin/redis-cli info > /tmp/redismetric

А затем каждый параметр мониторинга извлекался агентом из файла /tmp/redismetric при помощи средств самой операционной системы. Инструкции для этого размещались в конфигурации Zabbix-агента /etc/zabbix/zabbix_agent.conf.d/userparameter_redis.conf. Например, вот так выглядят инструкция для извлечения параметра used_memory (использование памяти Redis-сервером):


UserParameter=used_memory,grep -w 'used_memory' /tmp/redismetric | cut -d: -f2

То есть, в файле /tmp/redismetric с выводом redis-cli INFO по ключу used_memory ищется строка (grep -w ...)


used_memory:7153216

которая затем разбивается на столбцы по разделителю ":" (cut -d: -f2). На выходе агент получает число 7153216 и присваивает его параметру used_memory.


Остаётся через web-интерфейс настроить сервер, чтобы он периодически отправлял запросы агенту на получение данных по параметру used_memory, после чего данные начинают литься на сервер, сохраняться в базе, по ним можно строить графики и создавать триггера, реагирующие на изменения этого параметра.


Цель


Задачей мониторинга состояния любой системы явлется не только сбор статистики, но и предупреждение о возникновении ситуаций, требующих вмешательства человека. Так как с Redis'ом я работаю на уровне очень начинающего пользователя, то пришлось поискать информацию, на какие параметры "здоровья" обращать внимание и что они значат. Наиболее достойной показалась статья "6 Crucial Redis Monitoring Metrics You Need To Watch". Проанализировав её, я пришёл к выводу, что "для полного счастья" мне нужно собирать данные для обнаружения следующих событий:


  • Memory fragmentation: used_memory_rss / used_memory > 1.5
  • Low cache hit ratio: (keyspace_hits)/ (keyspace_hits + keyspace_misses) < 0.8
  • Rejected connections: rejected_connections > 0
  • Evicted keys: evicted_keys > 0

Также я хотел собирать статистику по дополнительным параметрам (версия Redis'а, uptime и т.п.). В общем, имея общее представление о том, каким образом данные собираются агентом и передаются на сервер, "хотелки" можно сильно не ограничивать. В итоге получился список параметров для мониторинга из 12 позиций.


Создание собственного плагина


Параметры мониторинга


Плагин, который я анализировал, предполагал выполнение отдельной команды для получения отдельного параметра (элемента данных, item'а):


grep -w 'used_memory' /tmp/redismetric | cut -d: -f2

Т.е., для получения данных по 12 параметрам агент должен будет 12 раз выполнить различные наборы команд. А если мне нужно мониторить параметры, которые сложно извлечь цепочкой команд и нужно будет писать отдельный shell-скрипт или полноценную программу? Для таких "хотелок" Zabbix предлагает вариант с зависимыми элементами данных. Суть его в том, что на стороне агента скриптом формируется набор данных (например, в формате JSON), который передаётся на сервер в виде строкового параметра. Затем на стороне сервера происходит разбор полученных данных и вычленение из них отдельных элементарных параметров.


Основной элемент данных


Я описал основной элемент данных redis.info строкового типа с периодом обновления в 1 мин., без сохранения истории изменений:


base item


Предположительно, на стороне агента должен генерироваться такой JSON:


{
  "version": "4.0.9",
  "uptime": 1897336,
  "used_memory": 1054943416,
  "used_memory_rss": 1138159616,
  "keyspace_hits": 75810274,
  "keyspace_misses": 13545949,
  "connected_clients": 15,
  "rdb_last_save_time": 1580126954,
  "total_connections_received": 1258614,
  "rejected_connections": 0,
  "expired_keys": 60270,
  "evicted_keys": 0
}

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


Зависимый элемент данных


Тестовый параметр redis.info.version зависит от redis.info и сохраняет свои значения в базе в течение 90 дней. Периодичность мониторинга параметра зависит от базового элемента (redis.info):


dependent item (base)


Значение параметра redis.info.version извлекается из значения redis.info при помощи инструкций JSONPath:


dependent item (preprocess)


По аналогичной схеме описываются остальные зависимые элементы данных (параметры мониторинга), которые передаются в виде JSON'а. Вот пример описания числового параметра redis.info.used_memory:


dependent number item (base)


Всё достаточно прозрачно, за исключением Units и Trend storage period. Со вторым пунктом я не разбирался, оставил по-умолчанию, а единицы измерения объяснены в документации. В данном случае значение redis.info.used_memory измеряется в байтах и в web-интерфейсе сворачивается до кило/мега/гига/...-байт.


Формула для извлечения значения из JSON'а: JSONPath = $.used_memory


Вычисляемый элемент данных


Для вычисления фрагментации памяти используется отношение used_memory_rss / used_memory и на его базе определяется триггер, срабатывающий при превышении отношением значения 1.5. В Zabbix'е есть вычисляемый тип элементов данных:


calc item


Значение для параметра redis.info.used_memory_ratio вычисляется каждую минуту на основании последних значений двух других параметров (redis.info.used_memory_rss и redis.info.used_memory), сохраняется в базе в течение 90 дней и т.д.


Триггеры


Вот пример триггера, срабатывающего при излишней фрагментации памяти:



Ничего необычного, за исключением формата выражений, используемого в формуле изменения состояния триггера. В Zabbix'е есть конструктор форм, можно воспользоваться им или обратиться к документации/примерам (список триггеров доступен через web-интерфейс по адресу "Configuration / Templates / ${TemplateName} / Triggers").


Триггер может базироваться на любых элементах данных (item'ах) вне зависимости от их типа (основной, зависимый, вычисляемый).


Настройка агента


Генерация JSON'а


Для получения значений параметров мониторинга и формирования JSON'а я использую вот такой shell-скрипт:


#!/bin/bash
## ===========================================================================
#   Script to generate Redis monitoring data in JSON format
#   for 'zabbix-agent'.
## ===========================================================================
# collect working variables/data
BIN_REDIS=$(whereis -b redis-cli | cut -d" " -f2) # /usr/bin/redis-cli
DATA_INFO=$(${BIN_REDIS} INFO | tr -d '\r')       # get info and remove trailing '\r' from output

##
#   Extract stats and save it into env. vars.
##
# find lines with 'grep', cut second field after ":" and put it into env. variable:
ITEM_VERSION=$(grep "^redis_version:" <<<"${DATA_INFO}" | cut -d: -f2)
ITEM_UPTIME=$(grep "^uptime_in_seconds:" <<<"${DATA_INFO}" | cut -d: -f2)
ITEM_USED_MEMORY=$(grep "^used_memory:" <<<"${DATA_INFO}" | cut -d: -f2)
ITEM_USED_MEMORY_RSS=$(grep "^used_memory_rss:" <<<"${DATA_INFO}" | cut -d: -f2)
ITEM_KEYSPACE_HITS=$(grep "^keyspace_hits:" <<<"${DATA_INFO}" | cut -d: -f2)
ITEM_KEYSPACE_MISSES=$(grep "^keyspace_misses:" <<<"${DATA_INFO}" | cut -d: -f2)
ITEM_CONNECTED_CLIENTS=$(grep "^connected_clients:" <<<"${DATA_INFO}" | cut -d: -f2)
ITEM_RDB_LAST_SAVE_TIME=$(grep "^rdb_last_save_time:" <<<"${DATA_INFO}" | cut -d: -f2)
ITEM_TOTAL_CONNECTIONS_RECEIVED=$(grep "^total_connections_received:" <<<"${DATA_INFO}" | cut -d: -f2)
ITEM_REJECTED_CONNECTIONS=$(grep "^rejected_connections:" <<<"${DATA_INFO}" | cut -d: -f2)
ITEM_EXPIRED_KEYS=$(grep "^expired_keys:" <<<"${DATA_INFO}" | cut -d: -f2)
ITEM_EVICTED_KEYS=$(grep "^evicted_keys:" <<<"${DATA_INFO}" | cut -d: -f2)

# compose output JSON for Zabbix server:
echo -n "{"
echo -n "\"version\": \"${ITEM_VERSION}\","
echo -n "\"uptime\": ${ITEM_UPTIME},"
echo -n "\"used_memory\": ${ITEM_USED_MEMORY},"
echo -n "\"used_memory_rss\": ${ITEM_USED_MEMORY_RSS},"
echo -n "\"keyspace_hits\": ${ITEM_KEYSPACE_HITS},"
echo -n "\"keyspace_misses\": ${ITEM_KEYSPACE_MISSES},"
echo -n "\"connected_clients\": ${ITEM_CONNECTED_CLIENTS},"
echo -n "\"rdb_last_save_time\": ${ITEM_RDB_LAST_SAVE_TIME},"
echo -n "\"total_connections_received\": ${ITEM_TOTAL_CONNECTIONS_RECEIVED},"
echo -n "\"rejected_connections\": ${ITEM_REJECTED_CONNECTIONS},"
echo -n "\"expired_keys\": ${ITEM_EXPIRED_KEYS},"
echo -n "\"evicted_keys\": ${ITEM_EVICTED_KEYS}"
echo -n "}"

Этот скрипт я поместил в файл /var/lib/zabbix/user_parameter/redis/get_info.sh на сервере с Redis'ом, на котором уже установлен агент Zabbix'а. Пользователь, под которым запускается Zabbix-агент (обычно zabbix) должен иметь права на выполнение файла get_info.sh.


Файл userparameter_XXX.conf


На стороне агента дополнительные параметры мониторинга прописываются в файлах userparameter_*.conf в каталоге /etc/zabbix/zabbix_agentd.d. Поэтому для того, чтобы агент узнал о том, каким образом ему нужно собирать данные по параметру redis.info, я создал файл /etc/zabbix/zabbix_agentd.d/userparameter_redis.conf с таким содержимым:


UserParameter=redis.info,/var/lib/zabbix/user_parameter/redis/get_info.sh

Т.е., для получения данных по параметру redis.info агент должен запустить скрипт /var/lib/zabbix/user_parameter/redis/get_info.sh и передать на сервер результат выполнения.


После рестарта Zabbix-агента (sudo service zabbix-agent restart) у него появляется возможность собирать данные для параметра redis.info и отправлять их на сервер.


Резюме


Понимание Zabbix'а ко мне приходило (и всё ещё приходит) достаточно тяжело. Тем не менее я считаю его прекрасным инструментом, особенно после того, как для меня открылась простота добавления собственных параметров мониторинга (элементов данных). По большому счёту, достаточно добавить один файл на сервер с агентом (userparameter_XXX.conf) с shell-командой для сбора данных и настроить Zabbix-сервер на получение этих данных через web-интерфейс. И всё — можно накапливать данные, строить графики, анализировать изменения и создавать триггера, реагирующие на эти изменения.


Код шаблона, файла userparameter_redis.conf и скрипта get_info.sh можно посмотреть в проекте flancer32/zabbix_plugin_redis.


Спасибо всем, кто дочитал до конца, а особенно тем, кто нашёл в публикации что-то полезное для себя.