先从一次真实的工作经历说起。去年我在一个多订阅环境里做云治理巡检,管理层要求一份“当前所有策略分配执行情况”和“不合规资源分布”的汇总清单。如果用 Azure 门户自带的策略符合性仪表盘,一个分配一个分配地翻,再跨订阅比对,运气好也要大半天;而且结果很难复现,也没法固定成团队日常报表。后来我把统计口径切到 Azure Policy 服务背后的 Azure Resource Graph,情况立刻变了:策略分配、符合性状态、策略定义这些数据,全都可以用类似 SQL 的 KQL 查询一次拉出来,还能按订阅、资源组、资源类型聚合,几分钟就能输出一份可共享的清单。
这个经验后来被我沉淀成了一套固定脚本,团队里新同事也能照着跑。这篇内容就围绕“如何借助 Azure Resource Graph 查询策略分配、符合性状态以及策略定义”展开,把我踩过的坑、验证过的 KQL 语句和排查思路都整理出来。不管你是刚接触云策略治理的运维,还是已经在用 Azure Policy 但被门户折腾得头疼的管理员,这篇文章应该都能给你几条能直接上手的路径。
1. 为什么在策略治理里一定要会用 Resource Graph
1.1 传统管理界面有多难用
Azure Policy 在门户里的体验适合“单点操作”:新建策略、分配策略、查看某个分配下的符合性结果,沿着导航点开都能看到。但一旦资源规模上来,痛点就很明显。第一,策略符合性页面只展示当前选中的分配范围,一旦分配作用在管理组、订阅、资源组多个层级,就得在不同页面之间来回切换。第二,多个订阅之间没有统一视图,想看全局不合规数量,只能挨个订阅打开再手动汇总。第三,策略定义和分配这两个“源头数据”没有和资源合规结果放到一张表里,遇到“这个资源到底是被哪条策略击中的”这类问题,门户给出的信息很碎片化。
这些场景恰好是 Resource Graph 的主场。它本质是 Azure 的一个资源目录服务,会对订阅内的资源、策略数据、容器信息做统一索引。我们不需要登录每一台虚拟机去检查配置,也不需要翻策略引擎的日志,只需要对 Resource Graph 暴露出来的表格做查询。而且它支持跨订阅甚至跨管理组聚合,天然适合做治理巡检和报表输出。
1.2 Resource Graph 在策略治理中的定位
同样的信息,Azure Resource Manager API 也能查,比如 Policy Insights 相关的 REST API。但 API 返回的是 JSON 对象,字段嵌套很深,跨订阅循环要自己写逻辑,分页、租户切换都要处理。Resource Graph 则把这些数据表化,列名更接近业务语义,比如complianceState直接就是一个字符串列;再加上 KQL 的summarize、join、mv-expand等操作符,可以像处理数据库表一样处理整个云环境的策略状态。
所以在做 Azure Policy 治理时,我会把 Resource Graph 当成“策略数据的统一查询层”:策略定义和分配沉淀在policyDefinitions、policyAssignments表,资源最终评估结果沉淀在policyResources表。只要把这三类对象搞清,绝大多数查询需求都能用两三行 KQL 解决,不需要额外建库或者写复杂的 PowerShell 循环。
2. 先搞明白三个核心对象
2.1 策略定义:规则从哪里来
策略定义是整个 Policy 服务的“规则本体”。一份策略定义就是一个 JSON 结构,里面包含policyRule(核心 if/then 逻辑),以及可供分配时调整的parameters。在 Resource Graph 的policyDefinitions表里,一条记录对应一个定义,常见列包括:
| 列名 | 说明 |
|---|---|
policyDefinitionName | 定义资源的名称,一般是小写加横杠 |
policyDefinitionDisplayName | 用户在门户中看到的中文/英文显示名 |
policyDefinitionPolicyType | Custom或BuiltIn,区分自定义和内置 |
policyDefinitionCategory | 定义所属分类,比如SecurityCenter、Storage |
policyDefinitionRule | 定义本身的规则体,动态类型,可直接展开查看 |
policyDefinitionParameters | 定义支持的参数对象 |
有个容易混淆的概念:policyDefinitionType和policyDefinitionPolicyType会同时出现,查询时注意用对列。我一般只关心policyDefinitionPolicyType是不是BuiltIn,这决定了这条规则是否能被直接分配给一个范围。比如审计某类存储账号是否开放匿名访问,通常会选内置的Audit效果定义,或者在它基础上做自定义。
2.2 策略分配:规则应用到谁身上
策略定义只是规则库,真正生效需要做“分配”。分配决定三件事:把哪条定义或哪个策略计划(Initiative,一组定义的集合)应用到哪个范围(管理组、订阅或资源组);分配时参数怎么填;是否强制启用。policyAssignments表就是用来记录这些分配动作的。常用列如下:
| 列名 | 说明 |
|---|---|
policyAssignmentName | 分配的资源名 |
policyAssignmentDisplayName | 分配显示名 |
policyAssignmentScope | 分配作用到的范围,可能是订阅或管理组路径 |
policyAssignmentEnforcementMode | Default表示强制,DoNotEnforce表示只报告不阻止 |
policyAssignmentParameters | 分配时传入的定义参数 |
policyAssignmentNotScopes | 显式排除的范围 |
很多人在查询时容易把“分配”和“定义”混在一起。比如想找“有哪些策略正在生效”,其实应该查policyAssignments表,而不是policyDefinitions表。一个定义可以分配给多个范围,产生多条分配记录;一条分配也可能引用策略集,包含多个定义。这个一对多关系,是理解后续合规查询的关键。
2.3 符合性状态:规则执行得怎么样
符合性状态是 Azure Policy 评估引擎对资源执行定义后的结果。它不单独存在,而是由评估过程产生,并且通过policyResources表暴露给 Resource Graph。每条记录可以理解为“某个资源在某条策略分配下的一次评估快照”。常用字段包括:
| 列名 | 说明 |
|---|---|
resourceId | 被评估资源的完整 ARM ID |
resourceType | 被评估资源的类型 |
resourceGroup/subscriptionId | 资源所在位置 |
policyAssignmentDisplayName/policyAssignmentName | 命中哪条分配 |
policyDefinitionDisplayName/policyDefinitionName | 命中哪条定义 |
policySetDefinitionDisplayName | 所属策略计划(如果是按计划分配) |
complianceState | 评估结果,常见Compliant、NonCompliant、NotApplicable、Error |
effectiveParameters | 实际参与评估的参数集合 |
实际使用policyResources表时,要注意它的数据不是即时刷新。Azure Policy 评估结果有一定延迟,原则性依赖的话,还是建议在门户“符合性”页面确认最后评估时间,再用 Resource Graph 批量拉取。不过在绝大多数报表场景下,这分钟级的延迟完全可以接受。
3. 查询前的环境准备与权限配置
3.1 需要的权限是什么
这是最容易栽跟头的地方。Resource Graph 能查到什么,取决于你在对应范围内有没有读权限。基础要求是需要对查询范围具备至少Reader角色;如果只想查策略相关数据,还需要有策略数据的读权限,例如Policy Reader角色。尤其是查询policyAssignments表时,如果只有普通读者角色,可能能看到资源,但看不到分配信息,结果查询出来为空。
实践中最稳妥的组合是在管理组或订阅层级分配“读者 + Policy Reader”。如果公司有统一的 RBAC 流程,可以自定义一个角色,包含Microsoft.Authorization/policyDefinitions/read、Microsoft.Authorization/policyAssignments/read、Microsoft.Authorization/policySetDefinitions/read和Microsoft.Resources/subscriptions/read这几个最小权限。这样既不影响日常运维,又能顺畅查询。
注意:当你在 Resource Graph Explorer 顶部的范围选择器里选定了“目录”或“管理组”,但账号没有在根管理组上获得足够权限时,查询结果可能被静默截断。遇到空结果或明显缺数据,优先怀疑权限,而不是 KQL 写错。
3.2 在门户里快速打开 Resource Graph Explorer
最快的方式是在 Azure 门户搜索栏输入“Resource Graph Explorer”,直接进入。界面左边是资源类型和表名,中间是查询窗口,右侧是输出网格。查询前先确认左上角范围,通常我会选“所有订阅”,这样policyResources能覆盖全部环境。如果你只想看某个管理组,也可以把范围定格到管理组,但要确保账号在该管理组有权限。
门户中还有“可视化”功能,能把查询结果转成图表,做简单报告很方便。不过图表的配置会随着查询字段变化,建议先把 KQL 输出的字段控制在可展示范围内,避免一张图里混入太多文本字段。
3.3 使用命令行工具做自动化
门户适合临时探查,但脚本化才是长期方案。Azure CLI 需要先安装resource-graph扩展:
az extension add --name resource-graph az account set --subscription "你的订阅ID" az graph query -q "policyResources | where type =~ 'microsoft.policyinsights/policystates' | where complianceState =~ 'NonCompliant' | project resourceId, policyAssignmentDisplayName"PowerShell 则依赖Az.ResourceGraph模块:
Install-Module -Name Az.ResourceGraph -Force Connect-AzAccount Search-AzGraph -Query "policyAssignments | where policyAssignmentEnforcementMode =~ 'Default' | project policyAssignmentDisplayName, policyAssignmentScope"这两个工具的好处是支持批量订阅查询。CLI 可以在命令里用--subscriptions参数传入多个订阅;PowerShell 的Search-AzGraph也支持-Subscription参数。输出出来后可以直接转成 CSV、JSON,或者推送给下游报表系统。我自己的习惯是先用门户写好 KQL,验证通过后,再落到.ps1或.sh脚本里,配合定时任务每天晚上跑一次。
4. 实战查询:从分配到符合性状态再到不合规资源
4.1 查询策略分配的两个高频场景
策略分配是治理的“入口”,可以先从整体分配情况看起。比如列出当前所有强制生效的分配:
policyAssignments | where policyAssignmentEnforcementMode =~ "Default" | project policyAssignmentName, policyAssignmentDisplayName, policyAssignmentScope, policyAssignmentParameters | sort by policyAssignmentScope asc这条查询直接过滤掉DoNotEnforce的分配,适合回答“我们到底强制了哪些策略”。如果一个分配作用在管理组、订阅、资源组不同层级,policyAssignmentScope字段会直接显示完整路径,一眼就能看出覆盖面。
如果只想看某个订阅下分配的策略计划(Initiative),可以加上where policyAssignmentScope contains "/subscriptions/你的订阅ID"。但注意contains是模糊匹配,可能误伤路径相近的记录。更精确的写法是:
policyAssignments | where policyAssignmentScope =~ "/subscriptions/你的订阅ID"这种写法对“某个资源组下的分配”“某个管理组下的分配”同样适用,只要把作用范围字符串填对。实际排障时,我经常先用这条查询把分配和定义的关系梳理清楚,再回头查符合性,避免被无效分配干扰。
4.2 查询策略定义清单
策略定义是所有规则的底座,但内置定义数量很大,全部拉出来会超过一千条,不适合直接灌进报表。建议先按类别或者按“自定义”过滤。比如查所有自定义策略:
policyDefinitions | where policyDefinitionPolicyType =~ "Custom" | project policyDefinitionName, policyDefinitionDisplayName, policyDefinitionCategory, policyDefinitionParameters | sort by policyDefinitionDisplayName asc如果需要看某个定义的具体规则体,直接project policyDefinitionRule即可,它会输出完整 JSON。这里有个小技巧:不要一次性输出所有字段,尤其是policyDefinitionRule很长,会把查询结果撑得很乱。先看名称和类别,确定目标之后再单独展示规则体。
查询内置定义也可以照葫芦画瓢:
policyDefinitions | where policyDefinitionPolicyType =~ "BuiltIn" | where policyDefinitionDisplayName contains "存储" | project policyDefinitionName, policyDefinitionDisplayName, policyDefinitionCategory这条适合快速定位某个类别下的内置策略。以前在门户里一个分类一个分类翻,现在用关键词过滤,秒级出结果。
4.3 分析资源的符合性状态
这是日常用得最多的查询。先看全局合规概览:
policyResources | where type =~ "microsoft.policyinsights/policystates" | summarize count() by complianceState | order by count_ desccount()默认生成的列名是count_,如果要在后续图表中使用,可以用count()asCount重命名。聚合后一般能看到Compliant、NonCompliant、NotApplicable、Error等状态。如果NonCompliant占比很高,别急着清理,先查具体是哪些定义导致。
按策略定义聚合不合规数量:
policyResources | where complianceState =~ "NonCompliant" | summarize NonCompliantCount = count() by policyDefinitionDisplayName, policyAssignmentDisplayName | order by NonCompliantCount desc | take 20这条查询的输出直接就是一张“Top 20 不合规来源”表。我在实际项目中遇到过很多次:门户显示一堆不合规,但责任归属不清楚。用这条查询一看,往往集中在两三条策略上,比如存储账号公网访问审计、虚拟机未启用托管磁盘、网络安全组默认规则过宽。定位到具体定义后,就可以判断是该调整资源,还是该修订策略参数。
4.4 定位具体不合规资源
如果想知道是哪台虚拟机、哪个存储账号不合规,用下面这条:
policyResources | where complianceState =~ "NonCompliant" | project subscriptionId, resourceGroup, resourceType, resourceId, policyDefinitionDisplayName, policyAssignmentDisplayName | sort by resourceType asc, resourceGroup asc输出里resourceId是完整资源路径,直接点击可以跳转到对应资源。如果需要附带订阅名称,可以用join把订阅表带进来:
policyResources | where complianceState =~ "NonCompliant" | join kind=leftouter ( resourcecontainers | where type =~ "microsoft.resources/subscriptions" | project subscriptionName = name, subscriptionId ) on subscriptionId | project subscriptionName, resourceGroup, resourceType, resourceId, policyDefinitionDisplayName这里用到resourcecontainers表,它是 Resource Graph 里存放管理组、订阅、资源组的容器表。join的on subscriptionId是逻辑关联键。注意leftouter保证即使有个别订阅在容器表里找不到,也不会把policyResources主记录直接丢弃。把订阅名称加进来,导出报表给非云背景的同事看,可读性会好很多。
4.5 结合生效参数排查疑难结果
有时候同一个策略定义下有大量不合规资源,但参差各不相同。比如一条“检查存储账号是否允许 blob 匿名访问”的策略,分配参数里可能指定了effect = Deny,也可能指定effect = Audit。Resource Graph 的effectiveParameters能查到运行时实际生效的参数:
policyResources | where complianceState =~ "NonCompliant" | where policyDefinitionDisplayName contains "存储" | project resourceId, effectiveParameters | take 20effectiveParameters是动态类型,输出结果是 JSON。想在表格里展开,可以用mv-expand:
policyResources | where complianceState =~ "NonCompliant" | where policyDefinitionDisplayName contains "存储" | mv-expand paramBag = parse_json(effectiveParameters) | project resourceId, ParamKey = tostring(paramBag[0]), ParamValue = tostring(paramBag[1]) | take 30这个方法比较粗暴,但对于参数不多的小策略足够用。真实项目里我更常用bag_keys或直接导出 JSON 后用 Excel 透视。核心思路是:不要让 Resource Graph 替你做太复杂的 JSON 解析,把原始参数先取出来,真正需要再结构化。
5. 避坑指南:我踩过的 Resource Graph 查询策略的坑
5.1 大小写和列名是第一个拦路虎
KQL 的运算符和函数通常不区分大小写,但字符串匹配值区分大小写,除非你用=~而不是==。比如complianceState的值可能是NonCompliant,也可能是noncompliant,实际环境里大小写并非完全一致。写死== "noncompliant"就可能漏数据。我的统一习惯是:所有状态值都用=~ "NonCompliant",既保证匹配值不区分大小写,也符合 KQL 的推荐写法。
还有一个更隐蔽的问题:Resource Graph 里有些表是“资源型表”,列名带properties前缀;但policyDefinitions、policyAssignments、policyResources这三张策略相关表大多已经扁平化。比如policyResources表里没有properties.complianceState,而是直接有complianceState列。如果照搬 REST API 的 JSON 结构写,大概率会报字段不存在,或者查询返回空。
5.2 查询结果为空,先查三件事
遇到空结果,我一般按顺序排查:第一,当前范围选对没有?Resource Graph Explorer 左上角如果只选中一个订阅,但你的资源在其他订阅,自然没有数据。第二,账号有没有对应范围的策略读取权限?检查一下自己在目标管理组是不是只有资源组读者权限。第三,数据是否真的存在?可以先跑一条不加任何过滤的policyResources | take 10,确认表里有数据。
如果加过滤后为空,再做二级排查:把where complianceState =~ "NonCompliant"去掉,先看有哪些状态值,确认字段拼写。很多时候是complianceState在当前环境里只有Compliant和NotApplicable,压根没有NonCompliant,那说明策略本身没有发现不合规资源,而不是查询写错。
5.3 分页和大量导出怎么办
Resource Graph 对单次查询结果默认返回有限行数,门户里大概最多拉几千行,命令行工具也类似。如果环境很大,比如不合规资源有几万条,直接take一下看前几条没问题,但导出全量就要处理分页。CLI 可以用--skip-token连续取下一页;PowerShell 的Search-AzGraph -ResultSize配合-Skip也可以翻页。更省事的办法是用查询里的summarize把数据先聚合,让结果总量降下来,再做分页或者直接导出。
我很少一次性拉全量明细。通常先聚合出“每个策略定义有多少不合规资源”,再把感兴趣的策略定义单独拉明细。这样既控制了查询规模,也减少浏览器或终端的渲染压力。
5.4 适合做报表的固定模板
使用一段时间后,我会把最常用的几条查询存成 Azure Resource Graph 的“已保存查询”。在门户里写好的 KQL 可以保存,下次直接打开;也可以把查询语句放到代码仓库里,方便团队维护。如果要做周期报表,建议用 PowerShell 脚本定时执行,并把结果写到存储账号或者推送到 Log Analytics。我个人的最小实现方案是:先建一个 Azure Automation Runbook,每天早晨跑一次Search-AzGraph,把不合规清单导出成 CSV,再通过邮件发送给相关团队。
如果你已经有 Azure Workbooks 或者 Power BI,Resource Graph 也是很好的数据源。最省事的路径是把查询结果可视化到门户 Workbook 中,团队成员甚至不用懂 KQL,每天刷新就能看到最新的合规状态。这种方式比让每个人都去门户里手动翻按钮高效得多。
最后分享一个我在实际项目里的习惯:不管查询看起来多简单,我都建议先跑最小范围的验证查询。比如想看全局合规,先跑policyResources | take 5,再慢慢加聚合和过滤。KQL 的好处是反馈快,坏处是错误提示有时候不够明确。每加一个过滤条件,就确认一次字段名和输出列,能省下不少排查时间。Azure Policy 的治理工作很容易被数据呈现方式卡住,Resource Graph 把这块打通之后,剩下的工作其实就变成了“用查询脚本回答问题”,而这个思路一旦形成,后续不管管理规模怎么扩大,都能稳稳兜住。