はじめに
前回はAzureのData Explorerを使ってWebサイトのアクセスログをリアルタイムに集計して、その時系列データを可視化する記事を書きました。
時系列データの可視化は、何らかの意思決定(アクション)につなげるための手段にすぎません。例えば、Webアクセスの可視化の場合、急なアクセス増加を検出して、アクセス数に応じてサーバを自動でスケールアウトさせたいから、時系列データを可視化する、といったシナリオ(異常検出のシナリオ)が考えられるかもしれません。
Azureでは時系列データの異常を検知するソリューションとしてAzure Metrics Advisorというサービスがあります。
似たサービスとして「Anomaly Detector」がありますが、こちらはAPIのサービスで、時系列データを入力として投げると、異常値を出力してくれるサービスです。Metrics AdvisorはこのAnomaly Detectorをラップしたサービスで、異常検出の前後に必要なプロセス(時系列データの収集やアラート通知)もマネージドに面倒を見てくれます。
今回の記事では、前回構築したWebサーバ+Data Explorerの仕組みをベースに、Metrics Advisorで異常値の閾値を設定し、メールで異常通知を送る部分を解説していきます。異常が発生してからメールで通知されるまでどれくらいの時間が掛かるかも検証していきます。
アーキテクチャ
今回のアーキテクチャは下記の通りです。まずは、Data Explorerで時系列データを加工し、Metrics Advisorに登録します。次に、Metircs Advisorにて異常検出のルールを設定します。最後にLogic Appsを使ってメール送信の仕組みを作成します。
時系列データの準備
時系列データを準備する前に、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)
クエリ結果は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埋めされていますね。
これにて時系列データの準備は完了です。
Metrics Advisorの設定
それではMetircs Advisorのリソースを作りましょう。Azure PortalからMetrics Advisorにアクセスし、リソースを作成します。
リソースの作成が完了すると、Metrics AdvisorからData Explorerへアクセスするための権限設定を行います。具体的にはマネージドIDが利用します。まず、[Data Explorer]->[データベース]へアクセスします。[アクセス許可]->[追加]をクリックし、権限[Viewer]を選択します。その後、サービスプリンシパルの検索枠からData Explorerのリソース名を入力し、[選択]をクリックしましょう。
次に、Metrics Advisor専用のポータルに移動します。[概要]->[ワークスペースに移動]をクリックします。
先ほど作成したリソースを選択して、[Get Started]をクリックします。
それではメトリックを登録していきます。左メニューの[Add data feed]をクリックします。
[Source type(データソース)]は「Azure Data Explorer (Kusto)」、[Authentication type(認証形式)]は、Data Explorerで権限を付与したサービスプリンシパルを使いたいので[Managed Identity]を選択します。
その他のデータソースや認証形式に関しては、下記公式リンクに記載されています。
[Granularity(粒度)]は、データをMetrics Advisorにインジェストする時刻の粒度になります。[Ingest data since(UTC)]が取り込み開始時刻ですが、こちらの開始時刻から[Granularity]で指定した粒度の時刻毎にデータを取得する仕様です。今回は「Minutely(毎分)」を選択します。[Ingest data since(UTC)]は、取り込みを開始したい時刻を選択します。
「Ingest data since(UTC)」で指定した時刻にデータが存在しないと、後の[Load Data]でエラーになるので注意しましょう(私はこれでハマりました。。。)
[Conection string]はData Source=<Server>;Initial Catalog=<db-name>
の形式で記述します。
[Query]には先ほどData Explorerで作成したクエリを記入します。最後に[TableName] | where [TimestampColumn] >= datetime(@IntervalStart) and [TimestampColumn] < datetime(@IntervalEnd);
の行を追加します。[TimestampColumn]
を時刻列に書き換えましょう。
すべての設定が完了した後に[Load Data]をクリックします。すると、データが上手くロードできている場合は下記のようにサンプルデータが表示されます。各カラムの役割(TimeStamp,DImension,Measure)を選択して[Veryfy schema]をクリックしましょう。
[Veryfy schema]をクリックすると、下記の画面が表示されます。今回[Automatic roll-up settings(自動ロールアップ)]は必要ないので、一番下を選択しましょう。[Advanced settings]は様々なオプションを選択できますが、Data feed作成後に変更できるので、今回は設定しません。最後に[Data feed name]を入力して、[Submit]を押しましょう。
自動ロールアップは、時系列を何らかのディメンションで集計したい場合に設定するオプションです。詳細は下記に公式に詳しく記載されています。
[Submit]が完了すると、左メニュー[Data feeds]から先ほど作成したData feedをクリックしましょう。
上のバーにデータインジェストの進捗が表示されています。それでは、実際に取り込まれた時系列グラフを見てましょう。[Count]をクリックします。
前回の記事通り、WEBサーバのdockerを起動した後localhost
に何回かアクセスした結果、下記の通りアクセス数がグラフとして表示されることが確認できました。
異常検知のルール設定
次に、異常値のルールを作成します。今回は「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のラインが引かれ、それ以上のデータ点は赤くなり異常と判定されました。
異常検出のメソッドは下記公式リンクに詳細が記載されています。今回のような閾値で判定するパターンは最もシンプルですが、その他周期から外れた値やスパイクの検出等、単純な閾値で判定できないパターンも柔軟に検出できるようになっています。これらのメソッドを柔軟に使えるのが、Metric Advisorの大きな強みですね。
異常判定のデータにポイントオーバーすると、下記のように詳細を確認できます。
以上で異常検出の部分は終了となります。次からは検知した異常を通知する仕組みを作っていきます。
メール送信の仕組みを構築
Logic Appsを利用したメール配信システムの構築を行います。まずは、Logic Appsのリソースを作成しましょう。[プラン]ですが今回はメールを送るだけなので従量課金の消費を選択しましす。
Metrics Advisorで異常を通知する方法として、email、Teams、webhook、Azure DevOpsの4つがサポートされています。詳細は下記公式リンクに記載されています。
22年12月現在でTeamsの方法が上手くいきませんでした。おそらく、Teams側のIncoming Webhookの仕様が変わったことにより、Metrics Advisor側でwebhookのレスポンスをパースできないことが原因です。対応を待ちましょう。
作成したリソースへアクセスすると、下記の通りLogic Appsデザイナーのページが表示されます。トリガーとして「HTTP 要求受信時」を選択しましょう。
Metrics AdvisorからLogic AppsへAPIコールした時の要求本文のスキーマを作成します。スキーマはサンプルデータから生成できるので、「サンプルペイロードを使用してスキーマを生成する」をクリックしましょう。
異常発生時の要求時のサンプルペイロードはこちらになります。
{
"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を選択しましょう。
ここで一度保存しておきます。保存すると右側にHTTPのURLが表示されます。このURLをMetrics Advisor側に登録します。
Metric Advisorの専用ポータルに戻り、左メニュー[Hooks]->[Create hook]をクリックします。[Hook Type]はWebhook、[Name]は任意の名前を入力し、[Endpoint]には先ほど生成されたLogic AppsのHTTP URLを入力し、[Save]をクリックします。
次にアラートとHookを紐づけます。メトリクスのグラフが画面に行き、左下の[Alerting configrations]のプラスボタンをクリックします。
[Configration name]にアラートの名前を入力し、[Hooks]に先ほど作成したSend Emailのhookを選択しましょう。その他は特にデフォルトのままでOKです。
これでMetrichs AdvisorからLogic Appsをキックする部分の設定は完了です。実際にWebアクセス数を増やして、アラートのトリガーを発生させてみましょう。Logic Appsの画面に戻り、[概要]->[実行の履歴]を確認すると、確かにMetrics Advisor側からLogic Apps側にAPIコールできていることが確認できました。2件ありますが、最初の実行履歴はhookを登録した時の空のアクセスになります。最新の実行履歴を確認してみましょう。
タイムスタンプの時刻を見て、自分が発生させたアラートがLogic Appsまで届いていることを確認しましょう。
中身を再度確認すると、アラート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」を選択します。
HTTPリクエストの形式は下記の通りです。ポイントはヘッダーでOcp-Apim-Subscription-Key
とx-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]に記載されています。
x-api-key
は、Metrics Advisorの専用ポータルの[API keys]->[Primary API Key]に記載されています。
それではHTTPの設定を行います。[方法]にGETを選択し、URIを[動的コンテンツの追加]からcallBackURIを選択します。
ヘッダーの行を埋めて、テストのため一旦保存します。
再び[実行の履歴]に戻り、先ほどの実行を[再送信]します。この再送信を実行することで、入力のペイロードはそのままで先ほど保存したロジックのテストができます。
下記画像の通り、アラートの詳細情報が取れていることが確認できます。
出力は下記の通りです。
{
"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の解析」を追加します。
動的なコンテンツ追加から本文
を追加し、「サンプルのペイロードを仕様してスキーマを生成する」に上記value
のJSONを張り付けて、スキーマを作成します。
スキーマが作成されたら、左上の保存をクリックします。
次に取得したアラート詳細情報から必要な項目を抜き出し、テーブル形式にします。メールでアラート通知が来た時に表形式の方が分かりやすい、という理由なので、必ずしも必要なアクションではありません。
開始にvalue
に追加し、列「カスタム」からデータテーブルを作成します。
次にメール送信のアクションを追加します。
メール本文を作成し、保存します。
実行の履歴に戻り、再送信をクリックします。アラートメールが受信できれば、動作確認完了です。
アラート発生からメールが送信されるまでの時間
実際にWebアクセス数を増やしてみて、どれくらいの時間でメールが届くか検証してみました。
UTC時間に統一すると8時58分から8時59分の区間にアラートが発生し、9時2分にメールが来ています。約3分といったところでしょうか。これよりリアルタイム性が必要な場合はよりエッジ側で異常を判定するソリューションが必要ということですね。
まとめ
今回はData Explorerに蓄積されたWebアクセスログに対して、Metrics Advisorを使って異常を検知し、Logic Appsでメール通知する仕組みを解説しました!少し分量がありましたが、自分で異常検知の仕組みを実装することに比べたらはるかに簡単に構築できそうですね。是非お試しください!