CentOS 7 の Pacemaker/Corosync で snmptrap やメールで通知

CentOS 7 のリポジトリからサクッとインストールできる下記で試しています。

- corosync-2.3.4-7.el7_2.1
- pacemaker-1.1.13-10.el7_2.2
- resource-agents-3.9.5-54.el7_2.9

## Pacemaker/Corosync の構成

確認用に次のような構成で Pacemaker/Corosync の環境を構築しています。

### corosync

- pm01
  - 192.168.33.11/24 (enp0s8 / ring0)
  - 192.168.99.11/24 (enp0s9 / ring1)
- pm02
  - 192.168.33.12/24 (enp0s8 / ring0)
  - 192.168.99.12/24 (enp0s9 / ring1)

### pacemaker

```sh
sudo pcs property set stonith-enabled="false"
sudo pcs property set no-quorum-policy="ignore"

sudo pcs resource defaults migration-threshold="1"

sudo pcs resource create dummy1 ocf:pacemaker:Dummy state=/tmp/pm-dummy1
sudo pcs resource create dummy2 ocf:pacemaker:Dummy state=/tmp/pm-dummy2

sudo pcs resource group add dummys dummy1 dummy2
```

### /etc/hosts

```sh
127.0.0.1       localhost localhost.localdomain localhost4 localhost4.localdomain4
192.168.33.11   pm01
192.168.33.12   pm02
192.168.99.11   pm01-back
192.168.99.12   pm02-back
```

## snmptrapd

snmptrap の確認のために ローカルに snmptrapd をセットアップします。

必要なもろもろをインストールします。

```sh
sudo yum -y install net-snmp net-snmp-utils net-snmp-perl mailx
```

snmptrapd ですべてのトラップをメールで root に転送するように設定します。

```sh
sudo tee /etc/snmp/snmptrapd.conf <<'EOS'
disableAuthorization yes
traphandle default /usr/bin/traptoemail root
EOS
```

root へのメールは自分のメールアドレスに転送させます。

```sh
echo ore@example.com | sudo tee /root/.forward
```

pacemaker や corosync の mib を読み込むために `/etc/snmp/snmp.conf` を作成します。

```sh
sudo tee /etc/snmp/snmp.conf <<'EOS'
mibs ALL
EOS
```

snmptrapd を開始します。

```sh
sudo systemctl start snmptrapd
sudo systemctl enable snmptrapd
```

試しに適当なトラップを送信します。トラップがメールで送信されれば正しくセットアップされています。

```sh
sudo snmptrap -v 1 -c hoge localhost corosync localhost 6 1 ''
sudo snmptrap -v 1 -c hoge localhost pacemaker localhost 6 1 ''
```

## corosync-notifyd

`corosync-notifyd` は corosync の重要なイベントを dbus や snmptrap で通知するサービスです。ノードの故障や復帰を通知することができますが・・・リソース障害は corosync の管轄じゃないので通知できません。

`/etc/sysconfig/corosync-notifyd` でコマンドラインオプションを指定できるので `127.0.0.1` にトラップを送信するように設定します。

```sh
sudo tee /etc/sysconfig/corosync-notifyd <<'EOS'
OPTIONS="-s -l -m 127.0.0.1"
EOS
```

`corosync-notifyd` を開始します。

```sh
sudo systemctl start corosync-notifyd
```

開始した時点で次のようなトラップが送信されました。

```sh
                SNMPv2-MIB::snmpTrapOID.0  COROSYNC-MIB::corosyncNoticesQuorumStatus
    COROSYNC-MIB::corosyncObjectsNodeName  "pm01"
      COROSYNC-MIB::corosyncObjectsNodeID  1
COROSYNC-MIB::corosyncObjectsQuorumStatus  "quorate"
```

### pacemaker や corosync を停止・開始したときの通知

片方の pacemaker を停止してみる。

```sh
sudo systemctl stop pacemaker
```

このときはトラップは送信されません。

corosync を停止してみる。

```sh
sudo systemctl stop corosync
```

次のようなトラップが片割れ（pm01 で corosync を停止したので pm02 から）から送信されました。

```sh
# from pm02
               SNMPv2-MIB::snmpTrapOID.0  COROSYNC-MIB::corosyncNoticesNodeStatus
   COROSYNC-MIB::corosyncObjectsNodeName  "pm01-back"
     COROSYNC-MIB::corosyncObjectsNodeID  1
COROSYNC-MIB::corosyncObjectsNodeAddress  "192.168.99.11"
 COROSYNC-MIB::corosyncObjectsNodeStatus  "left"
```

