Zabbix APIで監視データを取得して異常値分析をやってみる

はじめに

私の所属している部署では、主に法人のお客様のシステム監視・運用を24H365Dの体制で実施しています。 運用部隊にとっては、日々の運用業務を高度かつシンプルにしていくことが永遠の課題です。常にチームの業務を振り返り、どこか効率化できることはないかと模索しています。

数ある業務の中で今回は「監視データの異常分析」に注目します。 ある程度大きなシステムを運用していると、キャパシティ管理や障害の予兆キャッチの観点から、「ある期間の監視情報を調査してトピックを抜き出す」という作業が定期的に必要になります。 基本は人が監視データとにらめっこしながら変化点がないか調査するのですが、対象も多く、かなりの手間を要する作業です。 これをもっと手軽に実行できないかと思い、軽い検証をやってみました。

検証内容

検証には監視ツールと分析ツールが必要です。今回の道具としては、これらを使います。

zabbix

多くのプロジェクトで使われている、定番の監視ツールです。NTTコム ソリューションズが日本でのプレミアムパートナーとなっています。過去にzabbixを利用した案件で、必死でトラブルシュートした結果が少なからず貢献しているそうで、個人的に思い出深いソフトウェアです。 今回はバージョン3.0を使います。

twitter-anomalydetection

Twitter社が2年ほど前に公開したRのパッケージです。時系列と組み合わされた数値データから、異常値を検出します。

今回はpythonで

  • Zabbixからデータ取得
  • データ加工
  • Rに渡して分析させる

という一連の処理を記述してみたいと思います。

処理を書く

まずはzabbixから監視情報を取得します。zabbixにはJSONRPC準拠のAPIが実装されており、 JSON形式のデータを含んだHTTPリクエストを投げることで監視データを取得することができます。 JSONを生で書き下すのは面倒なので、こちらのライブラリを利用しました。

py-zabbix

pipで導入するときの名称は「py-zabbix」となります。pyzabbixとすると別のライブラリが入ります。

データ抽出のためのサンプルコードは下記のようになります。

import datetime
import time
from pyzabbix import ZabbixAPI
import pandas as pd

# Network, Account
zabbix_server = 'http://zabbix-host/zabbix'
zabbix_user = "*****"
zabbix_password = "*****"

# Local time
timefrom = "YYYY-MM-DD hh:mm:ss" # time from
timetill = "YYYY-MM-DD hh:mm:ss" # time till

# Local time -> Unix time
timefrom_unix = int(time.mktime(datetime.datetime.strptime(timefrom, "%Y-%m-%d %H:%M:%S").timetuple()))
timetill_unix = int(time.mktime(datetime.datetime.strptime(timetill, "%Y-%m-%d %H:%M:%S").timetuple()))

# Get token
z = ZabbixAPI(zabbix_server, user=zabbix_user, password=zabbix_password)

# Get resource trend on server
trend = z.trend.get(
    itemids="xxxxx",
    sortfield="clock",
    sortorder="ASC",
    time_from=timefrom_unix,
    time_till=timetill_unix,
    output=["clock", "value_avg"]
    )

# Store the trend data in pandas dataframe
trend_data = pd.DataFrame(trend)
trend_data['clock'] = pd.to_datetime(trend_data['clock'], unit='s') # Unixtime -> Datetime
trend_data['value_avg'] = trend_data['value_avg'].astype(int)

# Print CSV data
trend_csv = pd.DataFrame.to_csv(trend_data, columns=["clock", "value_avg"], index=True)
print(trend_csv)

大まかには、認証してトークンを取得→セッション用のインスタンス生成→特定アイテムのデータ取得、という流れで処理を進めています。 取れるデータやその形式については、公式のマニュアルページに詳細があります。

18. API [Zabbix Documentation 3.0]

コードの中に登場しているtrend(トレンド)というのは、zabbix側で1時間ごとの取得値を平均化してDBに格納しているデータです。 値のスパイクなどの異常検出という観点ではhistory.getで生の取得値を参照したほうがよいと思いますが、zabbix側での生値の保存期間および、分析処理時間の都合(後述)でトレンド値を利用しています。

本来はR連携のライブラリを使ってスクリプト単体で処理を完結したかったのですが、R側に時刻データを渡すときに型をうまく読ませることができず、仕方なく一度CSVに吐かせています。完全自動化するにあたってはもう少し調べなければならないです。

さて、一度CSVに吐かせたデータをRで分析します。試行錯誤する段階ではRStudioを使いました。

結果

手元の監視データで実行した結果は下図のような感じです。図中で青い丸でプロットされている点が、変化点として検出されています。 この例では、月末にかけてCPU使用率が急増したタイミングをきれいに検出できています。

上図のように単純なスパイクなどであればうまく拾えるのですが、たとえば「今まで平坦だったグラフが突然右肩上がりになる」といった変化に対しては難しいようです(実務でよく泣かされるパターンなのですが)。 そういった事象については別のアルゴリズムを援用したほうがよいのかもしれません。

また、アルゴリズムに与えるパラメータによっては、予期する異常点が取れなかったり、余計な異常点が取れてしまったりします。 継続的に運用しようとすると、誰がどのように分析対象の特性を見てチューニングしていくかが問題になります。

処理時間も課題です。今回はAWSのt2.standard上で処理を走らせたのですが、データ数が1000個程度でも処理に5秒程度要しています。 中のアルゴリズムの計算量評価まではできていないのですが、たとえば数分間隔で取得しているデータに対して1ヶ月間の傾向分析をしようとするだけでもかなりの処理時間が予想されます。 前節で、生の取得値を使わずトレンド値を利用することにした理由の1つです。

終わりに

今回の例については、実用に持っていこうとするといくつかハードルがありそうです。 しかし、運用の現場における手作業はほかにも数多くあります。 公開されているツールを組み合わせるだけで、コスト・労力をあまりかけずにアイデアを試せる時代なので、 現場ならではの目線を強みにしつつ、色々なことにトライしながら業務をもっとシンプルにしていければと思っています。

© NTT Communications Corporation 2014