ZABBIXの通知メールをスレッド化してみる

ZABBIXの通知メールをスレッド化してみる はてなブックマーク - ZABBIXの通知メールをスレッド化してみる


監視ツール ZABBIX には、トリガ(障害)発生時にメールを送信する機能があります。ただ、このメールはスレッド化(階層化)されません。通知・復旧毎にメールを送信している場合や、複数の監視環境が入り乱れてしまうと、どのアラートが対応中で、どのアラートが復旧しているのか、状況確認に手間取る場合があります。↓大量に届く通知メールの想像イメージ

もしも、ZABBIXのメールがスレッド化していたら、仕事が楽になるのになぁ…と思い、少しスクリプトを書いてみました。少々甘いところはありそうですが、ひとまず自分が必要な機能は実装できたので、公開します。

■ZABBIXの通知メールをスレッド化したい!概要

まずはじめに、メーラーで表示されるメールをスレッド化するために必要なものは、RFC 2822 で定義されています。やりたい事はシンプル。ZABBIXが通知するメール毎に、ヘッダを付与するだけで、スレッド表示されます。

障害発生メール… Message-ID: ヘッダを付与
継続メールや復旧メール … References: ヘッダを付与

つまり、トリガでイベントが発生する度に、Message-ID: を作成し、関連するメールは、すべてこの Message-ID に対する Reference: を指定するだけです。

では、この Message-ID は、どのように生成すべきでしょうか。そこで思い出したのが、マクロ {TRIGGER.ID} です。これはトリガ毎にユニークな番号が割り振られます。ただ、{TRIGGER.ID}は常に変わらないので、同じトリガはスレッドが繋がってしまいます。この問題を解決するために、ランダムな数字も入れ込みます。

Message-ID: {TRIGGER.ID}.ランダムな数字@ホスト名

あとは、関連するメールがあれば、復旧通知が届くまで、メールのヘッダに

References: {TRIGGER.ID}.ランダムな数字@ホスト名

この情報を付与します。(マクロ{EVENT.ID}を使う方法も検討しました。しかし、{EVENT.ID}は、復旧したものに対しては別のイベントを発生するため、シンプルに{TRIGGER.ID}を中心に考える事にしました)

しかし、ZABBIX にはヘッダを書き換える仕組みがありません。この部分を解決するのが、ZABBIX-JP で配付されている message-php の改変というアイディアでした。

■スクリプトの動作

自分が改変した message-php は、Github 上で公開しました。日本語の通知メールに対応する message-php を流用させていただき、スレッド用のヘッダを書き換えるような仕組みを取り入れました。

このスクリプトのポイントは、以下2つの動作です。

  • ZABBIX のアクションで、件名の先頭に「#{TRIGGER.ID}:」を入れる
  • message-phpは、「#{TRIGGER.ID}:」に一致する件名がある場合、以下の動作へ
    • 新規 {TRIGGER.ID} であれば、
      データファイルを作成し、メールに’Message-ID:’ ヘッダを付与
    • 既存の {TRIGGER.ID} であれば、’References:’ヘッダを付与
    • 既存かつ「復旧」が件名に含まれれば、データファイル削除

ZABBIX のアクションで送信されるメールの件名に「#{TRIGGER.ID}:」が含まれますが、message-php を通過した時点で、トリガ情報を削除して、通常の(本来送りたい)メールの件名に書き直しています。

さて、スクリプト本体は、以下の URL から取得いただけます。名称はオリジナルから変更して、sendmessage-thread.php としています。

https://raw.github.com/zembutsu/zabbix-emailthreading/master/sendmessage-thread.php

スクリプトを設置する前に、変数「$DATAPATH」と「$HOSTNAME」の書き換えを行います。

  • $DATAPATH … メールのヘッダ情報を管理するディレクトリのパスです。デフォルトでは「/opt/zabbix-messageid-manager/data」となっています。
    • ディレクトリの作成と、ZABBIX サーバの動作権限の指定が必要です。
      mkdir -p /opt/zabbix-messageid-manager/data
      chown -R zabbix.zabbix /opt/zabbix-messageid-manager/data
  • $HOSTNAME … Message-ID: ヘッダに記述するホスト名です。稼働しているサーバのものに書き換えてお使いください。

あとは、ZABBIX 側での設定です。各アカウントの通知先(メディア)が、メールだったり sendmessage-php となっている箇所を、この sendmessage-thread.php に置き換えます。

次に、ZABBIX のアクション指定で、件名に

#{TRIGGER.ID}:

を付与し、【保存】を押します。

基本的に、これだけで設定は完了です。