pacemaker を開始してみる（corosync も一緒に開始されます）。

```sh
sudo systemctl start pacemaker
```

次のようなトラップが送信されました。

```sh
# from pm02
               SNMPv2-MIB::snmpTrapOID.0  COROSYNC-MIB::corosyncNoticesNodeStatus
   COROSYNC-MIB::corosyncObjectsNodeName  "pm01-back"
     COROSYNC-MIB::corosyncObjectsNodeID  1
COROSYNC-MIB::corosyncObjectsNodeAddress  "192.168.99.11"
 COROSYNC-MIB::corosyncObjectsNodeStatus  "joined"
```

### リソース障害

リソース障害を発生させてみる。

```sh
sudo rm /tmp/pm-dummy1
```

が、リソース障害ではなにも通知されませんでした。

### 通信障害

NIC の 1 つを iptables で塞いでみます。

```sh
sudo yum -y install iptables-services
sudo systemctl start iptables
sudo iptables -F
sudo iptables -A INPUT -i enp0s9 -j DROP
```

次のようなトラップが送信されました。両方のノードから通知が来ると思っていたのですが、、、片方からしか来ませんでした（`corosync-cfgtool` で見た感じ、両方のノードで `faulty` になってます）。

```sh
# from pm02
             SNMPv2-MIB::snmpTrapOID.0  COROSYNC-MIB::corosyncNoticesRRPStatus
 COROSYNC-MIB::corosyncObjectsNodeName  "pm02"
   COROSYNC-MIB::corosyncObjectsNodeID  2
  COROSYNC-MIB::corosyncObjectsIfaceNo  1
COROSYNC-MIB::corosyncObjectsRRPStatus  "faulty"

```

塞いでた穴を開きます。

```sh
sudo iptables -F
```

次のようなトラップが送信されました。これも片方のノードからしか送信されませんでした。

```sh
# from pm02
             SNMPv2-MIB::snmpTrapOID.0  COROSYNC-MIB::corosyncNoticesRRPStatus
 COROSYNC-MIB::corosyncObjectsNodeName  "pm02"
   COROSYNC-MIB::corosyncObjectsNodeID  2
  COROSYNC-MIB::corosyncObjectsIfaceNo  1
COROSYNC-MIB::corosyncObjectsRRPStatus  "operational"
```

### ノード障害

サーバを強制終了してみます。Vagrant 環境なので次のように強制終了します。

```sh
vagrant halt --force pm01
```

次のようなトラップが送信されました。

```sh
# from pm02
               SNMPv2-MIB::snmpTrapOID.0  COROSYNC-MIB::corosyncNoticesNodeStatus
   COROSYNC-MIB::corosyncObjectsNodeName  "pm01-back"
     COROSYNC-MIB::corosyncObjectsNodeID  1
COROSYNC-MIB::corosyncObjectsNodeAddress  "192.168.99.11"
 COROSYNC-MIB::corosyncObjectsNodeStatus  "left"
```

サーバを起動します。

```sh
vagrant up pm01
vagrant ssh pm01
sudo systemctl start corosync-notifyd 
sudo systemctl start pacemaker
```

次のようなトラップが送信されました。

```sh
# from pm02
               SNMPv2-MIB::snmpTrapOID.0  COROSYNC-MIB::corosyncNoticesNodeStatus
   COROSYNC-MIB::corosyncObjectsNodeName  "pm01-back"
     COROSYNC-MIB::corosyncObjectsNodeID  1
COROSYNC-MIB::corosyncObjectsNodeAddress  "192.168.99.11"
 COROSYNC-MIB::corosyncObjectsNodeStatus  "joined"
```

```sh
# from pm01
                SNMPv2-MIB::snmpTrapOID.0  COROSYNC-MIB::corosyncNoticesQuorumStatus
    COROSYNC-MIB::corosyncObjectsNodeName  "pm01"
      COROSYNC-MIB::corosyncObjectsNodeID  1
COROSYNC-MIB::corosyncObjectsQuorumStatus  "quorate"

```

```sh
# from pm01
             SNMPv2-MIB::snmpTrapOID.0  COROSYNC-MIB::corosyncNoticesRRPStatus
 COROSYNC-MIB::corosyncObjectsNodeName  "pm01"
   COROSYNC-MIB::corosyncObjectsNodeID  1
  COROSYNC-MIB::corosyncObjectsIfaceNo  0
COROSYNC-MIB::corosyncObjectsRRPStatus  "operational"
```

