Проверяем Zabbix

11 апреля 2019

Правильно настроенный мониторинг позволяет вовремя (в идеале — заблаговременно) узнавать нам о проблемах. Однако что случится, если проблема возникнет с ним самим? Узнать о ней получится, когда произойдёт одно из двух:

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

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

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

  • небесплатность,
  • ненадёжность (истории о проспавшем аварию ночном саппорте по понятным причинам не афишируются, но наверняка имеются у многих, кто с ним связан).

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

  • внутренним, т.е. запускаемым внутри той же системы, на которой запущен сам 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/

Пример настройки бесплатного аккаунта на одном из них:

zabbix-health-montastic

Отдельного упоминания заслуживает гениальное решение, основанное на электронных таблицах Google(sic!): https://www.labnol.org/internet/website-uptime-monitor/21060/

Наконец, в эпоху тотального безлимита и 4G можно выполнять проверки прямо с собственного телефона. Судя по описанию, приложение Web Alert вполне подходит для этой цели.



← Назад в Блог

Подпишитесь на новые статьи: