Мониторинг транков Asterisk с помощью низкоуровнего обнаружения Zabbix

Предисловие


В прошлом году компания, в которой я работаю, стала активно переходить на IP-телефонию. Для этих целей был использован дистрибутив FreePBX. Опыта работы с телефонией практически не было, поэтому проблем было много. Всего у нас используется 2 сервера FreePBX, примерно по 30 транков на каждом. Некоторые транки ведут себя не очень хорошо и порой теряют регистрацию. Поэтому возникла необходимость мониторить состояние регистрации, чтобы узнавать о неработающей телефонии раньше пользователей.
Для мониторинга IT-инфраструктуры мы уже давно используем Zabbix. Заводить элементы данных на каждый транк (т.е. около 60) — задача нудная, утомительная и неинтересная. Кроме того, надо постоянно следить за списком транков в мониторинге и актуализировать его. Поэтому, решено было использовать одну из самых интересных особенностей этой системы мониторинга — низкоуровневое обнаружение. Итак, что было сделано.

Настройка сервера Zabbix


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

В веб-панели Zabbix заходим в Настроки->Шаблоны, и создаем пустой шаблон. Назовем его, например, Asterisk Trunks Discovery. Заходим в редактирование шаблона, и переходим в раздел «Обнаружение». Там создаем 2 правила обнаружения, одно с ключом asterisk.discovery[ips] — оно будет мониторить IP-адреса провайдеров телефонии, второе с ключом asterisk.discovery[trunks] — оно будет мониторить регистрацию. Ключи эти придуманы, можно использовать какие угодно, главное чтобы не пересекались со встроенными ключами ZAbbix.

image

Создание прототипов элементов данных

