Создание загружаемых модулей Zabbix на примере добавления протокола Modbus

Еще в версии Zabbix 2.2 добавились загружаемые модули, которые позволили расширять на новом уровне возможности системы. «Зачем это нужно?», — спросите вы, ведь запускать внешние скрипты и программы из Zabbix можно было всегда. Конечно, в первую очередь это скорость — модули, как и сам Zabbix, пишутся на C и при правильном подходе работают максимально быстро, в отличие от внешних программ, которые требуется запускать на каждый опрос. Многих может напугать необходимость писать код, но сегодня я хочу показать вам, что все не так уж и сложно.

Для примера, напишем модуль, который позволит Zabbix собирать информацию с устройств, работающих по широко распространённому в мире протоколу промышленной автоматизации — Modbus и снимем при помощи него показания температурных датчиков, а также получим параметры электроэнергии с счетчика Меркурий 230. В конце разместим наш модуль на портале share.zabbix.com, где пользователи могут делиться своими наработками по Zabbix.
3d5924c8386d4bb387d7e4e766eda0a1.jpg


Modbus — очень распространенный протокол, используемый в сетях промышленной автоматизации (заводы, цеха, прочие промышленные объекты, различные инженерные системы — везде возможно наткнуться на Modbus). Протокол был разработан в далеком 1979 году компанией Modicon, и за следующие десятилетия, благодаря его преимуществам, Modbus был реализован на огромном количестве различных устройств:
Простота
Modbus очень прост. Это позволяет запускать и реализовывать его на самых простых и дешевых железках. А также не отпугивает разработчиков от использования в своих решениях.
Поддержка Ethernet
Modbus вовремя подготовился к повсеместному проникновению Ethernet-сетей, в том числе и на промышленные объекты, и теперь работает не только по последовательным линиям RS-232,RS-485 (Modbus RTU/ ASCII), но и через TCP/IP (Modbus TCP).
Открытость
Все спецификации протокола открыты и доступны для всех желающих использовать его в своих решениях, без каких либо лицензионных отчислений кому-либо.

Теперь скажу пару слов о том, как устроен Modbus. Доступ к данным в памяти устройства осуществляется через специальные логические таблицы: Discrete Input, Coils, Input Registers, Holding Registers. В первых двух данные представлены в виде единичных битов, в двух последних — в виде 16-битных слов:

Таблица Тип данных Доступ
Discrete Input 1 бит только чтение
Coils 1 бит чтение и запись
Input Registers 16 бит только чтение
Holding Registers 16 бит чтение и запись


Первые две таблицы могут быть использованы для работы с простыми дискретными значениями: «открыто-закрыто», «авария-не авария» и т.д. А Input и Holding Registers могут быть использованы для всего остального. Причем проблема, когда данные не помещаются в 16 бит, легко решается путем использования двух соседних регистров. Так можно работать и с 32bit-ными float или long.
К элементам данных в Modbus можно обратиться через сквозную адресацию от 0 до 65535 по всем таблицам.

Доступ к данным может и не пересекаться, и каждая таблица ведет в отдельный блок памяти:
7c92d5ca1c7f413d952b3e4eb40993db.png
Так и несколько таблиц могут использоваться для доступа к одним и тем же данным:
01a5685803e44bc3b612ac6ab9eebc48.png

Например, если у нас контроллер с 16 сухими контактами, и мы хотим знать как там сейчас, замкнуто или разомкнуто? То тогда значения всех 16 контактов мы можем забрать разом через таблицу Input Registers, или прочитать все контакты по очереди через таблицу Input Discrete.
Если устройства Modbus находятся на последовательной линии (RS-485), то у каждого устройства в Modbus существует свой уникальный адрес (slave id, от 1 до 247), опрашивающее устройство всегда одно и называется мастером. В случае Modbus TCP устройства достаточно IP-адреса.

Где мы здесь разместим Zabbix с нашим будущим модулем?
Мы сможем разместить его в качестве Modbus-мастера, подключившись к сети RS-485 c контроллерами через конвертор, или работать с устройствами, которые поддерживают Modbus TCP напрямую:
a056b7505828423598ace81f63eb4435.PNG

Настройка окружения


Подготовим небольшой тестовый стенд, который поможет нам протестировать наш модуль Modbus в Zabbix. Оборудование возьмем у отечественного производителя Овен, чьи устройства ввода-вывода позволят нам контролировать температуру, различные дискретные датчики, параметры электросети. Еще в качестве бонуса будем снимать показания с распространенного трехфазного электросчетчика Меркурий 230R при помощи конвертора DU-1M MAX-Logic, который умеет превращать встроенный протокол Меркурия в Modbus. Все эти устройства подключаются на шину RS-485, к которой Zabbix получит доступ в роли мастера через конвертор RS232/RS485.

