9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Microsoft Azure TechAdvent Calendar 2022

Day 21

Azure Metrics AdvisorでWebアクセス数の異常を検出してみた

Last updated at Posted at 2022-12-21

はじめに

前回はAzureのData Explorerを使ってWebサイトのアクセスログをリアルタイムに集計して、その時系列データを可視化する記事を書きました。

時系列データの可視化は、何らかの意思決定(アクション)につなげるための手段にすぎません。例えば、Webアクセスの可視化の場合、急なアクセス増加を検出して、アクセス数に応じてサーバを自動でスケールアウトさせたいから、時系列データを可視化する、といったシナリオ(異常検出のシナリオ)が考えられるかもしれません。

Azureでは時系列データの異常を検知するソリューションとしてAzure Metrics Advisorというサービスがあります。

似たサービスとして「Anomaly Detector」がありますが、こちらはAPIのサービスで、時系列データを入力として投げると、異常値を出力してくれるサービスです。Metrics AdvisorはこのAnomaly Detectorをラップしたサービスで、異常検出の前後に必要なプロセス(時系列データの収集やアラート通知)もマネージドに面倒を見てくれます。
画像1.png

今回の記事では、前回構築したWebサーバ+Data Explorerの仕組みをベースに、Metrics Advisorで異常値の閾値を設定し、メールで異常通知を送る部分を解説していきます。異常が発生してからメールで通知されるまでどれくらいの時間が掛かるかも検証していきます。

アーキテクチャ

今回のアーキテクチャは下記の通りです。まずは、Data Explorerで時系列データを加工し、Metrics Advisorに登録します。次に、Metircs Advisorにて異常検出のルールを設定します。最後にLogic Appsを使ってメール送信の仕組みを作成します。
画像3.png

時系列データの準備

時系列データを準備する前に、Metrics Advisorでサポートされている時系列のスキーマの要件を確認しておきます。下記の通りです。

説明
Measure(必須) 監視したいメトリック(数値型)。複数列指定可能。
Timestamp(省略可) yyyy-MM-ddTHH:mm:ssZの形式の時刻列(DateTime型もしくはString型)。粒度は毎年、毎月、毎週、毎日、毎時間、毎分、カスタム(最小300秒)を対応しています。
Dimension(省略可) 1つまたは複数のカテゴリの値(String型)

それでは、Data Explorerで時系列の準備をしましょう。前回Webアクセス数を可視化するために使ったKustoクエリは下記の通りです。

Log
| project source, log
| extend ingestionTime = ingestion_time()
| extend DAY = toint(extract(@'.*\[(.*)/.*/.*:.*:.*:.*\].*', 1, log))
| extend MONTH = extract(@'.*\[.*/(.*)/.*:.*:.*:.*\].*', 1, log)
| extend YEAR = substring(extract(@'.*\[.*/.*/(.*):.*:.*:.*\].*', 1, log),2,2)
| extend TIME = extract(@'.*\[.*/.*/.*:(.*:.*:.*) .*\].*', 1, log)
| extend DATETIME = strcat(DAY, " ", MONTH, " ", YEAR, " ", TIME)
| extend accessTime = todatetime(DATETIME)
| project source, log, accessTime
| where source == "stdout"
| summarize count() by bin(accessTime,1m)

返却されるクエリ結果はこんな感じです。
画像4.png

クエリ結果は1分粒度で集計されていますが、アクセスがない時間帯のレコードはありません。一応、このクエリでもMetrics Advisorに登録できますが、アクセスがない時間帯は0で埋めておきます(0で埋めないとMetrics Advisor側で取り込みFailedのフラグが発生します)。Kustoの0埋め方法は、公式サイトに記載されています。

実際には下記のように処理を加えています。

Log
| project source, log
| extend ingestionTime = ingestion_time()
| extend DAY = toint(extract(@'.*\[(.*)/.*/.*:.*:.*:.*\].*', 1, log))
| extend MONTH = extract(@'.*\[.*/(.*)/.*:.*:.*:.*\].*', 1, log)
| extend YEAR = substring(extract(@'.*\[.*/.*/(.*):.*:.*:.*\].*', 1, log),2,2)
| extend TIME = extract(@'.*\[.*/.*/.*:(.*:.*:.*) .*\].*', 1, log)
| extend DATETIME = strcat(DAY, " ", MONTH, " ", YEAR, " ", TIME)
| extend accessTime = todatetime(DATETIME)
| project source, log, accessTime
| where source == "stdout"
| summarize Count=count() by bin(accessTime,1m)
| union ( 
  range x from 1 to 1 step 1 
  | mv-expand accessTime=range(datetime("2022-12-12"), now(), 1m) to typeof(datetime) 
  | extend Count=0 
  )
