基于 autoskills 的 Azure 成本管理实战指南:查询、预测与优化三大工作流全解析

原创2026-10-08 18:43:12484 阅读

基于 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 详解

来自 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 时必须提供;含 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 成本分析插件(命名空间级成本可见性)

来自 azure-aks-cost-addon.md:

# 检查插件是否启用
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,用于调查用户报告的成本/利用率突增:

  1. 确认时间窗:询问“什么时候注意到突增?”(如“上周二”“昨天凌晨 2-4 点”);
  2. 拉取成本数据:用 az rest 按日粒度 + ResourceId 分组查询异常窗口;
  3. 拉取节点数与扩缩容事件:先用 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 扩缩容事件;
  4. 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 成本治理流程。

登录后查看全文
autoskills