Настройка модулей Овен для работы по Modbus описана в документации на устройства. В целом, вот что нужно сделать:
Через конфигуратор Овен, подключившись ко всем модулям по очереди через конвертор RS232/RS485:

  • Поменять адреса slaveid , чтобы они были уникальны
  • Выставить протокол обмена Modbus RTU
  • Для MK110.224.2A: выбрать тип подключенных температурных датчиков

Для DU-1M MAX-Logic(Меркурий230) все немного сложнее: Сначала внимательно по инструкции на DU-1M подключаемся к счетчику Меркурий 230, затем вместо уютного графического конфигуратора как в Овене будем использовать консольную утилиту modpoll, посылая команды в конфигурационные регистры, чтобы настроить сопряжение с счетчиком.(уф!).

Вот в итоге что получится:
26d9007b1ecc42cbb5d487c0f6038bdb.PNG
А вот как это выглядит вживую:
9a429c18a55541dcaddb67f5c04e615b.jpg
Собрав стенд, протестируем работоспособность при помощи modpoll, например, подключив к МВ110-2А температурный датчик, опросим его через регистр 10, как сказано в документации на устройство:
9fc737b059714aa29b2efa2422b74f45.png
Или снимем энергопотребление счетчика Меркурий по тарифу Т1:
b77b5f4e433041169d42924f4ea1398a.png
Как мы видим, связь есть, и данные собираются. Настало время доставить эти данные в Zabbix.


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

modbus_read_registers[
  <connection>,
  <slave_id>,
  <reg_to_read>, 
  <modbus_function>,
  [<datatype>],[<endiannes>],[<first_reg>]
]


внутри этого ключа мы будем передавать четыре обязательных и три необязательных параметра:

  • connection — IP адрес или серийный порт с параметрами подключения
  • slave_id — ID устройства Modbus
  • reg_to_read — заветный регистр с данными
  • modbus_function — одна из функций чтения таблиц Modbus, 1 — для чтения COILS
  • 2 — для DISCRETE INPUT, 3 — для HOLDING REGISTERS и 4 для INPUT REGISTERS
  • datatype — тип возвращаемых данных из регистра. bit, 16bit int, 32bit int или 32bit float.
  • endianness, first_reg — нужны для правильного выставления порядка бит для 32битных значений, а также для указания того, какая адресация используется — модель данных Modbus (элементы таблицы, где первый элемент имеет адрес 1 и последний n) или адресация PDU (адресация от 0 до 65535).


Так, придумали, отлично, теперь перейдем непосредственно к модулю. Фактически нам нужно написать функцию сбора данных с регистров Modbus и добавить к ней специальный интерфейс Zabbix из следующих функций:
две обязательных функции:

int    zbx_module_api_version(void);
int    zbx_module_init(void);


А также три необязательных:

ZBX_METRIC    *zbx_module_item_list(void);
void    zbx_module_item_timeout(int timeout);
int    zbx_module_uninit(void);


Но их мы полностью копируем из примера dummy.c, который любезно описан в документации на Zabbix. Отлично, сэкономили силы и время, идем дальше.
А дальше стоит вопрос, как писать саму функцию сбора? Кстати, назовем ее так:

int zbx_modbus_read_registers(AGENT_REQUEST *request, AGENT_RESULT *result)


И здесь работа сводится к минимуму, поскольку, чтобы не напортачить в реализации работы протокола Modbus и не изобретать велосипед мы возьмем библиотеку на C, которая уже все это неплохо делает: libmodbus.
Используя интерфейс libmodbus, нам останется только сделать валидацию поступающих данных из Zabbix, затем запустить функции опроса из libmodbus, и затем преобразовать полученные значения в типы данных Zabbix'а и вернуть их. Ну или вернуть ошибку.
С остальными подробностями, которые я пропустил можно ознакомиться тут.

А я еще раз подчеркну пару ключевых моментов:
1) При успешном выполнении функции результат кладется в *result через макросы:

  • SET_UI64_RESULT — если результат целое число
  • SET_DBL_RESULT — если результат число с плавающей запятой
  • SET_STR_RESULT — если результат Char
  • SET_TEXT_RESULT — если результат текст
  • SET_LOG_RESULT — если результат лог


Например:

SET_DBL_RESULT(result, modbus_get_float(temp_arr));


А сама функция должна вернуть SYSINFO_RET_OK.

2) Если что-то пошло не так, то необходимо вернуть SYSINFO_RET_FAIL , а в *result положить сообщение ошибки, которое мы сможем увидеть в веб-интерфейсе Zabbix:

SET_MSG_RESULT(result, strdup("Check datatype provided."))


