
先说个现象。很多团队的云治理一开始都是靠人盯订阅少的时候还能应付等规模上来之后光靠 Azure Portal 一个个点开资源组看策略是否合规效率就完全跟不上了。这时候 Azure Policy 已经把能不能创建这个资源、资源必须带哪些标签这种治理规则铺到了订阅里真正缺的是一个统一的、跨订阅的、能快速回答哪些资源不合规、哪个策略被分到了哪个作用域、内置策略和自定义策略各有多少的查询通道。Azure Resource Graph简称 ARG就是干这个用的。这篇内容围绕用 Azure Resource Graph 查询策略分配、符合性状态以及策略定义展开适合正在做云治理、安全审计、成本管控或迁移评估的运维工程师、DevOps 和架构师。不夸张地说看完之后你基本能够丢掉到处点鼠标的习惯直接用一条 KQL 把治理台账拉出来再也不用靠 Excel 手工汇总。1. 场景破题为什么查 Azure Policy 资料要用 ARG 而不是控制台点鼠标1.1 云治理的信息迷雾困境说一个实际场景某团队管理 8 个订阅里面既有生产环境又有测试环境创建了 30 多个策略分配既有内置策略比如不允许创建公网 IP 的虚拟机又有自定义策略比如所有资源必须带 cost-center 标签。某天审计要求提供一次全量的合规性报告工程师的做法是什么大概率是打开 Policy 页面选中某个订阅再选中某个策略分配看下面列出多少不合法资源截图、复制再切换下一个。光是收集这些数据一个上午就没了。这是一个典型的信息孤岛问题数据存在但分散在多个视图中缺少一个能全局检索的入口。更麻烦的是策略定义、策略分配、符合性状态这三类数据本身是相互关联的但在 Portal 上你通常需要跳转多个页面才能把它们对应起来。策略分配到某个管理组定义在另一个页面符合性评估结果又是另一个接口返回的东西。要手动把这些数据对齐几乎等于在几千条 JSON 里做 vlookup。这时候就到了 ARG 该出场的时机。Azure Resource Graph 本质上是 Azure 控制平面之上的一个索引服务它预先把资源属性、策略信息、变更历史等构建成可查询的数据表通过 Kusto Query LanguageKQL提供跨订阅的快速检索能力。最关键的一点它同一时刻可以扫过你拥有读取权限的所有订阅而不是让你一个个订阅去切换查。这条特性能直接解决信息迷雾里的第一个问题看不到全貌。1.2 ARG 相比其他查询路径的差异化优势先别急着直接写 KQL我们需要把这条技术路线和其他几种可行路线对比一下你才知道什么时候用哪个。用 Azure CLI 的az policy assignment list之类命令去查当然也可以但它的输出是一次性快照拿到的是某个资源某时刻的状态。如果你想做的是所有策略分配的完整清单 每个分配对应的符合性统计 策略定义的类别和参数CLI 得写好几条命令再用脚本把结果串起来过程中还要处理 JSON 嵌套和分页问题。而且 CLI 命令通常面向管理操作不是面向复杂条件检索它的输出格式和查询能力都有限。用 ARM API 去查属于另一条路径灵活度更高能拿到原始 JSON但代价是要处理 API 版本、分页 token、不同资源类型的不同接口写出的脚本维护成本很高。ARG 相当于把繁杂的 API 拼接这一步给你省掉了让你直接对着已经整理好的逻辑视图写查询。这里要补充一个容易忽略的关键点ARG 的数据是有索引延迟的。它不是实时数据源而是一个异步构建的索引。资源属性变化之后通常要等几分钟才会在 ARG 中反映。这点我会在后面的排查部分详细展开。所以如果你要做的操作是对实时性要求极高的合规强校验比如刚创建完策略就立刻查那 ARG 未必是第一选择但如果你是要生成日报、周报、审计报表或者做一个全局治理大盘ARG 就是最优解。它的查询响应通常在秒级能在几秒内扫完成千上万的资源并给出聚合结果这个能力是 Portal 点鼠标完全给不了的。我做一个简单对比表方便你按场景快速决策查询方式跨订阅能力复杂条件检索实时性脚本友好度典型场景Azure Portal Policy 页面弱需逐订阅切换弱视图像素级检索较高差单资源排查、临时查看Azure CLI/Az PowerShell中需要循环订阅中命令受参数限制较高直接调控制平面中运维脚本、自动化修复ARM API中需要写循环和 token 处理高但需要拼接大量接口较高中深度集成、平台开发Azure Resource Graph强一次查询覆盖全部授权订阅强KQL 支持聚合、过滤、join中等索引延迟分钟级高治理盘点、报表统计、监控大盘、审计查询2. 先补基础策略分配、符合性状态与策略定义在 ARG 里到底长什么样2.1 三个核心概念的分工在写查询之前必须把这三个术语的关系理顺否则你在 KQL 里面很容易混淆字段来源。策略定义Policy Definition是最上层的规则描述它回答规则是什么。内置定义由微软提供比如Allowed locations定义了一个区域白名单自定义定义则由团队根据自身需求编写 JSON比如所有资源必须包含某个标签。一个定义本身不产生任何实际效果它就是一份规则说明书。定义还分 Policy 和 Initiative即 PolicySetDefinition策略计划后者是一组策略定义的集合。在 ARG 的 policyresources 表中这两种定义都在类型有microsoft.authorization/policydefinitions和microsoft.authorization/policysetdefinitions。策略分配Policy Assignment是把规则落到实际作用域的动作它回答规则在哪生效。同一个定义可以分配给管理组、订阅或资源组而且在分配时可以传入参数值比如定义是允许的区域列表分配时具体填入中国东部、中国北部这些值。所以一个定义可能对应多个分配每个分配在自己的作用域内产生评估结果。符合性状态Compliance State是策略评估之后产出的结果回答资源当前是否符合规则。这个状态来自 Policy Insights 的持续评估和按需评估结果。常见状态包括 Compliant合规、NonCompliant不合规、NotStarted未开始评估、Exempt豁免和 Conflict多个策略分配规则冲突导致无法判定等。这三个概念是三层结构定义是模板分配是实例符合性状态是实例作用于真实资源后的反馈。在 ARG 里定义和分配存在policyresources表符合性状态则主要在policyinsights表确切说ARG 暴露为policyinsights的扩展资源类型中体现。如果你已经把资源管理和治理数据混在一起那在查询时就要特别注意这两类数据表的使用区别。2.2 ARG 中 policyresources 和 policyinsights 两张表的数据结构打开 ARG Explorer 的 schema 侧边栏你就能看到policyresources这个资源表。它有点特殊因为它存储的不是单一资源类型的属性而是多个授权相关资源类型的聚合。在type字段里你可以看到microsoft.authorization/policydefinitions、microsoft.authorization/policyassignments、microsoft.authorization/policysetdefinitions等类型值。换句话说你只需要查policyresources这张表再按 type 过滤就能把定义和分配统一捞出来。policyresources中比较关键的字段包括name资源名称。对定义而言是定义名对分配而言是分配名。type资源类型用于区分定义、计划、分配。subscriptionId作用域所在的订阅。注意管理组层面的定义或分配subscriptionId 可能为空。properties.displayName显示名称通常比 name 字段更可读。properties.policyDefinitionId分配关联的策略定义 ID格式类似/subscriptions/{subId}/providers/Microsoft.Authorization/policyDefinitions/{name}。properties.policyDefinitionReferenceId普通分配没有只有计划分配Initiative Assignment中引用特定策略时才出现。properties.scope分配的作用域这是核心字段。properties.parameters分配的参数化配置JSON 格式。policyinsights表承载的是策略评估后的符合性状态数据。它相比policyresources更动态因为它是评估引擎不断写入的结果。在这个表里你会看到resourceId被评估的资源的完整 Azure 资源 ID。properties.complianceState合规状态值如 Compliant、NonCompliant。properties.policyAssignmentName对应策略分配的名称。properties.policyDefinitionName对应策略定义名称。properties.policyAssignmentScope策略分配的作用域。properties.timestamp评估结果的时间戳。把这两张表搞清楚了接下来写查询就是在做两张逻辑表的关联和过滤。打个比方policyresources相当于项目的配置清单policyinsights相当于项目的质检工单。前者告诉你我们定了哪些标准在哪些区域执行后者告诉你哪些工件通过了、哪些违反了标准。2.3 理解查询延迟和索引窗口使用 ARG 一定要在心里放一根弦这不是实时数据库。ARG 的后端会持续从 Azure Resource Manager 拉取资源状态、策略评估结果然后构建索引进可查询的表。但这个过程有天然延迟通常官方建议你接受分钟级延迟。我自己实测下来资源的新增和策略分配的变更在几分钟内基本能查到但有些极端情况比如评估任务还没跑完可能需要更久。这样设计是有原因的。ARG 追求的是大规模的快速查询它用预建索引换取了搜索性能。如果你能做到实时那意味着每次查询都要直接打到控制平面做全量扫描速度和跨订阅能力就会大打折扣。所以 ARG 的定位从来不是替代 ARM API而是提供一个面向治理、检索、聚合场景的分析型数据源。理解了这一点你就知道什么时候该用 ARG什么时候该用az policy state list这些命令直接找 Policy Insights 接口。3. 实操实录核心 KQL 查询与真实返回解读3.1 查询所有策略分配先给治理台账拍一张快照既然是要借助 ARG 查询那第一个实操就是从策略分配这个治理动作入口展开。在 ARG Explorer 里输入下面这条查询policyresources | where type ~ microsoft.authorization/policyassignments | project subscriptionId, name, properties.displayName, properties.scope, properties.policyDefinitionId, properties.parameters | sort by subscriptionId asc, properties.scope asc这条查询会返回当前你拥有读取权限的所有订阅中的所有策略分配。这里的关键过滤条件是type ~ microsoft.authorization/policyassignments大小写不敏感匹配这样就把表内混杂的 policydefinitions 和 policysetdefinitions 过滤掉了。project则用来裁剪字段让输出结果更聚焦。一个很实用的进阶版本是顺带统计每个策略分配在哪个作用域、挂在哪个订阅下同时把策略定义类型Policy 还是 Initiative也标记出来policyresources | where type ~ microsoft.authorization/policyassignments | extend definitionId tolower(properties.policyDefinitionId) | join kind leftouter ( policyresources | where type ~ microsoft.authorization/policydefinitions or type ~ microsoft.authorization/policysetdefinitions | project definitionId tolower(id), definitionType type, definitionDisplayName properties.displayName ) on definitionId | project subscriptionId, assignmentName name, assignmentDisplayName properties.displayName, scope properties.scope, definitionId, definitionType, definitionDisplayName | order by scope asc这里用join把策略分配和策略定义关联起来了通过policyDefinitionId字段匹配到定义表里的id字段。注意我把两边都做了tolower原因是不同来源的 ID 可能大小写不一致直接 join 很容易漏数据。这是我在实战中踩过的坑后来养成了 join 之前先规范字段的习惯。输出结果后你应该就能看到一份完整的策略分配台账。如果你管理多个订阅并且启用了管理组分配可能出现在管理组层级这时候properties.scope就会显示为/providers/Microsoft.Management/managementGroups/{mgName}订阅字段为空。遇到这种情况不要慌说明该分配管的是这个管理组下的所有子订阅。3.2 查询策略定义把内置策略和自定义策略拉成一张清单策略定义的数据也是放在policyresources里的区别在于 type 不同。要查询所有策略定义不含计划可以这样写policyresources | where type ~ microsoft.authorization/policydefinitions | project id, name, displayName properties.displayName, policyType properties.policyType, description properties.description | sort by policyType asc, displayName asc注意properties.policyType这个字段它会区分BuiltIn内置和Custom自定义。这个字段在做治理盘点时价值极高。内置策略大多是微软预设的通用基线自定义策略才是你团队真正投入精力设计的东西。所以你可以用下面这条快速统计自定义策略的占比policyresources | where type ~ microsoft.authorization/policydefinitions | summarize count() by policyType properties.policyType这条查询返回两行结果BuiltIn 多少个Custom 多少个。如果你发现 Custom 数量特别少说明团队治理能力还比较初级大量规则依赖微软默认基线反过来如果一个订阅下 Custom 比 BuiltIn 还多治理成本会显著上升需要关注策略之间的覆盖和冲突。如果你要查的是 Initiative策略计划即一组策略定义的集合那 type 改为microsoft.authorization/policysetdefinitions即可。在我们的治理实践中计划的使用非常推荐因为将多个定义打包成一个合规框架之后可以一次性分配给订阅或管理组后续查询也更好对应。在 ARG 中查询计划分配的关联定义时稍微麻烦一点因为计划分配引用的是计划中的各个定义引用 ID你需要解析properties.policyDefinitionReferenceId字段来关联。一个实际可用的扩展查询把分配给某个管理组或订阅的所有策略定义、计划以及它们的类型一并列出来policyresources | where type ~ microsoft.authorization/policyassignments | where properties.scope startswith /providers/Microsoft.Management/managementGroups/contoso-mg | project assignmentName name, definitionId tolower(properties.policyDefinitionId) | join kind innerunique ( policyresources | where type ~ microsoft.authorization/policydefinitions or type ~ microsoft.authorization/policysetdefinitions | project definitionId tolower(id), definitionType type, displayName properties.displayName, policyType properties.policyType ) on definitionId | project-away definitionId这条我会在生成治理报告时反复使用。它比在 Portal 上逐个展开管理组里的分配快得多而且可以直接灌进 Power BI 或 Excel 做后续分析。3.3 查询符合性状态找到不合规资源才是治理的第一动力列出定义和分配只是第一步真正让治理产生价值的是找出不合规资源。这部分数据在policyinsights表里。下方这条查询是日常巡检的高频入口policyinsights | where properties.complianceState NonCompliant | project resourceId, policyAssignmentName properties.policyAssignmentName, policyDefinitionName properties.policyDefinitionName, complianceState properties.complianceState, assignmentScope properties.policyAssignmentScope | take 1000如果需要按订阅聚合统计不合规资源数量可以使用policyinsights | where properties.complianceState NonCompliant | extend subscriptionId tostring(split(resourceId, /)[2]) | summarize nonCompliantResourceCount count() by subscriptionId | order by nonCompliantResourceCount desc这条查询在处理跨订阅巡检时价值明显。它直接按订阅分组统计了不合规资源数让你一眼就能看出哪个订阅是重灾区。我通常在月报里跑这条在订阅数量几十个的情况下这条查询仍然能在几秒内返回结果这在以前很难做到。如果你要更进一步希望看到哪个策略分配导致的不合规数量最多就再按分配名称聚合policyinsights | where properties.complianceState NonCompliant | summarize nonCompliantCount count() by policyAssignmentName properties.policyAssignmentName, policyDefinitionName properties.policyDefinitionName | order by nonCompliantCount desc返回结果的第一行往往就是当前治理缺口最大的策略值得优先处理。我见过很多团队的策略评估结果一塌糊涂但根本不知道问题出在哪条策略上就是因为没有做这种聚合分析。用 ARG 跑一次这个查询答案就摆在眼前了。3.4 进阶组合跨订阅汇总、按资源分组、关联分配与定义前面几个查询是单个逻辑表的操作但 ARG 真正的威力在跨表关联。除了join之外你还可以把策略数据和资源数据联合使用。比如想查看所有不合规虚拟机及其所在资源组policyinsights | where properties.complianceState NonCompliant | where resourceId startswith /subscriptions/ | project resourceId, policyAssignmentName properties.policyAssignmentName, policyDefinitionName properties.policyDefinitionName | join kind innerunique ( resources | where type ~ microsoft.compute/virtualmachines | project resourceId id, vmName name, resourceGroup, location ) on resourceId | project resourceId, vmName, resourceGroup, location, policyAssignmentName, policyDefinitionName这条查询把策略评估结果和资源表连接起来了定位到哪台虚拟机因为哪条策略不合规。相比去 Portal 一条条翻这个方式可以直接输出表格给负责整改的同事让他们分区域、分资源组去修复。这也是 ARG 跨数据源关联的典型用法。还可以做时间维度上的洞察。policyinsights表带时间戳你可以查询某个时间点之后的合规状态变化用于运维报告和整改效果跟踪policyinsights | where properties.complianceState NonCompliant | where properties.timestamp datetime(2025-01-01T00:00:00Z) | summarize nonCompliantCount count() by bin(properties.timestamp, 1d) | order by properties.timestamp asc这条按天聚合的查询可以生成一张趋势图观察不合规数量随时间的变化。在整改推动过程中这种趋势数据很有说服力到底是策略太严格、还是资源老化、还是评估规则触发有问题趋势一目了然。当然用得多了你会发展出自己的查询库但上面这些是入门的金三角。4. 常见问题与排查技巧实录4.1 为什么我查不到刚创建的策略最常被问到的问题就是我刚刚创建了一个自定义策略分配为什么 ARG 里查不到原因几乎都是索引延迟。ARG 建立索引需要时间官方不保证实时性。策略分配这种控制平面资源变更后通常要等几分钟才能被索引。我的建议是不要刚操作完立刻去 ARG 验证结果稍微等一下再查。如果等了几分钟依然没有再去检查权限、作用域和类型过滤条件。日常自动化脚本里如果对实时性要求高可以在创建策略后用 ARM API 或 CLI 做即时验证如果只是做治理报表那 ARG 的延迟完全可以接受。另外值得注意policyinsights表里的符合性状态不是立刻生成的。Azure Policy 的评估是异步执行的你在创建完策略分配后需要等待首次评估完成才会产出状态结果。如果你发现 ARG 里policyinsights是空的先确认策略分配是否已生效、评估是否已经触发必要时手动触发评估在 Azure Policy 页面点评估按钮。4.2 查询结果超过 1000 条怎么办ARG 查询默认单次返回最多 1000 条结果。这个限制经常让新手措手不及。如果你的资源数量很多只看到 1000 条却不提示就很容易产生数据是完整的错觉。解决办法有几种。第一种是用summarize做聚合让返回行数下降比如统计计数而不是列出所有资源。第二种是使用分页在 PowerShell 的Search-AzGraph中通过分页参数循环拉取所有结果或使用 Azure CLI 中的--skip参数配合操作。第三种是优化查询条件增加过滤分批查询比如按订阅拆分或者按资源类型拆分。ARG 官方文档里其实有说明分页查询中如果不显式处理分页 token工具的 SDK 通常会帮你循环抓取但是在 ARG Explorer 网页里你更容易看到截断结果。我的经验是在写查询时尽量先做聚合和裁剪不要用project *全量返回这样既能减少数据量也更容易发现数据模式上的问题。4.3 权限不足和 API 报错处理ARG 查询不是所有身份都能直接跑通的。它要求查询者在目标订阅或管理组上至少拥有Microsoft.ResourceGraph/resources/read权限。如果你用的是订阅的 Reader 角色那没问题如果你用的是自定义角色就要注意是否包含这个权限点。很多时候你发现某条查询在其他订阅能跑通在某个订阅却返回空结果或报错原因就是缺少资源图读取权限而不是数据不存在。此外返回 HTTP 403 时常见的原因还包括跨租户查询时没有 Guest User 权限、管理组没有授权、或者某些订阅状态异常比如订阅被禁用。如果你正在做的是跨租户的 Azure Lighthouse 管理还要注意 ARG 对 delegated 资源的支持情况。排查这类问题最有效的办法是先限定单订阅、单资源组逐层缩小范围定位到底是什么环节报错。4.4 这几种查询最常用的落地场景分享两个我实际做过的高价值落地场景。第一个是年度治理审计。我们的合规团队要求提供一份所有策略分配的全量清单 每个分配的符合性统计表格。以前合规工程师需要从多个页面手工导出做出来要两天。后来我直接用 ARG 查了三张表policyresources得到定义和分配policyinsights做聚合统计再用summarize把两类数据合并成最终报告。当天下午就交付了之后每个月自动跑一次配合Export to CSV就能生成口径一致的合规月报。第二个场景是迁移评估。我们准备把一批老旧虚拟机从本地数据中心迁移到 Azure但不确定它们是否符合当前订阅中的策略要求。我先用 ARG 查询了目标订阅的全部策略定义整理出现有规则清单然后在资源尚未真正创建时就知道了哪些策略可能会卡住迁移。我们甚至用 ARG 模拟了迁移后的状态将这些规则与新资源的属性做对照。这个方法在计划迁移阶段特别实用避免了资源和成本投入后才发现策略不匹配的尴尬。说到底ARG 的核心价值是让你在治理层面不再盲人摸象。你可以用 ARG 做资产盘点可以用它做合规检查还可以把它接入自动化流程定期清理不合规资源。我个人的经验是凡是涉及跨订阅的批量检索和治理状态报告的场景优先想到 ARG 就对了。数据模型不需要背得很细打开 ARG Explorer 用提示功能边写边探索再配合本文这些查询骨架很快就能形成你自己的查询模板库。