| summarize Count=sum(Count) by bin(accessTime, 1m)

ちゃんとアクセスがない時間帯は0埋めされていますね。
画像5.png
これにて時系列データの準備は完了です。

Metrics Advisorの設定

それではMetircs Advisorのリソースを作りましょう。Azure PortalからMetrics Advisorにアクセスし、リソースを作成します。
画像6.png
リソースの作成が完了すると、Metrics AdvisorからData Explorerへアクセスするための権限設定を行います。具体的にはマネージドIDが利用します。まず、[Data Explorer]->[データベース]へアクセスします。[アクセス許可]->[追加]をクリックし、権限[Viewer]を選択します。その後、サービスプリンシパルの検索枠からData Explorerのリソース名を入力し、[選択]をクリックしましょう。
画像10.png
次に、Metrics Advisor専用のポータルに移動します。[概要]->[ワークスペースに移動]をクリックします。
画像8.png
先ほど作成したリソースを選択して、[Get Started]をクリックします。
画像9.png
それではメトリックを登録していきます。左メニューの[Add data feed]をクリックします。
画像11.png
[Source type(データソース)]は「Azure Data Explorer (Kusto)」、[Authentication type(認証形式)]は、Data Explorerで権限を付与したサービスプリンシパルを使いたいので[Managed Identity]を選択します。
画像15.png

その他のデータソースや認証形式に関しては、下記公式リンクに記載されています。

[Granularity(粒度)]は、データをMetrics Advisorにインジェストする時刻の粒度になります。[Ingest data since(UTC)]が取り込み開始時刻ですが、こちらの開始時刻から[Granularity]で指定した粒度の時刻毎にデータを取得する仕様です。今回は「Minutely(毎分)」を選択します。[Ingest data since(UTC)]は、取り込みを開始したい時刻を選択します。
画像16.png

「Ingest data since(UTC)」で指定した時刻にデータが存在しないと、後の[Load Data]でエラーになるので注意しましょう(私はこれでハマりました。。。)

[Conection string]はData Source=<Server>;Initial Catalog=<db-name>の形式で記述します。
画像17.png
[Query]には先ほどData Explorerで作成したクエリを記入します。最後に[TableName] | where [TimestampColumn] >= datetime(@IntervalStart) and [TimestampColumn] < datetime(@IntervalEnd);の行を追加します。[TimestampColumn]を時刻列に書き換えましょう。
画像20.png
すべての設定が完了した後に[Load Data]をクリックします。すると、データが上手くロードできている場合は下記のようにサンプルデータが表示されます。各カラムの役割(TimeStamp,DImension,Measure)を選択して[Veryfy schema]をクリックしましょう。
画像21.png
[Veryfy schema]をクリックすると、下記の画面が表示されます。今回[Automatic roll-up settings(自動ロールアップ)]は必要ないので、一番下を選択しましょう。[Advanced settings]は様々なオプションを選択できますが、Data feed作成後に変更できるので、今回は設定しません。最後に[Data feed name]を入力して、[Submit]を押しましょう。

画像22.png

自動ロールアップは、時系列を何らかのディメンションで集計したい場合に設定するオプションです。詳細は下記に公式に詳しく記載されています。

[Submit]が完了すると、左メニュー[Data feeds]から先ほど作成したData feedをクリックしましょう。
画像23.png
上のバーにデータインジェストの進捗が表示されています。それでは、実際に取り込まれた時系列グラフを見てましょう。[Count]をクリックします。
画像25.png
前回の記事通り、WEBサーバのdockerを起動した後localhostに何回かアクセスした結果、下記の通りアクセス数がグラフとして表示されることが確認できました。
画像26.png

異常検知のルール設定

