Zabbix 1.8 Active Checkのデータフローについて

zabbix logoZabbix 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 形式でやりとりする。

zabbix-active-check-flow

1. Requests Check Items

Zabbix Agent が Zabbix Server に Active Check 設定を問い合わせる。

{
   "request":"active checks",
   "host":"<hostname>"
}

hostname は zabbix_agentd.conf のフィールド Hostname で指定したホスト名。

この問い合わせは zabbix_agentd.confRefreshActiveChecks で指定した間隔(秒数)ごとに行う。

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

Leave a comment