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

Подготовка

Для начала нам нужно определиться, что мы хотим видеть в результате работы вебхука. В большинстве случае сервисы, в которые мы будем слать события делятся на 2 типа:

  • Мессенджеры, в которые можно отправляться сообщения. Например: Telegram, Slack, Discord и т.д.
  • Сервисдески, где можно открывать, закрывать и обновлять тикеты. Например: Jira, Redmine, Servicenow и т.д.

В обоих случаях принцип создания вебхука отличаться не будет — различие только в сложности одного типа от другого.

В этой статье я буду описывать процесс создания вебхука для мессенджера — а конкретно для мессенджера Line.

После того, как мы определились с типом, необходимо узнать, поддерживает ли данный сервис возможность API запросов и если поддерживает — что для этого нужно. Обычно все сервисы, с которыми вы хотите интегрироваться имеют более или менее подробную документацию о методах, которые они поддерживают. Кстати, Zabbix тоже имеет своё API, которое подробно задокументировано.

Изучив документацию Line, выяснили, что отправка сообщений происходит с помощью метода POST на эндпоинт https://api.line.me/v2/bot/message/push, используя для авторизации токен Line бота в заголовке запроса и передавая в теле запроса специально отформатированный JSON c содержанием сообщения. Запутались? Не беда. Давайте разберём детальнее.

HTTP-запросы

Работа API построена на HTTP-запросах, которые делаются с учётом параметров, предусмотренных разработчиками этого API.

Есть несколько типов HTTP-запросов, которые используются чаще других:

  • GET — пожалуй самый распространённый, с которым каждый из нас сталкивается повседневно. Этот запрос подразумевает только получение данных. Например, эту статью, которую вы сейчас читаете, браузер запросил с веб-сервера запросом GET.
  • POST — запрос которые отправляет данные ресурсу. Это как раз случай, когда мы хотим передать что-то в сервис используя API запросы.
  • PUT — встречается значительно реже чем предыдущих 2, но не менее важен. Этот запрос заменяет значения в ресурсе.

Это не все методы HTTP-запросов, но для общего введения будет достаточно этих трёх.

С методами разобрались. Перейдём к эндпоинту.

Эндпоинт — это постоянный адрес ресурса, обращаясь к которому, мы передаём, получаем или изменяем данные. В данном случае https://api.line.me/v2/bot/message/push является эндпоинтом, который и принимает POST запросы для отправки сообщений.

C методом и эндпоинтом определились. Что дальше?

Вообще любой HTTP-запрос состоит из:

  1. URL
  2. Метода
  3. Заголовков
  4. Тело запроса
Структура HTTP-запроса
Структура HTTP-запроса

С первыми двумя мы уже разобрались, остались заголовки и тело запроса.

В заголовках обычно передают служебную информацию, которая позволяет правильно обрабатывать запрос. Например, заголовок Content-Type: application/json подразумевает, что тело нашего запроса нужно интерпретировать как json-объект. Также, достаточно часто в заголовках передаётся информация авторизации. Как и в случае с Line, заголовок Authorization: Bearer {channel access token} несёт в себе авторизационный токен бота, от имени которого будут отправляться сообщения.

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

Проверка API сервиса

Документация — это хорошо, но необходимо проверить, что все, что мы прочитали работает именно так, как об этом написано. Не редкость, что сервис может разрабатываться быстрее, чем за ним поспевает документация. Поэтому полевые испытания никогда не помешают. Если заранее исключить факт непредвиденного поведения, вы потратите намного меньше времени на поиски проблем.

Я сам пользуюсь и рекомендую использовать Postman для работы с API запросами — удобный инструмент, который экономит время. Но для статьи мы будем использовать cURL ввиду его распространённости и простоты.

Я не буду описывать процесс создания API токена Line бота, потому что это на прямую не касается статьи. Однако, ссылку для тех, кому этот процесс интересен, я оставлю тут.

