Правильно настроенный мониторинг позволяет вовремя (в идеале — заблаговременно) узнавать нам о проблемах. Однако что случится, если проблема возникнет с ним самим? Узнать о ней получится, когда произойдёт одно из двух:
- либо авария, о которой администратор узнает не из оповещений мониторинга, а от раздражённых коллег и разгневанных клиентов,
- либо (если повезёт) администратор обратит внимание на прекратившийся поток мелких уведомлений (об обновлениях пакетов, всплесках нагрузки и т.д.) и самостоятельно решит выяснить причину.
Поэтому, каким бы совершенным ни был мониторинг, ему обязательно требуется инструмент, производящий независимую внешнюю проверку.
Наиболее известным частным случаем такого инструмента являются дежурные операторы. Но как у всякого инструмента, наряду с бесспорными плюсами у них есть и существенные минусы:
- небесплатность,
- ненадёжность (истории о проспавшем аварию ночном саппорте по понятным причинам не афишируются, но наверняка имеются у многих, кто с ним связан).
Поэтому рассмотрим, какие автоматические инструменты могли бы нам помочь. Набор автоматических инструментов может быть:
- внутренним, т.е. запускаемым внутри той же системы, на которой запущен сам Zabbix,
- внешним.
Внутренний имеет как достоинства, так и недостатки.
С одной стороны, у него имеется прямой доступ ко всем проверяемым компонентам, в т.ч. недоступным снаружи. Это позволяет не только находить неисправность, но и сразу определять, где именно она произошла.
Например, следующий несложный сценарий проверяет, что запущены ядро Zabbix'a, веб-сервер, MySQL и ElasticSearch (про сохранение метрик в ElasticSearch см. в отдельной заметке).
#!/bin/sh
Alert() { echo "$@" | mail -s "Zabbix Server ALERT" root monitoring-admins@our-company.ru; }
Service_check() { systemctl status "$1" >/dev/null 2>&1 || Alert "Service $1 is not running." ; }
Port_check() { ss -ntlp "sport = :$1" | grep -q ":$1" || Alert "Port $1 is not listening."; }
Service_check zabbix-server
Service_check mariadb
Service_check apache2
Service_check elasticsearch
Port_check 443 # web server
Port_check 3306 # mysql
Port_check 10051 # zabbix server
Port_check 9200 # elasticsearch
Сохраните его в /etc/cron.hourly
, исправьте our-company.ru на домен своей организации и сделайте исполняемым.
Ключевой недостаток такого подхода состоит в уязвимости к общим с Zabbix'ом проблемам на сервере и в сети.
Например, если у сервера пропадёт сетевое соединение, приведённый сценарий не сумеет об этом сообщить,
потому что потеряет связь с внешним миром так же, как и сам Zabbix.
Аналогично, если сервер повиснет, то проверяющий перестанет работать вместе с проверяемым.
Поэтому внутренняя проверка не помешает, но только как дополнение к внешней, а не замена для неё.
Однако создание внешней проверки связано с определёнными сложностями:
- недостаточно просто проверить, что веб-интерфейс Zabbix'a возвращает HTTP-код 200, т.к. веб-интерфейс может продолжать работать, несмотря на отказ ядра и СУБД;
- доступ к странице report.status, на которой веб-интерфейс сообщает состояние ядра, требует прав администратора;
- JSON RPC API так же требует прав администратора для всех запросов, кроме бесполезного в данном случае apiinfo.version;
- авторизация и запрос данных и для веб-страниц, и для API производятся отдельными запросами, причём второму нужны данные из первого ответа;
- логин и пароль для авторизации придётся хранить во внешней системе в незашифрованном виде.
Эти недостатки можно смягчить, создав на стороне Zabbix'a собственный PHP-файл и перенеся в него:
- логин-пароль,
- процедуру авторизации,
- контрольный запрос и вывод ответа «всё-хорошо».
Разумеется, внешние обращения к нему должны быть ограничены:
- через IP access list в настройках веб-сервера,
- через HTTP-авторизацию по логину-паролю — там же,
- именем файла, про которое во внешнем мире будет знать только проверяющий робот, например, ZabbixCheckAlive-VerySecretSuffix.php
В идеале хотелось бы полностью отказаться от хранения админского логина-пароля в открытом виде даже на стороне веб-сервера, но это затруднительно, т.к. веб-интерфейс бОльшую часть запросов обрабатывает не самостоятельно, а передаёт ядру и обязан авторизоваться. Например, упомянутая выше веб-страница report.status вызывает функцию API status.get.
До версии 3.3.0 существовало несколько функций, возвращавших полезные сведения БЕЗ авторизации. Проверочная страница с ними выглядела примерно так:
<?php
$_POST = $_GET = $_COOKIE = $_REQUEST = array();
$badScript = 44444; //something non-existent
$badHost = 55555;
require_once dirname(__FILE__).'/include/config.inc.php';
$s = new CZabbixServer($ZBX_SERVER, $ZBX_SERVER_PORT, ZBX_SOCKET_TIMEOUT, 0);
printf("%s -- %s -- %s\n",
(string)$s->isRunning(),
(string)$s->executeScript($badScript, $badHost),
(string)$s->getError());
?>
Она в любом случае возвращала какую-то ошибку, но ошибка 1,,Unknown Host ID [55555].
свидетельствовала о том, что система работает нормально.
К сожалению, начиная с версии 3.3.0 данная лазейка исчезла (обсуждение и исправление).
К счастью, даже после исправления обработчик execute_script проверяет идентификатор сессии не ДО номера хоста, а ПОСЛЕ него. Поэтому новый вариант будет вызывать execute_script дважды:
- с неправильным HostID, чтобы получить ответ Unknown host identifier, т.е. убедиться, что HostID действительно проверяется перед SessionID,
- с правильным HostID, чтобы получить ответ Permission denied из-за неверного SessionID, т.е. убедиться, что поиск HostID в SQL-базе прошёл успешно, а значит, SQL-сервер жив и здоров.
<?php
$_POST = $_GET = $_COOKIE = $_REQUEST = array();
$badScript = 0;
$badHost = 0;
$goodHost = 10304;
require_once dirname(__FILE__).'/include/config.inc.php';
$s = new CZabbixServer($ZBX_SERVER, $ZBX_SERVER_PORT, ZBX_SOCKET_TIMEOUT, 0);
$a = $s->executeScript($badScript, $goodHost, '');
$b = $s->getError();
$s = new CZabbixServer($ZBX_SERVER, $ZBX_SERVER_PORT, ZBX_SOCKET_TIMEOUT, 0);
$c = $s->executeScript($badScript, $badHost, '');
$d = $s->getError();
print "$a,$b,$c,$d\n";
?>
Обратите внимание, что goodHost должен содержать реально существующее значение! Выбрать его из базы данных можно, например, следующим образом:
select min(hostid) from zabbix.hosts where status = 0;
Требуемый ответ теперь будет выглядеть так:
,Permission denied.,,Unknown host identifier.
Единственный остающийся вопрос — какому внешнему сервису поручить опрашивать наш PHP-обработчик и уведомлять нас в том случае, если:
- ответ не получен,
- либо имеет не код 200,
- либо не содержит текст «правильной» ошибки?
Собственные предпочтения мы навязывать не станем, вместо этого сошлёмся на свежий обзор возможных вариантов: https://hostingfacts.com/website-monitoring-services/
Пример настройки бесплатного аккаунта на одном из них:
Отдельного упоминания заслуживает гениальное решение, основанное на электронных таблицах Google(sic!): https://www.labnol.org/internet/website-uptime-monitor/21060/
Наконец, в эпоху тотального безлимита и 4G можно выполнять проверки прямо с собственного телефона. Судя по описанию, приложение Web Alert вполне подходит для этой цели.