91818394af314ceb96b041d9dc700b1a.png

3) Очень важно не забывать валидировать все поступающие входящие параметры. Иначе падение модуля приведет к падению самого Zabbix-сервера или агента.

4) Помните, что Zabbix запускает много процессов опроса параллелью. Так что если вы собираетесь работать с ресурсами, не терпящими одновременный доступ (файл или последовательный порт в нашем случае), то необходимо реализовывать контроль этого доступа своими силами. Я это сделал через семафоры, что несколько усложнило задачу, но к счастью это требуется делать не всегда.

5) Так как используется внешняя библиотека libmodbus, то она должна быть установлена в системе, где будет использоваться модуль Zabbix, для этого:

wget http://libmodbus.org/releases/libmodbus-3.1.2.tar.gz
tar zxvpf libmodbus-3.1.2.tar.gz
cd libmodbus-3.1.2
./configure
make
make install
ldconfig

В итоге у нас получился вот такой код у модуля:
Компилируем его и добавляем полученный .so файл в Zabbix Server или в Zabbix Agent
Тут только важно помнить, что модуль зависит от некоторых заголовков самого Zabbix. Поэтому, если хотите собирать все отдельно от Zabbix, то скопируйте из исходников Zabbix к себе как минимум zbxtypes.h, module.h, sysinc.h.
Ну вот и готово.


Для примера скомпилированный файл libzbxmodbus.so подгрузим на Zabbix Server, для этого в конфиг-файле Заббикса добавим:

LoadModulePath = /usr/local/lib
LoadModule = libzbxmodbus.so


Добавим пользователя zabbix в группу dialout для доступа к серийному порту:

usermod -a -G dialout zabbix


… и перезапустим Zabbix:

/etc/init.d/zabbix-server restart


Если все успешно перезапустилось, то идем в Zabbix и создадим шаблон для наших Modbus-устройств.

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


Для начала создадим элементы данных для MВ110-224.2A для чтения показаний температурного датчика:
c3e9663d3a2242169bf9d2192ea1f553.png
где ключ(key):

modbus_read_registers[{$MODBUS_PORT},{$MODBUS_SLAVE},10,3,f,1,0]


  • {$MODBUS_PORT} — Макрос, который будет перезаписываться параметрами подключения к нашему устройству. Например на уровне узла сети мы определим наш {$MODBUS_PORT} как /dev/ttyUSB0 9600 N 1
  • {$MODBUS_SLAVE} — еще один пользовательский макрос, который означает slave_id и его мы перезапишем как 32
  • 10 — регистр, в котором лежит значение температуры
  • 3 — функция Modbus, используемая для чтения, в данном случае 0x03 READ HOLDING REGISTERS
  • f — тип данных, float
  • 1 — так как контроллеры Овен используют Big endian для кодирования значений, которые не поместились в один регистр
  • 0 — используется PDU адресация


Подробнее как выбирать параметры тут:
Кроме того необходимо указать:
тип(type)
Simple check — если модуль подгружен на Zabbix Server и Zabbix Proxy,
Zabbix agent — если модуль подгружен на Zabbix Agent'е

тип данных(Type of information):
в нашем случае это Numeric(float).

Создание узла сети


Создаем узел сети , определяем макросы, как договаривались:
c1d95c8462954133a6d6ddef0afa9233.PNG
и прикрепляем шаблон, который только что создали:
c567fd6d33534b43aa8013046f2ed6cd.PNG

Данные


Переходим в раздел Последние данные и можем начинать наблюдать данные, которые удалось собрать с датчика:
7862ca972c8a410ea23e5cccd028da0b.PNG
А создав шаблоны для оставшихся регистров устройств получим вот такую картину:
c86ffbea4e004c91a69aff0699096df0.png
Ну а дальше мы можем создать триггеры, графики, комплексные экраны в шаблонах по желанию :)
При помощи загружаемых модулей разработчики Zabbix дали нам интерфейс для создания по-настоящему быстрых и хорошо интегрированных с Zabbix расширений. Да, возможности пока ограничены созданием новых типов элементов данных, и, например, не получится добавить новую триггерную функцию (ZBXNEXT-2650), но поживем, увидим :)
Чтобы модули не валялись по всему Интернету, также был сделан второй важный шаг в сторону организации пользовательских решений — share.zabbix.com — единый репозиторий, созданный для обмена шаблонами, модулями и прочими расширениями для Zabbix. Ищите шаблон под хитрую железку? Или написали шаблон под хитрую железку и готовы поделиться им с остальными? Искали решение как мониторить PostgreSQL? Или Docker?

Заходите и присоединяйтесь! Ну а наш модуль уже теперь тоже там.

© Habrahabr.ru