Как мы уже выяснили, тип запроса будет POST, URL точки доступа https://api.line.me/v2/bot/message/push, дополнительно необходимо передать заголовки: Content-Type: application/json, который задает тип отправляемых данных (в нашем случае это JSON) и Authorization: Bearer {значение токена}. Ну и сами сообщения в формате JSON. Для примера я использовал 2 сообщения — «Hello, world1» и «Hello, world2». В итоге у меня получился вот такой запрос:

После выполнения получаем ожидаемый результат в виде двух сообщений, пришедших в мессенджер, которые находились в теле запроса.

Отлично! Значит, полдела уже сделано: есть готовый запрос, который работает в ручном режиме и успешно отправляет сообщения в Line. Осталось дело за малым — в нужные места подставить необходимую информацию и автоматизировать процесс используя JS и Zabbix.

Интеграция в Zabbix

После успешных тестов переходим в Zabbix, создаём новый способ оповещения в разделе Администрирование, выбираем тип вебхук и называем его Line.

Для вебхук интеграций с внешними сервисами Zabbix использует встроенный движок JavaScript на Duktape. В скрипт передаются параметры, которые используются для построения логики работы вебхука. В результат работы скрипта могут быть возвращены теги, которые будут присвоены событию. Обычно это необходимо в случае интеграции с сервисдесками для возможности обновления статуса заявок.

Давайте подробнее разберём интерфейс настройки вебхука.

В разделе Способ оповещения находятся основные настройки для нового способа оповещения:

  • Имя — Название способа оповещения
  • Тип — Тип способа оповещения. Есть 4 типа: email, sms, webhook и скрипт
  • Параметры — это список переменных, которые передаются в код. Это связующее звено между Zabbix и вебхуком. Через параметры можно передать все необходимые данные: ID события, тип события, критичность триггера, источник события и т.д. В параметрах можно указать макросы и текстовые значения. Параметры передаются в виде JSON строки, доступной через встроенную переменную value. 
  • Скрипт — JS скрипт описывающий логику работы вебхука
  • Время ожидания — время, через которое скрипт завершит работу по таймауту
  • Обработка тегов — в случае, если эта опция включена, вебхук будет поддерживать генерацию тегов для событий, которые были отправлены с использованием этого хука
  • Добавить запись в меню события — эта опция делает доступными для использования поля Имя записи в меню и URL записи в меню
  • Имя записи в меню — текст, который будет отображаться в выпадающем меню события для URL записи, которое было отправлено с использованием этого хука
  • URL записи в меню — ссылка на внешний ресурс, которая будет находиться в меню события
  • Описание — текстовое поле, которое содержит в себе описание способа оповещения
  • Активировано — опция которая позволяет включать и выключать способ оповещения

В разделе Шаблоны оповещений находятся шаблоны, которые будут отправлены при срабатывании вебхука. Каждый шаблон содержит:

  • Тип сообщения — это тип события, при котором данное сообщение будет применяться. Например, Проблема — при срабатывании триггера и Восстановление проблемы — при восстановлении
  • Тема — заголовок сообщения
  • Сообщение — шаблон сообщения, который содержит полезную информацию о событии. Например время события, дату, имя события, имя узла сети и т.д

В разделе Опции находятся дополнительные параметры:

  • Одновременные сессии — количество параллельных сессий для отправки оповещения
  • Попыток — количество попыток в случае неудачи отправки
  • Интервал попыток — частота попыток отправки оповещения

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

Параметры для вебхука в Zabbix
Параметры для вебхука в Zabbix

Удобно задавать значения параметров макросами. Макрос — это переменная в Zabbix, которая содержит в себе определённое значение. Макросы позволяют оптимизировать и автоматизировать работу. Их можно использовать в различных местах — например в триггерах, в фильтрах, способах оповещения и т.д.

