複数の zabbix-agent から取得した値を集約する zabbix-aggregate-agent を書いた

Immutableなインフラがなんやかんやと喧しい今日この頃ですが、インスタンスが頻繁に増えたり減ったりすると、監視サービスで継続的な値を追うのが難しくなるよね、という問題を最近感じています。

サービス全体で複数のホストの合計値を取得しておくことで使用量の推移を見たいような値、具体的には以下のようなものです。

  • 外向けのネットワークトラフィック
  • クライアントと永続接続する daemon の持っているコネクション数
  • ジョブの処理数
  • 複数のMySQL slaveが処理した参照クエリ数

ということで、zabbix-agent への proxy として動作する、複数 agent からの数値を合計して返すようなものがあったら捗るんじゃないかと書いてみました。

公式ページ fujiwara.github.io/zabbix-aggregate-agent

リポジトリ zabbix-aggregate-agent Github

アーキテクチャ

[zabbix-server(or zabbix-proxy)]
    |           ^
    | (1)key    | (6)aggregated value
    |           |
    v           |
 [zabbix-aggregate-agent] <--- (2) list of zabbix-agents from static list or file or command output
    |           ^
    | (3)key    | (4)values(*)
    v           |
  [zabbix-agents(*)]

1. zabbix server(またはproxy) が aggregate-agent に値を要求
2. aggregate-agente はファイルやコマンドから値を集約する対象 agent を解決

    • 解決には、設定ファイルに定義した静的リスト、行区切りのテキストファイル、標準出力に対象を出力するコマンド、が利用できます

3. aggragate-agentは解決した複数の agent に値を並列に要求
4. それぞれの agent は自分の持っている値を返す
5. aggregate-agent が値を集約する
6. server(proxy) に値を返す

Zabbixには「Aggregate checks」という仕組みがあるけど…?

Aggregate checks - Zabbix

それぞれ、利点欠点があります。

Aggregate checks

Pros.

  • 標準機能です

Cons.

  • 集約したいアイテムにそれぞれについて、アイテムとグラフとトリガの定義を行う必要があります
    • Web UIでぽちぽちぽちぽち……
  • 集約したい zabbix-agent はすべて zabbix-server に登録する必要があります

zabbix-agentの自動登録によって登録することは簡単ですが、不要になった情報を別途削除するために別途 Zabbix API を利用する処理を書く必要があります。

zabbix-aggregate-agent

Pros.

  • アイテム、グラフ、トリガの定義は既に agent 向けに定義されたものをそのまま利用できます
    • zabbix-serverから見ると agent そのものに見える
  • 集約される zabbix-agent は、必ずしも zabbix-server 上に定義されている必要はありません
    • たとえばオートスケールで起動して短時間で落ちるような一時的なインスタンスについて aggregate-agent が存在を認知(APIなどで)できれば、zabbix-serverへの登録は不要です

Cons.

  • zabbix-aggregate-agentのdeamonã‚’1プロセス、起動する必要があります

使用方法

Goで書いてあるので、公式ページからバイナリをダウンロードするだけで動作可能です。

$ zabbix-aggregate-agent --config /path/to/config.toml

設定ファイル例は以下のような感じです。

複数の aggregate-agent インスタンスを1プロセスで起動可能です。

zabbix-serverには集約用のホスト定義を行い、Listenするアドレスを zabbix-agent のアドレスとして指定します。

[[agent]]
# Name: 識別子(ログに出力される)
Name = "web_servers"
# Listen: aggregate-agentが Listen する address (default "127.0.0.1:10052")
Listen = "0.0.0.0:10052"
# List : 静的リストによる解決
List = [ "web01:10050", "web02:10050" ]

[[agent]]
Name  = "app_servers"
Listen = "0.0.0.0:10053"
# ListFile: 改行区切りのテキストファイルによる解決
ListFile = "/path/to/agent.list"

[[agent]]
Name = "db_servers"
Listen = "0.0.0.0:10054"
# ListCommand: 標準出力に集約対象を改行区切りで出力するコマンドを指定
ListCommand = [ "/path/to/generate_list.sh", "arg1", "args2" ]
# CacheExpires : コマンド実行結果のキャッシュ保持期間
CacheExpires = 300

で実際どうなの

Aggregate checksでは、

  • すべての監視対象を zabbix-server に登録する
  • 集約対象ごとにホストグループを定義する

必要があるので、一時的に増えたり減ったりするすべてのホストをかっちりzabbix-serverで登録、削除する仕組みがあればこちらを使うほうがよいと思います。

aggregate-agentはファイルやコマンドの実行結果で集約対象を定義できるため、

  • zabbix以外のAPIでホストグループ的なものが解決できる

(つまりzabbix-serverにホストメタデータの管理を全部寄せきれていない場合) には便利に使えるかと思います。

あとは既存の監視対象アイテム、グラフ、トリガの定義をそのまま流用できるのが個人的には嬉しいところで、Web UIでぽちぽち定義をしていく必要がありません。

書いてみて

複数の agent にリクエストを飛ばして受けるところ、複数の aggregate-agent インスタンスを立てるところは Goroutine で簡単に並列化できたので、大変楽ですね。PerlでAnyEventで非同期処理とか、複数の子プロセスをforkしてプロセス間通信するのとは比較する気も起きないぐらいです。