Собака-спостерігака призначена для собачення та спостерігачення оперативного моніторингу навколишньої реальності. Вона надає гнучкий функціонал для опису позаштатних ситуацій практично будь-якої дивності, а також для сповіщення у разі їх виникнення. У тому числі, за допомогою відсилання SMS через послуги, що підтримуються собакою-посилакою, електронною поштою, месенджера Telegram а також може викликати запуск будь-якого зовнішнього скрипта при настанні якоїсь події. Для ввімкнення собаки-спостерігаки, потрібна зміна опції WATCHDOG_ENABLED в alter.ini. Також для надсилання повідомлень, очевидно, на допомогу собаці-спостерігаці знадобиться Собака-посилака.
Типи задач | Дія | Повертає |
---|---|---|
icmpping | виконується ICMP ping хоста вказаного в параметрі | bool |
tcpping | виконується спроба TCP з'єднання з хостом вказаним в параметрі у вигляді host:port | bool |
udpping | виконується спроба UDP з'єднання з хостом вказаним в параметрі у вигляді host:port | bool |
hopeping | Пінг надії. Тричі виконується ICMP ping хоста вказаного в параметрі, в надії, що хоч один з них повернеться | bool |
script | запуск shell-скрипту за шляхом, вказаному в параметрі | string |
httpget | отримання сирих даних з URL вказаного в параметрі | string |
getusertraff | отримання кількості трафіку в байтах логіну користувача вказаного в параметрі | int |
fileexists | перевірка на існування файлу по шляху вказаному в параметрі | bool |
opentickets | кількість відкритих тікетів хелпдеску. Потребує вказання рандомного параметра | int |
onepunch | Виконує запуск One-Punch скрипта з аліасом вказаним в параметрі. Результат очікується у вигляді змінної $watchdogCallbackResult | string |
snmpwalk | Виконується snmpwalk по OID хоста що вказано в параметрі у форматі host:community:OID | string |
freediskspace | Повертає кількість вільного місця на розділі (точці монтування) вказаній в параметрі. Повертає цифру в Гб. | float |
Оператор | Значення | Потребує “Умову”? |
---|---|---|
=true | Істинно | |
=false | Хибно | |
== | Рівне | + |
!= | Не рівне | + |
> | Більше | + |
< | Менше | + |
> = | Більше або рівне | + |
< = | Менше або рівне | + |
empty | Пустий результат | |
notempty | Непустий результат | |
changed | Змінилось | |
notchanged | Не змінилось | |
like | Містить | + |
notlike | Не містить | + |
rised | Збільшилось | +- |
decreased | Зменшилось | +- |
Дії | Результат |
---|---|
log | запис події в системний лог |
sms | надсилання SMS сповіщення на номери стільникових, вказаних в налаштуваннях Собаки-спостергаки. Додаткові номери стільникових можна вказати у форматі {номер,номер}. |
noprimary | у випадку наявності цієї дії, та дії sms та вказаних {додаткових номерах} - основні номери з налаштувань Собаки-спостерігаки будуть проігноровані. |
надсилання сповіщення електропоштою, на адреси вказані в налаштуваннях. | |
telegram | надсилання повідомлення Telegram, додаткові chatid можна вказати в форматі (чат1,чат2). |
no_tg_primary | у випадку наявності цієї дії, та дії telegram та вказаних (додаткових чатах) - основні чати Telegram ігноруються. |
andresult | у разі вказаних дій sms, telegram чи email до тексту повідомлення буде додано поточний результат завдання |
oldresult | у разі вказаних дій sms, telegram чи email до тексту повідомлення буде додано попередній результат завдання |
script | запуск скрипта чи будь-якого додатку, за шляхом, вказаному у вигляді [/повний/шлях/до_скрипта] |
Кожне завдання для Собаки-спостерігаки слід сприймати як “щось трапилося” або “ой яка подія” які трапляються у разі повернення “типом перевірки” за “параметром” результату передбаченого “оператором” з опціональною “умовою”. Як приклад, можна навести ось таке просте завдання:
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
Гугл не пінгається | icmpping | google.com | =false | log,sms,email |
При настанні події, коли ping на адресу google.com поверне значення “хибно” собака-спостерігака стурбовано надішле вам СМС-ку, почту та запише в лог сповіщення, про те, що “Гугл не пінгається”. Власне і продовжить це робити, допоки перевірка icmpping не перестане повертати значення “хибно”.
А що робити, якщо ми не хочемо, щоб собака постійно нам спамила за відсутнього пінгу кудись? А дуже просто.
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
Щось змінилось | icmpping | 192.168.0.22 | changed | log,sms,email,andresult |
В принципі, ніхто не забороняє нам робити і завдання такого плану:
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
В серверній пожежа | script | /bin/gettemp | > | 22 | log,sms,email, andresult |
Викликаючи зовнішній скрипт, що знімає температуру з термодатчиків і при перевищенні 22 градусів кричати всіма відомими способами також дописуючи в повідомленні про температуру що викликає паніку.
Якщо творчо підійти до парсингу виведення зовнішнього ПЗ - можна моніторити багато цікавих речей без дописування зовнішніх скриптів:
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
DNS зламався | script | nslookup google.com | tail -n 2 | like | find | log,sms |
Також ми можемо дуже просто та елегантно контролювати запущеність важливих сервісів типу stargazer, створивши завдання такого плану:
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
Stargazer впав | script | /bin/ps aux | /usr/bin/grep stg | notlike | stargazer | log,sms |
Хоча те саме завдання ми можемо оформити як
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
Stargazer впав | fileexists | /var/run/stargazer.pid | =false | log,sms |
Щоб не зосереджуватись на теоретичних речах, ми можемо передбачити ситуацію, коли у нас є дуже важливий клієнт на падіння якого, нам слід таки відреагувати. Описати таке ми можемо у вигляді:
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
Важливий клієнт подох | hopeping | 172.16.78.42 | changed | log,sms andresult |
Окей, а якщо ми хочемо також цю ж СМС послати скажемо адміністратору цього ж важливого клієнта, щоб він точно знав, що він здох?
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
Важливий клієнт подох | hopeping | 172.16.78.42 | changed | log,sms andresult {+380509999999} |
а якщо ми хочемо надіслати СМС лише на додатковий номер? Так, жодних проблем:
log,sms {+380509999999} noprimary
а якщо ми хочемо надіслати алерт тільки в додатковий чат Telegram? Так, так само без проблем:
log,telegram,email (ChatID1,ChatID2,ChatID3) no_tg_primary
а можна міксувати СМС та Telegram? Так, просто вказуємо в діях все що нам потрібно разом:
log,telegram,sms,email (ChatID1,ChatID2,ChatID3) no_tg_primary {+380509999999} noprimary
Хоча знову ж таки ніхто не забороняє нам, скажімо моніторити цього клієнта наприклад зовнішнім скриптом, що запитує по SNMP стан порту або лічильники трафіку на світі, куди його встромлено, або банально його смикати по якомусь TCP порту. Думаю вищенаведених прикладів достатньо для отримання уявлення про гнучкість собаки-спостерігаки та можливості побудови сповіщень при практично, будь-яких позаштатних ситуаціях. Обробка завдань собаки відбувається при виклику watchdog з RemoteAPI. Інтервалом, що рекомендується, є 10 хвилин. В crontab це виглядає наступним чином:
*/10 * * * * /bin/ubapi "watchdog"
А ще ми можемо реагувати не лише на зміни поточних значень щодо якихось порогів, а також і відносно попередніх значень, отриманих собакою. Різкість цих змін ми можемо опціонально вказувати в “умові”. А можемо й не вказувати. Тоді ми реагуватимемо взагалі на всі зміни цих циферок у бік збільшення або зменшення. Наприклад, ми можемо реагувати на збільшення зростання помилок на якомусь інтерфейсі.
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
Помилки полізли | onepunch | uplinkerrors | rised | log,sms,telegram |
Ну або якийсь рівень зростання помилок ми вважаємо припустимим, і наприклад, встановлюємо поріг у 100 помилок за 10 хвилин
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
Помилки полізли активно | onepunch | uplinkerrors | rised | 100 | log,sms,telegram |
Також ми можемо у такий спосіб відловлювати або різкі сплески або падіння утилізації, наприклад, того ж каналу. Типу, ми вважаємо, що якщо утилізація каналу за останні 10 хвилин зросла на вісім гіг.. типу щось пішло не так
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
Канал якось розігнався | onepunch | uplinktraffic | rised | 8000 | log,sms,telegram |
Ну чи навпаки різкі падіння щодо попередніх значень (типу утилізація аплінку провалилася на 20 гіг від останнього запуску собаки)
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
Трафік рухнов якось сильно | onepunch | uplinktraffic | decreased | 20000 | log,sms,telegram |
А ще ми можемо дуже просто перевіряти робочість сервісів, які повинні слухати з'єднання на якісь TCP або UDP порти, типу так
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
http на хості | tcpping | 192.168.42.18:80 | changed | log,sms,telegram andresult |
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
https на хості | tcpping | 192.168.42.18:443 | changed | log,sms,telegram andresult |
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
syslogd на хості | udpping | 192.168.42.18:514 | changed | log,sms,telegram andresult |
А ще ми можемо отримувати та перевіряти будь-які дані з будь-якого OID за допомогою snmpwalk:
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
Версія OS змінилась | snmpwalk | 192.168.42.18:changeme:.1.3.6.1.2.1.1.1.0 | changed | log,sms,telegram andresult |
Або банально нотифікувати себе про те, що у кореневому розділі закінчується місце
Ім`я | Тип перевірки | Параметр | Оператор | Умова | Дії |
---|---|---|---|---|---|
В корені закінчується місце | freediskspace | / | < | 100 | log,sms,telegram andresult |
Весь функціонал відправлення реалізований за допомогою підсистеми "Собака-посилака". Номери для відсилання розділяються комами та вказуються у міжнародному форматі (наприклад +380509999999) в налаштуваннях “Собаки-спостерігаки”. Їх як основних так і додаткових може бути скільки завгодно.
Здійснюється також під час проходу “Собаки-посилаки”. Як налаштувати свого бота для розсилки, можна почитати тут.
Як вже сказано вище, за допомогою собаки-спостерігаки, можливо заскриптувати і контролювати взагалі все, що завгодно - це обмежено тільки вашою фантазією та радіусом кривизни рук. Ми рекомендуємо зберігати кастомні скрипти такого плану в content/documents/myscripts/ - таким чином вони нормально переживатимуть оновлення. Ось кілька простих прикладів:
#!/usr/local/bin/php <?php //config section $port='24'; $oid='.1.3.6.1.2.1.31.1.1.1.6'; $ip='192.168.0.15'; $community='yourcommunity'; //end of config $cmd='/usr/local/bin/snmpwalk -v2c -On -c '.$community.' '.$ip.' '.$oid.'.'.$port; $raw=shell_exec($cmd); $newTime=time(); if (!empty($raw)) { $raw=explode('Counter64:',$raw); $raw=trim($raw[1]); if (!empty($raw)) { $cacheName=dirname(__FILE__).'/octets_'.$ip.'_'.$port; if (file_exists($cacheName)) { $oldTime=filemtime($cacheName); $oldOctets=file_get_contents($cacheName); $traffDiff=$raw-$oldOctets; $timeDiff=$newTime-$oldTime; if ($timeDiff!=0) { $speed=($traffDiff*8*100)/($timeDiff*10000); print(round($speed/10000)); } else { print('-1'); } file_put_contents($cacheName,$raw); } else { file_put_contents($cacheName,$raw); print('0'); } } }
#!/usr/local/bin/php <?php $ip='192.168.0.89'; $community='yourcommunity'; $oid='1.3.6.1.4.1.35160.1.16.1.13.1'; $result=0; $command='/usr/local/bin/snmpwalk -On -r 1 -t 1 -v2c -c '.$community.' '.$ip.' '.$oid; $resultRaw=shell_exec($command); if (!empty($resultRaw)) { $result=explode('INTEGER:',$resultRaw); $result=(isset($result[1])) ? $result[1] : 0; $result=$result/10; } print($result);
#!/usr/local/bin/php <?php $ip='192.168.0.89'; $community='yourcommunity'; $oid='1.3.6.1.4.1.35160.1.26.0'; $result=0; $command='/usr/local/bin/snmpwalk -On -r 1 -t 1 -v2c -c '.$community.' '.$ip.' '.$oid; $resultRaw=shell_exec($command); if (!empty($resultRaw)) { $result=explode('INTEGER:',$resultRaw); $result=(isset($result[1])) ? $result[1] : 0; $result=($result==1) ? 'OK' : 'FAILED!'; } print($result);
#!/usr/local/bin/php <?php $result=disk_free_space("/var/"); $result=$result/1024/1024/1024; $result=round($result,2); print($result); ?>
#!/bin/sh ping -c 1 8.8.8.8 | head -n 2 | tail -n 1 | awk -F "=" '{print $4}' | awk -F " " '{print $1}
#!/usr/local/bin/php <?php $wallet='72F7B047C19217871cef591Bc6e960aAD333B822'; $workersStatsUrl = 'https://api.ethermine.org/miner/:'.$wallet.'/workers'; $jsonRaw = file_get_contents($workersStatsUrl); $reportedHashrate = 0; if (!empty($jsonRaw)) { $workersStats = json_decode($jsonRaw, true); if (!empty($workersStats)) { foreach ($workersStats['data'] as $io => $each) { $reportedHashrate+=$each['reportedHashrate']; } } } print(round($reportedHashrate/1000000,2));
#!/bin/sh /usr/bin/netstat -w 1 -I bridge0 -q 1 | /usr/bin/tail -n 1 | /usr/bin/awk {'print $1'}
#!/usr/local/bin/php <?php /** CPU Statistics Load 1 minute Load: .1.3.6.1.4.1.2021.10.1.3.1 5 minute Load: .1.3.6.1.4.1.2021.10.1.3.2 15 minute Load: .1.3.6.1.4.1.2021.10.1.3.3 CPU percentage of user CPU time: .1.3.6.1.4.1.2021.11.9.0 raw user cpu time: .1.3.6.1.4.1.2021.11.50.0 percentages of system CPU time: .1.3.6.1.4.1.2021.11.10.0 raw system cpu time: .1.3.6.1.4.1.2021.11.52.0 percentages of idle CPU time: .1.3.6.1.4.1.2021.11.11.0 raw idle cpu time: .1.3.6.1.4.1.2021.11.53.0 raw nice cpu time: .1.3.6.1.4.1.2021.11.51.0 */ $oid='.1.3.6.1.4.1.2021.10.1.3.3'; $ip='192.168.0.70'; $community='yourcommunity'; //end of config $cmd='/usr/local/bin/snmpwalk -v2c -On -c '.$community.' '.$ip.' '.$oid; $raw=shell_exec($cmd); $newTime=time(); if (!empty($raw)) { $raw=explode('STRING:',$raw); $raw=trim($raw[1]); if (!empty($raw)) { print($raw); } else { print('FAIL'); } }
Ще більш доцільним є використання One-Punch скриптів замість просто скриптів, котрі лежать десь на вашій ФС. Використовуючи їх, ви отримуєте одразу дві головні переваги:
Допустимо беремо і створюємо One-Punch скрипт наступного виду:
$watchdogCallbackResult='sometest data';
Ось якось так
І припустимо ми хочемо контролювати чи не зміняться дані, що повертаються цим скриптом (з чого б це? ;)
Сподіваюсь очевидно, що собака-спостерігака сприйматиме як результат виконання скрипта лише дані, що знаходяться в змінній $watchdogCallbackResult?
Також є ще одне невелике обмеження, що стосується не тільки даних, що повертаються One-Punch скриптами, а також і до таких типів перевірок як script і httpget. Обмеження полягає в тому, що оператори changed і notchanged не працюють адекватно якщо обсяг даних, що повертаються цими перевірками, становить більше 255 байт. Тому якщо ви збираєтеся використовувати ці оператори для контролю змін у даних, що повертаються вищевказаними типами, вам слід це враховувати при розробці ваших скриптів. Для перевірок типу like або скажемо notempty це не важливо.
Ось наприклад те саме зняття даних про трафік з порту світча, але вже у вигляді One-Punch скрипта:
//config section $port='8'; $oid='.1.3.6.1.2.1.31.1.1.1.6'; $ip='192.168.18.234'; $community='changeme'; //end of config $cmd='/usr/local/bin/snmpwalk -v2c -On -c '.$community.' '.$ip.' '.$oid.'.'.$port; $raw=shell_exec($cmd); $newTime=time(); if (!empty($raw)) { $raw=explode('Counter64:',$raw); $raw=trim($raw[1]); if (!empty($raw)) { $cacheName='content/documents/myscripts/octets_'.$ip.'_'.$port; if (file_exists($cacheName)) { $oldTime=filemtime($cacheName); $oldOctets=file_get_contents($cacheName); $traffDiff=$raw-$oldOctets; $timeDiff=$newTime-$oldTime; if ($timeDiff!=0) { $speed=($traffDiff*8*100)/($timeDiff*10000); $watchdogCallbackResult=round($speed/10000); } else { $watchdogCallbackResult='-1'; } file_put_contents($cacheName,$raw); } else { file_put_contents($cacheName,$raw); $watchdogCallbackResult='0'; } } }
$result=disk_free_space("/"); $result=$result/1024/1024/1024; $watchdogCallbackResult=round($result,2);
$oid = '.1.3.6.1.4.1.2021.10.1.3.3'; $ip = '192.168.0.70'; $community = 'yoursnmpcommunity'; $cmd = '/usr/local/bin/snmpwalk -v2c -On -c ' . $community . ' ' . $ip . ' ' . $oid; $raw = shell_exec($cmd); $watchdogCallbackResult = ''; if (!empty($raw)) { $raw = explode('STRING:', $raw); $raw = trim($raw[1]); if (!empty($raw)) { $watchdogCallbackResult .= $raw; } }
$command='ps aux | grep dhcpd | grep -v grep'; $result=shell_exec($command); if (!empty($result)) { $watchdogCallbackResult='запущено'; } else { $watchdogCallbackResult='впав нахрін'; }
$watchdogCallbackResult = ''; $dvrs = new NyanORM('visor_dvrs'); $dvrs->where('type', '=', 'trassir'); $allDvrs = $dvrs->getAll(); if (!empty($allDvrs)) { foreach ($allDvrs as $io => $each) { $dvrName = $each['name']; $trassir = new TrassirServer($each['ip'], $each['login'], $each['password'], $each['apikey'], $each['port']); $health = $trassir->getHealth(); if (!$health['disks'] OR ! $health['database'] OR ! $health['network']) { $watchdogCallbackResult .= $dvrName . ' - FAILS '; } } }
$ip = '192.168.0.89'; $community = 'yourcommunity'; $oid = '1.3.6.1.4.1.35160.1.16.1.13.1'; $correction = 0; $snmp = new SNMPHelper(); $resultRaw = $snmp->walk($ip, $community, $oid, false); $watchdogCallbackResult = 0; if (!empty($resultRaw)) { $watchdogCallbackResult = zb_SanitizeSNMPValue($resultRaw) / 10; $watchdogCallbackResult = $watchdogCallbackResult + $correction; }
$ip = '192.168.0.89'; $community = 'yourcommunity'; $oid = '1.3.6.1.4.1.35160.1.26.0'; $result = 0; $snmp = new SNMPHelper(); $resultRaw = $snmp->walk($ip, $community, $oid, false); $watchdogCallbackResult = 0; if (!empty($resultRaw)) { $watchdogCallbackResult = zb_SanitizeSNMPValue($resultRaw); $watchdogCallbackResult = ($watchdogCallbackResult == 1) ? 'OK' : 'FAILED!'; }
$criticalTemp = 70; $watchdogCallbackResult = ''; $tempPath = OLTAttractor::TEMPERATURE_PATH; $tempExt = OLTAttractor::TEMPERATURE_EXT; $switchesDb = new nya_switches(); $switchesDb->where('desc', 'LIKE', '%OLT%'); $allOlt = $switchesDb->getAll(); foreach ($allOlt as $io => $eachOltData) { $tempData = $tempPath . $eachOltData['id'] .'_'. $tempExt; if (file_exists($tempData)) { $oltTemp = file_get_contents($tempData); if ($oltTemp >= $criticalTemp) { $watchdogCallbackResult .= $eachOltData['location'] . ' - ' . $oltTemp . ' °C '; } } } if (empty($watchdogCallbackResult)) { $watchdogCallbackResult='Всі температури OLT в порядку'; }
// ключ HTTP API $apiKey = 'xxxxxxxxxxxxxxxxxx'; // поріг коштів після якого нотифікувати $lowerLimit = 4000; $apiCallback = 'http://api.turbosms.ua/user/balance.json'; $turboSmsApi = new OmaeUrl($apiCallback); $turboSmsApi->dataGet('token', $apiKey); $balanceRaw = $turboSmsApi->response(); $watchdogCallbackResult = ''; if (!empty($balanceRaw)) { @$balanceRaw = json_decode($balanceRaw, true); if (!empty($balanceRaw)) { if (isset($balanceRaw['response_result'])) { if (isset($balanceRaw['response_result']['balance'])) { $balance = $balanceRaw['response_result']['balance']; if ($balance>=$lowerLimit) { $watchdogCallbackResult.=' - коштів достатньо. '; } else { $watchdogCallbackResult.=' - добре би поповнити! '; } } } } }
$watchdogCallbackResult = ' '; $deadOltCount=0; $deadSwitches = zb_SwitchesGetAllDead(); if (!empty($deadSwitches)) { $switchesDb=new NyanOrm('switches'); $switchesDb->where('desc','LIKE','%OLT%'); $allOlts=$switchesDb->getAll('ip'); if (!empty($allOlts)) { foreach ($allOlts as $oltIp=>$eachOltData) { if (isset($deadSwitches[$oltIp])) { $deadOltCount++; } } } } if ($deadOltCount>0) { $watchdogCallbackResult .= $deadOltCount.' завернулись шубою. Можна починати панікувати!'; } else { $watchdogCallbackResult .= 'зі всіма наразі все гаразд.'; }