Azure Cost Management 查询实战:基于 autoskills azure-cost Skill 的 Cost Query API 请求体示例详解
Azure Cost Management 查询实战:基于 autoskills azure-cost Skill 的 Cost Query API 请求体示例详解
本篇文章以 autoskills 仓库中 azure-cost Skill 的 Cost Management Query Examples 为核心骨架,系统讲解如何构造并执行 Azure Cost Management Query API 的请求体(request body)。你将掌握月度成本按服务汇总、30 天成本趋势、带标签过滤的资源组成本拆分、预留实例摊销成本分析、Top 10 昂贵资源等 5 类高频查询模式,并理解时间范围、粒度、聚合、分组、过滤、排序等请求体字段的约束与守则(guardrails),从而能直接用 az rest 命令获得准确、可复现的 Azure 成本数据。
说明:本文所有请求体示例均来自 examples.md,字段约束与执行流程分别参考 request-body-schema.md、workflow.md 与 guardrails.md。
背景:这是哪套 Skill,如何执行查询
azure-cost 是 autoskills 技能注册表中统一处理 Azure 成本管理的 Skill,覆盖历史成本查询、未来成本预测与成本优化三大工作流。本文聚焦其中的 Cost Query 工作流,其核心能力是通过 REST API 查询真实账单数据,回答"我花了多少钱""按服务/资源/标签如何拆分""成本趋势如何"等问题。
在动手构造请求体之前,需要先明确三个要素:
1. 查询 API 端点
| 项目 | 值 |
|---|---|
| 查询端点 | POST {scope}/providers/Microsoft.CostManagement/query?api-version=2023-11-01 |
| 推荐 CLI | az rest(比 az costmanagement query 更可靠) |
| 必备请求头 | ClientType: GitHubCopilotForAzure |
| 最小权限 | 作用域上的 Cost Management Reader + Monitoring Reader + Reader |
其中 {scope} 是作用域路径,例如订阅 /subscriptions/<subscription-id>、资源组 /subscriptions/<subscription-id>/resourceGroups/<resource-group-name>。完整的作用域 URL 模式可参考 SKILL.md。
2. 标准的执行流程(az rest)
workflow.md 给出了标准六步流程:确定作用域 → 选择报告类型 → 设定时间范围 → 配置 dataset → 构造并执行 API 调用 → 处理分页与错误。推荐的执行方式是把请求体写入临时文件,再通过 az rest 引用:
# 创建临时目录
New-Item -ItemType Directory -Path "temp" -Force
# 将请求体写入 temp/cost-query.json,然后执行查询
az rest --method post `
--url "<scope>/providers/Microsoft.CostManagement/query?api-version=2023-11-01" `
--headers "ClientType=GitHubCopilotForAzure" `
--body '@temp/cost-query.json'
也可以直接内联 JSON 请求体:
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" }
]
}
}'
3. 请求体的总体结构
request-body-schema.md 定义了请求体的完整骨架:
{
"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 时必须提供 from/to(ISO 8601 日期) |
dataset |
object | 是 | 定义粒度、聚合、分组、过滤、排序 |
预定义 timeframe 取值:WeekToDate、MonthToDate、BillingMonthToDate、YearToDate、TheLastWeek、TheLastMonth、TheLastBillingMonth、TheLastYear、TheLast7Days、TheLast3Months、Custom。
示例 1:按服务统计月度成本(Monthly Cost by Service)
最常用的查询模式——查看本月各 Azure 服务花了多少钱:
{
"type": "ActualCost",
"timeframe": "MonthToDate",
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"grouping": [
{ "type": "Dimension", "name": "ServiceName" }
],
"sorting": [
{ "direction": "Descending", "name": "Cost" }
]
}
}
要点拆解:
type: "ActualCost":返回实际账单成本(含购买项),是最直接的"我到底花了多少"口径。timeframe: "MonthToDate":从本月 1 日到今天,无需手动指定timePeriod。granularity: "None":不做日期拆分,只输出聚合后的总量。aggregation:别名totalCost对源列Cost做Sum。成本查询只支持Sum函数,源列可选Cost、PreTaxCost或UsageQuantity。grouping:按内置维度ServiceName分组。sorting:按Cost降序,让花费最高的服务排在最前面。
执行后会得到形如"Virtual Machines: 123.45 USD、Storage: 67.89 USD……"的逐服务成本列表,这正是"show my bill""cost by service"类问题的基础答案。
示例 2:近 30 天每日成本趋势(Daily Cost Trend)
如果需要观察成本的日粒度走势(例如排查成本突增),使用 Custom 时间范围配合 Daily 粒度:
{
"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" }
}
}
}
⚠️ Warning: Daily 粒度最多支持 31 天。
这里需要特别注意 guardrails.md 中的时间范围守则:
Custom时间范围必须提供timePeriod对象(from/to)。- 粒度与最大范围的对应关系:
Daily最大 31 天;Monthly与None最大 12 个月;API 的绝对上限是 37 个月,超过即返回 HTTP 400。 - 若请求范围超出粒度上限,系统会静默截断
from(Daily截断为to - 1 个月 + 1 天;Monthly/None截断为to - 12 个月 + 1 天),响应数据覆盖的是调整后的区间而非原始请求日期。 - 未来日期也会被静默调整:
from与to都在未来时,整个区间被平移至去年对应时段;只有to在未来时,to会被调整为今天。 - 最早允许的
from日期为 2014 年 5 月 1 日。
因此,做近 30 天趋势时,应把请求范围控制在 31 天以内,避免触发静默截断导致数据与预期不符。
示例 3:按资源组拆分成本并叠加标签过滤(Cost by Resource Group with Tag Filter)
当需要按资源组查看成本、同时只关注特定环境(如生产、预发)时,组合使用 grouping 与 filter:
{
"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" }
]
}
}
字段解析:
grouping使用维度ResourceGroupName,输出按资源组分组的成本。filter使用tags目标类型,对资源标签Environment应用In操作符,命中production或staging两个值中的任意一个即纳入统计。
过滤表达式的进阶能力
request-body-schema.md 进一步说明,过滤表达式支持逻辑运算符 and、or、not,并可在 dimensions 与 tags 两类目标上使用比较运算符:
| 运算符 | 说明 | 示例 |
|---|---|---|
In |
值在给定列表中,支持多值 | "values": ["vm", "storage"] |
Equal |
与单个值精确匹配 | "values": ["production"] |
Contains |
字符串包含指定子串 | "values": ["prod"] |
LessThan |
数值小于比较 | "values": ["100"] |
GreaterThan |
数值大于比较 | "values": ["0"] |
NotEqual |
与指定值不匹配 | "values": ["dev"] |
组合过滤的完整示例:
"filter": {
"and": [
{
"dimensions": {
"name": "ResourceGroupName",
"operator": "In",
"values": ["rg-prod", "rg-staging"]
}
},
{
"not": {
"tags": {
"name": "Environment",
"operator": "Equal",
"values": ["dev"]
}
}
}
]
}
⚠️ Warning:
and与or必须包含至少 2 个子表达式,not必须恰好包含 1 个子表达式;单个条件不要用逻辑运算符包裹,直接平铺过滤即可。
标签与维度的可用性约束
- 分组维度
TagKey需与grouping中的"type": "TagKey"配合使用;按标签过滤时目标类型为tags。 - 维度必须同时适用于协议类型与作用域类型的组合。例如 EA 专属维度
DepartmentName、EnrollmentAccountName、BillingPeriod在 MOSP(即用即付)订阅上会返回校验错误;MCA 的InvoiceSectionName同理。可用性矩阵详见 dimensions-by-scope.md。 - 每个查询最多 2 个 GroupBy 维度,且不允许重复列,也不允许同一列同时出现在聚合与分组中。
示例 4:预留实例摊销成本分析(Amortized Cost for Reservation Analysis)
查看按 Benefit(预留实例/节省计划)拆分的摊销成本,用于评估权益类购买的长期有效成本:
{
"type": "AmortizedCost",
"timeframe": "TheLastMonth",
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"grouping": [
{ "type": "Dimension", "name": "BenefitName" }
],
"sorting": [
{ "direction": "Descending", "name": "Cost" }
]
}
}
💡 Tip:
AmortizedCost会把预留实例的购买成本分摊到整个权益期限内,从而得到准确的每日/每月有效成本。
两种报告类型的口径差异(见 workflow.md):
| 类型 | 说明 |
|---|---|
ActualCost |
实际账单成本,含购买支出(如一次性购买预留实例的费用会在购买月全额体现) |
AmortizedCost |
预留/节省计划成本按使用期间分摊,更适合评估长期日均成本 |
Usage |
基于使用量的成本数据 |
需要注意:并非所有作用域都支持所有报告类型。若遇到"query usage is not supported for the scope"错误,应更换作用域或查询类型(详见 error-handling.md)。
示例 5:成本最高的 Top 10 资源(Top 10 Most Expensive Resources)
定位"钱都花在哪些具体资源上",这是成本优化与排查浪费的第一步:
{
"type": "ActualCost",
"timeframe": "MonthToDate",
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"grouping": [
{ "type": "Dimension", "name": "ResourceId" }
],
"sorting": [
{ "direction": "Descending", "name": "Cost" }
]
}
}
💡 Tip: 在 URL 末尾追加
&$top=10即可限制返回条数:...query?api-version=2023-11-01&$top=10
ResourceId 分组的作用域限制(重要)
guardrails.md 明确指出:按 ResourceId 分组仅在订阅作用域及以下(订阅、资源组)受支持,在计费账户、管理组等更高级别作用域不支持:
| 作用域 | ResourceId GroupBy |
|---|---|
| Subscription | ✅ 支持 |
| Resource Group | ✅ 支持 |
| Billing Account | ❌ 不支持 |
| Management Group | ❌ 不支持 |
| Billing Profile | ❌ 不支持 |
当用户在计费账户或管理组作用域要求按资源拆分成本时,应改用 ServiceName、SubscriptionName 等受支持维度;若确实需要资源级明细,先收窄到具体订阅作用域再查询。
分页
查询响应单页默认返回 1,000 行,最大 5,000 行。当结果集较大时,检查响应中的 nextLink 字段,若存在则继续请求下一页直到取完(详见 workflow.md)。
响应结构:如何读懂查询结果
request-body-schema.md 给出了统一的响应结构:
{
"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(Number 或 String) |
rows |
array | 行数组,每行的值顺序与 columns 一一对应 |
nextLink |
string | 下一页结果 URL,无更多页时为 null |
💡 Tip: 当粒度为
Daily或Monthly时,UsageDate以YYYYMMDD数字格式返回(例如20240115)。
常见错误与 429 限流处理
构造请求体时最常见的失败原因及对策,汇总自 error-handling.md:
| HTTP 状态 | 错误 | 典型原因 | 处理建议 |
|---|---|---|---|
| 400 | BadRequest |
请求体非法、维度不支持、日期范围超限、过滤表达式畸形 | 对照 request-body-schema.md 校验请求体,并在 dimensions-by-scope.md 中核对维度兼容性 |
| 401 | Unauthorized |
令牌缺失或过期 | az login 重新认证或刷新 bearer token |
| 403 | Forbidden |
目标作用域权限不足 | 在作用域上授予 Cost Management Reader 或 Cost Management Contributor |
| 404 | NotFound |
作用域/订阅/资源组不存在 | 核对作用域 URL、订阅 ID 与资源组名 |
| 429 | TooManyRequests |
QPU、实体或租户限流触发 | 读取全部 retry-after 头,取最大值等待后再重试 |
| 503 | ServiceUnavailable |
Cost Management 服务临时不可用 | 检查 Azure 状态页 |
限流是实践中最容易踩的坑。只有 429 可以重试,且最多重试 3 次;400/401/403/404/503 及其他 5xx 都应先修复问题,不要盲目重试。429 响应必须同时检查三类响应头并取最长的等待时间:
| 响应头 | 含义 |
|---|---|
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 |
租户限流等待秒数 |
各层级的限流阈值(见 guardrails.md):每用户 20 次/分钟、每作用域 4 次/分钟(最严格)、每租户 12 次/10 秒、60 次/分钟、600 次/小时、每客户端类型 2,000 次/分钟。对同一订阅/资源组/计费账户的连续查询共享每作用域限额,因此在批量查询时应合理规划请求节奏。
所有错误响应均遵循统一的 JSON 结构:
{
"error": {
"code": "<error-code>",
"message": "<human-readable-error-message>",
"details": [
{
"code": "<detail-code>",
"message": "<detail-message>"
}
]
}
}
查询守则速查表
综合 workflow.md 与 guardrails.md 的完整约束:
| 规则 | 约束 |
|---|---|
| Daily 粒度最大范围 | 31 天 |
| Monthly/None 粒度最大范围 | 12 个月 |
| API 绝对最大范围 | 37 个月(超出返回 400) |
最早允许的 from 日期 |
2014 年 5 月 1 日 |
| 最大 GroupBy 维度数 | 2 个(不允许重复列、聚合与分组列不能相同) |
| ResourceId 分组作用域 | 仅订阅与资源组 |
| 单页最大行数 | 5,000(默认 1,000) |
| Custom 时间范围 | 必须提供 timePeriod(from/to) |
and/or 子表达式 |
至少 2 个 |
not 子表达式 |
恰好 1 个 |
| 聚合函数 | 成本查询仅支持 Sum |
| 429 重试 | 读取全部 retry-after 头,取最大值等待,最多重试 3 次 |
此外,还有两个值得牢记的实战原则:一是过滤 EA + 管理组作用域下的 SubscriptionName 时,必须同时附带 SubscriptionId 过滤条件,否则会返回"To view cost data, the subscription ID is needed..."错误;二是按维度分组并叠加 Daily/Monthly 粒度时,系统会把时间区间调整为所请求范围的最后一天/最后一个月,如需完整的多日分组明细,请确保区间落在 31 天(Daily)或 12 个月(Monthly)上限内。
延伸阅读
- 查询全流程与命令细节:Cost Query Workflow
- 请求体字段完整定义:Request Body Schema
- 时间范围、分组、过滤等全部校验规则:Cost Query Guardrails
- 各作用域/协议类型下的维度可用性矩阵:Dimensions by Scope
- 错误码与重试策略完整参考:Error Handling
- 成本预测与成本优化工作流可分别参阅 cost-forecast 与 cost-optimization
- 该 Skill 的整体路由、作用域表与最佳实践见 SKILL.md
掌握以上 5 类请求体模板,再配合作用域、粒度与过滤约束,你就能用一条 az rest 命令覆盖日常成本查询的绝大多数场景——从账单汇总到资源级成本定位,全部基于 API 返回的真实数据,而非估算。