簡易的にフェイルオーバーの発生を nagios で通知する

とある Keepalived や Pacemaker で仮想IP(VIP)を用いたフェイルオーバーによる冗長化を行っているシステムで、フェイルオーバーが発生したことを nagios で簡易的に監視したときのメモ。

Keepalived の notify スクリプトや、Pacemaker の crm_mon --external-agent とかで nsca で Nagios の Passive check に通知すれば良いとは思うのですが、そのためにスクリプトを仕込んだりテストしたりとかまでやる意義があまりなかったので、Active check だけでもっと簡易的に実装しました。

要するに VIP を持っている実サーバに変化があればフェイルオーバーしたとみなせます。

監視対象のサーバには snmpd が動いていたので snmpget でホスト名が得られました。ので VIP に対して snmpget でホスト名を取得し、前回の Active check 実行時から変化があればフェイルオーバーが発生したとみなして通知します。

前回の Active check の結果との比較は、SERVICEOUTPUT マクロに前回の Active check の実行時のチェックコマンドの出力が入るので、ここにホスト名を出力するようにして、その内容を比較します。

監視用のスクリプトは次のようになります。これを Nagios の /usr/lib64/nagios/plugins/ とかに置いておきます。

#!/bin/bash

set -eu -o pipefail

trap 'exit 3' ERR

# $1 には監視対象のIPアドレスを入れる
target=$1

# snmp community は適当に書き換えるかこれも引数で受け取る
community=xxxxx

prev=

# $2 には前回のアクティブチェックの出力(SERVICEOUTPUT)を入れる
case "$2" in
  # SERVICEOUTPUT からホスト名を取り出す
  # SERVICEOUTPUT は Active:$hostname[, $message] みたいな形式になっています
  Active:*)
    prev=$2
    prev=${prev#*:}
    prev=${prev%%,*}
    ;;
esac

# snmpget でホスト名を取得
curr=$(snmpget -v2c -c "$community" -Oavq "$target" .1.3.6.1.2.1.1.5.0)

# 空かどうか&前回のホスト名と比較して結果を返す
if [ -z "$curr" ]; then
  # snmpget がしてるときは UNKNOWN(3)
  printf "%s\n" "snmpget returned empty"
  exit 3
elif [ -z "$prev" ]; then
  # 前回の結果が無いなら WARNING(1)
  printf "%s\n" "Active:$curr, Initial state"
  exit 1
elif  [ "$prev" != "$curr" ]; then
  # 前回の結果と異なるなら CRITICAL(2)
  printf "%s\n" "Active:$curr, Failover from $prev"
  exit 2
else
  # 前回の結果と同じなら OK(0)
  printf "%s\n" "Active:$curr"
  exit 0
fi

次に Nagios の設定です。

define command {
    command_name    check_failover
    # 対象サーバのIPアドレスとアクティブチェックの出力を渡す
    command_line    $USER1$/check_failover.sh "$HOSTADDRESS$" "$SERVICEOUTPUT$"
}
define service{
    use                     generic-service
    host_name               lb-active
    service_description     Failover
    check_command           check_failover
    # フェイルオーバが連続したときも通知したい
    is_volatile             1
    # 一発で HARD にして通知する
    max_check_attempts      1
    # 1回しか通知しない
    notification_interval   0
    # リカバリ(r)は通知しない
    notification_options    w,u,c
    # フラッピング検出は無効(有効でも良いかも)
    flap_detection_enabled  0
}

通知内容の時系列

  • [WARNING] Active:ore-no-server-01, Initial state
    • 監視を仕込んで初回のチェック
    • 直前の状態がわからないため発生します
  • [OK] Active:ore-no-server-01
    • 2回目以降のチェックは直前のホストと変わっていないので OK です
    • リカバリは通知しないのでこれは通知されません
  • [CRITICAL] Active:ore-no-server-01, Failover from ore-no-server-01
    • フェイルオーバーが発生したときにホスト名の変化を検出して通知します
  • [OK] Active:ore-no-server-02
    • フェイルオーバーを通知後はまた OK に戻ります

さいごに

ホスト名を直前のチェック時から比較すればよいかと思い、直前のチェック時の値を保持するためにどこかファイルにでも保存するかと思ったのですが、よく考えたら SERVICEOUTPUT マクロを見ればできました。

フェイルオーバーの前にアクティブ機への snmpget が失敗し、

  • [UNKNOWN] snmpget returned empty
  • [WARNING] Active:ore-no-server-02, Initial state

のように通知されることがありますが、まあ通知は飛ぶので良しとします。