次に、異常値のルールを作成します。今回は「Webアクセス数が15を以上」というルールを設定していきます。左側のMetir-level condigurationにて「Hard threshold」,「Above」,「Max 15」を選択します。その下の項目は、異常値の判断に連続値を使用したい場合に設定します。例えば、「5分間webアクセス数が15以上」というルールを設定したい場合は、「Do not report anomaly until 100% of latest 5 points are detected as anamolies.」のように設定します。今回は1点ずつ判断したいので、100%, 1pointのままでOKです。最後に[save]をクリックしましょう。すると、右のグラフにWebアクセス数=15のラインが引かれ、それ以上のデータ点は赤くなり異常と判定されました。

画像27.png

異常検出のメソッドは下記公式リンクに詳細が記載されています。今回のような閾値で判定するパターンは最もシンプルですが、その他周期から外れた値やスパイクの検出等、単純な閾値で判定できないパターンも柔軟に検出できるようになっています。これらのメソッドを柔軟に使えるのが、Metric Advisorの大きな強みですね。

異常判定のデータにポイントオーバーすると、下記のように詳細を確認できます。
画像28.png

以上で異常検出の部分は終了となります。次からは検知した異常を通知する仕組みを作っていきます。

メール送信の仕組みを構築

Logic Appsを利用したメール配信システムの構築を行います。まずは、Logic Appsのリソースを作成しましょう。[プラン]ですが今回はメールを送るだけなので従量課金の消費を選択しましす。
画像29.png

Metrics Advisorで異常を通知する方法として、email、Teams、webhook、Azure DevOpsの4つがサポートされています。詳細は下記公式リンクに記載されています。

22年12月現在でTeamsの方法が上手くいきませんでした。おそらく、Teams側のIncoming Webhookの仕様が変わったことにより、Metrics Advisor側でwebhookのレスポンスをパースできないことが原因です。対応を待ちましょう。

作成したリソースへアクセスすると、下記の通りLogic Appsデザイナーのページが表示されます。トリガーとして「HTTP 要求受信時」を選択しましょう。
画像30.png
Metrics AdvisorからLogic AppsへAPIコールした時の要求本文のスキーマを作成します。スキーマはサンプルデータから生成できるので、「サンプルペイロードを使用してスキーマを生成する」をクリックしましょう。
画像31.png
異常発生時の要求時のサンプルペイロードはこちらになります。

{
    "headers": {
        "Connection": "close",
        "Accept-Encoding": "gzip",
        "Host": "prod-15.japaneast.logic.azure.com",
        "User-Agent": "okhttp/3.14.9",
        "Content-Length": "495",
        "Content-Type": "application/json; charset=utf-8"
    },
    "body": {
        "value": [
            {
                "hookId": "c1f8b97f-bf4c-435d-9ec8-3e1474ad18f8",
                "alertType": "Anomaly",
                "alertInfo": {
                    "anomalyAlertingConfigurationId": "cca7eb03-2a55-44e7-ac03-b4cc154947ae",
                    "alertId": "1850bfc4400",
                    "timestamp": "2022-12-13T14:56:00Z",
                    "createdTime": "2022-12-13T14:58:47.823Z",
                    "modifiedTime": "2022-12-13T14:58:47.865Z"
                },
                "callBackUrl": "https://log-analytics-ma.cognitiveservices.azure.com/metricsadvisor/v1.0/alert/anomaly/configurations/cca7eb03-2a55-44e7-ac03-b4cc154947ae/alerts/1850bfc4400/incidents"
            }
        ]
    }
}

こちらの本文(body)の部分だけ切り取り、サンプルペイロードへ貼り付けると、そのスキーマが生成されます。

{
        "value": [
            {
                "hookId": "c1f8b97f-bf4c-435d-9ec8-3e1474ad18f8",
                "alertType": "Anomaly",
                "alertInfo": {
                    "anomalyAlertingConfigurationId": "cca7eb03-2a55-44e7-ac03-b4cc154947ae",
                    "alertId": "1850bfc4400",
                    "timestamp": "2022-12-13T14:56:00Z",
                    "createdTime": "2022-12-13T14:58:47.823Z",
                    "modifiedTime": "2022-12-13T14:58:47.865Z"
                },
                "callBackUrl": "https://log-analytics-ma.cognitiveservices.azure.com/metricsadvisor/v1.0/alert/anomaly/configurations/cca7eb03-2a55-44e7-ac03-b4cc154947ae/alerts/1850bfc4400/incidents"
            }
        ]
    }

