2017 年 07 月 05 日
ZabbixのSNMPトラップ受信をFluentdで負荷分散させる!
Zabbix(あるいは MIRACLE ZBX)では、短時間に大量の SNMP トラップが送信される " バースト " が発生した場合に、トラップが欠損することがあります。本ドキュメントでは、Zabbix と Fluentd を組み合わせることによってトラップ欠損を軽減するための方法を紹介します。
概要
Zabbix(あるいは MIRACLE ZBX)では、短時間に大量の SNMP トラップが送信される " バースト " が発生した場合に snmptrapd がトラップを受信しきれない事があります。特に RHEL7.x 系の OS ではトラップを受信する snmptrapd をバックグランドで複数起動する事ができなくなりましたので、複数の snmptrapd インスタンスで SNMP トラップを受信し Zabbix に送信する方法を考えます。
バーストによって SNMP トラップを欠損させる。
検証用に Centos7.2 の仮想マシンを用意し、Zabbix3.0 を構築して SNMP トラップのバーストを受けた時にトラップが欠損するか試してみます。SNMP トラップの受信には SNMPTT ではなく zabbix_trap_receiver.pl を使用しました。
仮想マシン構成
OS : CentOS7.2
CPU コア : 1 core
メモリ:2GB
擬似的にバーストを起こすために下記のスクリプトを作成し、受信側と同等スペックの仮想マシン 3 台から同時に実行します。
[root@localhost ~]# cat snmptrap_burst.sh
### 特定のホストに SNMP トラップ (linkDown) を指定回数送りつけるスクリプト
#!/bin/bash
declare -i i=5000
while [[ $i -gt 0 ]]
do
snmptrap -v 2c -c public < 送信先アドレス > '' .1.3.6.1.6.3.1.1.5.3
i=$((i-1))
done
Zabbix の WEB UI 上で SNMP Trap アイテムを作り、バースト後に DB 内の history_log の中身をカウントしてトラップが欠損していないかを確認します。バースト発生前は下記のように log 型のヒストリが無い状態でバーストを起こします。
MariaDB [zabbix]> select count(id) from history_log;
+-----------+
| count(id) |
+-----------+
| 0 |
+-----------+
1 row in set (0.01 sec)
バースト発生後、十分に時間を開けてから再び DB を確認します。
MariaDB [zabbix]> select count(id) from history_log;
+-----------+
| count(id) |
+-----------+
| 14985 |
+-----------+
1 row in set (0.01 sec)
疑似バーストで送信したトラップ総数は合計 15000 件 (5000 x 3) なので、15 件のトラップが欠損している事がわかります。( 受信側ノード側のトラップ流量はおおよそ 240 件 / 秒ほどでした。)
zabbix_trap_receiver.pl や Zabbix 自体の処理遅延によってトラップの欠損が起きている可能性を考慮して、snmptrapd のトラップ受信時の処理をログ出力のみに絞り出力されるログの行数を確認します。journald や rsyslog にはデフォルトで流量制限があるため、これを無効化してバーストを発生させます。
[root@localhost ~]# wc -l /var/log/snmpd/snmptrap.log
14996 /var/log/snmpd/snmptrap.log
件数は減少したものの、トラップの欠損は発生しました。
バーストの発生によって snmptrapd がトラップを欠損する事が確認できました。これは snmptrapd のバッファ ( 固定値 ) がオーバーフローすることが原因ですので、解決には複数の snmptrapd を用意しバースト時のトラップを負荷分散することが必要となります。
snmptrapd の分散構成を考える。
Zabbix の SNMP トラップ監視では同一ホスト上の snmptrapd が出力したログを監視するため、複数の snmptrapd ノードでトラップを受信する場合、各ノードで受信したトラップ情報を Zabbix サーバーへ登録する仕組みが必要となります。今回は下記図のように各 snmptrapd ノードで受信したトラップをローカルにログ出力させ、そのログを Fluentd によって整形し Zabbix_sender を使ってサーバーへ情報を登録します。
Fluentd はログの転送・集約ツールです。高度なバッファリング機能や豊富なプラグインによって様々な方法でログの取得・整形・送信が可能です。今回は下記の図の処理を行って snmp トラップを整形し、Zabbix sender を使って Zabbix へトラップの情報を送信します。
受信した SNMP トラップを snmptrapd.log に出力するように snmptrapd を設定し、td-agent.conf に以下の設定を行います。
# ログを取得する。snmptrapd は syslog フォーマットで出力するため、フォーマットは syslog でよい。
<source>
@type tail
path /var/log/snmp/snmptrapd.log
pos_file /var/log/td-agent/snmptrapd.log.pos
tag snmp.trap
format syslog
</source>
# 余計なトラップをフィルターする。
<filter snmp.trap>
@type grep
regexp1 message IF-MIB::(linkUp|linkDown)
</filter>
# ログの一部から OID(MIB) を識別できる箇所を切り出し、タグに付与する。
<match snmp.trap>
@type rewrite_tag_filter
rewriterule1 message IF-MIB::linkUp linkUp.${tag}
rewriterule2 message IF-MIB::linkDown linkDown.${tag}
</match>
# 一塊になっていた SNMP トラップの情報を分割してレコードとして記録する。
<filter *.snmp.trap>
@type parser
format /^(?<date>.{19}) (?<host>[^ ]+).+\[(?<from_ip>[^ ]+)\].+\].+\].+\#012(?<timeticks>.+)#011(?<mib>.+)?$/
key_name message
</filter>
# アイテムのキー名としてタグをレコードに付与する。
<filter *.snmp.trap>
@type record_transformer
<record>
item_key ${tag}
</record>
</filter>
# 任意のメッセージをレコードに付与する。
<filter linkUp.snmp.trap>
@type record_transformer
<record>
u_msg "test trap received : linkUp"
</record>
</filter>
<filter linkDown.snmp.trap>
@type record_transformer
<record>
u_msg "test trap received : linkDown"
</record>
</filter>
# Zabbix へトラップを送信するスクリプトを実行する。
<match *.snmp.trap>
@type exec
command bash /etc/td-agent/script/fluent2zbx.sh
buffer_path /var/log/td-agent/exe_buff-l01
format json
time_slice_format %Y-%M\m-%d-%H-%M-%S
time_slice_wait 1s
</match>
Fluentd から Zabbix sender を実行するためのラッパーシェルを作成します。
#!/bin/bash
IFS=$'\n';
RESULT=`bash /etc/td-agent/script/get_zbx_hosts.sh`
while read LINE
do
FROM_IP=`echo $LINE | jq -r .from_ip`
TIMETICKS=`echo $LINE | jq -r .timeticks`
MIB=`echo $LINE | jq -r .mib`
ITEM_KEY=`echo $LINE | jq -r .item_key`
U_MSG=`echo $LINE | jq -r .u_msg`
GET_HOST_NAME=`cat <<- EOS
map(select(.["ip"] == "$FROM_IP"))
EOS`
Z_HOST=`echo $RESULT | jq "${GET_HOST_NAME}" | jq .[].host`
ZBX_SENDER="zabbix_sender -z localhost -s $Z_HOST -k $ITEM_KEY -o \"$U_MSG [$TIMETICKS $MIB]\""
eval ${ZBX_SENDER}
done <$1
get_list_host-ip.sh は Zabbix API を使用して snmp トラップ内の IP アドレスから Zabbix に登録されているホスト名を逆引きします。逆引き用のスクリプトは以下の通りです。
#!/bin/bash
URL='http://localhost/zabbix/api_jsonrpc.php'
ZABBIX_USER='zabbix'
ZABBIX_PASSWORD='zabbix'
function get_token() {
PARAMS=`cat <<- EOS
{
"jsonrpc": "2.0",
"method": "user.login",
"params": {
"user": "${ZABBIX_USER}",
"password": "${ZABBIX_PASSWORD}"
},
"id": 1
}
EOS`
curl -s -H 'Content-Type:application/json-rpc' \
${URL} \
-d "${PARAMS}" | jq -r '.result'
}
function get_hosts() {
PARAMS=`cat <<- EOS
{
"jsonrpc": "2.0",
"method": "host.get",
"params": {
"output": "extend",
"selectInterfaces": "extend",
"monitored_hosts":"True"
},
"id": 1,
"auth": "${TOKEN}"
}
EOS`
curl -s -H 'Content-Type:application/json-rpc' \
${URL} \
-d "${PARAMS}" | jq -r '.result | map({host: .host, ip: .interfaces[].ip})'
}
TOKEN=$(get_token)
echo $(get_hosts)
上記の設定ファイルを作成・配置し、td-agent を再起動したら続いて Zabbix 側の設定を行います。
Zabbix の Web インタフェースから SNMP トラップの送信元 IP を設定したホストを作成します。
続いて、作成したホストに fluentd から情報を受信する為の Zabbix トラッパーアイテムを作成します。Fluentd で設定したタグをアイテムキーに設定し、データ型はログを指定します。
<図はいる Web インターフェイス Item>
ホストマシンからトラップを送信して、トラップが受信できるか確認します。
Zabbix + Fluentd の構成に、バーストを発生させる。
再び、バーストを発生させ Fluentd を使用した構成でログの欠損が発生するか確認します。
スクリプト実行後のログファイル
[root@localhost ~]# wc -l /var/log/snmp/snmptrapd.log
0 /var/log/snmp/snmptrapd.log
スクリプト実行前のデータベース
MariaDB [zabbix]> select id from history_log;
Empty set (0.00 sec)
スクリプト実行後のログファイル
wc -l /var/log/snmp/snmptrapd.log
15000 /var/log/snmp/snmptrapd.log
スクリプト実行後のデータベース
MariaDB [zabbix]> select id from history_log;
15000 rows in set (0.00 sec)
snmptrapd を分散させた為、snmp トラップの欠損が発生していないことが確認できました。
まとめ
snmptrapd は高機能である反面、単体ではバースト発生時の高負荷状態下ではトラップの欠損といった問題が発生しますが、Zabbix の標準的な snmp トラップ監視では snmptrapd と zabbix server が 1 対 1 で紐づく構成であるため、柔軟な分散が難しいです。今回紹介した構成であれば、snmptrapd と zabbix server を切り離して分散構成を組むことが出来るため snmp トラップ流量増加やバーストが予想される環境であっても柔軟に受信負荷を分散することが可能となります。
注意事項
- 本ドキュメントの内容は、予告なしに変更される場合があります。
- 本ドキュメントは、限られた評価環境における検証結果をもとに作成しており、全ての環境での動作を保証するものではありません。
- 本ドキュメントの内容に基づき、導入、設定、運用を行なったことにより損害が生じた場合でも、当社はその損害についての責任を負いません。あくまでお客さまのご判断にてご使用ください。