Machine Learningでの異常検知をトリガーにWatcherで通知するまで


# はじめに
**この記事は[Elastic stack (Elasticsearch) Advent Calendar 2018](https://qiita.com/advent-calendar/2018/elasticsearch)の21日目です。**

流行りのMachine LearningがElastic StackのFeaturesでも簡単にできます。
異常の検知だけではなく、その異常に対して対処を行うまでが一連の動作だと思うので、異常を検知した後に通知をしたり、他のシステムと連携させて異常の原因を解決できてこそ、良いシステムになるのではないでしょうか。
そこで今回はElastic Machine Learningで異常検知した結果をトリガーにしてWatcherを使って通知するところまでを書いていきます。
※ここで紹介している方法が最適かどうかは別として、まずはMachine LearningとWatcherを連携させてみましょう！という趣旨です。

以前ElasticのMachine Learningについて書いた記事は[こちら](https://www.netone.co.jp/report/column/column1/20180223.html) にあるので、基本的な部分が知りたい方はあわせてご覧ください。Elastic Machine LearningにはForecastの機能もあり、気になる方は[こちら](https://www.slideshare.net/YuKatano/jeug1lt)もどうぞ。

# 環境
elasticsearch: 6.5.1
kibana: 6.5.1

Machine LearningはPlatinum、WatcherはGoldの機能ですが、30日間はトライアルでPlatinum相当の機能を使えるので、今回はトライアル環境で構築しました。
また、最近のバージョンではサンプルデータが準備されているため、ユーザーがデータを準備しなくても機能を試すことができます。
今回使ったデータもサンプルデータなので、elasticsearchとkibanaの環境さえあれば、今回の記事の内容を試すことができます。

# データの準備
バージョン6.5.1では3つのサンプルデータが準備されています。

![add_data_to_kibana_2.png](https://qiita-image-store.s3.amazonaws.com/0/152168/7257de0d-9a5a-1c34-4b58-638de018411b.png "add_data_to_kibana_2.png")

- eCommerce orders
- flight data
- web logs


kibanaの画面を数クリックするだけで、データの投入が完了します。
今回は`web logs`を使っていきます。

投入したデータはDiscoverタブで確認することができます。
今回投入したデータは、`kibana_sample_data_logs`というIndex Pattern名で保存されています。

![kibana_sample_data_logs.png](https://qiita-image-store.s3.amazonaws.com/0/152168/793e01a5-8310-9006-a9fb-f87ae1ce95f8.png "kibana_sample_data_logs.png")

# Machine Learning

## Machine Learningの設定
では実際にMachine Learningで異常検知を試します。

### 余談①
いきなり余談ですが…
Machine Learningを行う際にはどのようなデータが含まれているのか、全体を把握することも重要になります。
Elastic Machine Learningには`Data Visualizer`という機能があり、これを使うとデータを俯瞰して見ることができます。

![DV_1.png](https://qiita-image-store.s3.amazonaws.com/0/152168/ce5a8984-efe9-c81a-d4dc-88eee692cadb.png "DV_1.png")

## Machine Learningの実施

今回は以下のような設定のもと、Machine Learningを行いました。

- Index Pattern: kibana_sample_data_logs
- Job: Single Metric
- Aggregation: Count
- Field: -
- Backet span: 1h
- Name(job_id): count_logs

![ml_job.png](https://qiita-image-store.s3.amazonaws.com/0/152168/dd18f64a-9878-ed6f-cf8f-b28c0872125f.png "ml_job.png")

今回はログの中身を見ているのではなく、ログが入ってきた数に注目してMachine Learningを行なっています。
今回は静的なデータを扱っているため、このままView Resultsに移りますが、リアルタイム異常検知をしたい場合は`Continue job in real-time`を使って、リアルタイムに送られてくるデータをMachine Learningにかけ続けることになります。

### 余談②
Backet Spanを今回は1hに設定しましたが、自分が検知したいものをきちんと検知するために、Backet Spanの設定は非常に重要です。
Backet Spanを短く設定すればよりリアルな数値を扱うことになり、長く設定すれば平滑化された数値を扱うことになります。
突発的な数値の変化を検知したいのであればBacket Spanは短く設定すべきですが、その分計算量も増えるので、マシンリソースと相談になるかもしれません。
※Machine Learning専用ノードというものもあり、本番環境でMachine Learningを行う際はこの専用ノードを使うことを推奨しているようです。

### 余談③
Machine Learningを行う際、事前にValidate jobをクリックすることで、設定が正しく行われているか検証することができます。
例えば、機械学習を行うにはある程度のデータ量が必要になりますが、参照するデータの期間を短く設定してValidate jobをクリックすると、以下のようなWariningが表示されます。

![validate_warn.png](https://qiita-image-store.s3.amazonaws.com/0/152168/8f56518b-690d-6f4b-4d9c-aa17398cbef6.png "validate_warn.png")

この内容を見ると、少なくとも2時間以上、25回のBucket Spanが含まれることを推奨しているようです。
たしかにデータ数は多いものの、1時間程度のデータでMachine Learningをかけると、正しく結果が表示されませんでした。

## 異常の確認
Single Metric ViewerでMachine Learningの結果を詳細に見ることができます。

![smv_3.png](https://qiita-image-store.s3.amazonaws.com/0/152168/3a88a437-8040-369b-f2d8-1b28c298063b.png "smv_3.png")

今回対象にしたデータはある程度周期性を持つデータであり、だいたい3周期分くらい過ぎたあとから予測値（うすい水色の値）が落ち着いてきています。
表示している範囲にはMachine Learningが予測した値よりもログの数が多く、`anomaly_score`が64を示しているデータがあることがわかります。
ではこの結果をトリガーにするような、Watcherの設定を行なっていきます。

# Watcher
Watcherは大きく2通りの方法で設定することができます。

- Create threshold alert
	- ガイドに従い、Watchの名前やIndex名を指定していくと、スクリプトを書かなくてもWatcherの設定を行うことができます。
	- ここではActionも設定することができ、E-mail、Logging、Slackを連携先に指定することができます。
- Create advanced watch
	- threshold alertでは設定できない、細かい設定を行う際にはこちらで行います。

今回は`advanced watch`で設定を行います。

## Machine Learningの結果はどこに格納されているか
Watcherは

- trigger
- input
- condition
- action

の4つの設定を行う必要がありますが、inputでelasticsearchに格納されたデータに対しクエリを投げ、取得した結果を使うことができます。
Machine Learning実行後は以下のように新しいindexが作られています。
※Include system indecesにチェックを入れないと見えません。

![index_management.png](https://qiita-image-store.s3.amazonaws.com/0/152168/779773f7-871f-fbe6-17cb-e23d813121b3.png "index_management.png")

Machine Learningを行うと、上記のように`.ml-`が頭についたindexが作成されます。
このうち、`.ml-anomalies-*`というindexに`anomaly_score`をfieldに持つdocumentが格納されていくので、このindexを対象にクエリを投げることになります。

![log_anomaly.png](https://qiita-image-store.s3.amazonaws.com/0/152168/3f93b1bd-0f15-51b1-9a28-c7aafe03eca3.png "log_anomaly.png")

## Watcherの設定
Watcherの設定例はこのような感じです。

```json
{
  "trigger": {
    "schedule": {
      "interval": "10s"
    }
  },
  "input": {
    "search": {
      "request": {
        "search_type": "query_then_fetch",
        "indices": [
          ".ml-anomalies-*"
        ],
        "types": [],
        "body": {
          "size": 0,
          "query": {
            "bool": {
              "must": {
                "match": {
                  "job_id": "count_logs"
                }
              },
              "filter": [
                {
                  "range": {
                    "anomaly_score": {
                      "gte": 50
                    }
                  }
                },
                {
                  "range": {
                    "timestamp": {
                      "gte": "now-7d"
                    }
                  }
                }
              ]
            }
          }
        }
      }
    }
  },
  "condition": {
    "compare": {
      "ctx.payload.hits.total": {
        "gte": 1
      }
    }
  },
  "actions": {
    "my-logging-action": {
      "logging": {
        "level": "info",
        "text": "There are {{ctx.payload.hits.total}} documents in your index. Threshold is 50."
      }
    }
  }
}
```

それぞれの項目ごとに設定内容を確認すると、

- trigger
	- 10秒ごとに実行する。
- input
	- `.ml-anomalies-*`というindexに対して、`job_idがcount_logs`であり、`anomaly_scoreが50以上`のdocumentを探す。その際、検索対象は`今から7日前まで`とする。
	
	※今回は確実に検索に引っかかるようにするため、`今から7日前まで(now-7d)`とかなり長めに設定をしています。
- condition
	- 検索に引っかかったdocumentが`1件以上`あれば、actionを実行する。
- action
	- ログに`info`として、`There are [検索でヒットした件数] documents in your index. Threshold is 50. `と表示する。

というものになります。

## Watcherの実施
上記の設定でWatcherを実行すると、

![OK.png](https://qiita-image-store.s3.amazonaws.com/0/152168/1d66afc5-cc2e-5ab4-e423-c9aaf48b4f5c.png "OK.png")

このように最初は`State: OK`となりますが、設定したconditionに合致した場合、

![Firing.png](https://qiita-image-store.s3.amazonaws.com/0/152168/cc1fbc10-074d-f60a-9e4b-cccc9146632f.png "Firing.png")

このように`State: Firing`となり、ログに

```
[INFO ][o.e.x.w.a.l.ExecutableLoggingAction] [YoCA0QT] There are 2 documents in your index. Threshold is 50.
```

と出力されます。

# おわりに
このように、Machine LearningとWatcherを連携させることができました。
実際には結果をログに出力して終了ではなく、webhookを使って外部システムと連携させて自動設定変更、ということも作り込んで行けば可能になります。
長々と書きましたが、Machine Learningがリリースされた当初に比べると使い始めるハードルも低くなっているので、まず使ってみることがいいのではないかと思います。

[Elastic stack (Elasticsearch) Advent Calendar 2018](https://qiita.com/advent-calendar/2018/elasticsearch)も残り4日！