スキーマが生成されていることを確認したら、下の[Add new Parameter]をクリックし、メソッドPOSTを選択しましょう。
画像32.png
ここで一度保存しておきます。保存すると右側にHTTPのURLが表示されます。このURLをMetrics Advisor側に登録します。
画像33.png

Metric Advisorの専用ポータルに戻り、左メニュー[Hooks]->[Create hook]をクリックします。[Hook Type]はWebhook、[Name]は任意の名前を入力し、[Endpoint]には先ほど生成されたLogic AppsのHTTP URLを入力し、[Save]をクリックします。
画像36.png
次にアラートとHookを紐づけます。メトリクスのグラフが画面に行き、左下の[Alerting configrations]のプラスボタンをクリックします。
画像34.png
[Configration name]にアラートの名前を入力し、[Hooks]に先ほど作成したSend Emailのhookを選択しましょう。その他は特にデフォルトのままでOKです。
画像37.png
これでMetrichs AdvisorからLogic Appsをキックする部分の設定は完了です。実際にWebアクセス数を増やして、アラートのトリガーを発生させてみましょう。Logic Appsの画面に戻り、[概要]->[実行の履歴]を確認すると、確かにMetrics Advisor側からLogic Apps側にAPIコールできていることが確認できました。2件ありますが、最初の実行履歴はhookを登録した時の空のアクセスになります。最新の実行履歴を確認してみましょう。
画像38.png
タイムスタンプの時刻を見て、自分が発生させたアラートがLogic Appsまで届いていることを確認しましょう。
画像39.png
中身を再度確認すると、アラートIDやタイムスタンプは書かれているものの、異常値等の詳細が掛かれていません。

{
    "headers": {
        "Connection": "close",
        "Accept-Encoding": "gzip",
        "Host": "prod-09.japaneast.logic.azure.com",
        "User-Agent": "okhttp/3.14.9",
        "Content-Length": "508",
        "Content-Type": "application/json; charset=utf-8"
    },
    "body": {
        "value": [
            {
                "hookId": "9a6c5ac9-d425-48fe-8a21-d88a6fd12189",
                "alertType": "Anomaly",
                "alertInfo": {
                    "anomalyAlertingConfigurationId": "2becdcff-3eb8-495e-9702-4efb9a1d309a",
                    "alertId": "18519b09100",
                    "timestamp": "2022-12-16T06:48:00Z",
                    "createdTime": "2022-12-16T06:50:32.729Z",
                    "modifiedTime": "2022-12-16T06:50:32.774Z"
                },
                "callBackUrl": "https://log-analytics-metrics-advisor.cognitiveservices.azure.com/metricsadvisor/v1.0/alert/anomaly/configurations/2becdcff-3eb8-495e-9702-4efb9a1d309a/alerts/18519b09100/incidents"
            }
        ]
    }
}

アラートの詳細を取得するには加えてcallBackUrlをコールする必要があります。[ロジック アプリ デザイナー]に戻り、アクションの追加から「HTTP」を選択します。
画像40.png
HTTPリクエストの形式は下記の通りです。ポイントはヘッダーでOcp-Apim-Subscription-Keyx-api-keyのキーが必要であることです。

{
    "uri": "https://log-analytics-ma.cognitiveservices.azure.com/metricsadvisor/v1.0/alert/anomaly/configurations/cca7eb03-2a55-44e7-ac03-b4cc154947ae/alerts/1850bfc4400/incidents",
    "method": "GET",
    "headers": {
        "Content-Type": "application/json",
        "Ocp-Apim-Subscription-Key": "<Ocp-Apim-Subscription-Key>",
        "x-api-key": "<x-api-key>"
    }
}

Ocp-Apim-Subscription-Keyは、Metric Advisorのリソースから[キーとエンドポイント]->[キー1]に記載されています。
画像43.png
x-api-keyは、Metrics Advisorの専用ポータルの[API keys]->[Primary API Key]に記載されています。
画像45.png
それではHTTPの設定を行います。[方法]にGETを選択し、URIを[動的コンテンツの追加]からcallBackURIを選択します。
画像42.png
ヘッダーの行を埋めて、テストのため一旦保存します。
画像47.png
再び[実行の履歴]に戻り、先ほどの実行を[再送信]します。この再送信を実行することで、入力のペイロードはそのままで先ほど保存したロジックのテストができます。
画像48.png
下記画像の通り、アラートの詳細情報が取れていることが確認できます。
画像49.png
出力は下記の通りです。

