読者です 読者をやめる 読者になる 読者になる

Cacti でアラートを thold プラグインを使わずにやるアイデア(中途半端)

Cacti の thold プラグインでアラートを仕込むのがめんどくさくて仕方なかったので、もういっそのこと直接 rra ファイルを見てアラートを出すスクリプトを自前で作れば良いんじゃないかと思ったときのメモ。

Cacti のバージョンは 0.8.8h です。

Prometheus に入門したので中途半端なままで放置。


rra ファイルのパスは cacti の DB から下記のような SQL で取ります。

SELECT
  host.id as host_id,
  host.description as host_description,
  data_template.name as data_template_name,
  data_template_rrd.data_source_name as data_source_name,
  poller_item.rrd_path as rrd_path
FROM data_local
INNER JOIN host
  ON data_local.host_id = host.id
INNER JOIN data_template
  ON data_local.data_template_id = data_template.id
INNER JOIN data_template_rrd
  ON  data_local.id = data_template_rrd.local_data_id
  AND data_local.data_template_id = data_template_rrd.data_template_id
INNER JOIN poller_item
    ON data_local.id = poller_item.local_data_id

メトリクスの値は rrdtool で下記のように取れます。

rrdtool fetch hoge_prod_ap01_load_1min_100.rrd MAX \
    -s $(( $(date +%s) - 3600 )) -e $(( $(date +%s) - 300 ))
                      load_1min

1490619600: 7.0000000000e-02
1490619900: 8.6600000000e-02
1490620200: 6.4900000000e-02
1490620500: 2.6266666667e-02
1490620800: 3.3333333333e-03
1490621100: 6.6933333333e-02
1490621400: 7.1633333333e-02
1490621700: 8.6400000000e-02
1490622000: 9.0000000000e-02
1490622300: 1.5533333333e-01
1490622600: 5.7533333333e-02
1490622900: 3.0000000000e-02

あるいは xport なら複数の rra から JSON で取得できるので、こっちのが良さそう。

rrdtool xport --json \
    -s $(( $(date +%s) - 3600 )) -e $(( $(date +%s) - 300 )) \
    DEF:mem_buffers=hoge_prod_ap01_mem_buffers_101.rrd:mem_buffers:MAX \
    DEF:mem_cache=hoge_prod_ap01_mem_cache_102.rrd:mem_cache:MAX \
    DEF:mem_free=hoge_prod_ap01_mem_free_103.rrd:mem_free:MAX \
    DEF:mem_total=hoge_prod_ap01_mem_total_104.rrd:mem_total:MAX \
    XPORT:mem_buffers:mem_buffers \
    XPORT:mem_cache:mem_cache \
    XPORT:mem_free:mem_free \
    XPORT:mem_total:mem_total
{ about: 'RRDtool xport JSON output',
  meta: {
    "start": 1490619900,
    "step": 300,
    "end": 1490619900,
    "legend": [
      'mem_buffers',
      'mem_cache',
      'mem_free',
      'mem_total'
          ]
     },
  "data": [
    [ 8.5200000000e+02, 1.9999189200e+06, 7.8921592000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0017213467e+06, 7.8882005333e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0034699067e+06, 7.8495132000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0018100000e+06, 7.8899000000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0028664133e+06, 7.8575062667e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0050689867e+06, 7.8603305333e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0068675200e+06, 7.8260720000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0052690000e+06, 7.8560060000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0062675333e+06, 7.8268226667e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0079922267e+06, 7.8281285333e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0063401600e+06, 7.8266224000e+05, 3.8821600000e+06 ],
    [ 8.5200000000e+02, 2.0078709467e+06, 7.8317032000e+05, 3.8821600000e+06  ]
  ]
}

がしかし、これよく見たら JSON としては invalid ですね。。。--json を指定しなければ XML なのでそっちの方が良いかも。

あとは・・・

  • ホスト名は host.description に対して正規表現でパターンで指定する
    • それっぽく description が設定されている前提
    • <service>-<env>-<role><num> みたいに
  • data_source_name だけではメトリクスを一意に特定できないことがあるのでデータテンプレートも指定する
    • データテンプレートは名前でしか識別できなさそうなのでエイリアスを正規表現で指定する
    • 実際の閾値設定ではエイリアス名で指定する
    • data_source_name だけで一意になるなら監視設定ではデータテンプレートのエイリアスは省略しても良い
  • メトリクスをコールバックで加工した値に閾値を設定する

例えば下記のように設定する。

return [
    // データテンプレートのエイリアス
    // 複数のデータテンプレートがマッチするがデータソース名と足して一意になる前提
    'alias_data_template' => [
        'memory' => '^ucd/net - Memory -',
    ],
    'alert' => [
        [
            // ホスト名のパターン
            'host' => '^hoge-prod-ap\d+$',

            // データテンプレートのエイリアス+データソースのリスト
            'ds' => [
                'memory:mem_total',
                'memory:mem_buffers',
                'memory:mem_cache',
                'memory:mem_free',
            ],
            // チェックする値、引数の並びは ds に対応
            'expr' => function ($total, $buffer, $cache, $free) {
                return ($total - $buffer - $cache - $free) / $total * 100;
            },
            // 閾値
            'threshold' => [
                'WARNING' => 80,
                'CRITICAL' => 90,
            ],
            // 通知先
            'notify' => [
                'WARNING' => ['ore@example.com', 'are@example.com'],
                'CRITICAL' => 'sore@example.com',
            ],
            // 通知のメッセージ
            'message' => '{severity} [{hostname}] memory usage too high ... now:{value} > {threshold}',
        ],
    ],
];

なにかしらスクリプトを実行することで、DB を舐めてホスト名やデータテンプレート名のパターンとデータソースに基づく rra のパスをキャッシュしておくことで、定期的な閾値のチェック処理を軽減する。

例えば下記のようなキャッシュになるだろうか。

return [
    'host_ids' => [
        // パターンに対するホストIDの一覧
        '^hoge-prod-ap\d+$' => [1, 2, 3, 4],
    ],
    'rra_paths' => [
        // ホストID => データテンプレート+データソース => RRA
        1 => [
            'memory:mem_total'   => '/path/to/rra/are_prod_ap01_mem_total_1.rra',
            'memory:mem_buffers' => '/path/to/rra/are_prod_ap01_mem_buffers_2.rra',
            'memory:mem_cache'   => '/path/to/rra/are_prod_ap01_mem_cache_3.rra',
            'memory:mem_free'    => '/path/to/rra/are_prod_ap01_mem_free_4.rra',
        ],
    ],

];

Prometheus を使ってみた感じ、さくさく設定できてもうこれでいいかなと思ったので、この案はお蔵入り。