Azure Cost Management 查询实战:基于 autoskills azure-cost Skill 的 Cost Query API 请求体示例详解

原创2026-10-08 19:05:371,750 阅读

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)上限内。

延伸阅读

掌握以上 5 类请求体模板,再配合作用域、粒度与过滤约束,你就能用一条 az rest 命令覆盖日常成本查询的绝大多数场景——从账单汇总到资源级成本定位,全部基于 API 返回的真实数据,而非估算。

登录后查看全文
autoskills