{
    "statusCode": 200,
    "headers": {
        "x-request-id": "868ef893-f4cd-411a-af42-fbfd403b3253",
        "x-envoy-upstream-service-time": "980",
        "apim-request-id": "868ef893-f4cd-411a-af42-fbfd403b3253",
        "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
        "x-content-type-options": "nosniff",
        "x-ms-region": "Japan East",
        "Date": "Fri, 16 Dec 2022 08:09:11 GMT",
        "Content-Length": "459",
        "Content-Type": "application/json; charset=utf-8"
    },
    "body": {
        "value": [
            {
                "dataFeedId": "44ea6aba-be42-45e0-8796-aa5a7eeafe85",
                "metricId": "2503eb5e-9ebc-4489-b117-df326bfe3c35",
                "anomalyDetectionConfigurationId": "36b17725-18ab-44ab-b983-f1ec49bc9ee8",
                "incidentId": "a5521777eef5d6fb1c9e72eb03d1144e-18519b09100",
                "startTime": "2022-12-16T06:48:00Z",
                "lastTime": "2022-12-16T06:48:00Z",
                "rootNode": {
                    "dimension": {}
                },
                "property": {
                    "maxSeverity": "High",
                    "incidentStatus": "Active",
                    "valueOfRootNode": 50,
                    "expectedValueOfRootNode": 15
                }
            }
        ]
    }
}

次に、このレスポンスvalue部分の各項目をLoggic Apps側で使えるようにJSON解析のアクションを追加します。

 {
        "value": [
            {
                "dataFeedId": "44ea6aba-be42-45e0-8796-aa5a7eeafe85",
                "metricId": "2503eb5e-9ebc-4489-b117-df326bfe3c35",
                "anomalyDetectionConfigurationId": "36b17725-18ab-44ab-b983-f1ec49bc9ee8",
                "incidentId": "a5521777eef5d6fb1c9e72eb03d1144e-18519b09100",
                "startTime": "2022-12-16T06:48:00Z",
                "lastTime": "2022-12-16T06:48:00Z",
                "rootNode": {
                    "dimension": {}
                },
                "property": {
                    "maxSeverity": "High",
                    "incidentStatus": "Active",
                    "valueOfRootNode": 50,
                    "expectedValueOfRootNode": 15
                }
            }
        ]
    }

アクション「JSONの解析」を追加します。
画像53.png
動的なコンテンツ追加から本文を追加し、「サンプルのペイロードを仕様してスキーマを生成する」に上記valueのJSONを張り付けて、スキーマを作成します。
画像54.png
スキーマが作成されたら、左上の保存をクリックします。
画像55.png
次に取得したアラート詳細情報から必要な項目を抜き出し、テーブル形式にします。メールでアラート通知が来た時に表形式の方が分かりやすい、という理由なので、必ずしも必要なアクションではありません。
画像56.png
開始にvalueに追加し、列「カスタム」からデータテーブルを作成します。
画像57.png
次にメール送信のアクションを追加します。
画像58.png
メール本文を作成し、保存します。
画像59.png
実行の履歴に戻り、再送信をクリックします。アラートメールが受信できれば、動作確認完了です。
画像60.png

アラート発生からメールが送信されるまでの時間

実際にWebアクセス数を増やしてみて、どれくらいの時間でメールが届くか検証してみました。
画像61.png
UTC時間に統一すると8時58分から8時59分の区間にアラートが発生し、9時2分にメールが来ています。約3分といったところでしょうか。これよりリアルタイム性が必要な場合はよりエッジ側で異常を判定するソリューションが必要ということですね。

まとめ

今回はData Explorerに蓄積されたWebアクセスログに対して、Metrics Advisorを使って異常を検知し、Logic Appsでメール通知する仕組みを解説しました!少し分量がありましたが、自分で異常検知の仕組みを実装することに比べたらはるかに簡単に構築できそうですね。是非お試しください!

9
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?