背景
Azure サービスの中には、処理の結果を Resource Graph のみに記録するものがあります。例えば Update Management Center はパッチの状態や適用処理の結果を記録します。Change Analysis は検出した変更結果を記録します。
そんな Resource Graph には、2023年2月現在2つの困りごとが存在します。
- 情報を記録するサービスによって保存期間が決められており、任意の保存期間を指定できない
- Azure Monitor のアラート発火条件に利用できない
この2つの困りごとを解決するアプローチの1つとして、Resource Graph に記録された情報を Log Analytics に投入する方法があります。Log Analytics に投入してしまえば、任意の保存期間を指定できますし、Azure Monitor のアラート発火条件にも利用できます。
ただし、このアプローチは現時点で Azure の機能として組み込まれていません。処理を自作する必要があります。動作検証をかねて実際に試してみました。今回は Change Analysis のデータを Log Analytics に投入してみます
利用するサービス
任意の処理を定期実行するサービスとなると、Azure Function か Logic Apps が候補に挙がります。今回は あえて 不自由な Logic Apps を採用します。
Logic Apps で Resource Graph のデータを取得する
残念なことに Logic Apps には Resource Graph のコネクタがありません。仕方がないので HTTP コネクタを利用して Rest API を直接叩きます。
チュートリアル: Azure ロジック アプリで Azure Resource Graph クエリを実行する
ドキュメントに記載されているとおり、Logic Apps が API にアクセスする際の認証には Managed ID を利用します。Logic Apps 用の Managed ID が Resource Graph の情報にアクセスできるように RBAC で権限を付与するのを忘れずに。
今回は、Logic Apps を1時間に一回起動して、直近1時間の Change Analysis のデータを抽出することにします。次のように where changeTime > ago(1h)
でログを抽出してしまうと、Resource Graph で検索した実行した時間に依存して抽出範囲が決まってしまうので、Logic Apps の実行時間が0分0秒から少しでもずれた場合にログの取りこぼしが発生しそうです。
resourcechanges
| extend changeTime = todatetime(properties.changeAttributes.timestamp), targetResourceId = tostring(properties.targetResourceId), changeType = tostring(properties.changeType), correlationId = properties.changeAttributes.correlationId, changedProperties = properties.changes, changeCount = properties.changeAttributes.changesCount
| where changeTime > ago(1h)
| order by changeTime desc
| project changeTime, targetResourceId, changeType, correlationId, changeCount, changedProperties
ログの取りこぼしを回避するためには、ログの抽出条件を「1時間前の0分0秒以上、かつ今の時間の0分0秒未満」のように絶対時間で指定する必要があります。この条件を Logic Apps の式を利用して動的に生成します。今回書いた式は次の通りです。
| where changeTime >= datetime(@{addHours(startOfHour(utcNow()),-1)}) and changeTime < datetime(@{startOfHour(utcNow())})
直近1時間の From の条件には addHours(startOfHour(utcNow()),-1)
という式を利用します。この式は、utcNow()
で取得した現在の時間から startOfHour()
でその時間の0分0秒を取得した上で、addHours('<timestamp>',-1)
することで1時間前の0分0秒の文字列を生成します。
同様に 直近1時間のTo の部分には startOfHour(utcNow())
という式を利用します。この式は utcNow()
で取得した現在の時刻から startOfHour()
で現在の時刻の0分0秒の文字列を生成します。
Logic Apps が UTC 2/13/2023 11:15 に実行された場合、Resource Graph 内の where 文は次のようになります。大丈夫そう。
| where changeTime >= datetime(2023-02-13T10:00:00.0000000Z) and changeTime < datetime(2023-02-13T11:00:00.0000000Z)
取得した Resource Graph の情報をパースする
取得した情報を後続の処理で利用できるように、一旦パースします。
パースの処理を追加するためには、スキーマを定義する必要があります。ゼロからスキーマを作るのはしんどいので、一度テストで Resource Graph の情報を取得する部分を動かした上で、その結果からスキーマを作ってもらうのが良いです。
Log Analytics 側の準備
情報を受け取る Log Analytics を整えます。投入には、ログインジェスト API を利用します。
まずは、Logic Apps からデータを受け取るためのデータ収集エンドポイントを作ります。データ収集エンドポイントのログ投入用 URLを後で利用するので控えておきます。
次に、データを受け取る Logic Analytics のカスタムテーブルを作ります。カスタムテーブルにデータを流し込むためのデータ収集ルールを作成したうえで、データ収集エンドポイントを紐づけます。
テーブルのスキーマを作成する際には、サンプルのファイルが必要になります。複数のログレコードを含む JSON ファイルであることが期待されるので、先ほどパース処理を作る際に利用したテスト結果の JSON から投入したいデータ部分だけを別ファイルにして利用すると楽です。
ただし、Resource Graph の結果には TimeGenerated が含まれていないので、スキーマの作成がエラーになってしまいます。
そこでデータ収集ルールの変換で次のクエリを実行することで、Change Analysis のデータに存在する changeTime の時刻を TimeGenerated としてログに追加します。
source
| extend TimeGenerated = todatetime(changeTime)
TimeGenerated を追加すればエラーは消えます。
これでデータ収集ルールの仕込みは OK です。最後にデータ収集ルールのイミュータブル ID を控えておきます。
Log Analytics にデータを送る
最後に、作成したデータ収集エンドポイントに対して Logic Apps でログを送ります。データ収集エンドポイントの FQDN に対してどのような形でログを送ればよいかは、次のサンプルスクリプトに書いてあります。
スクリプトの内容を踏まえると・・・
- 送信先
- https://<データ収集エンドポイントの URL>/dataCollectionRules/<データ収集ルールのイミュータブル ID>/streams/Custom-<カスタムテーブルの名前>?api-version=2021-11-01-preview
- メソッド
- POST
- トークンを取得する際のターゲットのリソース ID
- 送信するデータ形式
- 配列
上記を踏まえて、Logic Apps の HTTP アクションを作っていきます。
パースされた JSON を確認すると data に実際の変更結果が配列として記録されています。
{
"totalRecords": 3,
"count": 3,
"data": [
{
"changeTime": "2023-02-10T10:59:46.633Z",
"correlationId": "bc55db72-e160-4338-b3ae-d67087f51c21",
(中略)
}
},
{
"changeTime": "2023-02-10T10:56:44.44Z",
"correlationId": "dcdadd1f-8912-41fb-bb85-9096665b1769",
(中略)
},
{
"changeTime": "2023-02-10T10:27:24.604Z",
"correlationId": "f3f1df29-2853-4ba7-b691-e98aaa8beec4",
(中略)
}
}
],
"facets": [],
"resultTruncated": "false"
}
ですので、まずはこの data を ForEach でループさせて変更結果を1つずつ順番に処理します。このループの中で HTTP アクションでログインジェスト API を呼び出します。
送信するデータを配列形式にする必要があるので、createArray()
を利用してループで処理されている配列内の値を改めて配列に変換しています。また、audience を https://monitor.azure.com/ と指定するのを忘れずに。
"HTTP": {
"inputs": {
"authentication": {
"audience": "https://monitor.azure.com/",
"type": "ManagedServiceIdentity"
},
"body": "@createArray(items('For_each_3'))",
"headers": {
"Content-Type": "application/json"
},
"method": "POST",
"uri": "https://changeanalysis-ggy1.japaneast-1.ingest.monitor.azure.com/dataCollectionRules/dcr-57f3db084c234d0fbc0c965ebc321bcd/streams/Custom-changeanalysis_CL?api-version=2021-11-01-preview"
},
"runAfter": {},
"type": "Http"
}
手動で Logic App を実行してしばし待つと Log Analytics でデータを検索できるようになります。Log Analytics に入ってしまえば、あとは長期保存するなりアラートを送るなり、普段と同じように情報を扱えます。実際に仕組みを自作する際には、Resource Graph に記録されている情報と Log Analytics に投入された情報に差異がない事を確認するのを忘れずに。
まとめ
Resource Graph の課題を解決するために、Log Analytics にデータを投入する仕組みを自作してみました。動きはしましたが、ネイティブな仕組みが欲しいですね…