muninでヘルスチェック(死活監視/生存監視/サイト監視とか)

munin派とnagios派とZABBIX派が戦うときにたいてい焦点になるのが死活監視などの監視だと思う。
「muninってグラフを書くしかできないよね!」 とかいわれたり、いやいや alert のメール送信できますよみたいな。

しかし、「muninってURLのチェックとか、プロセスの生存チェックとか、logのチェックとかできるの?」っていわれると munin派はぐぬぬぬ・・・といわなきゃいけなかった。

昔、 http監視する奴を作ったりはしたけど、ちょっと本格的に作ってみることにした。

ダウンロードする場合

プロセス監視
http://rtilabs.net/files/2011_06_05/healthcheck_process


URL監視(サイト監視/コンテンツ監視)
http://rtilabs.net/files/2011_06_05/healthcheck_url


ログ監視
http://rtilabs.net/files/2011_06_05/healthcheck_log


インストール方法など

#プラグインディレクトリまで移動
cd /usr/share/munin/plugins

#過去のゴミを消す
/bin/rm healthcheck_*

#ダウンロード
wget 'http://rtilabs.net/files/2011_06_05/healthcheck_process'
wget 'http://rtilabs.net/files/2011_06_05/healthcheck_url'
wget 'http://rtilabs.net/files/2011_06_05/healthcheck_log'
wget 'http://rtilabs.net/files/2011_06_05/healthcheck_ping'

#実行権限付与
chmod +x healthcheck_*

#muninへ登録
ln -s /usr/share/munin/plugins/healthcheck_process /etc/munin/plugins/
ln -s /usr/share/munin/plugins/healthcheck_log /etc/munin/plugins/
ln -s /usr/share/munin/plugins/healthcheck_url /etc/munin/plugins/
ln -s /usr/share/munin/plugins/healthcheck_ping /etc/munin/plugins/

#設定を書く.
vim /etc/munin/plugin-conf.d/munin-node

例
------------------------------------------------------
[healthcheck_process]
env.process_1 httpd
env.process_2 mysqld

[healthcheck_url]
env.url_1  http://www.google.com/
env.slowspeed_1 5                    #limit time(sec)
#env.htmlgrep_1 google               #check egrep string

env.url_2  http://www.yahoo.com/
env.slowspeed_2 5                    #limit time(sec)
#env.htmlgrep_2 yahoo                #check egrep string
#env.htmlsize_2 10000                #check html size(byte).
#env.proxy_2 127.0.0.1:8080          #over proxy

[healthcheck_log]
user root
env.log_1  /var/log/messages

[healthcheck_ping]
env.ping_1  192.168.1.1
------------------------------------------------------

#muninノード再起動
/etc/init.d/munin-node restart

プロセス監視

healthcheck_process ファイル。
ふつーに動かすと、設定したプロセス名で ps -C httpd とかやって、そのプロセスが利用しているメモリ容量の合算をMB単位で表示する。
ただし、プロセスが落ちている場合は、-10 という値になり、アラートがあがる仕組みになっている。

複数のプロセスをまとめて死活チェックできる。

これでmunin ってプロセス監視できないよね!! というのは過去のものになるはずだ。

#!/bin/bash
#
#healthcheck on munin
#check process and alert.
#
#programed by rti (hiroyuki fujie) super.rti@gmail.com @super_rti
#LICENSE: NYSL (public domain)
#
#config file
#      /etc/munin/plugin-conf.d/munin-node
#
#example minimum config
#---------------------------------------------------
#[healthcheck_process]
#env.process_1 httpd
#---------------------------------------------------
#
#chcek two process
#---------------------------------------------------
#[healthcheck_process]
#env.process_1 httpd
#env.process_2 samba
#---------------------------------------------------
#
#chcek three process
#---------------------------------------------------
#[healthcheck_process]
#env.process_1 httpd
#env.process_2 samba
#env.process_3 mysqld
#---------------------------------------------------
#
#
#

#edakari speed up.
CHECKMAX=`env | grep process_ | wc -l`
let CHECKMAX="$CHECKMAX + 1"

if [ "$1" = "autoconf" ]; then
    if [ $CHECKMAX -le 1 ]; then
         echo no
         exit 1
    fi
    echo yes
    exit 0
fi

