Claude Codeでaz graph query — Azure Resource Graphで全資源を棚卸し
az graph queryで全サブスクリプションの資源を種類別・リージョン別・タグ無しで数える手順です。クエリはClaude Codeに書かせ、件数の合計は別のクエリとポータルで突き合わせます。
Azure Resource Graphは、複数のサブスクリプションをまたいで資源を問い合わせるためのAzureのサービスです。複雑な絞り込み、グループ化、並べ替えを資源のプロパティに対して行えます。コマンドラインからはaz graph queryで使います。
資源の棚卸しで厄介なのは、クエリを書く手間よりも「出てきた件数が本当に全件か」を言い切れないことです。この記事では、クエリはClaude Codeに書かせ、件数の検算は人の目ではなく別のクエリとスクリプトに任せる構成を組みます。
棚卸しの前に押さえるAzure Resource Graphの性質
最初に、結果の読み方を左右する性質を確認します。ここを外すと、後の突き合わせで合わない理由が分からなくなります。
クエリを書かせる前に前提にしておく4点
- クエリ言語はAzure Data Explorerで使うKQL(Kusto Query Language)をベースにしている
- 参照できるのは、実行したユーザーが読み取り権限を持つサブスクリプションの資源だけ
- 結果は強い整合性を持たず、インデックスされるまでに短い遅延がある
- 既定では1クエリあたり1,000件までしか返さない
読み取り権限が無い範囲は、エラーにならず結果から外れます。複数のサブスクリプションを渡した場合、一部にしか権限が無くても、権限のある分だけが返ります。「件数が少ない」ときに疑うのは、まずクエリより権限です。
データの鮮度にも注意が要ります。資源が更新されるとAzure Resource ManagerがResource Graphに通知し、Resource Graphは定期的な全体スキャンも行います。それでも直前に作った資源がすぐに載るとは限りません。
準備: 拡張機能の導入とスコープの確認
az graph queryを使うには、Azure CLIのResource Graph拡張機能が必要です。Azure CLIは2.22.0以上が前提です。初回実行時にインストールを促すプロンプトが出ますが、手動で入れることもできます。
az extension add --name resource-graph
az login
az account list --output table
az graph query --helpaz account listでログイン中のアカウントから見えるサブスクリプションを確認します。az graph queryは、サブスクリプションを指定しなければアクセスできる全サブスクリプションを対象にします。範囲を絞るときは--subscriptionsか--management-groupsを使い、テナント全体を指定する場合は管理グループの引数にテナントIDを渡します。
スコープには上限もあります。管理グループを指定すると、その階層に含まれる最初の10,000サブスクリプションまでが対象です。権限のあるサブスクリプションが10,000を超えると、応答はその範囲に切られ、x-ms-tenant-subscription-limit-hitヘッダーがtrueで返ります。
全資源を数える5本のクエリ
棚卸しの核はsummarize count()です。まず総数を取り、その総数を分解する形で切り口を増やします。列名をtotalのように自分で付けておくと、後でスクリプトから拾いやすくなります。
総数から分解していく順番
- 1
総数を取る
Resources | summarize total=count() - 2
種類別に数える
Resources | summarize n=count() by type | order by n desc - 3
リージョン別に数える
Resources | summarize n=count() by location | order by n desc - 4
タグ無しの資源を数える
Resources | where isempty(tags) | summarize untagged=count() - 5
サブスクリプション別に数える
Resources | summarize n=count() by subscriptionId | order by n desc
コマンドラインに載せるときは、クエリ全体を引用符で囲みます。
az graph query -q "Resources | summarize total=count()"
az graph query -q "Resources | summarize n=count() by type | order by n desc"typeの値はmicrosoft.network/networksecuritygroupsのように小文字で返ります。種類を指定して絞るときは、大文字小文字を区別しない=~を使うと取りこぼしません。プロパティ側の比較は==だと大文字小文字を区別するので、properties.…を見るクエリは値の大小を一次出力で確かめてから書きます。
サブスクリプション名で見たいとき
subscriptionIdのままでは報告に使いにくいので、ResourceContainersテーブルと結合して名前を付けます。このテーブルには管理グループ、サブスクリプション、リソースグループの情報が入っています。
Resources
| join kind=leftouter (
ResourceContainers
| where type =~ 'microsoft.resources/subscriptions'
| project SubName=name, subscriptionId
) on subscriptionId
| summarize n=count() by SubName
| order by n desc結合を使うときは、1クエリに入れられるjoinとunionの合計は3つまでです。結合の左側には小さい結果集合を置くのが推奨されています。
Claude Codeにクエリを書かせる
クエリの下書きはClaude Codeが得意な部分です。ただし「何を数えたいか」を曖昧にすると、limitで打ち切った一覧や、並べ替えの無い結果が混ざります。CLAUDE.mdに、Resource Graphを扱う約束事を書いておくと安定します。
## Azure Resource Graph
- 実行するのは `az graph query` だけ。az の変更系コマンドは使わない。
- 件数を出すときは `summarize` を使い、`limit` や `top` で打ち切らない。
- 列挙クエリは必ず `order by` を付け、`--first` は 1000 以下にする。
- 結果の合計は scripts/arg-verify.sh で検算する。limitで打ち切るなと書く理由は、後述するページングの仕様にあります。limitやtakeを含むクエリでは、結果が途中で切られてもページングのためのトークンが返りません。
権限は「問い合わせだけ」に絞る
azコマンドは、Claude Codeが読み取り専用として自動許可するコマンド群には含まれません。何も設定しなければ毎回確認が出るので、az graph queryだけを許可し、削除系を拒否します。Bash(az graph query *)のように*をサブコマンドの後ろに置くと、az graph query単体も含めて一致します。
{
"permissions": {
"allow": [
"Bash(az graph query *)",
"Bash(az account list *)",
"Bash(az account show *)",
"Bash(bash scripts/arg-verify.sh)"
],
"deny": [
"Bash(az group delete *)",
"Bash(az resource delete *)"
]
}
}*を含まないBash(bash scripts/arg-verify.sh)は、その1コマンドだけに一致します。Claude Codeの許可ルールは&&や|などの区切りごとに判定されるので、az … | jq …のようなパイプはjq側にも許可が要ります。検算をスクリプトにまとめ、スクリプトだけを許可すると、この問題を避けられます。
denyは、より具体的なallowがあっても優先されます。削除系の拒否は、他のルールをどう足しても外れません。
非対話実行でレポートまで回す
定期的に棚卸しするなら、claude -pで非対話に実行できます。--allowedToolsで許可を絞り、--permission-mode dontAskにすると、許可に当てはまらない呼び出しは確認なしで拒否されます。人がいない実行で止まらないための組み合わせです。
claude -p "scripts/arg-verify.sh を実行し、不一致があれば原因の候補を挙げて" \
--allowedTools "Bash(bash scripts/arg-verify.sh)" \
--permission-mode dontAsk \
--output-format json | jq -r '.result'--output-format jsonにすると、結果がresultフィールドに入り、コストなどのメタデータも付きます。jqでその部分だけを取り出せます。
件数を突き合わせる
棚卸しの価値は、合計を信用できるかで決まります。ここでは3段階で確かめます。
同じ総数を別の切り口から再構成する
総数、種類別の合計、リージョン別の合計、サブスクリプション別の合計は、同じテーブルを同じスコープで数えているので一致するはずです。タグ付きとタグ無しの合計も総数に一致します。次のスクリプトは、その等式を機械的に確かめます。
#!/usr/bin/env bash
set -euo pipefail
q() { az graph query -q "$1" -o json | jq -r "$2"; }
total=$(q 'Resources | summarize total=count()' '.data[0].total')
by_type=$(q 'Resources | summarize n=count() by type
| summarize s=sum(n)' '.data[0].s')
by_loc=$(q 'Resources | summarize n=count() by location
| summarize s=sum(n)' '.data[0].s')
by_sub=$(q 'Resources | summarize n=count() by subscriptionId
| summarize s=sum(n)' '.data[0].s')
tagged=$(q 'Resources | where isnotempty(tags)
| summarize c=count()' '.data[0].c')
untagged=$(q 'Resources | where isempty(tags)
| summarize c=count()' '.data[0].c')
echo "total=$total type=$by_type location=$by_loc sub=$by_sub"
echo "tagged+untagged=$((tagged + untagged))"
[ "$total" = "$by_type" ] && [ "$total" = "$by_loc" ] \
&& [ "$total" = "$by_sub" ] \
&& [ "$total" = "$((tagged + untagged))" ] \
&& echo OK || { echo MISMATCH; exit 1; }az graph queryの出力JSONで、行がdata配列に入ることは、実行してお手元で確かめてからスクリプトに固定してください。上のスクリプトはその前提で.data[0]を読んでいます。
集計クエリは返る行数が少ないので、1,000件の既定上限には当たりません。上限に当たるのは、資源を1件ずつ列挙するクエリです。
一覧が欠けていないかを見る
資源を1行ずつ書き出す場合は、1,000件の上限が効きます。--firstの最大値も1,000です。それを超えるなら--skipで読み進めます。
az graph query -q "Resources | project id, name | order by id asc" \
--first 1000 --skip 3000REST APIの応答にはtotalRecordsとcountがあり、countがtotalRecordsより小さければ、まだ返っていない行があります。resultTruncatedがtrueのときも同じ意味です。一覧の行数と、先ほどの総数が一致するかを最後に必ず見ます。
並べ替えのないskipやfirstは、毎回違う行が返ることがあります。ページ送りをするクエリには、idのように重複しない列でorder byを付けます。
ポータルの件数と照らし合わせる
Azure Resource Graph Explorerは、Azureポータルの中でクエリを実行できる画面です。ポータルの検索バーや「すべてのリソース」の画面も、Resource Graphを土台にしています。そのため、同じスコープで見れば件数は近い値になります。
照らし合わせるときは、次の表のどれに当たるかを順に疑います。
| 食い違いの出方 | 疑う原因 | 確かめ方 |
|---|---|---|
| ポータルのほうが多い | 疑う原因クエリ実行者に読み取り権限が無いサブスクリプションがある | 確かめ方az account listの一覧とポータルの絞り込みを揃える |
| 直前に作った資源だけ無い | 疑う原因インデックスまでの短い遅延 | 確かめ方時間を置いて再実行する |
| サブスクリプションやリソースグループの数が合わない | 疑う原因Resourcesにはそれらが入っていない | 確かめ方ResourceContainersを別に数える |
| 一覧の行数だけが足りない | 疑う原因1,000件の既定上限やlimitの打ち切り | 確かめ方--firstと--skipで読み進め、totalRecordsを見る |
| たまに空やエラーになる | 疑う原因30秒のタイムアウトまたはスロットリング | 確かめ方スコープを小さくして分割して実行する |
Resourcesテーブルにはサブスクリプションやリソースグループが入らず、それらはResourceContainersにあります。「資源の総数」を報告するときは、どちらを数えたのかを明記します。
また、Resources以外にも、ポリシーや権限、変更履歴などを持つ別のテーブルがあります。全部を1本のクエリで数えられるとは限らないので、棚卸しの対象テーブルは先に決めておきます。
実行回数が増えたときの落とし穴
Resource Graphは無償のサービスで、クエリはユーザー単位でスロットリングされます。応答にはクォータの残りを示すx-ms-user-quota-remainingと、リセットまでの時間を示すx-ms-user-quota-resets-afterが付きます。例として、5秒のウィンドウにつき最大15クエリという値が挙げられていますが、クォータは変わりうる値です。
サブスクリプションごとにクエリを分けて回すより、まとめて1本で問い合わせるほうがクォータの消費は小さくなります。まとめるときの目安は、1回のグループサイズを300未満にすることです。
Claude Codeに「全サブスクリプションについて、順に確かめて」と頼むと、サブスクリプション単位のループを組みがちです。CLAUDE.mdに「サブスクリプションをまとめて渡す」と書いておくか、summarize … by subscriptionIdを使わせると、クエリ数を抑えられます。
クエリのタイムアウトは30秒です。重いクエリが落ちたら、対象のサブスクリプションを減らして再実行します。
Azure MCPサーバーや他のクラウドとの使い分け
Resource Graphは「資源を横断して数える・探す」ためのサービスで、資源を作ったり消したりはしません。変更を伴う作業は、別の記事にある型に分けると安全です。たとえばAzure MCPサーバーはClaudeから個別のAzure資源を操作する入口で、Bicepとwhat-ifは変更を承認の境界で止める構成を扱っています。
AWSで同じ棚卸しをする場合は、describe系だけ許可し削除はTerraformに任せる構成が対になります。KQLを使う別の題材には、Entra IDのサインインログをKQLで調べる手順があります。非対話実行の基本はclaude -pの解説、許可の絞り方はallowedToolsとdisallowedToolsにまとめています。
まとめ
Resource Graphの棚卸しは、クエリ自体は短く済みます。時間を取られるのは、その結果を全件として扱ってよいかの判断です。クエリはClaude Codeに任せ、等式で確かめられる部分はスクリプトに、権限や遅延に由来する差はポータルとの照合に分けると、報告する数字に根拠を付けられます。
まずsummarize total=count()を1本実行し、種類別・リージョン別の合計がそれと一致するかを確かめるところから始めると、スコープや権限の問題が早めに見つかります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- learn.microsoft.com/en-us/azure/governance/resource-graph/overview
- learn.microsoft.com/en-us/azure/governance/resource-graph/first-query-azurecli
- learn.microsoft.com/en-us/azure/governance/resource-graph/samples/starter
- learn.microsoft.com/en-us/azure/governance/resource-graph/concepts/work-with-d…
- learn.microsoft.com/en-us/azure/governance/resource-graph/concepts/query-langu…
- learn.microsoft.com/en-us/azure/governance/resource-graph/concepts/guidance-fo…
- code.claude.com/docs/en/headless
- code.claude.com/docs/en/permissions