Инструменты пользователя

Инструменты сайта


raspberry_pi:pi_4_model_b:raspberry_pi_os:librenms:configuration:alerts:internet_ping_check

Проверка доступности Интернета на LibreNMS

Достаточно давно у меня работает оповещение о восстановлении работоспособности Интернета, настроенное на MikroTik'e. Но недавно я таки настроил систему мониторинга LibreNMS на Raspberry Pi 4 Model B и подумал… почему бы не свалить все яйца в одну корзину. :-)

Задача

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

Поэтому нужно проверять доступность сразу нескольких независимых хостов и уведомлять только, если они все одновременно не доступны. Вот в этом месте и затаилась главная проблема – как реализовать это в LibreNMS?

Реализация

Если кратко резюмировать все нижеприведенные этапы, то все сводится к главному – написать собственный запрос к базе данных, который будет учитывать мои потребности. Благо в LibreNMS это возможно без танцев с бубном! Но, по порядку.

Устройства

Для начала добавляем несколько, в моем случае 4, «устройств» через меню «Devices» → «Add Device», для которых указываем только «Hostname or IP»1) и, соответственно, выключаем «SNMP».

:!: Важно выбирать такие хосты, которые не могут одновременно быть не доступны в глобальном плане.

Сперва я остановился на NTP-пулах из-за того, что в них крайне просто выбрать разные регионы, и они, в силу своей специфики, должны быть достаточно стабильные:

  • 0.jp.pool.ntp.org
  • 0.ru.pool.ntp.org
  • 0.us.pool.ntp.org
  • 0.de.pool.ntp.org

FIXME Но оказалось, что для этой цели они подходят слабо, т.к. по какой-то причине крайне часто один, а зачастую и не один, кратковременно не доступен для мониторинга! Т.е. в целом схема рабочая и ложных срабатываний нет, но в логах скапливается огромное количество записей типа «down/up».

Поэтому изменил свой выбор на публичные DNS сервера:

  • Quad9 [9.9.9.9];
  • Yandex DNS [77.88.8.8];
  • Cloudflare DNS [1.1.1.1];
  • Google Public DNS [8.8.8.8].

Далее нужно зайти во все устройства, кроме одного, и включить «Disable alerting», чтобы приходило только одно уведомление, а не по количеству устройств2). Так же нужно указать для всех устройств «Display Name» и включить «Override sysLocation», добавив туда что-то уникальное – я выбрал слово «World».

Группа

Создаем динамическую группу через «Devices» → «Manage Groups» → «New Device Group», где в качестве критерия выбираем «devices.hostname», «ends with» и, в моем случае, «ntp.org» «locations.id», «equal» и, в моем случае, «10»3).

Правило

Добавляем новое правило через «Alerts» → «Alert Rules» → «Create new alert rule» и на закладке «Main»:

  1. вводим произвольное название «Rule name»;
  2. ниже выбираем любой критерий и значение4);
  3. выбираем строгость по желанию;
  4. устанавливаем «Max alerts», «Delay» и «Interval» в необходимые значения5);
  5. обязательно включаем «Recovery alerts»;
  6. в «Match devices, groups and locations list» выбираем созданную выше группу;
  7. ну и «Transports» завершает наш марафон!

:!: Касательно транспортов есть нюанс – если нет локального SMS-шлюза, и, к примеру, используется электронная почта, то можно будет получить, что логично, только сообщение о восстановлении работоспособности Интернет-соединения, чего более, чем достаточно для домашнего, ну и не только, использования.

Теперь самое интересное! – переходим на закладку «Advanced», включаем «Override SQL» и добавляем следующий запрос:

SELECT * FROM devices WHERE ( SELECT count( devices.device_id ) FROM devices WHERE devices.device_id IN ( SELECT device_group_device.device_id FROM device_group_device WHERE device_group_device.device_group_id = ( SELECT @my_group := device_group_device.device_group_id FROM device_group_device WHERE device_group_device.device_id = ? ) ) AND devices.status = 0 ) = ( SELECT count( device_group_device.device_id ) FROM device_group_device WHERE device_group_device.device_group_id = @my_group ) AND devices.device_id IN ( SELECT device_group_device.device_id FROM device_group_device WHERE device_group_device.device_group_id = @my_group )

Разбор

Вот код в более читабельном виде.

SELECT * #10
FROM
       devices