```sh
# from pm01
             SNMPv2-MIB::snmpTrapOID.0  COROSYNC-MIB::corosyncNoticesRRPStatus
 COROSYNC-MIB::corosyncObjectsNodeName  "pm01"
   COROSYNC-MIB::corosyncObjectsNodeID  1
  COROSYNC-MIB::corosyncObjectsIfaceNo  1
COROSYNC-MIB::corosyncObjectsRRPStatus  "operational"
```

### corosync-notifyd を起動すると corosync も起動する

ちなみに `corosync-notifyd` の systemd のユニットファイルに `Wants=corosync.service` が書かれているので、`corosync-notifyd` を開始すると `corosync` も一緒に開始します。

```sh
cat /usr/lib/systemd/system/corosync-notifyd.service
# [Unit]
# Description=Corosync Dbus and snmp notifier
# Wants=corosync.service
# After=corosync.service
# :
```

サーバのブート時に `corosync` を起動させたくないのであれば注意が必要です。

## crm_mon で snmp-traps とかメール通知

たしか `crm_mon` に `--snmp-traps` とか `--mail-to` とかのオプションがあって、リソース障害を snmptrap やメールで通知できるはず、、、なのだけど `man crm_mon` にも `crm_mon --help` にもその情報がない。。。

だいぶ前に LinuxHA Japan のリポジトリからインストールした `pacemaker-1.0.13-2.el6.x86_64` の `crm_mon 1.0.13 for OpenAIS and Heartbeat (Build: a83fae5)` だと `--snmp-traps` とか `--mail-to` とかもあるんだけど。。。

とりあえずダメ元で試してみる。

pacemaker のインストール時に `crm_mon` のための systemd のユニットファイルがインストールされています。

```sh
cat /usr/lib/systemd/system/crm_mon.service
```

内容は次の通り。

```
:
[Service]
Type=forking
EnvironmentFile=-/etc/sysconfig/crm_mon
ExecStart=/usr/sbin/crm_mon $OPTIONS
Restart=always
:
```

`Type=forking` とあるので `crm_mon` はデーモン化するのが良さそう。`EnvironmentFile` でファイル名の頭に `-` がついているのは `man systemd.exec` によるとファイルが存在しないときには無視するという意味とのこと。

次のように `/etc/sysconfig/crm_mon` でコマンドライン引数を指定します。

```sh
sudo tee /etc/sysconfig/crm_mon <<'EOS'
OPTIONS="--daemonize --snmp-traps=127.0.0.1"
EOS
```

`crm_mon.service` を開始します。

```sh
sudo systemctl start crm_mon.service
sudo systemctl status crm_mon.service
```

意図的にリソース障害を発生させてみる・・・が、トラップが送信される気配がない。

ためしに下記でも試してみるも・・・が、メールが送信される気配はない。

```sh
sudo tee /etc/sysconfig/crm_mon <<'EOS'
OPTIONS="--daemonize --mail-to=root"
EOS
```

どうもこれらの機能は CentOS 7 のリポジトリの pacemaker では無効になっているっぽい。

- http://clusterlabs.org/doc/en-US/Pacemaker/1.1/html/Pacemaker_Explained/ch07.html#idm140617236272336

> Depending on your system settings and compilation settings, SNMP or email alerts might be unavailable. Check the output of crm_mon --help to see whether these options are available to you. In any case, executing an external agent will always be available, and you can use this agent to send emails, SNMP traps or whatever action you develop.

## crm_mon の external-agent でコマンド呼び出し

`man crm_mon` に `--external-agent` はあったので、こっちを試してみる。

```sh
sudo tee /etc/sysconfig/crm_mon <<'EOS'
OPTIONS="--daemonize --external-agent=/vagrant/agent.sh --external-recipient=ore-recipient"
EOS
```

`/vagrant/agent.sh` は次のような内容です。

```sh
#!/bin/bash
env | sort | /bin/logger -t ore-agent -i
```

`crm_mon.service` を再起動します。

```sh
sudo systemctl restart crm_mon.service
sudo systemctl status crm_mon.service
```

意図的にリソース障害を発生させると `/var/log/messages` に次のようなログが記録されました。

```
Apr 26 07:25:30 pm01 ore-agent[4553]: CRM_notify_desc=OK
Apr 26 07:25:30 pm01 ore-agent[4553]: CRM_notify_node=pm02
Apr 26 07:25:30 pm01 ore-agent[4553]: CRM_notify_rc=0
Apr 26 07:25:30 pm01 ore-agent[4553]: CRM_notify_recipient=ore-recipient
Apr 26 07:25:30 pm01 ore-agent[4553]: CRM_notify_rsc=dummy2
Apr 26 07:25:30 pm01 ore-agent[4553]: CRM_notify_status=0
Apr 26 07:25:30 pm01 ore-agent[4553]: CRM_notify_target_rc=0
Apr 26 07:25:30 pm01 ore-agent[4553]: CRM_notify_task=monitor
 :
```