あとは、実際にアラートを出してみて、イベントやトリガの詳細画面で、スクリプトが正常に処理されているかどうかを確認します。

なお、この方法では、データディレクトリ「/opt/zabbix-messageid-manager/data」に一時ファイルが作成されたまになる事があります。データ削除のタイミングが、件名に「復旧」が含まれる場合だからです。もしも、復旧通知を行わないトリガの場合は、データが残り続けてしまいます。そのため、cronなどで自動削除する仕組みを入れてください。

# find /opt/zabbix-messageid-manager/data -ctime +1 -exec rm -f '{}' ';'

これはファイル生成から1日経過しているファイルを rm (削除) するものです。

もし「復旧」以外の文字をトリガにされている場合は、スクリプトの 46 行目、

} elseif (ereg( '復旧' ,$MAIL_SUBJECT )) {

この部分を任意の文字に書き換えて下さい。

…というわけで、スクリプトそのものは、もっと改善しがいがありそうな予感もしますが、ひとまず自分の環境でスレッド化できたので公開しますた(;´Д`)

■スクリプトに関する補足とか

40~45行目の記述:

if (ereg( '(High|Disaster)]障害' ,$MAIL_SUBJECT )) {
        $MAIL_HEADER .= 'X-Priority: 1'."\r\n";
} elseif (ereg( '(Average|Warning)]障害' ,$MAIL_SUBJECT )) {
        $MAIL_HEADER .= 'X-Priority: 2'."\r\n";
} elseif (ereg( '(Average|Warning|High)]通知' ,$MAIL_SUBJECT )) {
        $MAIL_HEADER .= 'X-Priority: 5'."\r\n";

メール件名に応じて、X-Priority: ヘッダを付与しています。メーラーの色を変えるためです。

50行目:

if (preg_match("/^(\#)(\d+)(\:)(.*)$/",$MAIL_SUBJECT,$regex)) {

ここが、メールの件名をチェックしています。「#数字:」の形式が一致したら、処理を開始します。逆に、この条件に一致しないと、Message-ID: や Reference: を発行しません。アクションのメール件名が「#{TRIGGER.ID}:」でなくとも、任意のモノに変更できます。その時は、ここも書き直してください。

51行目・52行目:

$MAIL_SUBJECT = $regex[4];
        $id = $regex[2];

そして、正規表現の要素を取り出します。2番目に一致する数字列( TRIGGER.ID  )を変数 $id に格納します。そして元々のオリジナルの件名(4番目の文字列)を $MAIL_SUBJECT に格納しています。もし、{TRIGGER.ID} もメール件名に表示したままでよければ、51行目は消しても構いません。

54~58行目:

if ( file_exists("$DATAPATH/$MAIL_TO.$id")) {
$rndf = fopen ("$DATAPATH/$MAIL_TO.$id", "r");
$rnd = chop( fgets($rndf) );
fclose($rndf);
$MAIL_HEADER .= 'References: <APPLI.'.$id. '.'. $rnd .'@'.$HOSTNAME.'>' . "\r\n";

$MAIL_TOで区切るのは、エスカレーションするメールに対応するためです。これが無いと、1通目のメールに対して Refereces: ヘッダが貼られても、2通目以降のメールしか受け取らない方でメールのスレッドが繋がらなくなるためです。そのため、メールアドレス毎にデータを作成しますファイルの中身は、ランダムに作成している数値です。

60~67行目:

} else {
$rnd = rand (10000000,99999999);
$MAIL_HEADER .= 'Message-ID: <APPLI.'.$id.'.'. $rnd .'@'.$HOSTNAME.'>' . "\r\n";
$idf = fopen("$DATAPATH/$MAIL_TO.$id","w");
$rt = fwrite($idf, $rnd);
fclose($idf);
$MAIL_HEADER .= 'X-RT: '.$rt . "\r\n";
}

ここは、データの新規作成時の処理です。Message-ID: を発行しています。$rnd でランダムな変数を作成しているのは、「トリガ」毎に、Message-ID: を変えたいからです。トリガが復旧した後、再び同じトリガで通知が出てもスレッドがつながらないようにするためです。ランダムな数値でなくとも、シリアルでもいいかもしれませんね。

68~70行目:

        if ($RECOVER == 1) {
                unlink ("$DATAPATH/$MAIL_TO.$id");
        }

メール件名に「復旧」が含まれていれば、$RECOVER 変数に1が格納されるので、その時はデータファイルを削除します。

…というわけで、何かしら皆さんの参考になれば、とっても嬉しいなって( ^ω^)