Azure Resource Graph のデータを Logic App を介して Blob ストレージに保存する
はじめに
Azure においてリソースの設定等 (コントロールプレーン) の変更があった場合、Azure ポータルの変更分析 (2025/01 時点でプレビュー) を使用して確認できます。
-
例)VM を停止した場合 (runnning → deallocating のステータス変更が記録されている)
-
ただし、変更分析は14日間のみしか確認できません。
データ保持
変更のクエリを実行できる期間は 14 日間です。 保持期間を長くするには、Resource Graph クエリを Azure Logic Apps と統合し、クエリ結果を Log Analytics などの Azure データ ストアに手動でエクスポートし、必要な期間保持できます。
- 上記公式ドキュメントの通り、長期保存できライフサイクル管理も容易な Blob に保存します。
- 色々と方法はあるかと思いますが (Functions/Automation/VMでスクリプト動作等)、最も管理の楽な Logic Apps で実行します。
思うところとして、ユーザー側が手動で設定値を変えてしまい terraform apply で tfstate と差異が出てハマる (苦しむ) ことは、IT ガバナンスの効いていない現場やあるいは現場の預かり知らぬところで変わっちゃう (ex.グローバル企業でグローバル側が"機動的に"変えちゃう) など、意外にあるかと思います。
アクティビティログの保存で事足りるところはありますが、それはそれとして「リソースがどう変わったか」が分かる Resource Graph のデータをストレージアカウントに残すことを目的としてこの記事を記述します。
Azure Resource Graph のクエリ
変更分析の「クエリを開く」から Azure Resource Graph エクスプローラーにて変更分析クエリを実行可能です。
- ちなみに「Resource Graph のクエリ」にクエリを保存できます。
ここでは変更分析クエリではなく、resourcechanges テーブル全体を残すようなクエリを実行します。
Azure Resource Graph にはリソース変更を記録するテーブルがあります
resourcechanges というテーブルに ARM のリソース変更履歴が記録されています。
サンプルクエリです。日時については 24時間範囲の変更をキャッチします。
resourcechanges
| extend changeTime = todatetime(properties.changeAttributes.timestamp),
exeDate = format_datetime(datetime_add('Hour', 9, now()), 'yyyy/MM/dd HH:mm:ss'),
subscriptionId = tostring(split(properties.targetResourceId, '/', 2)[0])
| where changeTime >= todatetime('2025-01-08T00:00:00Z') and changeTime < todatetime('2025-01-09T00:00:Z') // changeTime > ago(1d)
| order by changeTime desc
Logic Apps
以下が公式ドキュメントのチュートリアルです。
細かい説明は割愛しますが、以下を設定します。
- Logic Apps のマネージド ID をオン
- 当該マネージド ID に、サブスクリプションの閲覧者権限を付与
- 当該マネージド ID に、書き込みたいストレージアカウントのあるリソースグループまたはストレージアカウントへ BLOB データ共同作成者権限を付与
パターン1:単に保存する場合
24時間に一度実行し、Resource Graph への HTTP リクエストを行い、JSON を解析し、BLOB に保存するだけの簡易なパターンです。
- HTTP リクエストのサンプル
- BLOB 作成のサンプル
この場合、サブスクリプションが複数あると混ざってしまい、特定のサブスクリプションのログが欲しいときに見つけることが困難になります。
これに対応するため、パターン2ではサブスクリプションと年月日でフォルダ分けしたうえでファイルを作成します (ファイル名を / で区切るだけ)。
パターン2:サブスクリプション毎にファイルを分ける
ここでのポイントは以下となります。
-
日時を変数化して HTTP リクエストする
- 前日の AM00:00:00~PM23:59:59(AM00:00:00未満) を範囲とする
- Recurrence に関しては、開始時刻を直近ではなく AM01:00 に実行する
-
For each で SubscriptionId の重複を除外する
- 並列実行をオフにする必要アリ
- 並列実行をオフにする必要アリ
-
抽出した SubscriptionId を For each で回し、元データをフィルタする
- 並列実行はオン
-
抽出したデータをカンマで Join し、且つ [] で括り、JSON 配列化する
-
SubscriptionId と yyyy/MM/dd 形式のファイル名でフォルダ分けして書き込む
このパターンではサブスクリプション毎・年月日毎にフォルダ分けされたうえで JSON を作成し、BLOB に保存できます。
しかし、大量のデータがあった場合、並列実行していない部分が遅いと想定されます。それによりコスト増や実運用に耐えられるか等も懸念点となります。キレイでもないです。
このパターンのネックである、SubscriptionId の重複削除を For each を使用しないようにしたものがパターン3です。
パターン3:サブスクリプション毎にファイル名を分ける (並列実行版)
ここでのポイントは以下となります。
- Select で SubscriptionId だけを射影する
- union で SubscriptionId の重複を削除する
- Compose でデータの作成を行うことで、For each で並列実行しても変数割当を正常に行う
- 並列実行時、Initialize variables では一件一件 SubscriptionId が変数に割当されないため同じ SubscriptionId で書き込もうとしてコンフリクトします。
- 並列実行時、Initialize variables では一件一件 SubscriptionId が変数に割当されないため同じ SubscriptionId で書き込もうとしてコンフリクトします。
実際のデータを見てみる
-
サブスクリプション毎にフォルダ分け
-
年月日でフォルダ分け
-
JSON ファイルの中身
実際にパターン3のほうが有意に早くなります。
この記事が参考になれば幸甚です。
Discussion