基于 autoskills 的 Azure 成本管理实战指南:查询、预测与优化三大工作流全解析
基于 autoskills 的 Azure 成本管理实战指南:查询、预测与优化三大工作流全解析
本指南以 autoskills 仓库内置的 azure-cost 技能文档(SKILL.md 及其 cost-query、cost-optimization、cost-forecast 三个子工作流目录)为核心骨架,系统讲解如何通过 Azure Cost Management REST API、Azure Monitor、Azure Quick Review (azqr) 与 Azure Resource Graph 完成历史成本查询、未来支出预测与成本优化三大任务。读者学完后将掌握:基于 az rest 的完整成本查询请求体构造、维度/分组/过滤/排序的防护规则、429 限流与错误码的处置策略、Redis 与 AKS 专项优化清单,以及端到端成本优化报告的生成方法。
该技能由仓库通过 skills-registry/index.json 收录,来源为 microsoft/github-copilot-for-azure/azure-cost(见 index.json),并映射在 Azure 技术栈检测规则中(见 skills-map.ts:检测到 @azure/* 依赖时自动关联该技能)。下文所有命令与请求体均来自该技能目录下的原始文档,可直接复制运行。
一、技能概览与适用场景
azure-cost 是一个统一管理 Azure 成本的全流程技能,覆盖三种核心意图:
| 用户意图 | 对应工作流 | 典型提问 |
|---|---|---|
| 理解当前成本 | Cost Query Workflow | “how much am I spending”“cost by service”“show my bill” |
| 降低成本 / 消除浪费 | Cost Optimization Workflow | “optimize costs”“find orphaned resources”“reduce spending” |
| 预测未来支出 | Cost Forecast Workflow | “forecast costs”“end of month estimate”“how much will I spend” |
| 全量成本视图 | 三个工作流组合执行 | “give me the full picture of my Azure costs” |
技能明确的适用边界(来自 SKILL.md 的 frontmatter description):适用于查询历史成本、按服务/资源/位置/标签拆分成本、分析成本趋势、预测未来支出与月末账单、优化成本、发现孤儿/闲置资源、调整 VM/容器/服务规格、生成优化报告;不适用于资源部署、基础设施供给、诊断、安全审计,以及对尚未部署的新资源做成本估算。
重要:在做成本优化时,必须同时呈现总账单与成本拆分(来自 Cost Query Workflow),再附上优化建议,避免脱离真实账单谈“省钱”。
快速参考速查表
| 属性 | 值 |
|---|---|
| 查询 API 端点 | POST {scope}/providers/Microsoft.CostManagement/query?api-version=2023-11-01 |
| 预测 API 端点 | POST {scope}/providers/Microsoft.CostManagement/forecast?api-version=2023-11-01 |
| MCP 工具 | azure__documentation、azure__extension_cli_generate、azure__get_azure_bestpractices、azure__extension_azqr、azure__aks |
| CLI 工具 | az rest、az monitor metrics list、az resource list |
| 所需角色 | 作用域上的 Cost Management Reader + Monitoring Reader + Reader |
提示:优先使用 MCP 工具而非直接执行 CLI 命令;仅当 MCP 工具无法覆盖特定操作时,才回退使用
az rest。
MCP 工具清单
| 工具 | 描述 | 参数 | 适用场景 |
|---|---|---|---|
azure__documentation |
检索 Azure 官方文档 | query(必填):搜索词 |
研究 Cost Management API 参数与选项 |
azure__extension_cli_generate |
生成 Azure CLI 命令 | intent(必填):任务描述;cli-type(必填):"az" |
构造成本查询用的 az rest 命令 |
azure__get_azure_bestpractices |
获取 Azure 最佳实践 | intent(必填):优化上下文 |
用成本管理最佳实践指导查询设计 |
azure__extension_azqr |
运行 Azure Quick Review 合规扫描 | subscription(必填):订阅 ID;resource-group(可选):资源组名 |
发现孤儿资源与成本优化机会 |
azure__aks |
Azure Kubernetes Service 操作 | 随子命令变化 | AKS 成本分析:列集群、取节点池、检查配置 |
二、作用域(Scope)参考:所有工作流共用
所有 Cost Management API 请求都要求以作用域路径开头。SKILL.md 给出五种作用域 URL 模式:
| 作用域 | URL 模式 |
|---|---|
| 订阅 | /subscriptions/<subscription-id> |
| 资源组 | /subscriptions/<subscription-id>/resourceGroups/<resource-group-name> |
| 管理组 | /providers/Microsoft.Management/managementGroups/<management-group-id> |
| 计费账户 | /providers/Microsoft.Billing/billingAccounts/<billing-account-id> |
| 计费档案 | /providers/Microsoft.Billing/billingAccounts/<billing-account-id>/billingProfiles/<billing-profile-id> |
提示:这些只是作用域路径,并非完整 URL,需要与 API 端点和版本号(
?api-version=2023-11-01)拼接使用。更完整的维度矩阵中还包含发票分区(Invoice Section)、部门(Department)、注册账户(Enrollment Account)、客户(Customer/Partner)等更多作用域(见 dimensions-by-scope.md)。
Part 1:成本查询工作流(Cost Query Workflow)
当用户想理解当前成本——拆分、趋势、总额、Top 消耗者——时,使用本工作流。完整流程见 workflow.md。
步骤 1:确定作用域
依据上文作用域参考表确定查询作用域;当上下文存在多个作用域标识时,dimensions-by-scope.md 给出了解析优先级(从高到低):管理组 > 资源组 > 订阅 > 计费档案+发票分区 > 计费档案 > 部门 > 注册账户 > 客户 > 发票分区 > 计费账户。提示:用户同时提供订阅与资源组时,优先选用资源组作用域以获得更精准的结果。
步骤 2:选择报告类型
| 类型 | 说明 |
|---|---|
ActualCost |
实际计费成本(含购买项) |
AmortizedCost |
预留实例/节省计划成本分摊到使用期 |
Usage |
基于用量的成本数据 |
步骤 3:设置时间范围(Timeframe)
可使用预置时间范围(MonthToDate、TheLastMonth、TheLastYear 等)或 Custom + timePeriod 对象。预置值全集(来自 request-body-schema.md):WeekToDate、MonthToDate、BillingMonthToDate、YearToDate、TheLastWeek、TheLastMonth、TheLastBillingMonth、TheLastYear、TheLast7Days、TheLast3Months、Custom。
时间范围关键防护(详见 guardrails.md):
- 日粒度:最大 31 天;月粒度/无粒度:最大 12 个月;
Custom时间范围必须提供timePeriod对象,含from与to日期;- 历史查询中的未来日期会被静默调整(详见下文防护规则);
- 绝对 API 上限为 37 个月,超限直接返回 HTTP 400。
步骤 4:配置 dataset
在 dataset 对象中定义粒度、聚合、分组、过滤与排序:
- Granularity(粒度):
None、Daily、Monthly; - Aggregation(聚合):对
Cost或PreTaxCost使用Sum求总成本(成本查询仅支持Sum函数); - Grouping(分组):最多 2 个
GroupBy维度(如ServiceName、ResourceGroupName),不允许重复列; - Filtering(过滤):使用
dimensions或tags过滤器,包含name、operator(In/Equal/Contains)与values字段; - Sorting(排序):按成本或维度列排序。
提示:并非所有维度在每个作用域都可用,具体见 dimensions-by-scope.md 的可用性矩阵。
完整请求体 schema 见 request-body-schema.md。
步骤 5:构造并执行 API 调用
使用 az rest 调用 Cost Management Query API(官方文档明确此方式比 az costmanagement query 更可靠)。
创建 temp/cost-query.json:
{
"type": "ActualCost",
"timeframe": "MonthToDate",
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": {
"name": "Cost",
"function": "Sum"
}
},
"grouping": [
{
"type": "Dimension",
"name": "ServiceName"
}
]
}
}
执行查询(PowerShell):
# 创建 temp 文件夹
New-Item -ItemType Directory -Path "temp" -Force
# 使用 REST API 查询(比 az costmanagement query 更可靠)
az rest --method post `
--url "<scope>/providers/Microsoft.CostManagement/query?api-version=2023-11-01" `
--headers "ClientType=GitHubCopilotForAzure" `
--body '@temp/cost-query.json'
注意:所有 Cost Management API 请求都必须携带
ClientType: GitHubCopilotForAzure请求头,这是 SKILL.md 最佳实践中明确要求的审计标识。
步骤 6:处理分页与错误
- API 每页最多返回 5,000 行(默认 1,000 行);
- 响应中出现
nextLink时,继续请求下一页直至取完; - 遇到 HTTP 429 限流时,检查响应中的所有
x-ms-ratelimit-microsoft.costmanagement-*-retry-after请求头(qpu、entity、tenant),等待最长的值再重试,同一作用域在该时长内不得再发请求。
查询 API 请求体 Schema 详解
{
"type": "<report-type>",
"timeframe": "<timeframe>",
"timePeriod": { "from": "2024-01-01T00:00:00Z", "to": "2024-01-31T23:59:59Z" },
"dataset": {
"granularity": "<granularity>",
"aggregation": { "<alias>": { "name": "<column>", "function": "<function>" } },
"grouping": [{ "type": "<column-type>", "name": "<column-name>" }],
"filter": { "<filter-expression>" },
"sorting": [{ "direction": "<direction>", "name": "<column>" }]
}
}
顶层字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type |
string | 是 | ActualCost、AmortizedCost 或 Usage |
timeframe |
string | 是 | 预置时间窗或 Custom |
timePeriod |
object | 条件必填 | timeframe 为 Custom 时必须提供;含 ISO 8601 格式的 from/to |
dataset |
object | 是 | 定义粒度、聚合、分组、过滤、排序 |
聚合(Aggregation):
"aggregation": { "totalCost": { "name": "Cost", "function": "Sum" } }
| 字段 | 必填 | 说明 |
|---|---|---|
<alias>(键) |
是 | 输出列别名(如 totalCost) |
name |
是 | 源列:Cost、PreTaxCost 或 UsageQuantity |
function |
是 | Sum(成本查询唯一支持的函数) |
分组(Grouping):
"grouping": [
{ "type": "Dimension", "name": "ServiceName" },
{ "type": "TagKey", "name": "Environment" }
]
type:Dimension(内置维度)或TagKey(资源标签);- 每次查询最多 2 个 GroupBy 维度,不允许重复。
过滤(Filter)结构:
"filter": {
"and": [
{
"dimensions": {
"name": "ResourceGroupName",
"operator": "In",
"values": ["rg-prod", "rg-staging"]
}
},
{
"not": {
"tags": {
"name": "Environment",
"operator": "Equal",
"values": ["dev"]
}
}
}
]
}
逻辑运算符:
| 运算符 | 说明 | 子表达式数量 |
|---|---|---|
and |
所有子表达式都必须匹配 | 2 个或更多 |
or |
任一子表达式匹配即可 | 2 个或更多 |
not |
取反单个子表达式 | 恰好 1 个 |
警告:
and/or必须至少包含 2 个子表达式,not必须恰好 1 个,否则校验失败。
比较运算符(ComparisonOperator 枚举):
| 运算符 | 说明 | 示例 |
|---|---|---|
In |
值在给定列表中,支持多值 | "values": ["vm", "storage"] |
Equal |
与单个值精确匹配 | "values": ["production"] |
Contains |
字符串包含指定子串 | "values": ["prod"] |
LessThan |
数值小于比较 | "values": ["100"] |
GreaterThan |
数值大于比较 | "values": ["0"] |
NotEqual |
值不等于指定值 | "values": ["dev"] |
过滤目标类型: dimensions(内置维度,如 ResourceGroupName、ServiceName)与 tags(Azure 资源标签,如 Environment、CostCenter)。
排序(Sorting):
"sorting": [
{ "direction": "Descending", "name": "Cost" }
]
direction 必填(Ascending/Descending);name 必填,必须是聚合或分组中出现的列。
响应结构:
{
"id": "<query-id>",
"name": "<query-name>",
"type": "Microsoft.CostManagement/query",
"properties": {
"nextLink": "<url-for-next-page-or-null>",
"columns": [
{ "name": "Cost", "type": "Number" },
{ "name": "ServiceName", "type": "String" },
{ "name": "UsageDate", "type": "Number" },
{ "name": "Currency", "type": "String" }
],
"rows": [
[123.45, "Virtual Machines", 20240115, "USD"],
[67.89, "Storage", 20240115, "USD"]
]
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
columns |
array | 列定义数组,含 name 与 type |
rows |
array | 行数组,值顺序与 columns 对应 |
nextLink |
string | 下一页 URL,无更多页时为 null |
提示:粒度为
Daily/Monthly时,UsageDate以YYYYMMDD数字格式返回(如20240115)。
查询防护规则(Guardrails)
系统会自动执行这些规则,理解它们有助于避免意外修改或报错(详见 guardrails.md)。
时间周期验证:
| 场景 | 系统行为 |
|---|---|
| 未指定时间周期 | 默认当前月月初 → 今天 |
from 晚于 to |
系统静默交换 from 与 to |
from 与 to 都在未来 |
整个周期平移到去年同一时段 |
仅 to 在未来 |
to 调整为今天 |
警告:未来日期的平移是静默发生的,响应数据覆盖的是调整后的周期,而非原始请求日期。
粒度范围限制:
| 粒度 | 最大范围 | 截断行为 |
|---|---|---|
Daily |
31 天 | from 截断为 to - 1 个月 + 1 天 |
Monthly |
12 个月 | from 截断为 to - 12 个月 + 1 天 |
None |
12 个月 | from 截断为 to - 12 个月 + 1 天 |
- 绝对 API 上限 37 个月,超限无论粒度均返回 HTTP 400;
- 最早的
from日期为 2014 年 5 月 1 日。
分组与时间周期交互:
| 组合 | 系统行为 |
|---|---|
| GroupBy + 日粒度 | 时间周期调整到请求范围的最后一天 |
| GroupBy + 月粒度 | 时间周期调整到请求范围的最后一月 |
提示:使用 GroupBy 且日粒度覆盖多天时,系统可能只返回最后一天的数据;如需完整日拆分+分组,确保范围在 31 天限制内。
ResourceId 分组作用域限制:
警告:按
ResourceId分组仅支持订阅及其以下作用域(订阅、资源组),在更高作用域(计费账户、管理组等)不支持。此时应改用ServiceName、SubscriptionName等维度;若需要资源级明细,先把作用域收窄到具体订阅。
dataset 校验:
| 规则 | 限制 | 错误行为 |
|---|---|---|
| GroupBy 最大维度数 | 2 | 超过即校验错误 |
| GroupBy 中重复列 | 不允许 | 校验错误 |
| 同一列同时出现在聚合与分组 | 不允许 | 校验错误 |
| 聚合函数 | 仅 Sum |
— |
粒度下聚合 Date 列 |
不允许 | 粒度为 Daily/Monthly 时不能聚合 Date |
作用域与维度兼容性:
维度必须对“协议类型 × 作用域类型”的交集有效。例如:
- EA 独有维度:
DepartmentName、EnrollmentAccountName、BillingPeriod; - MCA 独有维度:
InvoiceSectionName; - MOSP(按需付费):只有公共维度。
在 MOSP 订阅上使用 EA 独有维度(如 DepartmentName)会返回校验错误,使用前务必确认协议类型。
EA + 管理组特例:
在 EA 管理组作用域下仅用 SubscriptionName 过滤(不带 SubscriptionId)会报错,错误信息为:“To view cost data, the subscription ID is needed…”。修复方法是同时加上 SubscriptionId 过滤:
{
"and": [
{
"dimensions": {
"name": "SubscriptionId",
"operator": "In",
"values": ["<subscription-id>"]
}
},
{
"dimensions": {
"name": "SubscriptionName",
"operator": "In",
"values": ["My Subscription"]
}
}
]
}
限流阈值:
| 限制维度 | 值 |
|---|---|
| 每用户 | 每分钟 20 次请求 |
| 每作用域 | 每分钟 4 次请求(最严格) |
| 每租户 | 每 10 秒 12 次、每分钟 60 次、每小时 600 次 |
| 每客户端类型 | 每分钟 2,000 次 |
限流响应头:
| 请求头 | 说明 |
|---|---|
x-ms-ratelimit-microsoft.costmanagement-qpu-retry-after |
QPU 限流下的等待秒数 |
x-ms-ratelimit-microsoft.costmanagement-entity-retry-after |
实体/作用域限流等待秒数 |
x-ms-ratelimit-microsoft.costmanagement-tenant-retry-after |
租户限流等待秒数 |
收到 429 时,检查响应中出现的所有 retry-after 请求头(可能同时返回多个),取最大值,在该时长完全过去前不得向同一作用域发送任何请求;过早重试会被拒绝并可能延长限流时间。
查询错误处理速查
| HTTP 状态 | 错误 | 补救措施 |
|---|---|---|
| 400 | 请求体无效 | 检查 schema、日期范围与维度兼容性 |
| 401 | 未授权 | 验证登录(az login) |
| 403 | 禁止 | 确保作用域上分配 Cost Management Reader 角色 |
| 404 | 作用域不存在 | 校验作用域 URL 与资源 ID |
| 429 | 请求过多 | 检查全部 retry-after 请求头(qpu/entity/tenant),等待最长值,最多重试 3 次 |
| 503 | 服务不可用 | 查看 Azure 状态页 |
常见错误场景与修复(详见 error-handling.md):
- “Agreement type X does not support Y scope”——作用域类型与协议类型不兼容(EA 不能用 Invoice Section,MOSP 不能用 Department);
- “Dimension Z is not valid for scope”——维度与当前作用域/协议组合不匹配,对照维度矩阵修正;
- 日期范围超粒度限制——系统自动截断
from;若想避免静默截断,主动控制范围; - 日期范围超绝对上限(37 个月)——拆分为多次查询;
- 请求体为 null 或无效——保证
type、timeframe、dataset字段齐全且 JSON 合法; BillingSubscriptionNotFound——作用域 URL 中的订阅 ID 无效或未关联到计费账户。
重试策略:只有 429 可以重试,其余状态(400/401/403/404/503/其他 5xx)都必须先修复问题,不要盲目重试。所有错误响应遵循统一 JSON 结构:error.code(机器可读,如 BadRequest)、error.message(人类可读描述)、error.details(可选更详细信息数组)。
查询示例(可直接运行)
1. 按服务的本月成本:
az rest --method post `
--url "/subscriptions/<subscription-id>/providers/Microsoft.CostManagement/query?api-version=2023-11-01" `
--headers "ClientType=GitHubCopilotForAzure" `
--body '{
"type": "ActualCost",
"timeframe": "MonthToDate",
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"grouping": [
{ "type": "Dimension", "name": "ServiceName" }
]
}
}'
2. 近 30 天每日成本趋势(注意日粒度上限 31 天):
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "2024-01-01T00:00:00Z",
"to": "2024-01-31T23:59:59Z"
},
"dataset": {
"granularity": "Daily",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
}
}
}
3. 带标签过滤的资源组成本:
{
"type": "ActualCost",
"timeframe": "MonthToDate",
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"grouping": [
{ "type": "Dimension", "name": "ResourceGroupName" }
],
"filter": {
"tags": {
"name": "Environment",
"operator": "In",
"values": ["production", "staging"]
}
},
"sorting": [
{ "direction": "Descending", "name": "Cost" }
]
}
}
4. 预留分析用的 AmortizedCost:
{
"type": "AmortizedCost",
"timeframe": "TheLastMonth",
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"grouping": [
{ "type": "Dimension", "name": "BenefitName" }
],
"sorting": [
{ "direction": "Descending", "name": "Cost" }
]
}
}
提示:
AmortizedCost将预留购买分摊到整个期限,反映真实的每日/每月有效成本。
5. Top 10 最贵资源:
{
"type": "ActualCost",
"timeframe": "MonthToDate",
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"grouping": [
{ "type": "Dimension", "name": "ResourceId" }
],
"sorting": [
{ "direction": "Descending", "name": "Cost" }
]
}
}
提示:在 URL 末尾追加
&$top=10可限制返回行数:...query?api-version=2023-11-01&$top=10。
完整示例集见 examples.md。
维度可用性矩阵(Dimensions by Scope)
公共维度(大多数作用域可用):ResourceGroupName、ResourceId、ResourceLocation、ServiceName、ServiceFamily、ServiceTier、MeterCategory、MeterSubCategory、Meter、ChargeType(如 Usage/Purchase/Refund)、PublisherType(如 Azure/Marketplace/AWS)、PricingModel(如 OnDemand/Reservation/SavingsPlan/Spot)、SubscriptionName、SubscriptionId、TagKey、Product、BenefitName、BillingPeriod(详见 dimensions-by-scope.md)。
三种协议类型的维度对比(✅ 可用,❌ 不可用):
| 维度 | EA | MCA | MOSP |
|---|---|---|---|
ResourceGroupName、ResourceId、SubscriptionName、Product、ResourceLocation、ServiceName、ServiceFamily、TagKey、MeterSubCategory、PublisherType、PricingModel、ChargeType、ServiceTier、BenefitName |
✅ | ✅ | ✅ |
BillingProfileName |
✅ | ✅ | ❌ |
DepartmentName、EnrollmentAccountName、BillingPeriod |
✅ | ❌ | ❌ |
InvoiceSectionName |
❌ | ✅ | ❌ |
注:
ResourceId作为维度在三种协议下都可用,但仅在订阅/资源组作用域支持 GroupBy;更高作用域请改用ServiceName或SubscriptionName。
Part 2:成本优化工作流(Cost Optimization Workflow)
当用户想降低成本——消除浪费、孤儿资源、规格调整机会——时使用。完整流程见 workflow.md。
重要:始终将总账单与成本拆分(来自 Cost Query Workflow)和优化建议一起呈现。
步骤 0:校验前置条件
必需工具:
- 已安装并登录的 Azure CLI(
az login); - Azure CLI 扩展:
costmanagement、resource-graph; - 已安装 Azure Quick Review(azqr),安装说明见 azure-quick-review.md。
必需权限: Cost Management Reader 角色、Monitoring Reader 角色、订阅/资源组上的 Reader 角色。
验证命令:
az --version
az account show
az extension show --name costmanagement
azqr version
步骤 1:加载最佳实践
azure__get_azure_bestpractices({
intent: "Get cost optimization best practices",
command: "get_bestpractices",
parameters: { resource: "cost-optimization", action: "all" }
})
步骤 1.5:Redis 专项分析(条件触发)
用户提到 “Redis”“Azure Cache for Redis”“Azure Managed Redis”,且重点是 Redis 资源优化而非全订阅分析时,改用专项参考 azure-cache-for-redis.md。
步骤 1.6:选择分析作用域(Redis 专项)
让用户从以下选项中选择并等待回复:1)指定订阅 ID;2)订阅名称;3)订阅前缀(如 “CacheTeam”);4)我所有的订阅;5)租户级。
步骤 1.7:AKS 专项分析(条件触发)
用户提到 “AKS”“Kubernetes”“cluster”“node pool”“pod”“kubectl”,或要启用 AKS 成本分析插件、命名空间成本可见性,或报告成本突增、集群利用率异常、预算告警时,使用 AKS 专项参考:
- 工具选择:优先 MCP 的
azure__aks(列集群、取节点池、检查配置),CLI 的az aks/kubectl仅作为回退; - 参考文件按需加载:azure-aks-cost-addon.md(启用命名空间级成本可见性)、azure-aks-anomalies.md(成本突增、扩缩容事件、预算告警)。
步骤 1.8:选择分析作用域(AKS 专项)
向用户提供:1)指定集群名;2)资源组内所有集群;3)订阅内所有集群;4)跨订阅所有可访问集群。等待用户回复后再进入步骤 2。
步骤 2:运行 Azure Quick Review(azqr)
运行 azqr 发现孤儿资源(立即可省的钱):
azure__extension_azqr
subscription: "<SUBSCRIPTION_ID>"
resource-group: "<RESOURCE_GROUP>" # 可选
重点排查:孤儿资源(未挂载磁盘、未使用 NIC、闲置 NAT 网关)、过度配置资源(过长保留期、过大 SKU)、缺失成本标签。完整用法见 azure-quick-review.md:创建 filters.yaml 聚焦成本扫描(includeSections: [Costs, Advisor, Inventory, Orphaned],excludeSections: [Recommendations, AzurePolicy, DefenderRecommendations]),然后运行 azqr scan --subscription "<SUBSCRIPTION_ID>" --resource-group "<RESOURCE_GROUP>" --filters ./filters.yaml --output json,将报告存为 output/azqr_report_<YYYYMMDD_HHMMSS>.json 并清理临时 filters.yaml。
步骤 3:发现资源
使用 Azure Resource Graph 做跨订阅的高效资源发现(见 azure-resource-graph.md):
az account show
az resource list --subscription "<SUBSCRIPTION_ID>" --resource-group "<RESOURCE_GROUP>"
步骤 4:查询实际成本
从 Cost Management API 获取近 30 天实际成本。创建 temp/cost-query.json:
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "<START_DATE>",
"to": "<END_DATE>"
},
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": {
"name": "Cost",
"function": "Sum"
}
},
"grouping": [
{
"type": "Dimension",
"name": "ResourceId"
}
]
}
}
操作要求:按 ISO 8601 格式计算
<START_DATE>(30 天前)与<END_DATE>(今天)。执行后把结果保存到output/cost-query-result<timestamp>.json,所有 Cost Management API 请求都带--headers "ClientType=GitHubCopilotForAzure"。
提示:同时跑一次按
ServiceName分组的成本查询,用总账单拆分支撑优化建议。警告:同一作用域的连续查询共享每作用域 4 次/分钟的限流,429 时按 retry-after 头等待。
步骤 5:验证定价
用 fetch_webpage 从 Azure 官方定价页获取当前价格,重点服务:Container Apps、Virtual Machines、App Service、Log Analytics。
重要:核对免费额度——许多 Azure 服务有慷慨的免费限制,忽略免费层是常见误区。
步骤 6:收集利用率指标
查询 Azure Monitor 近 14 天利用率数据支撑规格调整建议:
$startTime = (Get-Date).AddDays(-14).ToString("yyyy-MM-ddTHH:mm:ssZ")
$endTime = Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ"
# VM CPU 利用率
az monitor metrics list `
--resource "<RESOURCE_ID>" `
--metric "Percentage CPU" `
--interval PT1H `
--aggregation Average `
--start-time $startTime `
--end-time $endTime
步骤 7:生成优化报告
生成 output/costoptimizereport<YYYYMMDD_HHMMSS>.md,包含:执行摘要、按服务成本拆分、免费层分析、孤儿资源、分级优化建议、实施命令。成本查询结果另存为 output/cost-query-result<YYYYMMDD_HHMMSS>.json 作审计轨迹,随后清理临时文件。完整模板见 report-template.md:
# Azure Cost Optimization Report
**Generated**: <timestamp>
## Executive Summary
- Total Monthly Cost: $X (ACTUAL DATA from Cost Management API)
- Top Cost Drivers: [List top 3 services with costs]
- Potential Savings: $Y/month
## Cost Breakdown by Service
| Service | Cost (USD) | % of Total |
|---------|-----------|------------|
| ... | ... | ... |
| **Total** | **$X** | **100%** |
## Free Tier Analysis
[Resources operating within free tiers]
## Orphaned Resources (Immediate Savings)
[From azqr — resources that can be deleted immediately]
## Optimization Recommendations
### Priority 1: High Impact, Low Risk
- ACTUAL cost: $X/month
- ESTIMATED savings: $Y/month
- Commands to execute
### Priority 2: Medium Impact, Medium Risk
- ACTUAL baseline, ACTUAL metrics, VALIDATED pricing
- ESTIMATED savings with commands
### Priority 3: Long-term Optimization
[Reserved Instances, Storage tiering]
## Total Estimated Savings
- Monthly: $X | Annual: $Y
## Implementation Commands
[Safe commands with approval warnings]
## Validation Appendix
- Cost Query Results: `output/cost-query-result<timestamp>.json`
- Pricing Sources: [Links]
Portal 链接格式(报告中为所有资源附带 Azure Portal 链接):
https://portal.azure.com/#@<TENANT_ID>/resource/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>/providers/<RESOURCE_PROVIDER>/<RESOURCE_TYPE>/<RESOURCE_NAME>/overview
审计轨迹与清理: 保存成本查询结果为 JSON 用于复现;报告生成后清理临时文件:Remove-Item -Path "temp" -Recurse -Force -ErrorAction SilentlyContinue。
资源发现利器:Azure Resource Graph(KQL 查询模式)
前提:az extension add --name resource-graph。执行方式:az graph query -q "<KQL>" --query "data[].{name:name, type:type}" -o table。常用表:Resources(所有 ARM 资源)、ResourceContainers(订阅/资源组/管理组)、AdvisorResources(成本与性能建议)。
找未挂载托管磁盘:
Resources
| where type =~ 'microsoft.compute/disks'
| where isempty(managedBy)
| project name, resourceGroup, location, diskSizeGb=properties.diskSizeGB, sku=sku.name
找未关联的公网 IP:
Resources
| where type =~ 'microsoft.network/publicipaddresses'
| where isempty(properties.ipConfiguration)
| project name, resourceGroup, location, sku=sku.name
找孤儿网卡:
Resources
| where type =~ 'microsoft.network/networkinterfaces'
| where isempty(properties.virtualMachine)
| project name, resourceGroup, location
按 SKU/层级统计资源数(发现过大规格):
Resources
| where isnotempty(sku.name)
| summarize count() by type, tostring(sku.name)
| order by count_ desc
标签覆盖率(成本分摊):
Resources
| extend hasCostCenter = isnotnull(tags['CostCenter'])
| summarize total=count(), tagged=countif(hasCostCenter) by type
| extend coverage=round(100.0 * tagged / total, 1)
| order by total desc
找闲置负载均衡器:
Resources
| where type =~ 'microsoft.network/loadbalancers'
| where array_length(properties.backendAddressPools) == 0
| project name, resourceGroup, location, sku=sku.name
取 Advisor 成本建议:
AdvisorResources
| where properties.category == 'Cost'
| project name, impact=properties.impact, description=properties.shortDescription.solution
技巧:用
=~做大小写不敏感的类型匹配(资源类型都是小写);用properties.fieldName导航属性;用--first N限制结果数;用--subscriptions限定订阅范围;最后把孤儿资源与 Cost Management API 的实际成本数据交叉验证。
Redis 成本优化规则(按优先级)
来自 azure-cache-for-redis.md。订阅输入支持订阅 ID、订阅名称、订阅前缀、租户 ID、或 “All my subscriptions”。
| 优先级 | 规则 | 检测逻辑 | 建议 | 平均节省 |
|---|---|---|---|---|
| 🔴 严重 | 失败的缓存 | provisioningState == 'Failed' |
立即删除 | $50-300/月 |
| 🔴 严重 | 卡在创建中 | provisioningState == 'Creating' 且年龄 > 4 小时 |
删除/开支持工单 | $50-300/月 |
| 🟠 高 | 开发环境用 Premium | sku.name == 'Premium' 且 tags.environment in ['dev','test','staging'] |
降级为 Standard | $175/月 |
| 🟠 高 | Enterprise 未使用 | sku.name startsWith 'Enterprise' 且无模块/集群 |
降级为 Premium/Standard | $300-1000/月 |
| 🟠 高 | 过期测试缓存 | tags.purpose == 'test' 且年龄 > 60 天 |
删除或降级 | $50-150/月 |
| 🟡 中 | 大型开发缓存 | sku.capacity > 3 且 tags.environment == 'dev' |
缩小规模 | $100-300/月 |
| 🟡 中 | 无过期标签 | 缺少 expirationDate 或 ttl 标签 |
添加清理策略 | N/A |
| 🟢 低 | 未打标签资源 | 缺少必需标签(environment、owner) |
打标签 | N/A |
| 🟢 低 | 老旧缓存 | 年龄 > 365 天 | 评估是否仍需 | 视情况 |
相关命令:az account list、az redis list --subscription <id>、az redis show、az redis delete(删除需先获得批准)。.NET 侧可用 Azure.ResourceManager.Redis 包进行缓存管理(安装 dotnet add package Azure.ResourceManager.Redis 与 Azure.Identity),SDK 速查见 azure-resource-manager-redis-dotnet.md,其最佳实践包括:用 WaitUntil.Completed 等待必须完成的操作、用 CreateOrUpdateAsync 做幂等操作、生产环境最低 TLS 1.2、关闭非 SSL 端口、定期轮换密钥。
AKS 成本分析插件(命名空间级成本可见性)
# 检查插件是否启用
az aks show \
--name "<cluster-name>" --resource-group "<resource-group>" \
--query "addonProfiles.costAnalysis" -o json
# 检查集群层级(插件要求 Standard 或 Premium)
az aks show \
--name "<cluster-name>" --resource-group "<resource-group>" \
--query "{tier:sku.tier, name:name}" -o table
# 启用插件(需 Standard 或 Premium 层级)
az aks update \
--name "<cluster-name>" --resource-group "<resource-group>" \
--enable-cost-analysis
若集群为 Free 层级,需警告用户:从 Free 升级到 Standard 会产生持续的集群管理费用。必须先用官方 AKS 定价页确认当前成本并取得用户明确批准,再执行:
az aks update \
--name "<cluster-name>" --resource-group "<resource-group>" \
--tier standard
az aks update \
--name "<cluster-name>" --resource-group "<resource-group>" \
--enable-cost-analysis
启用后,命名空间级成本数据可在 Azure Portal(AKS 集群 → Cost Analysis 面板)或 Azure Cost Management(按集群资源 ID + kubernetes namespace 维度过滤)中查看。风险提示:启用插件本身风险低,但 Free → Standard 升级有成本,务必先与用户确认。
AKS 成本异常调查
来自 azure-aks-anomalies.md,用于调查用户报告的成本/利用率突增:
- 确认时间窗:询问“什么时候注意到突增?”(如“上周二”“昨天凌晨 2-4 点”);
- 拉取成本数据:用
az rest按日粒度 +ResourceId分组查询异常窗口; - 拉取节点数与扩缩容事件:先用
az monitor metrics list-definitions --resource "<aks-resource-id>"确认可用指标,再查节点数指标(--interval PT5M --aggregation Count),用kubectl get events --all-namespaces --field-selector reason=SuccessfulRescale --sort-by='.lastTimestamp'查 HPA 扩缩容事件; - Top 消耗者:
kubectl top nodes、kubectl top pods --all-namespaces --sort-by=cpu。
常见原因对照:
| 症状 | 可能原因 | 处理 |
|---|---|---|
| 非高峰节点数激增 | HPA/VPA 配置错误 | 检查 HPA 最小副本数 |
| 单 Pod 占满 CPU | 内存泄漏或失控进程 | 查日志、加资源限制 |
| 某天成本突增 | 批处理任务意外运行 | 检查 CronJob 调度 |
| 节点数持续偏高 | CAS 缩容受阻 | 检查 PodDisruptionBudgets、系统 Pod |
| 命名空间成本骤增 | 新部署未设资源限制 | 添加 requests/limits |
设置预算告警:
az consumption budget create \
--budget-name "aks-monthly-budget" \
--amount <budget-amount> \
--time-grain Monthly \
--start-date "<YYYY-MM-01>" \
--end-date "<YYYY-MM-01>" \
--resource-group "<resource-group>" \
--threshold 80 \
--contact-emails "<contact-email>"
Part 3:成本预测工作流(Cost Forecast Workflow)
当用户想预测未来成本时使用。完整流程见 workflow.md。
警告:如果用户要的是历史成本,用 Cost Query Workflow;如果要降低成本,用 Cost Optimization Workflow。
与 Query API 的关键差异
| 方面 | Query API | Forecast API |
|---|---|---|
| 用途 | 历史成本数据 | 预测未来成本 |
| 时间周期 | 仅过去日期 | 必须包含未来日期 |
| 分组 | 最多 2 个维度 | 不支持 |
includeActualCost |
不适用 | 将历史实际成本与预测并列 |
| 响应列 | Cost、Date、Currency | Cost、Date、CostStatus、Currency |
| 最大响应行数 | 每页 5,000 行 | 建议 40 行 |
| 时间范围 | 多种预置 + Custom | 通常仅 Custom |
步骤 1-2:确定作用域与报告类型
作用域同上文;报告类型 ActualCost 最常用,AmortizedCost 用于预留/节省计划预测。
步骤 3:设置时间周期
警告:
to日期必须在未来。
timeframe设为Custom并提供timePeriod(from、to);from可在过去——响应会先给出到今天为止的实际成本,再预测到to;- 至少需要 28 天历史成本数据;
- 最大预测周期 10 年。
步骤 4:配置 dataset
- Granularity:推荐
Daily或Monthly; - Aggregation:通常对
Cost做Sum; -
警告:预测不支持分组(
grouping)。需要分组历史数据时,改用 Cost Query Workflow。
步骤 5:设置预测专属选项
| 字段 | 默认值 | 说明 |
|---|---|---|
includeActualCost |
true |
在预测旁附带历史实际成本 |
includeFreshPartialCost |
true |
包含最近几天的不完整成本数据。要求 includeActualCost: true |
步骤 6:构造并执行
创建 temp/cost-forecast.json:
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "<first-of-month>",
"to": "<last-of-month>"
},
"dataset": {
"granularity": "Daily",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"sorting": [{ "direction": "Ascending", "name": "UsageDate" }]
},
"includeActualCost": true,
"includeFreshPartialCost": true
}
执行:
New-Item -ItemType Directory -Path "temp" -Force
az rest --method post `
--url "/subscriptions/<subscription-id>/providers/Microsoft.CostManagement/forecast?api-version=2023-11-01" `
--headers "ClientType=GitHubCopilotForAzure" `
--body '@temp/cost-forecast.json'
步骤 7:解读响应
| CostStatus | 含义 |
|---|---|
Actual |
历史实际成本(当 includeActualCost: true 时) |
Forecast |
预测的未来成本 |
提示:“Forecast is unavailable for the specified time period” 不是错误——它表示该作用域历史数据不足。此时建议改用 Cost Query Workflow 获取可用数据。
预测 API 请求体 Schema 详解
完整 schema 见 request-body-schema.md:
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "2024-01-01T00:00:00Z",
"to": "2024-03-31T00:00:00Z"
},
"dataset": {
"granularity": "Daily",
"aggregation": {
"totalCost": {
"name": "Cost",
"function": "Sum"
}
},
"sorting": [
{ "direction": "Ascending", "name": "UsageDate" }
],
"filter": {
"dimensions": {
"name": "ResourceGroupName",
"operator": "In",
"values": ["my-resource-group"]
}
}
},
"includeActualCost": true,
"includeFreshPartialCost": true
}
字段参考:
| 字段 | 类型 | 必填 | 值 | 说明 |
|---|---|---|---|---|
type |
string | ✅ | ActualCost、AmortizedCost、Usage |
预测的成本类型 |
timeframe |
string | ✅ | Custom |
预测请求必须为 Custom |
timePeriod |
object | ✅ | — | 预测窗口起止日期 |
timePeriod.from |
string | ✅ | ISO 8601 | 开始日期;可在过去以包含实际值 |
timePeriod.to |
string | ✅ | ISO 8601 | 结束日期;必须在未来 |
dataset |
object | ✅ | — | 预测的数据集配置 |
dataset.granularity |
string | ✅ | Daily、Monthly |
预测结果的时间粒度 |
dataset.aggregation |
object | ✅ | — | 应用聚合函数 |
dataset.sorting |
array | 可选 | — | 结果排序 |
dataset.filter |
object | 可选 | — | 过滤表达式(dimensions/tags) |
includeActualCost |
boolean | 可选 | true/false |
是否附带历史实际成本。默认 true |
includeFreshPartialCost |
boolean | 可选 | true/false |
是否包含最近天数的不完整成本。默认 true,要求 includeActualCost=true |
响应列: Cost(Number)、UsageDate/BillingMonth(Datetime)、CostStatus(String,历史或预测)、Currency(String)。粒度与日期列映射:Daily → UsageDate,Monthly → BillingMonth。
预测防护规则(Guardrails)
来自 guardrails.md:
| 规则 | 约束 |
|---|---|
to 日期 |
必须在未来;整个周期都在过去会返回 CantForecastOnThePast 错误 |
from 日期 |
可在过去(实际成本从 from 到今天,预测从今天到 to) |
| 时间周期有效性 | 提供 timePeriod 时 from/to 都必须是可解析的 ISO 8601 字符串 |
| 月粒度 + includeActualCost | 需要显式 timePeriod;省略产生 DontContainsValidTimeRangeWhileMonthlyAndIncludeCost |
| 最大预测周期 | 10 年 |
| 最小训练数据 | 4 周(28 天)成本数据;新订阅(< 28 天)无法预测 |
| 数据延迟容忍 | 计费数据到达容忍 2 天 |
| 响应行上限 | 每响应 40 行(日粒度示例:30 实际天 + 30 预测天 = 60 行会超限;建议日粒度预测窗口控制在 2~3 周,更长期限改用月粒度) |
| 分组 | 不支持(硬限制);需要分组预测时,告知用户并用 Cost Query Workflow 查分组历史数据 |
includeFreshPartialCost |
依赖 includeActualCost=true,否则报 DontContainIncludeActualCostWhileIncludeFreshPartialCost |
预测不可用(返回 “Forecast is unavailable for the specified time period”)的三种情况:响应无数据行、作用域历史不足 28 天、作用域从未有成本数据。这不是错误,不要重试,建议改用 Cost Query Workflow。
限流规则与查询 API 相同(每作用域 4 次/分钟最严格,429 时取最长 retry-after 等待)。
预测错误处理速查
| 状态 | 错误 | 补救措施 |
|---|---|---|
| 400 | 无法预测过去 | 确保 to 日期在未来 |
| 400 | 缺少 dataset | 添加必填的 dataset 字段 |
| 400 | 无效依赖 | 使用 includeFreshPartialCost 时设置 includeActualCost: true |
| 403 | 禁止 | 作用域上需要 Cost Management Reader 角色 |
| 424 | 训练数据不良 | 历史不足;若 includeActualCost=true 则回退到实际成本 |
| 429 | 限流 | 检查全部 retry-after 请求头,等待最长值,最多重试 3 次 |
| 503 | 服务不可用 | 查看 Azure 状态页 |
校验错误码参考: EmptyForecastRequestBody(请求体为空)、InvalidForecastRequestBody(JSON 结构无效)、DontContainsDataSet(缺 dataset)、DontContainsValidTimeRangeWhileContainsPeriod(from/to 无效)、DontContainsValidTimeRangeWhileMonthlyAndIncludeCost(月粒度+includeActualCost 缺有效 timePeriod)、DontContainIncludeActualCostWhileIncludeFreshPartialCost(includeFreshPartialCost 缺 includeActualCost)、CantForecastOnThePast(两个日期都在过去)。
重试策略: 只有 429 可重试;424 训练数据问题重试无意义(回退实际成本或用查询工作流);其余错误先修复。
预测示例
1. 预测本月剩余天数(日粒度): 见上文步骤 6 的请求体。from 设为当月 1 号,响应将包含到今天为止的 Actual 行与剩余天数的 Forecast 行。
2. 预测未来 3 个月(月粒度):
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "<first-of-month>",
"to": "<3-months-out>"
},
"dataset": {
"granularity": "Monthly",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"sorting": [
{ "direction": "Ascending", "name": "BillingMonth" }
]
},
"includeActualCost": true,
"includeFreshPartialCost": true
}
提示:月粒度响应使用
BillingMonth列。
3. 资源组作用域预测: 把 URL 换成 /subscriptions/<subscription-id>/resourceGroups/<rg-name>/providers/Microsoft.CostManagement/forecast,请求体不变。
4. 计费账户作用域预测: URL 使用 /providers/Microsoft.Billing/billingAccounts/<id>/providers/Microsoft.CostManagement/forecast,建议月粒度。
完整示例集见 examples.md。
数据分类、最佳实践与常见陷阱
数据分类(贯穿所有工作流)
| 分类 | 来源 |
|---|---|
| ACTUAL DATA(实际数据) | 来自 Azure Cost Management API |
| ACTUAL METRICS(实际指标) | 来自 Azure Monitor |
| VALIDATED PRICING(已验证定价) | 来自 Azure 官方定价页 |
| ESTIMATED SAVINGS(估算节省) | 基于实际数据与已验证定价计算 |
最佳实践
- 始终先查询实际成本——绝不估计或假设;
- 呈现优化建议时必须同时呈现总账单;
- 从官方渠道验证定价——考虑免费额度;
- 成本查询用 REST API(比
az costmanagement query更可靠); - 所有 Cost Management API 请求都带
ClientType: GitHubCopilotForAzure请求头(az rest中为--headers "ClientType=GitHubCopilotForAzure"); - 保存审计轨迹——包含所有查询与响应;
- 为所有资源附带 Azure Portal 链接;
- 月成本 < $10 的资源,强调运营改进而非财务节省;
- 绝不未经明确批准执行破坏性操作;
- 429 响应时检查所有
x-ms-ratelimit-microsoft.costmanagement-*-retry-after请求头(qpu-retry-after、entity-retry-after、tenant-retry-after),等待最长值,期间不重试。每作用域 4 次/分钟的限流最严格。
常见陷阱
- 假设成本:始终从 Cost Management API 查询实际数据;
- 忽略免费额度:许多服务有慷慨的免费限额;
- 用错日期范围:成本 30 天、利用率 14 天;
- 不展示账单:优化建议必须配成本拆分;
- 成本查询失败:用
az rest+ JSON body,不要用az costmanagement query。
安全要求
- 删除资源前必须获得批准;
- 先在非生产环境验证变更;
- 提供 dry-run 命令用于验证;
- 包含回滚步骤。
附:认证与 SDK 最佳实践
自动化成本分析脚本/应用的身份认证遵循 auth-best-practices.md 的原则:
| 环境 | 推荐凭据 | 原因 |
|---|---|---|
| 生产(Azure 托管) | ManagedIdentityCredential(系统或用户分配) |
无密钥管理,Azure 自动轮换 |
| 生产(本地部署) | ClientCertificateCredential 或 WorkloadIdentityCredential |
确定性强,无回退链开销 |
| CI/CD 流水线 | AzurePipelinesCredential / WorkloadIdentityCredential |
限定到流水线身份 |
| 本地开发 | DefaultAzureCredential |
自动串联 CLI、PowerShell、VS Code 凭据 |
为什么生产环境不用 DefaultAzureCredential? 回退链不可预测、检查面过大(环境变量/CLI 令牌等)、非确定性(实际用哪个凭据取决于环境)、性能差(每次失败的凭据尝试都增加网络往返)。
环境感知模式示例(TypeScript):
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
function getCredential() {
if (process.env.NODE_ENV === "development") {
return new DefaultAzureCredential(); // 本地:拾取 az login / VS Code 凭据
}
return process.env.AZURE_CLIENT_ID
? new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID) // 用户分配
: new ManagedIdentityCredential(); // 系统分配
}
安全清单: 所有 Azure 托管应用使用托管身份;绝不硬编码凭据/连接串/密钥;最小权限 RBAC 分配到最窄作用域;生产用 ManagedIdentityCredential 而非 DefaultAzureCredential;密钥存 Azure Key Vault;定期轮换密钥与证书;生产资源启用 Microsoft Defender for Cloud。
结语
azure-cost 技能将 Azure 成本管理收敛为三条可复用的工作流:查询(历史事实)、优化(削减浪费)、预测(未来规划)。其核心方法论是用实际数据说话——所有优化建议都以 Cost Management API 的实际成本、Azure Monitor 的实际指标与官方定价页的已验证定价为证据,并以总账单拆分、分级建议、实施命令与审计轨迹构成完整闭环。在 autoskills 仓库中,该技能目录下的 21 份文档(SKILL.md 与 cost-query、cost-optimization、cost-forecast 三个子目录)构成了可直接交付给 Agent/LLM 执行的操作手册;读者可将本文中的命令与请求体直接用于自己的订阅,按“先查询、再预测、后优化”的顺序落地一套可持续的 Azure 成本治理流程。