if [ "$1" = "config" ]; then

    echo 'graph_title process memory Usage(MB)'
    echo "graph_args --base 1000 -l 0 --vertical-label MB"
    echo 'graph_scale no'
    echo 'graph_vlabel process memory'
    echo 'graph_category healthcheck'
    echo 'graph_info This graph shows the Memory used by process'

    for(( I = 1; I < $CHECKMAX; ++I ))
    do
         eval process=\$process_${I}
         eval alertmemory=\$alertmemory_${I}
         if [ "x${process}" = "x" ]; then
              continue
         fi

         echo "$process.label $process"
         echo "$process.info Memory used by $process"
         echo "$process.draw LINE2"
         echo "$process.min -10"
         echo "$process.critical 0:"
    done

    exit 0
fi

for(( I = 1; I < $CHECKMAX; ++I ))
do
     eval process=\$process_${I}
     if [ "x${process}" = "x" ]; then
         continue
     fi

     vrets=(`ps u --no-headers -C $process | awk 'BEGIN { count = 0 ; sum = 0; } { count ++ ; sum += $6/1024 ; } END { printf("%d %d\n",count,sum); }'`)
     count=${vrets[0]}
     value=${vrets[1]}
     if [ $count -le 0 ]; then
         echo "$process.value -10"
         echo "$process.extinfo process down"
     else
         echo "$process.value $value"
     fi
done

URL監視

healthcheck_url ファイル。
指定したURL に curl でアクセスして処理時間(秒)を記録する。
あまりに遅いと遅すぎということでアラートがあがる。
で、もし、アクセス出来ない場合は -10 という値になりアラートが上がる。
ついでにコンテンツの中身をegrepして、特定の文字列が入っていないと、アラートを上げることもできる。(バグっていたので直した。ごめんなさい)
さらに、応答のhtmlのサイズをチェックするオプションも付けました。エラー画面のhtmlが帰ってきてしまって気がつかないみたいなことがないようにしました。応答のhtmlが特定のサイズ以下だとアラートを上げます。


また、proxyも対応しているので、インターネット経由(外回り)でのサイト監視もできるヨ。

もちろん、複数サイトを一つのグラフでまとめて管理することも可能。
整合性チェックのphpとかと組み合わせれば、結構面白いことができそうな気がする。

これで munin ってサイト監視できないよね! っていうのは過去のものになるはずだ。

#!/bin/bash
#
#healthcheck on munin
#check site speed.
#egrep contents string
# ... and alert.
#
#programed by rti (hiroyuki fujie) super.rti@gmail.com @super_rti
#LICENSE: NYSL (public domain)
#
#config file
#      /etc/munin/plugin-conf.d/munin-node
#
#example minimum config
#---------------------------------------------------
#[healthcheck_url]
#env.url_1  http://127.0.0.1/
#---------------------------------------------------
#
#chcek two site 
#---------------------------------------------------
#[healthcheck_url]
#env.url_1  http://127.0.0.1/
#env.url_2  http://www.google.com/
#---------------------------------------------------
#
#chcek three site 
#---------------------------------------------------
#[healthcheck_url]
#env.url_1  http://127.0.0.1/
#env.url_2  http://www.google.com/
#env.url_3  http://www.yahoo.com/
#---------------------------------------------------
#
#set name
#---------------------------------------------------
#[healthcheck_url]
#env.url_1  http://127.0.0.1/
#env.name_1 homhom
#---------------------------------------------------
#
#check over proxy
#---------------------------------------------------
#[healthcheck_url]
#env.url_1  http://127.0.0.1/
#env.proxy_1 127.0.0.1:8080
#---------------------------------------------------
#
#set slow speed(second)
#---------------------------------------------------
#[healthcheck_url]
#env.url_1  http://127.0.0.1/
#env.slowspeed_1 30
#---------------------------------------------------
#
#grep string
#---------------------------------------------------
#[healthcheck_url]
#env.url_1  http://127.0.0.1/
#env.htmlgrep_1 saysaya
#---------------------------------------------------
#
#check html contents byte size.
#---------------------------------------------------
#[healthcheck_url]
#env.url_1  http://127.0.0.1/
#env.htmlsize_1 50000                  #check 50000 bytes over.
#---------------------------------------------------
#
#full option
#/etc/munin/plugin-conf.d/munin-node
#---------------------------------------------------
#[healthcheck_url]
#env.url_1  http://127.0.0.1/          #check url
#env.htmlgrep_1 apache                 #check egrep string
#env.name_1 127.0.0.1                  #set line name. default by url domain
#env.proxy_1 127.0.0.1:8080            #over proxy
#env.slowspeed_1 30                    #slow time on alert
#env.htmlsize_1 50000                  #check 50000 bytes over.
#---------------------------------------------------
#
#

