Ключевые слова:monitor, shell, log, apache, web, mysql, (найти похожие документы)
From: Foxy S. Aries (Alexei A. Abramov) <foxy@randconsult.ru.>
Newsgroups: http://www.freelance.pp.ru
Date: Mon, 20 Sep 2004 18:21:07 +0000 (UTC)
Subject: Разработка скрипта для протоколирования событий и мониторинга MySQL и Apache
Оригинал: http://www.freelance.pp.ru
Скрипт протоколирования событий на shell.
Содержание:
* Зачем это нужно;
* Формулируем основные требования;
* Интерфейс и расположение;
* Переменные скрипта;
* Отправка e-mail;
* Запись в файл протокола и его ротация;
* Что получилось;
* Просмотр записей в браузере;
* Заключение.
Зачем это нужно
Предположим, что у нас есть UNIX-box или целая ферма серверов,
возможно, есть набор сервисов у хостинг-провайдера или удаленные
серверы. Время от времени состояние серверов и демонов изменяется:
свободное пространство какого-либо раздела может исчерпаться, вслед за
этим тихо <<валится>> squid, может <<упасть>> web-сервер или
отключиться репликация баз данных. Предположим, что наступление всех
критических событий контролируется. Неважно, какими средствами, хотя
бы и скриптами из cron. Возникает вопрос: как оперативно оповестить
системного администратора о том или ином событии, не заставляя его
денно и нощно читать логи?
Формулируем основные требования
Некоторого представления о скриптинге на shell и настроенного MTA
(sendmail) оказывается вполне достаточно, чтобы разработать несложную
и вместе с тем надежную и гибкую систему протоколирования. Итак,
исходные требования к ней:
* простота и гибкость - по-моему, проще shell нет ничего и,
вооружившись руководствами, можно легко добавить скрипту
дополнительные возможности или же заставить его работать
по-другому;
* оперативность оповещения - система будет отправлять почтовые
сообщения на e-mail системного администратора;
* надежность - дополнительный протокол, в который не только
дублируются сами критические события, но и результаты отправки
почтовых сообщений;
* безопасность - дело сисадмина закрывать <<дыры>>, но никак не
обратное;
* бережное отношение к дисковому пространству - весьма неплохо
хранить протоколы как можно дольше, чтобы при этом они <<весили>>
как можно меньше.
Интерфейс и расположение
Сначала определим местоположение скрипта и директории протоколов нашей
системы. На мой взгляд, резонно создать
/root/scripts/ с режимом 500 (r-x) и владельцем root:wheel и
расположить скрипт там с такими же правами, назовем его
report.error.sh. К тому же, наверняка, контроль событий ведется от
имени rootа и у вызывающих программ хватит прав вызывать скрипт.
Протоколы будут находиться в /var/log/ и называться error.reports.log.
Такое расположение оптимально, по сравнению, например, с /usr/local/,
особенно, если в операционной системе есть аккаунты "живых"
пользователей.
Положим порядок и значение передаваемых скрипту report.error.sh
параметров:
1. имя вызывающей программы;
2. процедура или проверяемое событие;
3. код ошибки или завершения (иногда здорово помогает);
4. поток вывода и/или ошибок вызывающей программы.
Переменные скрипта
Начнем c инициализации переменных. Иногда, удобно определить даже то,
что только гипотетически может измениться как переменную и вынести ее
наверх. Удобство проявляется в громоздком скрипте, который до этого
был забыт на год-два:
#!/bin/sh
recipient=admin\@host.ru
ccrecipient=admin_ cell_phone\@nwgsm.ru
log_file=/var/log/error.reports.log
max_log_file_size=500000 # in bytes
date=`date`
Получатель email в recipient, копия направляется ccrecipient,
<<собака>> в адресах обязательно экранируется "\". Из соображений
повышения надежности доставки, хост у recipient лучше выбрать с
наименьшей вероятностью того, что он будет недоступен.
Сверхоперативное оповещение можно организовать элементарно,
зарегистрировав почтовый ящик у оператора сотовой связи и отправляя
копии на него. У <<Мегафона>> такая услуга есть, доставка практически
моментальная. Имя файла протокола - log_file, max_log_file_size -
максимальный размер в байтах, при достижении которого будет
производиться ротация. В переменную date заносится текущее время и
дата в формате и локали rootа:
Tue Oct 14 15:31:16 MSD 2003
Отправка e-mail
Первым делом, пытаемся отправить почтовое сообщение:
mail -s "Сообщение об ошибке" -c $ccrecipient $recipient <<!
Время: $date
Скрипт: $1
Действие: $2
Дополнительная информация:
Код ошибки: $3
Поток вывода ошибочной команды:
$4
!
Для единственного получателя вызов mail немного иной:
mail -s "Сообщение об ошибке" $recipient <<!
Если основным почтовым клиентом будет сотовый телефон администратора,
темой сообщения стоит сделать второй передаваемый параметр, чтобы без
дополнительных манипуляций (открытия тела сообщения) и расходов
получить представление о произошедшем.
По коду возврата mail проверяем, ушла ли почта. В условном операторе
можно расположить и другой полезный код, например, реанимирующий
sendmail:
if [ $? -ne 0 ]
then not_string=" не"
fi
Запись в файл протокола и его ротация
Затем, записываем переданные параметры и отчет отправки в наш
лог-файл. Разделителями полей могут служить любые символы, не только
табуляции. Все зависит от того, кто будет читать протоколы: человек,
парсер или все понемногу. По моему мнению, для последней ситуации,
один символ табуляции как разделитель полей - компромисс: MS Excel
будет счастлив при импорте, с текстовой консоли лог вполне читаем и
парсер на Perl написать несложно:
echo "$date $1 $2 $3 $4 \
Уведомление $not_string отправлено ${recipient}, \
копия ${ccrecipient}." >> $log_file
Немного белой магии. Если размер файла протокола стал больше
max_log_file_size, его нужно ротировать, а ротированный - сжать.
Выясняем размер протокола в байтах и сравниваем с предельным:
log_file_size=`wc -c $log_file | awk '{print $1}'`
if [ $log_file_size -gt $max_log_file_size ]
При положительном результате, проверяем наличие в /var/log/ файлов
error.reports.log.0.gz, потом error.reports.log.1.gz,
error.reports.log.2.gz и так далее. Это суть ротированные и сжатые до
этого логи. Последовательный перебор идет до тех пор, пока не найдется
последний файл или <<окно>> между ними (очевидно, что заглядывать в
/var/log/ нужно чаще, чем i станет больше MAXINT):
then
i=0
while [ -f $log_file.$i.gz ]
do
i=`expr $i + 1`
done
Ротируем, сжимаем и оставляем запись об этом в следующем протоколе:
mv $log_file $log_file.$i
gzip $log_file.$i
echo "`date`: Ротация логов. \
Предыдущий сохранен с номером $i" >> $log_file
fi
Что получилось
Скрипт целиком выглядит так:
#!/bin/sh
recipient=admin\@host.ru
ccrecipient=admin_ cell_phone\@nwgsm.ru
log_file=/var/log/error.reports.log
max_log_file_size=500000 # in bytes
date=`date`
### send mail ###
mail -s "Сообщение об ошибке" -c $ccrecipient $recipient <<!
Время: $date
Скрипт: $1
Действие: $2
Дополнительная информация:
Код ошибки: $3
Поток вывода ошибочной команды:
$4
!
### test if mail is sent ###
if [ $? -ne 0 ]
then not_string=" не"
fi
### log into file ###
echo "$date $1 $2 $3 $4 \
Уведомление $not_string отправлено ${recipient}, \
копия ${ccrecipient}." >> $log_file
### turn over log file if needed ###
log_file_size=`wc -c $log_file | awk '{print $1}'`
if [ $log_file_size -gt $max_log_file_size ]
then
i=0
while [ -f $log_file.$i.gz ]
do
i=`expr $i + 1`
done
mv $log_file $log_file.$i
gzip $log_file.$i
echo "`date`: Ротация логов. \
Предыдущий сохранен с номером $i" >> $log_file
fi
Теперь его можно вызывать из других скриптов или программ мониторинга.
Вызов из скрипта /root/scripts/monitor.something.sh, запущенного от
имени rootа делается примерно так:
#!/bin/sh
...
some_parameters="process"
...
result=`/usr/local/bin/some_program -$some_parameters 2>&1`
result_code=$?
if [ $result_code -ne 0 ]
then
/root/scripts/report.error.sh $0 "нет ответа \
от $some_parameters в течение 1 сек." $result_code "$result"
fi
...
В качестве уведомления на адреса [email protected] и admin_
[email protected] приходит письмо:
From: Charlie Root
To: [email protected]
Subject: Сообщение об ошибке
Время: Tue Oct 14 15:31:16 MSD 2003
Скрипт: /root/scripts/monitor.something.sh
Действие: Нет ответа от process в течение 1 сек.
Дополнительная информация:
Код ошибки: 1
Поток вывода ошибочной команды:
Can't connect to server (Operation timed out)
Просмотр записей в браузере
Можно пойти дальше - дополнить нашу систему протоколирования несложным
парсером для просмотра протоколов с помощью браузера. Для этого
подойдет web-сервер apache, у которого есть настроенная на
аутентифицированный доступ (<<закрытая паролем>>, как говорят в
народе) директория cgi-bin. Вот [30]вполне работоспособный пример
скрипта парсера на Perl, который помещается в эту самую директорию с
режимом 500 (r-x) и владельцем www:www (или тем, который прописан в
конфигурации web-сервера):
#!/usr/bin/perl
my $logfile="/var/log/error.reports.log";
print "Content-type:text/html\n\n";
print "<html><title> Статистика ошибок</title>\n";
print "<table border=1>\n";
print "<td>дата</td><td>время</td><td>скрипт</td>
<td>действие/описание ошибки</td><td>код ошибки</td>
<td>поток вывода</td><td>получатель уведомления</td>\n";
open(LOGHANDLE,"<$logfile");
flock(LOGHANDLE,2);
readline(*LOGHANDLE);
while(my $stat_line=readline(*LOGHANDLE))
{
my @stat_strings=split(/\t/,$stat_line);
if ($stat_strings[0]=~s/(\d\d:\d\d:\d\d)//)
{
print "</td><tr><td>$stat_strings[0]</td>\n";
print "<td>$1</td>\n";
print "<td>$stat_strings[1]</td>\n";
print "<td>$stat_strings[2]</td>\n";
print "<td>$stat_strings[3]</td>\n";
print "<td>$stat_strings[4]\n";
}
else
{
print "$stat_line<br>\n";
}
if ($stat_line=~/Уведомление\s+отправлено\s+(\S+)\./)
{
print "</td><td>$1</td>\n";
}
}
flock(LOGHANDLE,8);
close(LOGHANDLE);
print "</td></table>\n";
print "</body></html>\n";
exit 0;
Нетрудно заметить, что скрипт справляется с потоком вывода, содержащим
несколько строк, но совершенно не приспособлен для просмотра архивов
gzip. Частично эта проблема решается, если сначала ротировать протокол
в самом начале, до отправки почтовых сообщений, тогда лог будет
содержать, как минимум, запись о последнем событии.
Заключение
Основная цель данной статьи заключается в том, чтобы создать фундамент
для размышлений и дальнейшего развития и совершенствования скрипта,
поделиться накопленным опытом, но никак не дать готовые решения для
бездумного, слепого копирования или же научить принципам
программирования. Как говаривал агент Малдер о своем автомобиле:
<<Руль где-то рядом>>.
Скрипт мониторинга web-сервера
Содержание:
* Жизненные ситуации;
* Формулируем основные требования;
* Расположение скрипта и взаимодействие с другим ПО;
* Приступаем к разработке;
* Собираем все вместе;
* Запуск скрипта из cron;
* Подведение итогов;
* Заключение.
Жизненные ситуации
Иногда требуется уверенность в том, что web-сервер доступен для
клиентов. Конфигурации web-серверов могут быть очень и очень разные,
да и всегда есть место человеческому фактору - все мы время от времени
ошибаемся, забываем что-то. В моей практике частенько встречаются
подобные вещи, особенно это касается виртуального хостинга и
программирования динамических сайтов - отладили на локальном сервере,
<<залили>> к хостинг-провайдеру - не работает. Другая ситуация:
старались люди, программировали - обновил администратор
хостинг-провайдера версию PHP, а в ней какая-то команда или код ее
возврата поменялся; результат - сайт <<лег>>.
В общем, ситуаций, когда нужно периодически проверять
работоспособность web-сервера, много, и в этой статье автор поделился
опытом в решении этой проблемы. Возможно, кому-то проще решить
проблему <<в лоб>> - ps ax | grep httpd для локального хоста или
специально нанять сотрудника и усадить его за Internet Explorer.
Формулируем основные требования
Дабы не изобретать велосипед (хотя бы в этот раз), скрипт мониторинга
будет пользоваться для оповещения и протоколирования другим скриптом
/root/scripts/report.error.sh, разработка которого детально описана в
статье <<Скрипт протоколирования событий на shell>>. Итак,
сформулируем требования к скрипту мониторинга:
* простота - язык скриптинга shell;
* генерация минимального трафика - платить за лишний трафик ни к
чему;
* надежность оповещения - скрипт будет вызывать протоколирующий и
отсылающий сообщение на e-mail скрипт;
* оперативность оповещения - разумный компромисс между объемом
генерируемого трафика и частотой запуска скрипта из cron;
* безопасность - куда ж без нее;
* возможность модернизации - допустим, в будущем неплохо было бы
вести статистику времени ответа сервера.
Расположение скрипта и взаимодействие с другим ПО
Определим местоположение скрипта. Доводы в пользу /root/scripts/ с
режимом 500 (r-x) и владельцем root:wheel приведены в статье
<<Скрипт протоколирования событий на shell>>, поэтому не буду
повторяться. Скрипт мониторинга, назовем его monitor.websites.sh,
будет иметь режим 500 (r-x) и владельца root:wheel. Этим
обеспечиваются требования информационной безопасности.
В принципе, можно ограничиться банальным запуском telnet по 80-му
порту. Есть более интересный, к тому же отвечающий требованиям
расширяемости, вариант - малюсенькая, но очень функциональная
программа echoping, разработанная Stephane Bortzmeyer
([email protected], [email protected]). В коллекции
портов FreeBSD она находится в /usr/ports/net/echoping, в исходниках
ее можно взять с ftp://ftp.internatif.org/pub/unix/echoping . Кроме
очень толкового man и readme, есть страничка echoping на
http://echoping.sourceforge.net/. Размеры архива небольшие, поэтому
взять исходники или инсталлировать echoping из портов вполне реально,
даже с медленным модемом.
Инсталляция echoping из портов (две команды с консоли - вот в чем вся
прелесть портов):
server# cd /usr/ports/net/echoping
server# make install
Инсталляция из исходников чуть сложнее:
server# tar -xzf echoping-5.1.0.tar.gz
server# cd echoping-5.1.0
server# ./configure
server# make
server# make install
Советую почитать документацию и посетить [34]домашнюю страничку
echoping, оттуда можно почерпнуть много весьма полезного не только по
теме, но и для общего развития, так сказать - областей применения для
этой программы не счесть.
Приступаем к разработке
Функции скрипта мониторинга сводятся к вызову echoping, обработке
информации по завершению и вызову скрипта протоколирования, если
что-то не в порядке. Тестироваться будет хост http://remote.host.ru:
#!/bin/sh
ping_host="remote.host.ru"
Следующий этап - решить, что же именно нужно тестировать: доступность
хоста вообще или же доступность определенного URL. Чтобы тестировать
доступность хоста создадим небольшой текстовый файл
echoping.answer.txt, с произвольным содержанием. Размещение его на
web-сервере роли не играет, лишь бы echoping смог его открывать (это
легко проверить с помощью браузера). Файл такого содержания имеет
размер всего 44 байта:
This is the sample answer file for echoping.
По логике вещей, доступность хоста - есть доступность URL, например,
index.html на нем. В чем разница? В том, что порядок размера того же
index.html - десятки килобайт, а echoping.answer.txt - десятки байт.
Получается немалый выигрыш в трафике: если требуется знать всего лишь
доступность хоста, незачем брать большой файл:
ping_file="/echoping.answer.txt"
Чтобы протестировать доступность страницы, переменной ping_file просто
присваивается соответствующее значение:
ping_file="/some_dir/some-page.html"
Вызов echoping с сохранением кода завершения и потока вывода будет
выглядеть так:
result=`/usr/local/bin/echoping -h $ping_file $ping_host 2>&1`
result_code=$?
У некоторых хостинг-провайдеров виртуальные хосты сконфигурированы
так, что echoping может работать только со следующей командной
строкой:
result=`/usr/local/bin/echoping -h http://${ping_host}${ping_file} \
$ping_host 2>&1`
По документации, код возврата не нулевой, если что-то не в порядке.
Следующий фрагмент кода запускает в этом случае скрипт
/root/scripts/report.error.sh, который отсылает уведомление с
подробностями (потоком вывода echoping) по e-mail и оставляет запись в
протоколе:
if [ $result_code -ne 0 ]
then
/root/scripts/report.error.sh $0 "\
Нет ответа от $ping_host в течение 1 сек." $result_code "$result"
fi
Собираем все вместе
Привожу этот простой [36]скрипт целиком:
#!/bin/sh
ping_host="remote.host.ru"
ping_file="/echoping.answer.txt"
result=`/usr/local/bin/echoping -h $ping_file $ping_host 2>&1`
result_code=$?
if [ $result_code -ne 0 ]
then
/root/scripts/report.error.sh $0 "\
Нет ответа от $ping_host в течение 1 сек." $result_code "$result"
fi
Запуск скрипта из cron
Осталось только сконфигурировать cron, чтобы скрипт действительно
занимался мониторингом. Существует одна тонкость: в частоте запуска
скрипта следует соблюсти баланс между объемами трафика (не стоит
ориентироваться на размеры echoping.answer.txt, HTTP-сессия сама по
себе генерирует трафик) и достоверностью информации (вероятность того,
что хост может быть недоступен, увеличивается со временем после
очередного запуска echoping).
Задания, выполняемые от имени root перечисляются в
/var/cron/tabs/root. Если его нет, можно создать файл в текстовом
редакторе или доверить создание непосредственно самому crontab:
server# crontab -e -u root
Строка, конфигурирующая cron для запуска
/root/scripts/monitor.websites.sh каждые 30 минут:
*/30 * * * * /root/scripts/monitor.websites.sh
Советую проследить режим файла заданий - 600 (rw-) с владельцем
root:wheel и наличие в нем переменных окружения:
SHELL=/bin/sh
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin
HOME=/var/log
После внесения изменений нужно, чтобы cron перечитал список заданий:
server# crontab -u root /var/cron/tabs/root
В /var/log/cron появится запись:
Mar 30 11:11:11 server crontab[75680] REPLACE (root)
Подведение итогов
Все. С этого момента в своих протоколах /root/scripts/report.error.sh
будет писать:
Tue Mar 30 11:11:11 MSD 2004 \
/root/scripts/monitor.websites.sh \
нет ответа от remote.host.ru в течение 1 сек. 1 \
Can't connect to server (Operation timed out) \
Уведомление отправлено [email protected], \
копия [email protected]
А на e-mail, указанные в скрипте будут приходить письма:
Date: Tue, 30 Mar 2003 11:11:11 +0300 (MSK)
From: Charlie Root <root>
To: admin@ host.ru
Subject: Сообщение об ошибке
Время: Tue Mar 30 11:11:11 MSD 2004
Скрипт: /root/scripts/monitor.websites.sh
Действие: нет ответа от remote.host.ru в течение 1 сек.
Дополнительная информация:
Код ошибки: 1
Поток вывода ошибочной команды:
Can't connect to server (Operation timed out)
Например, такое письмо будет свидетельствовать о том, что на
компьютере, где работает echoping проблемы с DNS:
Date: Tue, 30 Mar 2003 11:11:11 +0300 (MSK)
From: Charlie Root <root>
To: [email protected]
Subject: Сообщение об ошибке
Время: Tue Mar 30 11:11:11 MSD 2004
Скрипт: /root/scripts/monitor.websites.sh
Действие: нет ответа от remote.host.ru в течение 1 сек.
Дополнительная информация:
Код ошибки: 1
Поток вывода ошибочной команды:
getaddrinfo error for host: remote.host.ru \
No address associated with hostname
Изучив документацию к echoping, нетрудно заметить, что скрипт
мониторинга может дополниться <<фичей>> мониторинга прокси-сервера, а
применив awk можно вести статистику среднего времени отклика
web-сервера. По-моему, звучит заманчиво. Кто-то, наоборот посчитает
статью ерундой - отлично, те же самые функции реализуются скриптом на
Perl socket-программированием, правда, по-любому, такой скрипт будет
гораздо более объемным.
Заключение
Повторюсь, что целью данной статьи, как и других, впрочем, не было
написание краткого руководства по программированию на shell. Было
время - была конкретная проблема, но не было опыта ее решения.
Проблема решена - появился опыт, значит, нужно им поделиться, дабы
другие на те же грабли не наступали. В любом случае, буду признателен
за конструктивную критику.
Скрипт мониторинга репликации MySQL на shell
Содержание:
* А в ответ - тишина...;
* Основные требования и возможности;
* Местоположение скрипта и аккаунт мониторинга;
* Разработка скрипта. Проблема первая;
* Разработка скрипта. Проблема вторая;
* Доводим до совершенства;
* Собираем все вместе;
* Запуск скрипта из cron;
* Подведение итогов;
* Заключение.
А в ответ - тишина...
Поступив на работу в организацию, где тружусь на чье-то благо и по сей
день, постепенно стало обнаруживаться, что хоть UNIX и беспрецедентно
надежная операционная система, а приложения для нее все же могут
давать сбои. Тогда и пришлось разрабатывать целую систему скриптов
контроля за состоянием целого ряда демонов. Отдел программирования в
то время занимался девелопингом весьма запутанной автоматизированной
информационной системы средствами PHP и MySQL. Средством синхронизации
была выбрана репликация баз данных того самого MySQL. Хорошо это или
плохо - не время и не место обсуждать, но жить с этим приходится, и
будет приходиться еще долго.
Основная проблема состоит в том, что при обнаружении несоответствия
или невозможности выполнить INSERT статус репликации тихо меняется на
<<OFF>>. Обнаружить причину удается не сразу, учитывая сложность
окружения (Apache, DNS, GD) и наличие <<глюков>> в скриптах PHP на
этапе разработки.
Основные требования и возможности
Как и рассмотренный в статье <<Скрипт мониторинга web-сервера на
shell>>, скрипт мониторинга репликации будет пользоваться для
оповещения и протоколирования другим скриптом
/root/scripts/report.error.sh, разработка которого детально описана в
статье [31]<<Скрипт протоколирования событий на shell>>.
Итак, сформулируем требования к скрипту мониторинга:
* простота - язык скриптинга shell;
* генерация минимального трафика - платить за лишний трафик ни к
чему;
* надежность оповещения - скрипт будет вызывать протоколирующий и
отсылающий сообщение на e-mail скрипт;
* оперативность оповещения - разумный компромисс между объемом
генерируемого трафика и частотой запуска скрипта из cron;
* полнота оповещения - скрипт должен оповещать не только об
остановке репликации, но и о факте <<падения>> сервера;
* безопасность - MySQL очень уязвим в связках и сам по себе;
* возможность модернизации - в будущем возможна смена
хостинг-провайдера, адресов или портов серверов.
Местоположение скрипта и аккаунт мониторинга
Определим местоположение скрипта. Доводы в пользу /root/scripts/ с
режимом 500 (r-x) и владельцем root:wheel приведены в статье
<<Скрипт протоколирования событий на shell>>, поэтому не буду
повторяться. Скрипт мониторинга, назовем его monitor.mysql.sh, будет
иметь режим 500 (r-x) и владельца root:wheel. Этим обеспечиваются
требования информационной безопасности на уровне операционной системы.
В составе MySQL поставляется широко известная пакетная утилита
mysqladmin, которая и будет источником информации для скрипта.
Соответственно, чтобы подсоединиться к серверу, необходим аккаунт
пользователя. Для целей мониторинга вполне подойдет пользователь,
назовем его repcontrol, с единственным правом USAGE, причем без права
его передачи и длинным паролем. Резонным будет ограничить возможность
соединения по хостам, если нет веских причин на обратное - мониторинг
ведется только с одного хоста. Этим мы удовлетворим требования
информационной безопасности по отношению к MySQL. На каждом
контролируемом сервере от имени rootа отдадим:
GRANT USAGE ON *.* TO 'repcontrol'@'host.ru' IDENTIFIED BY 'guessable';
Подробнее с добавлением аккаунтов пользователей MySQL можно
ознакомиться на странице [36]5.5.3 Adding New User Accounts to MySQL.
Теперь можно взглянуть на перечень параметров MySQL-сервера, которые
поддаются мониторингу:
server# mysqladmin -hhost.ru -P3306 -urepcontrol -pguessable extended-status
+--------------------------+--------+
| Variable_name | Value |
+--------------------------+--------+
| Aborted_clients | 0 |
| Aborted_connects | 0 |
| Bytes_received | 15949 |
| Bytes_sent | 414411 |
| Com_admin_commands | 63 |
| Com_alter_table | 0 |
| Com_analyze | 0 |
| Com_backup_table | 0 |
| Com_begin | 0 |
| Com_change_db | 2 |
| Com_change_master | 0 |
| Com_check | 0 |
| Com_commit | 0 |
| Com_create_db | 0 |
| Com_create_function | 0 |
| Com_create_index | 0 |
| Com_create_table | 0 |
| Com_delete | 0 |
| Com_drop_db | 0 |
| Com_drop_function | 0 |
| Com_drop_index | 0 |
| Com_drop_table | 0 |
| Com_flush | 0 |
| Com_grant | 0 |
| Com_insert | 0 |
| Com_insert_select | 0 |
| Com_kill | 0 |
| Com_load | 0 |
| Com_load_master_table | 0 |
| Com_lock_tables | 0 |
| Com_optimize | 0 |
| Com_purge | 0 |
| Com_rename_table | 0 |
| Com_repair | 0 |
| Com_replace | 0 |
| Com_replace_select | 0 |
| Com_reset | 0 |
| Com_restore_table | 0 |
| Com_revoke | 0 |
| Com_rollback | 0 |
| Com_select | 46 |
| Com_set_option | 46 |
| Com_show_binlogs | 0 |
| Com_show_create | 46 |
| Com_show_databases | 0 |
| Com_show_fields | 46 |
| Com_show_grants | 0 |
| Com_show_keys | 0 |
| Com_show_logs | 0 |
| Com_show_master_status | 0 |
| Com_show_open_tables | 0 |
| Com_show_processlist | 0 |
| Com_show_slave_status | 0 |
| Com_show_status | 34 |
| Com_show_innodb_status | 0 |
| Com_show_tables | 2 |
| Com_show_variables | 0 |
| Com_slave_start | 0 |
| Com_slave_stop | 0 |
| Com_truncate | 0 |
| Com_unlock_tables | 0 |
| Com_update | 0 |
| Connections | 101 |
| Created_tmp_disk_tables | 0 |
| Created_tmp_tables | 0 |
| Created_tmp_files | 0 |
| Delayed_insert_threads | 0 |
| Delayed_writes | 0 |
| Delayed_errors | 0 |
| Flush_commands | 1 |
| Handler_delete | 0 |
| Handler_read_first | 8 |
| Handler_read_key | 0 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 4520 |
| Handler_update | 0 |
| Handler_write | 0 |
| Key_blocks_used | 0 |
| Key_read_requests | 0 |
| Key_reads | 0 |
| Key_write_requests | 0 |
| Key_writes | 0 |
| Max_used_connections | 1 |
| Not_flushed_key_blocks | 0 |
| Not_flushed_delayed_rows | 0 |
| Open_tables | 46 |
| Open_files | 97 |
| Open_streams | 0 |
| Opened_tables | 52 |
| Questions | 320 |
| Select_full_join | 0 |
| Select_full_range_join | 0 |
| Select_range | 0 |
| Select_range_check | 0 |
| Select_scan | 39 |
| Slave_running | ON |
| Slave_open_temp_tables | 0 |
| Slow_launch_threads | 0 |
| Slow_queries | 0 |
| Sort_merge_passes | 0 |
| Sort_range | 0 |
| Sort_rows | 0 |
| Sort_scan | 0 |
| Table_locks_immediate | 51 |
| Table_locks_waited | 0 |
| Threads_cached | 0 |
| Threads_created | 99 |
| Threads_connected | 2 |
| Threads_running | 2 |
| Uptime | 228103 |
+--------------------------+--------+
Первой переменной скрипта и будут реквизиты созданного аккаунта:
mysql_user="-urepcontrol -pguessable"
Разработка скрипта. Проблема первая
В разработке скрипта мониторинга серверов MySQL есть две проблемы, по
сути, решение которых и будет составлять код скрипта: первая -
получить часть строки из таблицы напротив параметра Slave_running,
вторая - серверов может быть несколько, и работать они могут на разных
хостах и портах - избежать путаницы.
Первая проблема весьма просто решается применением awk и grep. Сначала
утилита mysqladmin соединяется с сервером и получает расширенную
таблицу статусов, ее листинг приведен в конце предыдущего раздела:
server# mysqladmin -hhost.ru -P3306 -urepcontrol -pguessable \
extended-status 2>&1
Затем из потока вывода с помощью grep можно получить интересующую
строку:
server# mysqladmin -hhost.ru -P3306 -urepcontrol -pguessable \
extended-status 2>&1 | grep Slave_running
| Slave_running | ON |
По умолчанию awk считает разделителями полей пробельные символы,
следовательно, статус репликации - четвертый столбец:
server# mysqladmin -hhost.ru -P3306 -urepcontrol -pguessable \
extended-status 2>&1 | grep Slave_running | awk '{print $4}'
ON
Так как поток диагностических сообщений перенаправляется в стандартный
поток вывода, в случае возникновения каких-либо проблем, например,
отсутствия связи с сервером, переменная result будет пустой. Условная
конструкция будет выглядеть примерно так:
result=`mysqladmin -hhost.ru -P3306 -urepcontrol -pguessable \
extended-status 2>&1 | grep Slave_running | awk '{print $4}'`
case $result in
ON) ;;
OFF) /root/scripts/report.error.sh $0
"репликация на host.ru:3306 отключена" 0 "пусто";;
*) /root/scripts/report.error.sh $0 "невозможно \
получить статус репликации на host.ru:3306 0 "пусто";;
esac
Недостаток подобной конструкции состоит в невозможности диагностики
конкретной причины пустого (или отличного от <<ON>> и <<OFF>>) result.
Существенным он может быть только в случае неустойчивого модемного
соединения по плохому каналу в цепочке между хостом мониторинга и
контролируемым хостом. По логике вещей, в любом другом случае,
невозможность получить статус репликации говорит о том, что сервер
<<лег>>.
Разработка скрипта. Проблема вторая
Если серверов MySQL несколько, а такая ситуация не так уж редка, то
скрипт превращается в запутанную последовательность строк с result и
case. Причем, чем больше серверов нуждаются в контроле, тем больше
вероятность допустить где-нибудь ошибку. Выход напрашивается сам собой
- использовать цикл. Но как быть, если на одном хосте сервера работают
на одних портах, а на другом - на совершенно отличных, и найти
какую-то закономерность сложно? Оригинальным решением будет
воспользоваться shell-подпрограммой, и уже внутри функции
mysql_slave_running организовать цикл. Первым параметром передается
хост, а в цикле перебираются оставшиеся - номера портов. Нетрудно
заметить, что первым параметром передавать предпочтительнее наиболее
общий реквизит, не обязательно это должен быть хост:
mysql_slave_running()
{
mysql_host=$1; shift
for mysql_port in $*
do
result=`mysqladmin -h$mysql_host -P$mysql_port \
$mysql_user extended-status 2>&1
| grep Slave_running | awk '{print $4}'`
case $result in
ON) ;;
OFF) /root/scripts/report.error.sh $0 \
"репликация на ${mysql_host}:${mysql_port} \
отключена" 0 "пусто";;
*) /root/scripts/report.error.sh $0 \
"невозможно получить статус репликации \
на ${mysql_host}:${mysql_port}" 0 "пусто";;
esac
done
}
Путаницы в скрипте становится значительно меньше, тем более, ничто не
мешает использовать для форматирования табуляцию:
mysql_slave_running "host.ru 3306 64080 64098"
mysql_slave_running "provider.ru 64098"
Доводим до совершенства
И снова, если в скрипте присутствуют повторяющиеся из строки в строку
сочетания, значит, нужен цикл. Shell имеет одну интересную и весьма
мощную, но малоизвестную возможность, многие из руководств как-то
вскользь раскрывают ее, а то и вовсе обходят стороной. Похоже,
некоторые из авторов не очень четко представляют, о чем пишут.
Возможность эта - обыкновенные двойные кавычки <<">>, вернее то, где и
каким образом трактуются shell текст и переменные, заключенные в них.
Автору пришлось изучать этот аспект shell-программирования методом
проб и ошибок.
Итак, воспользуемся кавычками для передачи в цикл параметров - группы
символов и пробелов, заключенных в кавычки, будут считаться циклом как
отдельные параметры, а функцией mysql_slave_running - как набор
параметров. Форматированием можно добиться весьма наглядного
представления:
for mysql_host in \
"host.ru 3306 64080 64098" \
"provider.ru 64098" \
"another.host.ru 3306 64088 64068"
do
mysql_slave_running $mysql_host
done
Собираем все вместе
Целиком, уже доведенный до совершенства, скрипт будет выглядеть так:
#!/bin/sh
mysql_user="-urepcontrol -pguessable"
mysql_slave_running()
{
mysql_host=$1; shift
for mysql_port in $*
do
result=`mysqladmin -h$mysql_host -P$mysql_port \
$mysql_user extended-status 2>&1
| grep Slave_running | awk '{print $4}'`
case $result in
ON) ;;
OFF) /root/scripts/report.error.sh $0 \
"репликация на ${mysql_host}:${mysql_port} \
отключена" 0 "пусто";;
*) /root/scripts/report.error.sh $0 \
"невозможно получить статус репликации \
на ${mysql_host}:${mysql_port}" 0 "пусто";;
esac
done
}
for mysql_host in \
"host.ru 3306 64080 64098" \
"provider.ru 64098" \
"another.host.ru 64088 64068"
do
mysql_slave_running $mysql_host
done
Единственное, что автору так и не удалось <<победить>> в приведенном
варианте, так это расположение массива хостов и портов в середине
скрипта.
Запуск скрипта из cron
Осталось только сконфигурировать cron. В принципе, процесс ничем не
отличается от описанного в статье <<Скрипт мониторинга web-сервера
на shell>>. Объем трафика невелик, поэтому требования к частоте
запуска более мягкие, по сравнению с аналогичными требованиями
мониторинга web-сервера.
Задания, выполняемые от имени root, перечисляются в
/var/cron/tabs/root. Если его нет, можно создать файл в текстовом
редакторе или доверить создание непосредственно самому crontab:
server# crontab -e -u root
Строка, конфигурирующая cron для запуска
/root/scripts/monitor.mysql.sh каждые 33 минуты:
*/33 * * * * /root/scripts/monitor.mysql.sh
Период в 33 минуты выбран с целью развязать мониторинг серверов MySQL
с другими скриптами по времени. Развязка по времени в свою очередь
уменьшает пиковые нагрузки на канал доступа. Советую проследить режим
файла заданий - 600 (rw-) с владельцем root:wheel и наличие в нем
переменных окружения:
SHELL=/bin/sh
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin
HOME=/var/log
После внесения изменений нужно, чтобы cron перечитал список заданий:
server# crontab -u root /var/cron/tabs/root
В /var/log/cron появится запись:
Apr 28 11:11:11 server crontab[75680] REPLACE (root)
Подведение итогов
С момента снесения в cron скрипта мониторинга
/root/scripts/report.error.sh в своих протоколах будет писать:
Wed Apr 28 11:11:11 MSD 2004 \
/root/scripts/monitor.mysql.sh \
репликация на host.ru:3306 отключена 0 \
пусто \
Уведомление отправлено [email protected], копия [email protected]
А на e-mail, указанные в скрипте будут приходить письма:
Date: Wed, Apr 28 2004 11:11:11 +0300 (MSK)
From: Charlie Root <root>
To: [email protected]
Subject: Сообщение об ошибке
Время: Wed Apr 28 11:11:11 MSD 2004
Скрипт: /root/scripts/monitor.mysql.sh
Действие: репликация на host.ru:3306 отключена.
Дополнительная информация:
Код ошибки: 0
Поток вывода ошибочной команды:
пусто
Например, такое письмо будет свидетельствовать о том, что MySQL сервер
<<лег>>:
Date: Wed, Apr 28 2004 11:11:11 +0300 (MSK)
From: Charlie Root <root>
To: [email protected]
Subject: Сообщение об ошибке
Время: Wed Apr 28 11:11:11 MSD 2004
Скрипт: /root/scripts/monitor.mysql.sh
Действие: невозможно получить статус репликации на host.ru:3306.
Дополнительная информация:
Код ошибки: 0
Поток вывода ошибочной команды:
пусто
Код ошибки и поток вывода подавляются заглушками <<0>> и <<пусто>>,
так как содержимое их не несет какой-либо достаточной смысловой
нагрузки. Таковая появляется лишь при очень неустойчивом модемном
соединении. В разделе [57]<<Разработка скрипта. Проблема первая>>
приведено объяснение этому, к тому же, поток вывода пропускается через
каналы. Хранить в транзитных переменных, затем отправлять по почте и
протоколировать исходное содержимое потока, по моему личному опыту, не
информативно.
Заключение
По предыдущим публикациям, уже вошло в привычку в заключении
акцентировать внимание на том, что данная статья не является учебным
руководством по программированию на shell, а призвана дать информацию
для размышления. Но, например, ситуация с кавычками в разделе
<<Доводим до совершенства>> несколько напоминает руководство, и,
смею надеяться, приведенный пример и объяснение, в отличие от
<<истинных>> руководств, более доходчив и понятен.
Если говорить о перспективах рассмотренного скрипта, то путей лично
мне видится два: мониторинг и/или ведение статистики каких-либо других
параметров MySQL, или же приспособление скрипта под другой сервер баз
данных.
Буду признателен за конструктивную критику.
Все права защищены. Статья не может быть скопирована или воспроизведена
с помощью любых методов или носителей информации без письменного разрешения
владельца прав на копирование.
(На opennet.ru документ размещен с разрешения автора.)