elastalertでアプリの異常を自動検知して通知したい

私のチームでは週２回、当番制でサービスのログをチェックして問題がないか確認しています
こんな感じで当番の人に通知して当番をまわしています
![スクリーンショット 2016-02-11 0.54.13.png](https://qiita-image-store.s3.amazonaws.com/0/82441/b957c82f-a317-49fb-0675-cf1b33fb2e22.png)
サービスのエラーログはelasticsearchとkibanaで可視化しているので、確認自体はさほど大変ではないのですが、問題が起きていない場合がほとんどで作業コストの方が大きく感じてしまいます...
そのせいか、最近はみんなの妹への反応が薄いような気もします...

#エラーログの確認を自動化して異常があれば通知する
そんな話をしていたら誰かに「**elastalert**」というツールを教えてもらいました
elastalertは、Elasticsearchのインデックスをモニタリングして設定した条件とマッチした時にalertを通知するためのツールのようです
本家ドキュメント: https://elastalert.readthedocs.org/en/latest/index.html
GitHub: https://github.com/Yelp/elastalert
elastalertでサービスのエラーログの数を監視して、設定した閾値を超えたらslackに通知してみようと思います

#elastalertをインストールする
[本家ドキュメントのインストール手順](https://elastalert.readthedocs.org/en/latest/running_elastalert.html) 通りに作業します
###### git clone してツールをダウンロードする
`$ git clone https://github.com/Yelp/elastalert.git`
###### moduleをインストール
`$ python setup.py install`
`$ pip install -r requirements.txt`

私の環境には既にElasticsearchが導入されており、アプリサーバのエラーログが収集されているので、Elasticsearchの導入やログの収集方法などの説明は割愛します
私の環境にはversion 1.2.1のElasticsearchがインストールされています

#elastalertの設定ファイルを準備する
git cloneしたディレクトリに`config.yaml.example`というファイルがあるはずなので、`config.yaml`という名前にコピーして設定ファイルを準備します
`$ cp config.yaml.example config.yaml`

作成した`config.yaml`に必要な設定を書きます

```yaml:config.yaml
rules_folder: rules # ruleの設定ファイルが配置されているディレクトリ
run_every:          # Elasticsearchにクエリを発行する間隔
  minutes: 1 
es_host: 127.0.0.1  # 監視するElasticsearchのhost
es_port: 9200       # 監視するElasticsearchのport
```


#elastalert用のindexをElasticsearchに作成する
`elastalert-create-index`を実行すれば必要なindexを作成してくれます
`$ elastalert-create-index`
`New index name (Default elastalert_status)`
`Name of existing index to copy (Default None)`
`New index elastalert_status created`
`Done!`
実行すると２回入力を求められます
"New index name": 保存するindexの名前を名前を入力する。defaultでいいなら入力しない
"Name of existing index to copy": すでにindexがある場合、それをコピーして作成するかどうか。コピーする場合はコピー元のindex名を入力する

#ruleを作成する
ruleというのはelastalertで監視するindexの設定や、alertを送信する条件とalertの方法などを書いた設定ファイルです

######ruleを配置するディレクトリを作成する
`config.yaml`のrules_folderに設定した名前でディレクトリを作成します
`$ mkdir rules`

######ruleを作成する
example_rulesというディレクトリにsampleのruleがいくつか配置してあるので、コピーして作成すると良いかもしれません
ruleの設定についてのドキュメント: https://elastalert.readthedocs.org/en/latest/ruletypes.html
私が今回作成したruleは↓です

```yaml:exp_spike.yaml
# rule name
name: exp spike rule
# rule type
type: spike
spike_height: 3
spike_type: "up"
threshold_cur: 50
timeframe:
  minutes: 10
# query key
query_key: "exp-name"
# index
index: exp-log.*
# top count
top_count_keys:
  - "exp-name"
raw_count_keys: False
# filter
filter: []
# alert type
alert:
- "slack"
slack_webhook_url: "https://hooks.slack.com/services/~~~~"
# kibana
use_kibana_dashboard: "exp.dashboard"
kibana_url: "http://xxx.xxx.xxx.xxx"
```

<dl>
  <dt>name</dt>
  <dd>作成したruleの名前です、分かりやすければ何でもいいです</dd>
  <dt>type</dt>
  <dd>ruleのタイプです、ここの設定でalertする条件のロジックが変わります</dd>
  <dd>設定できるtype: https://elastalert.readthedocs.org/en/latest/ruletypes.html#ruletypes</dd>
  <dd>各タイプについて、下の方に軽く説明を書こうと思います</dd>
  <dt>timeframe</dt>
  <dd>選択したtypeのロジックで使用する値を集計する間隔</dd>
  <dd>今回の設定だと、10分間の間に発生したexceptionを集計します</dd>
  <dt>query_key</dt>
  <dd>使用するElasticsearchのindexにあるfield名を設定</dd>
  <dd>設定すると設定したfield名をユニークなkeyとして集計するようになります</dd>
  <dd>sqlでいう'GROUP BY field名'と同じだと思います</dd>
  <dt>index</dt>
  <dd>使用するElasticsearchのindex名を設定</dd>
  <dt>top_count_keys</dt>
  <dd>使用するElasticsearchのindexにあるfield名を設定</dd>
  <dd>設定すると設定したfield名毎にcountした結果をalertのメッセージに付けてくれます</dd>
  <dd>デフォルトで上位5つが出力されますが、変更可能です</dd>
  <dt>raw_count_keys</dt>
  <dd>top_count_keysのオプション設定、trueにすると設定したfield名に.rawをつけてクエリを発行します</dd>
  <dd>デフォルトがtrueなのでfield名.rawのfieldがなければ設定しておくといいと思います</dd>
  <dt>filter</dt>
  <dd>Elasticsearchになげるクエリの結果をフィルターする設定</dd>
  <dd>特定のfield値のときはルールを適用しないとかができます</dd>
  <dd>詳しくは https://elastalert.readthedocs.org/en/latest/recipes/writing_filters.html</dd>
  <dd>フィルターしない場合は[]を設定してください</dd>
  <dt>alert</dt>
  <dd>alertの方法を設定<dd>
  <dd>email, hipchat, slackなどが設定できます<dd>
  <dd>詳しくは https://elastalert.readthedocs.org/en/latest/ruletypes.html#alerts<dd>
  <dt>use_kibana_dashboard</dt>
  <dd>既存のkibanaにあるダッシュボード名を設定<dd>
  <dd>設定すると既存のダッシュボードをテンプレートにして生成したkibanaリンクをalertメッセージに付けてくれます<dd>
  <dd>generate_kibana_linkという設定もありますが、フィルターと併用出来ないみたいなのでこっちの方がオススメです<dd>
</dl>

#作成したruleをテストする

作成したruleは`elastalert-test-rule`というコマンドでテストすることが出来ます
詳しくは https://elastalert.readthedocs.org/en/latest/ruletypes.html#testing-your-rule
実行するとElasticsearchにある過去のindexにruleを適用して、何件alertされたかなどの結果が出力されます
`elastalert-test-rule rules/exp_spike.yaml --alert --days 3`

<dl>
  <dt>--days</dt>
  <dd>過去何日分のデータに対してruleを適用するかを指定</dd>
  <dd>elastalert-test-ruleはデフォルトで過去24h分のindexに対してruleを適用します</dd>
  <dt>--alert</dt>
  <dd>このオプションを付けるとalertに設定した方法で実際にalertを送信します<dd>
  <dd>指定しない場合は、alert件数とメッセージが標準出力されます</dd>
</dl>
テストするruleのyamlにes_hostとes_portの設定がないとエラーになるので、テストするときはruleに設定を追加してください

#elastalertを起動する
`python elastalert/elastalert.py`で起動できます

#elastalertをデーモンとして起動する
本家ドキュメントに'supervisor'というツールを使うと良いとあるので使ってみます
supervisorのドキュメント: http://supervisord.org/

######supervisorのinstall
`easy_install supervisor`

######設定ファイルを準備する
`echo_supervisord_conf`というコマンドを実行すると設定ファイルのsampleが出力されるので
`echo_supervisord_conf > /etc/supervisord.conf`を実行して設定ファイルを準備します

######設定ファイルを編集する
必要な設定を`supervisord.conf`に追記します

```supervisord.conf
[program:elastalert]
command=python /usr/share/elastalert/elastalert/elastalert/elastalert.py
user=hogehoge
autorestart=true
stdout_logfile=/var/log/supervisor/elastalert.log
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=5
stdout_capture_maxbytes=1MB
redirect_stderr=true
```

######supervisorを起動する
`supervisord -c /etc/supervisord.conf`

######elastalertをデーモンとして起動する
`supervisorctl start elastalert`
startの後には`supervisord.conf`の[program:elastalert]で設定した名前を指定してください

#これで自動検知できたはず..！
とりあえず、elastalertの導入は完了しました
いまのところ、エラーログが増えた時にちゃんとalertが来ているので導入して良かったと思っています
↓こんな感じで妹が異常を知らせてくれます
![スクリーンショット 2016-02-18 19.01.02.png](https://qiita-image-store.s3.amazonaws.com/0/82441/b93bc7a4-7e2e-2c4f-7891-300bad7112bd.png)

でも、まだ必要のないalertが来てしまうことがあるので、ruleの設定の調整はしばらく必要そう...

#【補足】設定できるrule type
<dl>
  <dt>Any</dt>
  <dd>どなんな結果にもマッチする</dd>
  <dt>BlackList</dt>
  <dd>ブラックリストに登録した値とマッチしたらalert</dd>
  <dd>- 追加設定項目 -</dd>
  <dd>【必須】</dd>
  <dd>compare_key: ブラックリストと比較するfield名を設定</dd>
  <dd>blacklist: マッチさせたいfield値のリスト</dd>
  <dt>Whitelist</dt>
  <dd>ホワイトリストに登録した値以外とマッチしたらalert</dd>
  <dd>- 追加設定項目 -</dd>
  <dd>【必須】</dd>
  <dd>compare_key: ホワイトリストと比較するfield名を設定</dd>
  <dd>ignore_null: trueにするとnull値を無視する</dd>
  <dd>whitelist: alertさせたくないfield値のリスト</dd>
  <dt>Change</dt>
  <dd>fied値を監視して値が変化したときにalert</dd>
  <dd>- 追加設定項目 -</dd>
  <dd>【必須】</dd>
  <dd>compare_key: 監視するfield値</dd>
  <dd>ignore_null: trueにするとnull値を無視する</dd>
  <dd>query_key: field値を設定する compare_keyの変化を、設定したfield値毎の範囲で判断する 例えばhost毎にcompare_keyの変化を監視したい場合はhostを設定する</dd>
  <dd>【オプション】</dd>
  <dd>timeframe: 比較する期間の最大値を設定できる</dd>
  <dt>Frequency</dt>
  <dd>設定した期間内に発生したイベント数が閾値を超えたらalert</dd>
  <dd>- 追加設定項目 -</dd>
  <dd>【必須】</dd>
  <dd>num_events: alertする閾値</dd>
  <dd>timeframe: イベント数をカウントする間隔</dd>
  <dd>【オプション】</dd>
  <dd>use_count_query: trueにすると、Elasticsearchの'count api'を使用するようになる ドキュメントのデータを使用する必要がない場合はtrueにした方がいい この設定を使うには'doc_type'の設定が必須になる</dd>
  <dd>doc_type: 'use_count_query'と'use_terms_query'を使う場合は設定必須 使用するfieldの_typeを設定する</dd>
  <dd>use_terms_query: trueにするとElasticsearchの'aggregation query'を使用するようになる この設定を使用するには'doc_type'と'query_key'の設定が必須 (私はaggregation queryを使うことのメリットが分かっていません...分かる方教えて下さいmm)</dd>
  <dd>query_key: field名を設定 設定すると設定したfield毎にイベントをカウントする</dd>
  <dd>attach_related: trueにするとalertのトリガーになったイベントの情報が'related_events'という名前でalerterから参照できるようになる...と思います</dd>
  <dt>Spike</dt>
  <dd>設定した間隔でイベント数をカウントして、その結果を直前とその前で比較してx倍になっていたらalert</dd>
  <dd>- 追加設定項目 -</dd>
  <dd>【必須】</dd>
  <dd>spike_height: 何倍になっていたらalertするかを設定</dd>
  <dd>spike_type: alertする条件を、イベント数が増えていた時にするか減った時にするかを設定</dd>
  <dd>timeframe: イベント数をカウントする間隔時間を設定</dd>
  <dd>【オプション】</dd>
  <dd>threshold_ref: 比較するtimeflameのうち、前のカウント結果の最小値条件 設定した値を超えていないとalertしない</dd>
  <dd>threshold_cur: 比較するtimeflameのうち、後のカウント結果の最小値条件 設定した値を超えていないとalertしない</dd>
  <dd>query_key: 'Frequency'と同じ</dd>
  <dd>use_count_query: 'Frequency'と同じ</dd>
  <dd>use_terms_query: 'Frequency'と同じ</dd>
  <dt>Flatline</dt>
  <dd>設定した間隔でイベント数をカウントして、閾値よりもイベント数が少なければalert</dd>
  <dd>- 追加設定項目 -</dd>
  <dd>【必須】</dd>
  <dd>threshold: alertする閾値</dd>
  <dd>timeframe: イベント数をカウントする間隔時間</dd>
  <dd>【オプション】</dd>
  <dd>query_key: 'Frequency'と同じ</dd>
  <dd>use_count_query: 'Frequency'と同じ</dd>
  <dd>use_terms_query: 'Frequency'と同じ</dd>
  <dt>New Term</dt>
  <dd>field値を監視して未確認の値が出現したらalert</dd>
  <dd>- 追加設定項目 -</dd>
  <dd>【必須】</dd>
  <dd>fields: 監視するfield名を設定</dd>
  <dd>【オプション】</dd>
  <dd>terms_window_size: 未確認かを確認するために遡る時間を設定 デフォルトは30日間</dd>
  <dd>use_terms_query: 'Frequency'と同じ</dd>
  <dt>Cardinality</dt>
  <dd>field値の種類数を監視して、種類数が閾値を超えたらalert</dd>
  <dd>- 追加設定項目 -</dd>
  <dd>【必須】</dd>
  <dd>timeframe: 種類数をカウントする間隔時間</dd>
  <dd>cardinality_field: 監視するfield名を設定</dd>
  <dd>max_cardinality: 種類数の最大値を設定 この値を超えたらalert</dd>
  <dd>min_cardinality: 種類数の最小値を設定 この値より少ない場合alert</dd>
  <dd>【オプション】</dd>
  <dd>query_key: 'Frequency'と同じ</dd>
</dl>