#edakari speed up.
CHECKMAX=`env | grep url_ | wc -l`
let CHECKMAX="$CHECKMAX + 1"

CURL=/usr/bin/curl

if [ "$1" = "autoconf" ]; then
    if [ $CHECKMAX -le 1 ]; then
         echo no
         exit 1
    fi
    echo yes
    exit 0
fi

if [ "$1" = "config" ]; then
    echo 'graph_title site speed (second)'
    echo "graph_args --base 1000 -l 0 --vertical-label second"
    echo 'graph_scale no'
    echo 'graph_vlabel second'
    echo 'graph_category healthcheck'
    echo 'graph_info This graph shows the site speed'

    for(( I = 1; I < $CHECKMAX; ++I ))
    do
         eval url=\$url_${I}
         eval name=\$name_${I}
         eval slowspeed=\$slowspeed_${I}
         if [ "x${url}" = "x" ]; then
              continue
         fi
         if [ "x${name}" = "x" ]; then
             #default name by domain
             name=`echo $url | sed 's#\.#_#g' | sed -e 's/[^/]*\/\/\([^@]*@\)\?\([^:/]*\).*/\2/'`
         fi
         if [ "x${slowspeed}" = "x" ]; then
             slowspeed=10
         fi
         let slowspeed15="slowspeed * 3 / 2" #slowspeed * 1.5

         echo "$name.label $name"
         echo "$name.info $url"
         echo "$name.draw LINE2"
         echo "$name.min -10"
         echo "$name.max ${slowspeed15}"
         echo "$name.critical 0:${slowspeed}"
    done

    exit 0
fi