Немного подробнее про каждый макрос в отдельности, чтобы понимать зачем каждый из них нужен:

  • {ALERT.SUBJECT} — тема сообщения события. Это значение берется из поля Тема соответствующего типа Шаблона оповещения
  • {ALERT.MESSAGE} — тело сообщения события. Это значение берется из поля Сообщение соответствующего типа Шаблона оповещения
  • {EVENT.ID} — id события в Zabbix. Будем использовать при генерации ссылки на событие
  • {EVENT.NSEVERITY} — цифренное определение критичности события от 0-5. Будем использовать для изменения сообщения в случае разных критичностей
  • {EVENT.SOURCE} — источник события. Необходим, чтобы корректно обрабатывать события. В большинстве случаев нас интересуют именно триггеры, это источник ‘0’.
  • {EVENT.UPDATE.STATUS} — возвращает ‘1’ в случае обновления проблемы. Например, acknowledge или изменения критичности
  • {EVENT.VALUE} — состояние события. Если ‘0’ — восстановление, если ‘1’ — проблема
  • {ALERT.SENDTO} — поле из способа оповещения, назначенного пользователю. Из него мы будем брать ID пользователя или группы в Line, куда необходимо будет отправить сообщение
  • {TRIGGER.DESCRIPTION} — макрос, который будет раскрыт в случае если источник события — триггер. Возвращает описание триггера
  • {TRIGGER.ID} — ID триггера. Необходимо для генерации ссылки на событие в Zabbix

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

Написание скрипта

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

  • скрипт должен описывать логику отправки сообщений
  • обрабатывать возможные ошибки
  • иметь возможность логирования для дебага

Я не буду описывать весь код целиком, чтобы не повторять однотипные блоки и сконцентрироваться только на важных аспектах.

Для отправки сообщений, напишем функцию, которая будет принимать переменные messages и params. У нас получилась вот такая функция:

function sendMessage(messages, params) {
    // Объявляем переменные
    var response,
        request = new HttpRequest();

    // Добавляем необходимые заголовки к запросу
    request.addHeader('Content-Type: application/json');
    request.addHeader('Authorization: Bearer ' + params.bot_token);

    // Формируем непосредственно запрос, который будет посылать сообщение
    response = request.post('https://api.line.me/v2/bot/message/push', JSON.stringify({
        "to": params.send_to,
        "messages": messages
    }));

    // В случае если ответ будет отличный от 200 (OK) вернуть ошибку с содержанием ответа
    if (request.getStatus() !== 200) {
        throw "API request failed: " + response;
    }
}

Конечно эта функция не «эталонная», и в зависимости от требований для запроса будет отличаться. Могут быть другие необходимые заголовки и тело запроса. В некоторых случаях может понадобиться добавить дополнительный этап для получения авторизационных данных через другой API запрос.

В данном случае запрос на отправку сообщения возвращает пустой объект {}, поэтому нет смысла возвращать его из функции. Но например, при отправке сообщения в Telegram, возвращается объект с данными об этом сообщении. Если эти данные передать в теги, то можно написать логику, которая будет менять уже отправленное сообщение — например, в случае закрытия или обновления проблемы.

Теперь опишем функцию, которая будет принимать параметры вебхука и валидировать их значения. В примере мы не станем описывать все условия, потому что они однотипны:

function validateParams(params) {
    // Проверяем, чтобы параметр bot_token был строкой и не был пустым
    if (typeof params.bot_token !== 'string' || params.bot_token.trim() === '') {
        throw 'Field "bot_token" cannot be empty';
    }

    // Проверяем чтобы параметр event_source был только числом от 0-3
    if ([0, 1, 2, 3].indexOf(parseInt(params.event_source)) === -1) {
        throw 'Incorrect "event_source" parameter given: "' + params.event_source + '".\nMust be 0-3.';
    }

    // В случае если событие типа "Обнаружение" или "Авторегистрация" задаем event_value 1,
    // что значит "Проблема", и будем обрабатывать эти события по аналогии с проблемами
    if (params.event_source === '1' || params.event_source === '2') {
        params.event_value = '1';
    }

    ...

    // Проверяем, что trigger_id - это число и не равно нулю
    if (isNaN(params.trigger_id) && params.event_source === '0') {
        throw 'field "trigger_id" is not a number';
    }
}

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