WHERE
       (
              SELECT #5
                     count( devices.device_id )
              FROM
                     devices
              WHERE
                     devices.device_id IN
                     (
                            SELECT #4
                                   device_group_device.device_id
                            FROM
                                   device_group_device
                            WHERE
                                   device_group_device.device_group_id =
                                   (
                                          SELECT #3
                                                 @my_group := device_group_device.device_group_id #1
                                          FROM
                                                 device_group_device
                                          WHERE
                                                 device_group_device.device_id = ? #2
                                   )
                     )
                     AND devices.status = 0 #6
       )
       = #8
       (
              SELECT #7
                     count( device_group_device.device_id )
              FROM
                     device_group_device
              WHERE
                     device_group_device.device_group_id = @my_group
       )
       AND devices.device_id IN
       (
              SELECT #9
                     device_group_device.device_id
              FROM
                     device_group_device
              WHERE
                     device_group_device.device_group_id = @my_group
       );
  1. Для уменьшения количества команд «SELECT», вводим переменную «@my_group» (#1) и присваиваем ей значение, в виде номера группы, куда входит устройство (#2), инициализировавшее тревогу, выбираемое командой (#3);
  2. следующим SELECT'ом мы получаем «id» всех устройств (#4), состоящих в группе, а следующим (#5) – считаем количество устройств, имеющих статус «0» (#6), т.е. кол-во недоступных устройств в группе;
  3. потом, очередным SELECT'ом, считаем общее количество устройств в группе (#7) и сравниваем (#8) с количеством недоступных;
  4. если количество равно, в очередной раз получаем «id» устройств из группы (#9), и получаем все данные по этим устройствам (#10).

:!: Обратите внимание на заполнитель «?» (#2) – он обязателен, без него не будет работать правило! И он должен быть только один, иначе в отладке будет ошибка «SQLSTATE[HY093]: Invalid parameter number»!

:!: Еще нюанс – в переменную можно поместить только одно значение и, если в нее передается массив, то сохранится только одно последнее! А очень жалко, можно было бы еще упростить код, убрав повторную выборку устройств, принадлежащих группе (#4 и #9).

Отладка

Для отладки запроса нужно открыть консоль MySQL mysql -u root -p, если надо, узнать название базы SHOW DATABASES; и подключиться к базе use librenms. Просмотр существующих таблиц – SHOW TABLES;.

Если что-то не работает, то нужно зайти в одно из устройств, нажать на троеточие справа и выбрать «Capture», затем перейти на закладку «Poller» или «Alerts», нажать «Run» и изучать… LOL

Шаблон

Теперь осталось только создать шаблон, перейдя в «Alerts» → «Alert Templates» и нажав «Create new alert template». После чего, назначить его созданному ранее правилу, выбрав в «Attach template to rules».

Template

Мой шаблон.

Internet connection is {{ $alert -> state ? 'FAULTED' : 'OK' }} from {{ $alert -> timestamp }}! <br><br>
 
@if ( $alert -> state == 0 )Approx. time elapsed: {{ $alert -> elapsed }}. <br><br>
@endif
 
@if ( $alert -> faults )Faults list: <br><br>
 
@foreach ( $alert -> faults as $key => $value )
{{ $key }}) {{ $value[ 'display' ] }} (id {{ $value[ 'device_id' ] }}): <br>
last poll at {{ $value[ 'last_polled' ] }}, <br>
last ping at {{ $value[ 'last_ping' ] }} ({{ $value[ 'last_ping_timetaken' ] }}ms). <br><br>
 
@endforeach @endif
 
Rule: @if ( $alert -> name ){{ $alert -> name }}@else{{ $alert -> rule }}@endif () (id: {{ $alert -> rule_id }}, severity: {{ $alert -> severity }}). <br><br>
 
Unique-ID: {{ $alert -> uid }}.

Крайне не удобная разметка! Особенно, поди угадай, чтобы между словами и знаками препинания было нужное количество пробелов! m(

:!: Обратите внимание на пустые скобки «()» после «@endif» – они нужны, чтобы операторы не «съедали» то, что дальше. Так же нужно делать и с «@else»… Говорю же, не разметка, а глюк какой-то!

Alert title

{{ $alert -> name }} [{{ strtoupper( $alert -> severity ) }}]

Recovery title

{{ $alert -> name }} [OK]

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

Для тестирования шаблона и просмотра доступных значений и их заполнителей, нужно авторизоваться под пользователем «librenms»

sudo su - librenms

и выполнить команду

./scripts/test-template.php -t X -d -h HOST -r Y

где «X», это номер шаблона, а «Y» – номер правила. Обе цифры можно посмотреть в первой колонке соответствующего списка – списка шаблонов или списка правил. Естественно, «HOST» должен соответствовать значению, указанному в поле «Hostname or IP» соответствующего устройства.


Дисклеймер

  • Использование материалов данной базы знаний разрешено на условиях лицензии, указанной внизу каждой страницы! При использовании материалов активная гиперссылка на соответствующую страницу данной базы знаний обязательна!
  • Автор не несет и не может нести какую либо ответственность за последствия использования материалов, размещенных в данной базе знаний. Все материалы предоставляются по принципу «как есть». Используйте их исключительно на свой страх и риск.
  • Все высказывания, мысли или идеи автора, размещенные в материалах данной базе знаний, являются исключительно его личным субъективным мнением и могут не совпадать с мнением читателей!
  • При размещении ссылок в данной базе знаний на интернет-страницы третьих лиц автор не несет ответственности за их техническую функциональность (особенно отсутствие вирусов) и содержание! При обнаружении таких ссылок, можно и желательно сообщить о них в комментариях к соответствующей статье.
1)
Желательно добавлять именно по доменному имени, чтобы избавить себя от исправлений в будущем.
2)
Я пытался побороть эту проблему альтернативными методами, но этот оказался самым простым и… рабочим!
3)
ID группы можно узнать в адресной строке, открыв нужную группу.
4)
Это нужно только, чтобы можно было сохранить правило.
5)
Я выбрал «1», «0» и «0» соответственно, т.к. нет смысл генерировать оповещения, когда нет Интернета…

Обсуждение

Ваш комментарий:
Q T T᠎ C​ R Q X​ A G J᠎ C P H X D᠎ M
 
Последнее изменение: 2023/04/02 01:29 — Николай Солошин