for(( I = 1; I < $CHECKMAX; ++I ))
do
     eval url=\$url_${I}
     eval grep=\$htmlgrep_${I}
     eval size=\$htmlsize_${I}
     eval name=\$name_${I}
     eval proxy=\$proxy_${I}
     eval slowspeed=\$slowspeed_${I}

     if [ "x${url}" = "x" ]; then
        continue
     fi
     if [ "x${name}" = "x" ]; then
        #default name by domain
        name=`echo $url | sed 's#\.#_#g' | sed -e 's/[^/]*\/\/\([^@]*@\)\?\([^:/]*\).*/\2/'`
     fi

     PROXY_CMD=""
     if [ "x${proxy}" != "x" ]; then
        PROXY_CMD=" --proxy ${proxy} "
     fi
     if [ "x${slowspeed}" = "x" ]; then
        slowspeed=10
     fi
     let timeout="${slowspeed} + 1"

     START=`date +%s`
     HTML_RESULT=`$CURL "${url}" -s --connect-timeout ${timeout} ${PROXY_CMD}`
     CURLEXITCODE=$?
     END=`date +%s`

     if [ $CURLEXITCODE -ne 0 ]; then
         echo "$name.value -10"
         echo "$name.extinfo curl return $CURLEXITCODE"
         continue
     fi

     GREPEXITCODE=0
     if [ "x${grep}" != "x" ]; then
         echo $HTML_RESULT | egrep -i "${grep}" > /dev/null
         GREPEXITCODE=$?
         if [ $GREPEXITCODE -ne 0 ]; then
             echo "$name.value -9"
             echo "$name.extinfo can not found $grep regex strings"
             continue
         fi
     fi


     if [ "x${size}" != "x" ]; then
         if [ ${#HTML_RESULT} -lt ${size} ]; then
             echo "$name.value -8"
             echo "$name.extinfo html size ${#HTML_RESULT} is smaller than ${size}"
             continue
         fi
     fi


     let SPEED="$END - $START"
     echo "$name.value $SPEED"
done

ログ監視

healthcheck_log ファイル。
ログを egrep して特定の文字列が含まれていないかチェックすることができる。
これで深刻なエラーがログには出ていたけど、チェック忘れれて気が付きませんでしたということもなくなるヨ。

#!/bin/bash
#
#healthcheck on munin
#egrep system log and alert.
#
#programed by rti (hiroyuki fujie) super.rti@gmail.com @super_rti
#LICENSE: NYSL (public domain)
#
#
#config file
#      /etc/munin/plugin-conf.d/munin-node
#
#example minimum config
#---------------------------------------------------
#[healthcheck_log]
#user root
#env.log_1  /var/log/messages
#---------------------------------------------------
#
#check two log
#---------------------------------------------------
#[healthcheck_log]
#user root
#env.log_1  /var/log/messages
#env.log_2  /var/log/syslog
#---------------------------------------------------
#
#check two three
#---------------------------------------------------
#[healthcheck_log]
#user root
#env.log_1  /var/log/messages
#env.log_2  /var/log/syslog
#env.log_3  /var/log/dmesg
#---------------------------------------------------
#
#set name
#---------------------------------------------------
#[healthcheck_log]
#user root
#env.log_1  /var/log/messages
#env.name_1 my_server_messages
#---------------------------------------------------
#
#set egrep string
#---------------------------------------------------
#[healthcheck_log]
#user root
#env.log_1  /var/log/messages
#env.grep_1 alert|warning
#---------------------------------------------------
#
#set egrep string
#---------------------------------------------------
#[healthcheck_log]
#user root
#env.log_1  /var/log/messages
#env.grep_1 alert|warning
#---------------------------------------------------
#
#full option
#/etc/munin/plugin-conf.d/munin-node
#---------------------------------------------------
#[healthcheck_log]
#user root                          #log file is read only root user.
#env.log_1  /var/log/messages       #target log filename
#env.grep_1 critical|error          #egrep string.
                                    #defualt by critical|error|warning|crash|fatal|kernel
#---------------------------------------------------
#


#edakari speed up.
CHECKMAX=`env | grep log_ | wc -l`
let CHECKMAX="$CHECKMAX + 1"
MINUTE_BY_GREP_RANGE=10

if [ "$1" = "autoconf" ]; then
    if [ $CHECKMAX -le 1 ]; then
         echo no
         exit 1
    fi
    echo yes
    exit 0
fi

if [ "$1" = "config" ]; then
    echo 'graph_title log grep (match count)'
    echo "graph_args --base 1000 -l 0 --vertical-label match_count"
    echo 'graph_scale no'
    echo 'graph_vlabel match_count'
    echo 'graph_category healthcheck'
    echo 'graph_info This graph shows the bad event count on log'

    for(( I = 1; I < $CHECKMAX; ++I ))
    do
         eval log=\$log_${I}
         eval name=\$name_${I}
         eval grep=\$grep_${I}
         if [ "x${log}" = "x" ]; then
              continue
         fi
         if [ "x${name}" = "x" ]; then
             name=`echo $log | sed 's#[/|\.]#_#g'`
         fi
         if [ "x${name}" = "x" ]; then
             grep="critical|error|warning|crash|fatal|kernel"
         fi

         echo "$name.label $name"
         echo "$name.info egrep $grep $log | wc -l"
         echo "$name.draw LINE2"
         echo "$name.min 0"
         echo "$name.max 20"
         echo "$name.critical 0:0"
    done

    exit 0
fi

NOWTIME=`date --date "$MINUTE_BY_GREP_RANGE minute ago" +%s`

for(( I = 1; I < $CHECKMAX; ++I ))
do
    eval log=\$log_${I}
    eval name=\$name_${I}
    eval grep=\$grep_${I}
    if [ "x${log}" = "x" ]; then
         continue
    fi
    if [ "x${name}" = "x" ]; then
         name=`echo $log | sed 's#[/|\.]#_#g'`
    fi
    if [ "x${grep}" = "x" ]; then
         grep="critical|error|warning|crash|fatal|kernel"
    fi

    COUNT=0
    MESSAGE=
    IFS=$'\n'
    MATCHLINES=(`egrep -i "$grep" "$log"`)
    for(( N = ${#MATCHLINES[@]} - 1; N >= 0 ; --N ))
    do
         LINE=${MATCHLINES[$N]}
         DATESTRING=`echo $LINE | awk '{ printf("%s %s %s",$1,$2,$3)}'`
         LOGTIME=`date --date "$DATESTRING" +%s`
         if [ $LOGTIME -lt $NOWTIME ]; then
               break
         fi
         let COUNT="$COUNT + 1"
         MESSAGE="$MESSAGE$LINE //@LINE@// "
    done


    if [ $COUNT -eq 0 ]; then
        echo "${name}.value 0"
    else
        echo "${name}.value ${COUNT}"
        echo "${name}.extinfo ${MESSAGE}"
    fi
done

ping監視(追加)

ついでに ping 監視も作ってみた。
healthcheck_ping ファイル。
ping をなげて応答速度を見ます。

#!/bin/bash
#
#healthcheck on munin
#check ping speed.
#egrep contents string
# ... and alert.
#
#programed by rti (hiroyuki fujie) super.rti@gmail.com @super_rti
#LICENSE: NYSL (public domain)
#
#config file
#      /etc/munin/plugin-conf.d/munin-node
#
#example minimum config
#---------------------------------------------------
#[healthcheck_ping]
#env.ping_1  127.0.0.1
#---------------------------------------------------
#
#chcek two site 
#---------------------------------------------------
#[healthcheck_ping]
#env.ping_1 127.0.0.1
#env.ping_2  www.google.com
#---------------------------------------------------
#
#chcek three site 
#---------------------------------------------------
#[healthcheck_ping]
#env.ping_1  127.0.0.1
#env.ping_2  www.google.com
#env.ping_3  192.168.1.1
#---------------------------------------------------
#
#set name
#---------------------------------------------------
#[healthcheck_ping]
#env.ping_1 127.0.0.1
#env.name_1 homhom
#---------------------------------------------------
##
#set slow speed(second)
#---------------------------------------------------
#[healthcheck_ping]
#env.ping_1  127.0.0.1
#env.slowspeed_1 3
#---------------------------------------------------
#
#full option
#/etc/munin/plugin-conf.d/munin-node
#---------------------------------------------------
#[healthcheck_ping]
#env.ping_1  127.0.0.1                 #check ping
#env.name_1  sayasaya                  #set line name. default by ping domain
#env.slowspeed_1 3                     #slow time on alert
#---------------------------------------------------
#
#

#edakari speed up.
CHECKMAX=`env | grep ping_ | wc -l`
let CHECKMAX="$CHECKMAX + 1"

if [ "$1" = "autoconf" ]; then
    if [ $CHECKMAX -le 1 ]; then
         echo no
         exit 1
    fi
    echo yes
    exit 0
fi

if [ "$1" = "config" ]; then
    echo 'graph_title ping speed (mili second)'
    echo "graph_args --base 1000 -l 0 --vertical-label milisecond"
    echo 'graph_scale no'
    echo 'graph_vlabel milisecond'
    echo 'graph_category healthcheck'
    echo 'graph_info This graph shows the site speed'

    for(( I = 1; I < $CHECKMAX; ++I ))
    do
         eval ping=\$ping_${I}
         eval name=\$name_${I}
         eval slowspeed=\$slowspeed_${I}
         if [ "x${ping}" = "x" ]; then
              continue
         fi
         if [ "x${name}" = "x" ]; then
             #default name by domain
             name=`echo $ping | sed 's#\.#_#g'`
         fi
         if [ "x${slowspeed}" = "x" ]; then
             slowspeed=3
         fi
         let slowspeedMS="slowspeed * 1000"    #convert milisecond.
         let slowspeedMS15="slowspeed * 3 / 2 * 1000" #slowspeedMS * 1.5

         echo "$name.label $name"
         echo "$name.info $ping"
         echo "$name.draw LINE2"
         echo "$name.min -10"
         echo "$name.max ${slowspeedMS15}"
         echo "$name.critical 0:${slowspeedMS}"
    done

    exit 0
fi

for(( I = 1; I < $CHECKMAX; ++I ))
do
     eval ping=\$ping_${I}
     eval name=\$name_${I}
     eval slowspeed=\$slowspeed_${I}

     if [ "x${ping}" = "x" ]; then
        continue
     fi
     if [ "x${name}" = "x" ]; then
        #default name by domain
        name=`echo $ping | sed 's#\.#_#g'`
     fi

     if [ "x${slowspeed}" = "x" ]; then
        slowspeed=3
     fi
     let timeout="${slowspeed} + 1"

     PING_RESULT=`ping -c 1 -n -w ${timeout} "${ping}" | grep "icmp_seq=1"`
     PINGRESULT=$?

     if [ $PINGRESULT -ne 0 ]; then
         if [ "x$PING_RESULT" = "x" ]; then
              PING_RESULT="ping can not respons. timeout $slowspeed second"
         fi
         echo "$name.value -10"
         echo "$name.extinfo $PING_RESULT"
         continue
     fi

     TIMEMS=`echo $PING_RESULT | sed 's/.*time=\([^ ]*\).*/\1/g'`
     if [ "x$TIMEMS" = "x" ]; then
         echo "$name.value -8"
         echo "$name.extinfo bad time: $PING_RESULT"
         continue
     fi
     echo "$name.value $TIMEMS"
done