Основной блок кода помещаем внутрь try…catch блока, чтобы корректно обрабатывать ошибки:

try {
    // Объявляем переменную params и записываем в нее параметры вебхука
    var params = JSON.parse(value);

    // Вызываем функцию валидации и передаем в нее параметры на проверку
    validateParams(params);

    // Если событие - триггер, и он в статусе проблема, собираем тело сообщения
    if (params.event_source === '0' && params.event_value === '1') {
        var line_message = [
            {
                "type": "text",
                "text": params.alert_subject + '\n\n' +
                    params.alert_message + '\n' + params.trigger_description
            }
        ];
    }

    ...

    // Отправляем сформированное сообщение 
    sendMessage(line_message, params);

    // Возвращаем OK, чтобы вебхук понял, что работа скрипта завершена в статусе ОК
    return 'OK';
}
catch (err) {
    // Добавляем функцию лога, чтобы в случае проблем можно 
    // было посмотреть ошибку в консоли Zabbix сервера
    Zabbix.log(4, '[ Line Webhook ] Line notification failed : ' + err);

    // В случае ошибки возвращаем ее из вебхука
    throw 'Line notification failed : ' + err;
}

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

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

Тестирование

После того, как мы закончили со скриптом для вебхука, пришло время протестировать, как работает наш код. Для этого в Zabbix предусмотрена функция отправки тестовых сообщений. Переходим в раздел АдминистрированиеМетоды оповещений, находим Line и на против него жмём на кнопку Test. В появившемся окне заполняем все поля необходимыми данными и делаем запрос. Проверяем мессенджер и видим, что сообщение пришло с данными, которые мы указали в тесте.

Готовую интеграцию Line можно найти в git репозитории Zabbix и во всех свежих сборках Zabbix инстанса.

Траблшутинг

Конечно, в статье все выглядит так, как будто я это сделал с первого раза и не столкнулся ни с одной ошибкой или проблемой. Естественно, на практике это не так. Работа с каждым новым продуктом включается в себя  Research & Development. Как можно отлавливать ошибки, и, самое главное, понимать в чем проблема?

Ну, как я писал ранее, — читать документацию и тестировать все запросы до написания кода. На этом этапе проще всего отловить все проблемы. В ответе http-запроса будет явно описана ошибка. Например, если ошибиться в теле запроса и отправить объект с неправильным значениями API, сервис вернёт тело с ошибкой и статус ответа 400 (Bad request)

Пример ошибки возвращаемой при неправильно сформированном запросе
Пример ошибки возвращаемой при неправильно сформированном запросе

В случае ошибок, которые могут возникнуть при написании скрипта вебхука, есть несколько вариантов:

  • Ориентироваться на ошибки, которые будут отображаться при исполнении способа оповещения. Например, если вы опечатались и задали неправильное имя функции и переменной.
  • Описывать логирование в коде для вывода сервисной информации. Например, пока у вас идёт стадия разработки скрипта, содержимое функции можно вывести в лог с помощью функции Zabbix.log(). Zabbix поддерживает 6 уровней дебага (0-5), которые можно задать в этой функции. Обычно, в вебхуках используется 4 уровень, который содержит информацию для дебага.
  • Можно использовать утилиту zabbix_js. В неё можно передать файл со скриптом и параметры. Подробнее про неё можно почитать тут.

Заключение

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

Вопросы

В: Я не знаю JS, но умею программировать на других языках. Планируется ли нативная поддержка в Zabbix, например Python?

О: Пока что такая поддержка не предвидится.

В: Есть ли какие-то ограничения в написании JS скрипта для вебхука?

О: Да, есть. Для исполнения кода используется встроенный движок Duktape, и у него есть не весь функционал, который доступен в последних релизах JS. Поэтому рекомендую ознакомиться с документацией этого движка и со встроенными объектами, чтобы подробнее узнать о доступных методах.

Подписаться
Уведомить о
2 Comments
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
dmitrii_
dmitrii_
6 месяцев назад

Did I understand correctly that in a webhook i can only use json and nothing else?

2
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x