つまり、指定したコマンドがこれらの環境変数を付けて実行されるということなので、次のようにメール通知を設定してみます。

```sh
#!/bin/bash
env | grep ^CRM_notify | sort |
  mail -s "[$CRM_notify_node] $CRM_notify_rsc $CRM_notify_task $CRM_notify_desc" root
```

意図的にリソース障害を発生させると、メールがだだだっと送信されました。

## crm_mon を pacemaker のリソースにする

下記の例のように `crm_mon` を systemd ではなく pacemaker のリソースとして管理しても良いかもしれません。

- http://clusterlabs.org/doc/en-US/Pacemaker/1.1/html/Pacemaker_Explained/s-notification-external.html

systemd の `crm_mon.service` を停止します。

```sh
sudo systemctl stop crm_mon.service
sudo systemctl disable crm_mon.service
sudo systemctl status crm_mon.service
```

リソースエージェント `ocf:pacemaker:ClusterMon` の説明を見てみます。

```sh
sudo pcs resource describe ocf:pacemaker:ClusterMon
```

下記のとおりです。

```
ocf:pacemaker:ClusterMon - Runs crm_mon in the background, recording the cluster status to an HTML file

This is a ClusterMon Resource Agent.
It outputs current cluster status to the html.

Resource options:
  user: The user we want to run crm_mon as
  update: How frequently should we update the cluster status
  extra_options: Additional options to pass to crm_mon. Eg. -n -r
  pidfile: PID file location to ensure only one instance is running
  htmlfile: Location to write HTML output to.
```

次のように Clone リソースとして設定します。

```sh
sudo pcs resource create crm_mon ocf:pacemaker:ClusterMon \
  user="root" extra_options="--external-agent=/vagrant/agent.sh" \
  meta migration-threshold="INFINITY" --clone
```

## crm_mon の external-agent で通知を間引く

すべてを通知すると結構な量のメールが飛んで来るので、下記のように間引きました。

```sh
#!/bin/bash

if [ "$CRM_notify_node" != "$(uname -n)" ]; then
  # 自ノード以外は除外
  exit 0
fi

if [ "$CRM_notify_task" == "monitor" ]; then
  case "$CRM_notify_desc" in
    OK|ok)
      # monitor の OK は除外
      exit 0
      ;;
  esac
fi

env | grep ^CRM_notify | sort |
  mail -s "[$CRM_notify_node] $CRM_notify_rsc $CRM_notify_task $CRM_notify_desc" root
```

このスクリプトを `--external-agent` に設定すれば概ね以下のようなときだけ通知されます。

- リソースの開始/停止（失敗でも成功でも）
- リソースの監視で異常を検出

自ノードのリソース障害しか通知しないため、ノード障害が発生したときにはそのノードで稼働していたリソースの障害は通知されません（通知する `crm_mon` が死んでいるので）。

Active/Standby のアクティブ側のノード障害なら、スタンバイ側のリソースが開始するので、その通知を持ってノード障害を推測することができますが、Clone リソースしか無い、とか、Active/Standby のスタンバイ側の障害、とかだと、この方法だけではノード障害を検出することができません。

ので、`corosync-notifyd` と併用するのが良いかもしれません。

あるいは `crm_mon` を Clone リソースとして設定しているなら `crm_mon` リソースだけはノードに依らず通知してみるとか。

```sh
#!/bin/bash

if [ "$CRM_notify_node" != "$(uname -n)" ]; then
  if [ "$CRM_notify_rsc" != "crm_mon" ]; then
    exit 0
  fi
fi

if [ "$CRM_notify_task" == "monitor" ]; then
  case "$CRM_notify_desc" in
    OK|ok)
      exit 0
      ;;
  esac
fi

env | grep ^CRM_notify | sort |
  mail -s "[$CRM_notify_node] $CRM_notify_rsc $CRM_notify_task $CRM_notify_desc" root
```

これなら、例えば pm01 のノード障害時に、pm02 から、pm01 の `crm_mon` が停止した旨の通知が送られてきます。ただし、pm01 が開始したときには pm01 と pm02 の両方から pm01 の `crm_mon` が開始した旨の通知が送られてきますが。