Zabbix 1.8 には Passive Check と Active Check の2種類の監視があり
- Passive Check はディスク容量や PING 疎通などを Zabbix Server から Zabbix Agent に問い合わせてデータを取得
- Active Check は Zabbix Agent が Zabbix Server に送信内容を問い合わせ、対象データを Zabbix Server に送信
という違いがある。
Passive/Active Check ともに次のページに仕様はまとめられている。
https://www.zabbix.com/documentation/1.8/protocols/agent
Passive Check は単純だからいいとして、複雑な Acive Check もザクッとしか書かれていない。
Windows のイベントをアクティブ監視しているシステムで、Zabbix Agent から Zabbix Server に送信されないという現象に出くわし、深追いした内容をメモ。
事前準備
Windows 向けにイベントログを抽出する Item を追加。
Zabbix Agent のログ出力レベルを DEBUG にすると、実際に送受信する JSON を確認できる。
Active Check のデータフロー
基本となるプロトコルは Zabbix Agent と同じ <HEADER><DATALEN><DTA> という形を取る。 Active Check では DATA
部分を JSON 形式でやりとりする。
1. Requests Check Items
Zabbix Agent が Zabbix Server に Active Check 設定を問い合わせる。
{ "request":"active checks", "host":"<hostname>" }
hostname は zabbix_agentd.conf
のフィールド Hostname
で指定したホスト名。
この問い合わせは zabbix_agentd.conf
の RefreshActiveChecks
で指定した間隔(秒数)ごとに行う。
2. Responds With Check Items
items テーブルに ホストで問い合わせてアクティブチェックの一覧を取得
{"data": [{"delay": "600", "lastlogsize": "1714", "key": "eventlog[Application,,Info,]", "mtime": "0"}], "response": "success"}
Agentの起動直後だったり、新規に追加された条件の場合、Agent はレスポンス内容をそのままオンメモリで管理する監視一覧に追加。キーがAgentに登録済みの場合は特に何もせず。
3. Check For New Events
取得した監視内容 key をもとに新しい送信対象データを確認。
ここで肝になるのが lastlogsize。
ログ系データを送信する場合、毎回全データを送信するとあまりにも無駄が多い。
差分データだけを Zabbix Server に通知できるようにするために lastlogsize をオフセットとして利用している。
初めてアイテム追加したときは lastlogsize には 0 が設定される。
lastlogsize をもとに、送信対象データがあるか確認。
このロジックはややこしいので後述。
4. Sends New Events
送信対象データがあれば、Zabbix Manager に送信
{ "request":"agent data", "data":[ { "host":"10.0.1.2", "key":"eventlog[Application,,Info]", "value":"...", "lastlogsize":1715, "timestamp":1380801445, "source":"WgaSetup", "severity":1, "eventid":1006, "clock":1380802912}, { "host":"10.0.1.2", "key":"eventlog[Application,,Info]", "value":"...", "lastlogsize":1716, "timestamp":1380801445, "source":"WgaSetup", "severity":1, "eventid":1002, "clock":1380802912}, ] }
Zabbix Agent は (Zabbix Server, key) をキーにした lastlogsize を更新。
上の例では key が “eventlog[Application,,Info]”、 lastlogsize が一番最後の 1716 に対応する。
5. Responds With Process Status
Zabbix Server が history_log テーブルに取り込み、item.lastlogsize を更新。以降、Active Item の問い合わせがあった場合は、更新された lastlogsize で返す。データの取り込み結果を Agent に通知して完了
{ "response":"success", "info":"Processed 2 Failed 0 Total 2 Seconds spent 0.002070" }
新規送信対象データの判定ロジック
一番のややこしいのはオフセット lastlogsize をもとにしてどのようにして新規データを判定するのか、ということ。
自分が利用している Windows イベント取得を例に説明。Linux のログファイルなどでも、読みかえれば同様の処理をやっているはず。
lastlogsize が更新されるタイミング
ポイントになるのは
- Zabbix Agent がオンメモリで管理している lastlogsize
- Zabbix Server のデータベースで管理している items.lastlogsize
Zabbix Agent の lastlogsize の初期値は NULL。 手順 #2 のレスポンスをもとに Agent に未登録の Item が lastlogsize とともに更新されたり、 #4 の Server へのイベント送信時に、最新の値に更新される。
Zabbix Sserver の lastlogsize は Item 登録時の初期値は 0。Agent からイベントを受信するたびに、最新の値に更新する。
あとは、Agent サイドで lastlogsize より大きい ID のイベントがあった場合、無条件で送信。
上記に対応するソースコードは src/zabbix_agent/eventlog.c
。
以下では lastlogsize がかわる主要なケースで判定条件を確認
監視開始時
監視開始時は、Server.lastlogsize = 0, Agent.lastlogsize = Null
Agent が Server に問い合わせて監視条件の一覧を取得。取得後は各Item の lastlogsize = 0。
その後 Agent のイベント一覧から Key にマッチするものがすべて Server に送信される。
送信した最新のイベントIDが100とすると、Agent Server で同期完了後 Server.lastlogsize = Agent.lastlogsize = 100 となる。
Agent で新規イベント発生時
Server.lastlogsize = Agent.lastlogsize = 100 の時に、key にマッチする新しいイベント(ID=105)が発生したとする
105 > Agent.lastlogsize なので、このイベントだけが送信される。
送信完了後、 Server.lastlogsize = Agent.lastlogsize = 105 となる。
Agent デーモンの再起動時
Zabbix Agent を再起動すると、オンメモリの lastlogsize カウンターはクリアされる。
lastlogsize = 105 まで Server に通知済みであったとしても、再起動直後は
- Server.lastlogsize = 105
- Agent.lastlogsize = NULL
となる。
“1. Requests Check Items” で Server に抽出条件を問い合わせ、Agent 側が NULL のため、そのレスポンスをもとに Agent の lastlogsize が更新され、 Server.lastlogsize = Agent.lastlogsize = 105 となる。
Server の Item が物理削除された!
自分が出くわしたのがこのケース。
監視 Item のステータス(items.status)を無効にするのはいいとして、削除すると該当レコードが物理削除されるため、lastlogsize が失われてしまう。
同じホスト・キーの条件で再登録すると、items.lastlogsize は初期化されて 0 となる。サーバでは同じ条件が異なる items.itemid で登録されるが、エージェントから見ると、(サーバー、抽出条件) のキーは同じなので、Server でアイテムが再登録されたことは気づかない。
Server に監視条件を問い合わせても、Agent 側は新規にItem登録されない。
Item の再登録直後は
- Server.lastlogsize = 0
- Agent.lastlogsize = 105
Agent が Server に送信済みと思っている過去のイベントは送信されず、Agent 側で新規に発生したイベントのみ Server に通知される。
過去分が送信されなくても困らないケースが多そうだけど、残念なビジネス要件があったりするとグチグチ言われてしまう。
過去のイベントも送信するには、Agent を再起動すればよい。
Server.lastlogsize = 0、Agent.lastlogsize = NULL の状態になるので、監視開始時と同じ状態になり、過去分も送信され、送信完了後は Server.lastlogsize = Agent.lastlogsize = 105 のような状態になる。
イベントIDのオーバーフロー
Zabbix JP のフォーラムで他のかたが発見していた潜在するバグ。
http://www.zabbix.jp/node/1121
Windows の イベント はリングバッファになっており、1 からシーケンシャルに採番され、ULONG_MAX に到達すると、0 から再採番される。また、最も古いイベントIDを取得し(GetOldestEventLogRecord)、イベント数(GetNumberOfEventLogRecords)を足して、最新のイベントIDを求める仕様になっている。
The RecordNumber member of EVENTLOGRECORD contains the record number for the event log record. The very first record written to an event log is record number 1, and other records are numbered sequentially. If the record number reaches ULONG_MAX, the next record number will be 0, not 1; however, you use zero to seek to the record. If the Retention registry value is set to zero, the event records are overwritten when the maximum log size is reached. Therefore, the oldest record in an event log may not be record number 1. To identify the oldest record in the log, call the GetOldestEventLogRecord function. You can then call the GetNumberOfEventLogRecords function and add the returned value to the oldest record number to determine the newest record.
http://msdn.microsoft.com/en-us/library/aa363660(v=vs.85).aspx
Zabbix Agent は ULONG_MAX の循環を考慮せずに単純な足し算だけをして最新のイベントIDを求め、lastlogsize+1 から 最新のイベントIDまでインデックス指定でイベントを取得しているっぽく、 ULONG_MAX を超えたら残念なことになりそう。
まとめ
Zabbix Agent のログ出力は DebugLevel=4 にし、LogFileSize もできるだけ大きな値を設定し、できる限りログ出力すべし。
補足:PowerShell からイベント情報の取得
PowerShell で全Applicationイベントを取得
PS C:\> $query = "Select * From Win32_NTLogEvent Where Logfile = 'Application' AND RecordNumber = 1" PS C:\> Get-WmiObject -Query $query
Win32_NTLogEvent のフィールドで lastlogsize に相当するのは EventIdentifier
PowerShell で Application イベントのイベント数を取得
PS C:\> $s = "Select NumberOfRecords from Win32_NTEventLogFile Where LogFileName = 'Application'" PS C:\> Get-WmiObject -Query $s __GENUS : 2 __CLASS : Win32_NTEventlogFile __SUPERCLASS : __DYNASTY : __RELPATH : __PROPERTY_COUNT : 1 __DERIVATION : {} __SERVER : __NAMESPACE : __PATH : NumberOfRecords : 17965