Скрипты для системы мониторинга Nagios
Отправка СМС
В скрипте для отправки сообщений используется программа gnokii. С небольшими правками скрипта можно переделать на использование gammu. Описаний настройки gnokii/gammu не привожу, они выходят за рамки описания скрипта.
Для диагностических целей скрипт пишет свои действия в лог-файл /var/log/nagios4/sendsms.log
"set -o pipefail" в данном скрипте необязателен, но я почти всегда задаю опцию pipefail, которая указывает оболочке, что код завершения конвейера будет совпадать с первым ненулевым кодом завершения одной из команд конвейера или же нулю в случае, если все команды завершились корректно
Если отправка оповещений делается нескольким адресатам, то параллельное обращение к модему/телефону приведет к тому, что какие-то смс не будут доставлены. Чтобы этого избежать я делаю отправку в цикле, пока она не будет успешной (код возврата 0). Чтоб в случае постоянной ошибки скрипт не ушел в бесконечный цикл ограничиваю число попыток отправки (100 попыток думаю достаточно)
"LANG=ru_RU.UTF-8" перед выполнением программы gnokii нужно, чтоб кириллический текст в смс нормально отображался.
Нажмите, чтобы отобразить
- smssend.sh
#!/bin/bash
set -o pipefail
echo "$(date +%Y-%m-%dT%H:%M:%S) Send SMS to $2: $1"
echo "$(date +%Y-%m-%dT%H:%M:%S) Send SMS to $2: $1">>/var/log/nagios4/sendsms.log
retcode=1
try=1
while [[ $retcode -ne 0 |]]
do
echo "Try$try: LANG=ru_RU.UTF-8 echo \"$1\" | /usr/bin/gnokii --sendsms \"$2\" --smsc \"+79289900028\"">>/var/log/nagios4/sendsms.log 2>&1
LANG=ru_RU.UTF-8 echo "$1" | /usr/bin/gnokii --sendsms "$2" --smsc "+79289900028"
retcode=$?
try=$((try++))
if [[ $try -ge 100 |]]; then echo "Max try 100 exceeded"; break; fi
sleep 1
done
echo "$(date +%Y-%m-%dT%H:%M:%S) Send SMS to $2: $1 RetCode:$retcode">>/var/log/nagios4/sendsms.log 2>&1
echo "$(date +%Y-%m-%dT%H:%M:%S) Send SMS to $2: $1 RetCode:$retcode"
exit $retcode
Отправка оповещений в Telegram
Для работы скрипта в нем нужно задать правильные параметры телеграм-канала: TOKEN и CHAT_ID
Нажмите, чтобы отобразить
- tgsend.sh
#!/bin/bash
set -o pipefail
TOKEN="SpecifyYourTelegramToken"
CHAT_ID="SpecifyChatId"
APIURL="https://api.telegram.org/bot${TOKEN}/sendMessage"
if [[ -z "$1$2" ]]; then echo "Missing arguments" >&2; exit 2; fi
if [[ -z "$2" ]]
then
SUBJECT=""
MESSAGE="$1"
else
SUBJECT="$1"
MESSAGE="$2"
fi
curlres=$(curl -s --header 'Content-Type: application/json' --request 'POST' --data "{\"chat_id\":\"${CHAT_ID}\",\"text\":\"${SUBJECT}\n${MESSAGE}\"}" "${APIURL}")
curlerr="$?"
if [[ $curlerr -ne 0 ]]; then echo "Curl error:$curlerr" >&2; exit 2;fi
if [[ "$(echo "$curlres"jq ".ok")" != "true" ]]
then
echo "api.telegram error" >&2
echo "cmd=curl -s --header 'Content-Type: application/json' --request 'POST' --data \"{\"chat_id\":\"${CHAT_ID}\",\"text\":\"${SUBJECT}\n${MESSAGE}\"}\" \"${APIURL}\"" >&2
echo "ressult=$curlres" >&2
fi
exit 0
Проверка загрузки CPU
Для проверки загрузки процессора используется программа mpstat (входит в состав пакета sysstat)
Нажмите, чтобы отобразить
- check_cpu.sh
#!/bin/bash
E_OK=0;E_WARN=1;E_CRIT=2;E_UNKNOWN=3 #Коды возврата Nagios
WARN=20; CRIT=10 #Задаем пороги предупреждений и критического состояния по-умолчанию.
# Считываем пороги, если они заданы в командной строке
if [[ -n "$1" ]]; then WARN="$1";fi
if [[ -n "$2" ]]; then CRIT="$2";fi
res=$E_OK; res_txt="OK"
read check_time CPU usr nice sys iowait irq soft steal guest gnice idle <<<$(S_TIME_FORMAT=ISO mpstat --dec=0 1 1|tail -n2|head -n1)
if [[ $idle -le $WARN ]]; then res=$E_WARN; res_txt="WARN"; fi
if [[ $idle -le $CRIT ]]; then res=$E_CRIT; res_txt="CRIT"; fi
echo -n "$res_txt: Idle=$idle% usr=$usr% nice=$nice% sys=$sys% iowait=$iowait% irq=$irq% soft=$soft% steal=$steal% guest=$guest% gnice=$gnice%"
echo "|Idle=$idle%;$WARN;$CRIT;; usr=$usr%;;;; nice=$nice%;;;; sys=$sys%;;;; iowait=$iowait%;;;; irq=$irq%;;;; soft=$soft%;;;; steal=$steal%;;;; guest=$guest%;;;; gnice=$gnice%;;;;"
exit $res
Проверка состояния сетевого интерфейса
Нажмите, чтобы отобразить
- check_ip_link.sh
#!/bin/sh
# Exit-Codes:
STATE_OK=0;STATE_WARNING=1;STATE_CRITICAL=2;STATE_UNKNOWN=3
usage()
{
cat <<EOF
usage: $0 interface
This script checks interface state
EOF
}
if [ -z "$1" ]; then usage; exit $STATE_UNKNOWN; fi
int="$1"
res=$STATE_OK; txt="OK"
lnk=$(ip link show $int|head -n1 |sed 's/\(.*)(state \w*)(.*\)/\2/') #'
if [ "$lnk" != "state UP" ]; then res=$STATE_CRITICAL; txt="CRITICAL"; fi
echo "$txt:Interface $int $lnk"
exit $res
Пинг большими нефрагментированными пакетами
Зачем? Ну например для проверки линков с включенным JumboFrame.
Нажмите, чтобы отобразить
- check_ping_big.sh
#!/bin/sh
E_OK=0;E_WARN=1;E_CRIT=2;E_UNKNOWN=3
#Defaults
WARN=100
CRIT=200
WARNPCT=10
CRITPCT=20
PKTCNT=5
PKTSIZE=6050
res=$E_OK;txt="OK"
usage()
{
cat <<EOF
usage: $0 -H host [OPTIONS]
This script checks ping to host and verifies packets loss and round trip time
OPTIONS:
-h Help
-w wrtt WARNING RTT
-c crtt CRITICAL RTT
-W wpct WARNING Packets loss %
-C cpct CRITICAL Packets loss %
-p num Number of packets
-s size Packet size
EOF
}
while getopts H:c:w:C:W:p:s: OPTION
do
case $OPTION in
H)
CHECKHOST=`echo $OPTARG | grep -v "^-"`
[ ! "$?" = 0 ] && echo "Error: missing or illegal option value" && exit $STATE_UNKNOWN;;
c)
CRIT=`echo $OPTARG | grep -v "^-"`
[ ! "$?" = 0 ] && echo "Error: missing or illegal option value" && exit $STATE_UNKNOWN;;
w)
WARN=`echo $OPTARG | grep -v "^-"`
[ ! "$?" = 0 ] && echo "Error: missing or illegal option value" && exit $STATE_UNKNOWN;;
C)
CRITPCT=`echo $OPTARG | grep -v "^-"`
[ ! "$?" = 0 ] && echo "Error: missing or illegal option value" && exit $STATE_UNKNOWN;;
W)
WARNPCT=`echo $OPTARG | grep -v "^-"`
[ ! "$?" = 0 ] && echo "Error: missing or illegal option value" && exit $STATE_UNKNOWN;;
p)
PKTCNT=`echo $OPTARG | grep -v "^-"`
[ ! "$?" = 0 ] && echo "Error: missing or illegal option value" && exit $STATE_UNKNOWN;;
s)
PKTSIZE=`echo $OPTARG | grep -v "^-"`
[ ! "$?" = 0 ] && echo "Error: missing or illegal option value" && exit $STATE_UNKNOWN;;
?)
usage; exit $STATE_UNKNOWN;;
esac
done
if [ -z "$CHECKHOST" ]; then usage; exit $STATE_UNKNOWN; fi
cmd_res="$(ping $CHECKHOST -c $PKTCNT -s $PKTSIZE -W 1 -M dont|tail -n2)"
PCT=$(echo "$cmd_res"|head -n1|awk '{print $6}'|tr -d '%')
TIME=$(echo "$cmd_res"|tail -n1|awk -F '/' '{print $5}')
if [ -z "$TIME" ]; then TIME=65565; fi
if [ $PCT -ge $WARNPCT ] && [ $res -lt $E_WARN ]
then
res=$E_WARN;txt="WARNING"
if [ $PCT -ge $CRITPCT ] && [ $res -lt $E_CRIT ]
then
res=$E_CRIT;txt="CRITICAL"
fi
fi
if [ $(echo "$TIME >= $WARN"|bc) -ne 0 ] && [ $res -lt $E_WARN ]
then
res=$E_WARN;txt="WARNING"
if [ $(echo "$TIME >= $CRIT"|bc) -ne 0 ] && [ $res -lt $E_CRIT ]
then
res=$E_CRIT;txt="CRITICAL"
fi
fi
echo "$txt: Host $CHECKHOST - $PCT% packet loss (warn:$WARNPCT,crit:$CRITPCT), AVG rtt $TIME (warn:$WARN,crit:$CRIT)|PacketLoss=$PCT%;$WARNPCT;$CRITPCT;0;100 RTT=${TIME}ms;$WARN;$CRIT;;"
exit $res
Сравнение локального файла с файлом на удаленной системе
Область применения - например проверка идентичности конфиг-файла на двух серверах.
Сравнение делается с помощью подключения по ssh. Для корректной работы скрипта требуется настроить ключи ssh для пользователя из под которого будет работать скрипт.
Нажмите, чтобы отобразить
- check_rdiff.sh
#!/bin/bash
E_OK=0;E_WARNING=1;E_CRITICAL=2;E_UNKNOWN=3
usage()
{
cat <<EOF
Usage: $0 remotehost filename
This script checks differecies of local and remote files
EOF
}
if [[ -z "$1" |]]; then usage; exit $E_UNKNOWN
else serv="$1"
fi
if [[ -z "$2" |]]; then usage; exit $E_UNKNOWN
else fname="$2"
fi
difs="$(diff $fname <(ssh "$serv" "cat $fname"))"
if [[ "$?" -eq "1" |]]
then
echo "CRITICAL: File $fname. Local and on host $serv has differencies"
echo "$difs"
exit $E_CRITICAL
fi
echo "OK: File $fname. Local and on host $serv has no differencies"
exit $E_OK
Модифицированная версия. Может сравнивать несколько файлов заданных шаблоном (напр. /etc/application/*.conf)
Нажмите, чтобы отобразить
- check_rdiff.sh
#!/bin/bash
STATE_OK=0;STATE_WARNING=1;STATE_CRITICAL=2;STATE_UNKNOWN=3
usage()
{
cat << EOF
Usage: $0 remotehost filename
This script checks differecies of local and remote files
EOF
}
if [[ -z "$1" ]]; then usage; exit $STATE_UNKNOWN; else serv="$1"; fi
if [[ -z "$2" ]]; then usage; exit $STATE_UNKNOWN; else fname="$2"; fi
difs=""
ndifs=0
for f in $fname
do
dif="$(ssh "$serv" "cat $f"|diff - $f)"
if [[ $? -ne 0 ]]; then.
ndifs=$(($ndifs + 1 ))
difs="$difs$(echo $f):$dif"
fi
done
if [[ $ndifs -eq 0 ]]
then
echo "OK: File $fname. Local and on host $serv has no differencies"; exit $STATE_OK
else
echo "CRITICAL: File $fname. Local and on host $serv has differencies"; echo "$difs"; exit $STATE_CRITICAL
fi
Проверка DNS-записей на соответствие заданным
В nagios-plugins есть check_dns, который позволяет (кроме другого функционала) проверить соответствуют ли записи о домене на днс-сервере желаемым. Однако check_dns не дружит с интернациональными доменами, даже если указать домен в punycode. Поэтому может потребоваться скрипт ниже.
Пример запуска скрипта
check_dns_recs.sh domain.com 1.1.1.1,2.2.2.2 8.8.8.8
В этом примере проверяется соответствуют ли записи домена domain.com на сервере 8.8.8.8 списку 1.1.1.1, 2.2.2.2
Адреса в списке можно указывать разделяя их между собой запятыми. Скрипт проверяет только А-записи (может позже это исправлю).
В основном действия в скрипте несложные. Немного описать имеет смысл следующую строку:
host -t $TYPE $DOMAIN $SERVER|grep "$DOMAIN has address "|grep -oE '[^ ]+$'|sort|tr '\n' ','|sed s/,$//
grep -oE '[^ ]+$' - эта команда удаляет из строк все кроме последнего слова, а в последнем слове содержатся требуемые адреса домена. Если разделителем слов будет не только пробел, но и другие разделители, то может понадобиться преобразовать регулярное выражение [^ ] к виду [^[:space:]]
sort - если не отсортировать вывод, то порядок запиесей может меняться и при сравнении один и тот же список адресов не совпадет с заданным шаблоном.
tr '\n' ','|sed s/,$// - зменяем все переводы строк запятыми (объединяем строки в одну с разделителем запятая) и удаляем лишнюю запятую в конце строки.
Нажмите, чтобы отобразить
- check_dns_recs.sh
#!/bin/bash
E_OK=0;E_WARN=1;E_CRIT=2;E_UNKNOWN=3 #Коды возврата Nagios
set -o pipefail
#Defaults
TYPE="A"
usage()
{
cat <<EOF
This script checks dns records corresponding to given list
usage: $0 domain list [server]
list - addresses list (separated by comma(,))
EOF
}
if [[ -n "$1" ]]; then DOMAIN="$1"; else usage; exit $E_UNKNOWN; fi
if [[ -n "$2" ]]; then LIST="$2"; else usage; exit $E_UNKNOWN; fi
if [[ -n "$3" ]]; then SERVER="$3"; fi
res=$E_OK; txt="OK"
dnsrecs="$(host -t $TYPE $DOMAIN $SERVER|grep "$DOMAIN has address "|grep -oE '[^ ]+$'|sort|tr '\n' ','|sed s/,$//)"
if [[ $? -ne 0 ]]; then echo "DNS lookup error for command: host -t $TYPE $DOMAIN $SERVER";exit $E_UNKNOWN; fi
if [[ "$dnsrecs" != "$LIST" ]]
then res=$E_CRIT; txt="CRIT"; check_txt=" not equal to '$LIST'"
fi
echo "$txt:Records $TYPE for $DOMAIN: '$dnsrecs'$check_txt"
exit $res
Проверка является ли заданный сервер кластера PgPool-II primary-хостом
check_pgpool_node_is_primary.sh pg_master_host
Нажмите, чтобы отобразить
- check_pgpool_node_is_primary.sh
#!/bin/bash
E_OK=0;E_WARN=1;E_CRIT=2;E_UNKNOWN=3
if [[ -z "$1" ]]; then echo "Usage: $(basename $0) PostgresMasterHostname"; exit $E_UNKNOWN; fi
mastername="$1"
HOST="localhost"
#HOST="/var/run/postgresql"
res=$E_OK; res_txt="OK"; res_txt2="Host $mastername is Master"
strings="$(echo "node_id hname status role last_status_change")"
while IFS='|' read -r node_id hname port status lb_weight role select_cnt load_balance_node replication_delay replication_state replication_sync_state last_status_change
do
strings="$strings\n$(echo "$node_id $hname $status $role $last_status_change")"
if [[ "$hname" == "$mastername" && "$role" != "primary" ]]
then
res=$E_CRIT; res_txt="CRIT"; res_txt2="Host $hname is not Master"
fi
if [[ "$hname" != "$mastername" && "$role" == "primary" ]]
then
if [[ $res -lt $E_WARN ]]; then res=$E_WARN; res_txt="CRIT"; res_txt2="Host $hname is Master (myst be a slave)"; fi
fi
done < <(psql -tA -h localhost -c "SHOW pool_nodes;" postgres postgres)
echo "$res_txt: $res_txt2"
echo -e "$strings"
exit $res
Проверка состояния streaming-репликации PostgreSQL-серверов через PgPool-II
Скрипт на пгпуле определяет который из серверов является мастером. Затем на мастере проверяется статистика по репликации.
Если позиции (LSN) в журналах различаются (не все отправлено на слейв, не все записано, не все подтверждено), то будет выдано предупреждение
Если состояние репликации будет не streaming, то будет выдано критическое состояние
В графики из получаемых данных ничего не запишешь, но все-таки я вывожу в них код завершения нагиос-скрипта. Это будет полезно для статистики. Коды возврата: E_OK=0;E_WARN=1;E_CRIT=2;E_UNKNOWN=3
Нажмите, чтобы отобразить
- check_pg_replication.sh
#!/bin/bash
E_OK=0;E_WARN=1;E_CRIT=2;E_UNKNOWN=3
#HOST="localhost"
HOST="/var/run/postgresql"
while IFS='|' read -r node_id hname port status lb_weight role select_cnt load_balance_node replication_delay replication_state replication_sync_state last_status_change
do
if [[ "$role" == "primary" ]]; then master=$hname; break; fi
done < <(psql -tA -h localhost -c "SHOW pool_nodes;" postgres postgres)
res=$E_OK; res_txt="OK"; res_txt2=""
IFS='|' read -r client_addr state sent_lsn write_lsn flush_lsn replay_lsn \
<<< $(echo $(psql -tA -h $master -c 'select client_addr, state, sent_lsn, write_lsn,flush_lsn, replay_lsn from pg_stat_replication;' postgres postgres))
lsn_equal=1
for i in "$write_lsn" "$flush_lsn" "$replay_lsn"
do
if [[ "$sent_lsn" != "$i" ]]; then lsn_equal=0;break; fi
done
if [[ $lsn_equal -ne 1 ]]; then res=$E_WARN; res_txt="WARN"; res_txt2="LSNs is not equal"; fi
if [[ "$state" != "streaming" ]]; then res=$E_CRIT; res_txt="CRIT"; res_txt2="state != streaming"; fi
echo -n "$res_txt: Master is $master. $res_txt2 "
echo "client_addr:$client_addr state:$state sent_lsn:$sent_lsn write_lsn:$write_lsn flush_lsn:$flush_lsn replay_lsn:$replay_lsn|result=$res"
exit $res