Для каждого правила обнаружения нужно создать прототипы элементов данных, который собственно и будут собирать данные о каждом транке. Для правила asterisk.discovery[trunks] мы используем элемент данных с ключом asterisk.registry[{#TRUNKNAME}]. Имя ключа, опять таки не важно, важен только его параметр #TRUNKNAME. Все остальное видно на картинке.

image

Для правила asterisk.discovery[ips] мы использовали элементы данных из стандартного шаболона для пинга, заменив имя хоста на макрос {#TRUNKIP}. На картинке все наглядно видно.

image

Осталось только присоединить шаблон к нужным серверам. На этом, с настройкой Zabbix закончим.

Настройка серверов Asterisk


Создаем папку для скриптов /usr/scripts. Копируем в нее скрипт для обнаружения транков trunk_disc.php и скрипт для мониторинга транков reg_mon.php (да-да, они на php, да и кривенькие, т.к. набросаны на скорую руку, а доделать потом руки так и не дошли). Сами скрипты:
Скрипт trunk_disc.php
#!/usr/bin/php
<?php
error_reporting(1);
exec("sudo asterisk -rx 'sip show registry'", $tr_list);
foreach($tr_list as $line) {
        $ar_line = null;
        $ar_line[] = trim(substr($line, 0, 40));
        $ar_line[] = trim(substr($line, 47, 19));
        $ar_line = array_diff($ar_line, array(''));
        $ar_line = array_combine(array(0,1), $ar_line);
        $trunks[] = $ar_line['1'];
        $ip = explode(":", $ar_line['0']);
        if (!in_array($ip['0'], $ips)) {
                $ips[] = $ip['0'];
        }
}
$trunks[0] = '';
$ips[0] = '';
$trunks = array_diff($trunks, array(''));
$ips = array_diff($ips, array(''));

function getJson($items, $name) {
        $first = 1;
        print "{\n";
        print "\t\"data\":[\n\n";
        foreach ($items as $item) {

                if (!$first) {
                        print "\t,\n" ;
                        $first = 0;
                }
                print "\t{\n";
                print "\t\t\"{#$name}\":\"$item\"\n";
                print "\t},\n";
        }
        print "\n\t]\n";
        print "}\n";
}
if ($argv[1] == 'trunks') {
        getJson($trunks, "TRUNKNAME");
}
elseif ($argv[1] == 'ips') {
        getJson($ips, "TRUNKIP");
}
else {
        print "error";
}


Скрипт reg_mon.php
#!/usr/bin/php
<?php
error_reporting(0);
$find = "*".$argv[1]."*";
exec("sudo asterisk -rx 'sip show registry'", $tr_list);
$filter = preg_grep($find, $tr_list);

foreach($filter as $line) {
        if (preg_match("*Registered*", $line) & preg_match($find, $line)) {
                exit('1');
        }
        else {
                exit('0');
        }
}
exit('0')


В файле конфигурации агента Zabbix добавляем:

UserParameter=asterisk.registry[*],/usr/scripts/reg_mon.php $1
UserParameter=asterisk.discovery[*], /usr/scripts/trunk_disc.php $1

И перезапускаем агент Zabbixa.

Проверка


Для проверки работы логинимся в в консоль Zabbix-сервера. Запускаем команду
zabbix_get -s  -k asterisk.discovery[trunks]
Вывод должен быть примерно таким
{ "data":[ { "{#TRUNKNAME}":"trunk1" }, { "{#TRUNKNAME}":"trunk2" }, { "{#TRUNKNAME}":"trunk3" }, { "{#TRUNKNAME}":"trunk4" }, { "{#TRUNKNAME}":"trunk5" }, { "{#TRUNKNAME}":"trunk5" }, ...

Здесь имена своих транков я сменил на trunk1,trunk2 и т.д. Далее запускаем
zabbix_get -s  -k asterisk.discovery[ips]
Вывод примерно такой:
{ "data":[ { "{#TRUNKIP}":"213.141.252.17" }, { "{#TRUNKIP}":"188.187.255.6" }, { "{#TRUNKIP}":"sip.pctel.ru" }, ...

Здесь я даже адреса менять не буду. И напоследок, проверяем мониторинг регистрации zabbix_get -s -k asterisk.registry[trunk1] Для зарегистрированного транка выведет 1, для незарегистрированного 0. Пожалуй, на этом настройку можно считать оконченной. Теперь можно открыть веб-интерфейс Zabbix и насладиться результатом.

Заключение


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

Комментарии 17

    +4
    Конечно молодец что пытаетесь оптимизировать рутинные вещи. Но зачем везде пихать то что там не нужно. Ну вот не нужен здесь php совсем
    Вот пример который пришел в голову сразу
    asterisk -rx "sip show registry" | awk 'BEGIN{print "{\n\"data\": ["; } NF>6 {gsub(/:.+/, "", $1); print "\t{ \"{#TRUNKIP}\": \""$1"\" },";} END{print "\t]\n}";}'
    Вывод на одном пире такой
    { "data": [ { "{#TRUNKIP}": "172.21.0.23" }, ] }

    Второй скрипт
    asterisk -rx "sip show registry" | awk 'BEGIN{print "{\n\"data\": ["; } NF>6 {print "\t{ \"{#TRUNKNAME}\": \""$3"\" },";} END{print "\t]\n}";}'

    Вывод
    { "data": [ { "{#TRUNKNAME}": "050" }, ] }

    Ну и уж если надо выводить состояние
    asterisk -rx "sip show registry" | awk '$$3 ~ /'$1'/ || $$1 ~ /''$1/ {if($5 ~ /Registered/) print 1; else print 0;}'
    именно в таком виде кормить заббиксу
    и не надо никаких php и тп
    Надеюсь поможет в дальнейшем ))
      0
      Спасибо. Действительно, без php будет лучше. Впрочем, главное, чтобы скрипт давал нужный вывод — язык дело вторичное.
        0
        Проверьте на своих данных
        у меня на одном пире все гуд
        хочется узнать как будет на больших данных.
          0
          В первом скрипте, для {#TRUNKIP}, не отфильтровываются повторяющиеся IP-адреса. Это не страшно, но и доставляет некоторые неудобства.
            0
            sort -k1 -u думаю выручит
            ну или uniq
      0
      Создал шаблон низкоуровневого обнаружения — в результате выдается ошибка Value should be a JSON object., хотя zabbix_get возвращает следующее:
      m0ps@zabbix:~$ zabbix_get -s pbx.company.com -k asterisk.discovery[ips]{
              "data":[
      
              {
                      "{#TRUNKIP}":"sip.skype.com"
              },
              {
                      "{#TRUNKIP}":"natsip.datagroup.com.ua"
              },
      
              ]
      }
      

      ЗЫ скрипты на php а не awk.
        0
        какая версия заббикс?
        у меня все скрипты возвращают примерно тоже самое и все норм, версия заббикса 2.0
        Хотя некоторые парсеры json ругаются на не валидность данных из-за последней запятой {
        "{#TRUNKIP}":«natsip.datagroup.com.ua»
        },

          0
          У нас 2.2.3
            0
            может в новой весии поменялся парсер json и ему уже не нравится запятая в конце?
              0
              А можешь выгрузить шаблон, возможно я его неправильно создал.
                0
                ооооо
                так это к автору статьи
                я то просто работаю с заббиксом
                могу предложить проверить вот таким скриптом
                #!/bin/bash
                
                ARRAY=(
                one
                two
                three
                four
                )
                
                echo -en "{\"data\": ["
                
                for i in ${ARRAY[@]}; do
                    echo -en "{\"{#QUEUENAME}\":\"$i\"},"
                done
                
                echo -en "]}"
                

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

                Блин парсер схавал переводы строк
                  0
                  Проверил вывод скрипта тут: jsonlint.com/ — как и в скриптах из статьи — ошибка валидации. Последняя строка с запятой в конце не нравиться :(
                    0
                    решение в лоб
                    #!/bin/bash
                    
                    ARRAY=(
                    one
                    two
                    three
                    four
                    )
                    
                    s=""
                    echo -en "{\"data\": ["
                    
                    for i in ${ARRAY[@]}; do
                        echo -en "$s"
                        s=","
                        echo -en "{\"{#QUEUENAME}\":\"$i\"}"
                    done
                    
                    echo -en "]}"
                    

                    Не очень оптимальное но валидацию проходит
                      0
                      Допилил скрипт из статьи:
                      trunk_disc.php
                      #!/usr/bin/php
                      <?php
                      error_reporting(1);
                      exec("/usr/bin/sudo -u asterisk /usr/sbin/asterisk -rx 'sip show registry'", $tr_list);
                      foreach($tr_list as $line) {
                              $ar_line = null;
                              $ar_line[] = trim(substr($line, 0, 40));
                              $ar_line[] = trim(substr($line, 47, 19));
                              $ar_line = array_diff($ar_line, array(''));
                              $ar_line = array_combine(array(0,1), $ar_line);
                              $trunks[] = $ar_line['1'];
                              $ip = explode(":", $ar_line['0']);
                              if (!in_array($ip['0'], $ips)) {
                                      $ips[] = $ip['0'];
                              }
                      }
                      $trunks[0] = '';
                      $ips[0] = '';
                      $trunks = array_diff($trunks, array(''));
                      $ips = array_diff($ips, array(''));
                      
                      function getJson($items, $name) {
                              $first = 1;
                              print "{\n";
                              print "\t\"data\":[\n\n";
                              $lastitem = end($items);
                              foreach ($items as $item) {
                                      if ($item != $lastitem) {
                                              if (!$first) {
                                                      print "\t,\n" ;
                                                      $first = 0;
                                              }
                                              print "\t{\n";
                                              print "\t\t\"{#$name}\":\"$item\"\n";
                                              print "\t},\n";
                                      } else {
                                              if (!$first) {
                                                      print "\t,\n" ;
                                                      $first = 0;
                                              }
                                              print "\t{\n";
                                              print "\t\t\"{#$name}\":\"$item\"\n";
                                              print "\t}\n";
                                      }
                              }
                              print "\n\t]\n";
                              print "}\n";
                      }
                      
                      if ($argv[1] == 'trunks') {
                              getJson($trunks, "TRUNKNAME");
                      }
                      elseif ($argv[1] == 'ips') {
                              getJson($ips, "TRUNKIP");
                      }
                      else {
                              print "error";
                      }
                      


                      ЗЫ В php полный н0ль, так что сори за индусятину :)
                        0
                        Хм… Меня эта запятая тоже поначалу смущала. Но Заббикс ее прекрасно схавал. На днях буду переходить на версию 2.2 — посмотрим как там.
            0
            У меня тоже один из транков периодически терял регистрацию. Не придумал, зачем мониторить это в заббиксе, ведь первым делом надо починить регистрацию. Не будучи особо гуру в asterisk, в гугле выяснил, что заставить транк перерегистрироваться можно релоадом sip.
            Итого получилось нечто вроде:
            #!/bin/bash
            CID="7383xxxxxxx" #пользователь, под которым подключается глючный транк, дабы отделить от остальных транков
            reg=`asterisk -rx  "sip show registry"| grep $CID | awk '{print $5}'`
            
            if [ "$reg" != "Registered" ]; then
                asterisk -rx "sip reload"
                echo `date`" sip reloaded" >> /var/log/sipreg.log
            fi
            

            и было засунуто в cron
              0
              Вы издеваетесь такое писать на PHP?:)

              function getJson($items, $name) {
                      $first = 1;
                      print "{\n";
                      print "\t\"data\":[\n\n";
                      $lastitem = end($items);
                      foreach ($items as $item) {
                              if ($item != $lastitem) {
                                      if (!$first) {
                                              print "\t,\n" ;
                                              $first = 0;
                                      }
                                      print "\t{\n";
                                      print "\t\t\"{#$name}\":\"$item\"\n";
                                      print "\t},\n";
                              } else {
                                      if (!$first) {
                                              print "\t,\n" ;
                                              $first = 0;
                                      }
                                      print "\t{\n";
                                      print "\t\t\"{#$name}\":\"$item\"\n";
                                      print "\t}\n";
                              }
                      }
                      print "\n\t]\n";
                      print "}\n";
              }
              


              есть встроенная функция json_encode()

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое