如何用 Python 按特定 tag 查询 AWS CostExplorer 成本?
结论
AWS Cost Explorer 支持按成本分配标签(cost allocation tag)查询费用。如果查询结果始终为 0,通常需要检查两个问题:
- 查询使用了
NormalizedUsageAmount。它表示归一化用量,不是账单金额。查询成本时,应改用UnblendedCost、AmortizedCost等成本指标。 - 资源上的普通 AWS tag 不会自动成为 Cost Explorer 可用的成本分配标签。必须先在 Billing 控制台激活
client_id。激活只影响之后产生的成本,无法追溯标记历史费用。
Filter 的基本结构没有明显问题。排查重点是确认标签是否已激活,以及 Metric 是否选对。
正确的查询方式
如果要计算某个客户上个月产生的费用,可以使用 UnblendedCost:
import json
from datetime import date, timedelta
import boto3
def previous_month():
first_day_this_month = date.today().replace(day=1)
last_day_previous_month = first_day_this_month - timedelta(days=1)
first_day_previous_month = last_day_previous_month.replace(day=1)
return (
first_day_previous_month.isoformat(),
first_day_this_month.isoformat(),
)
start_date, end_date = previous_month()
client = boto3.client(
"ce",
region_name="us-east-1",
)
response = client.get_cost_and_usage(
TimePeriod={
"Start": start_date,
"End": end_date,
},
Granularity="MONTHLY",
Metrics=["UnblendedCost"],
Filter={
"Tags": {
"Key": "client_id",
"Values": ["1234567890"],
"MatchOptions": ["EQUALS"],
}
},
)
print(json.dumps(response, indent=2))
Cost Explorer 的 End 日期不包含当天。例如,查询 2021 年 9 月时应使用:
TimePeriod={
"Start": "2021-09-01",
"End": "2021-10-01",
}
结果中的金额可以从以下位置读取:
result = response["ResultsByTime"][0]
cost = result["Total"]["UnblendedCost"]
amount = cost["Amount"]
currency = cost["Unit"]
print(amount, currency)
Amount 是字符串。如果要计算发票金额,应使用 Decimal,以免引入浮点数误差:
from decimal import Decimal
invoice_amount = Decimal(amount)
必须先激活成本分配标签
给 Lambda、SQS 或其他资源添加:
client_id=1234567890
这一步只是配置了资源标签。还需要在 AWS Billing and Cost Management 中完成以下操作:
- 打开 Cost allocation tags。
- 找到用户定义标签
client_id。 - 激活该标签。
- 等待 Billing 和 Cost Explorer 处理数据。
- 查询激活之后产生的费用。
标签创建后可能不会立刻出现在 Billing 控制台,激活后的成本数据也可能需要一段时间才能处理完成。
尤其要注意,成本分配标签通常不会追溯到激活前的历史成本。如果 client_id 在 2021 年 10 月才激活,用它筛选 2021 年 1 月至 9 月的费用,很可能得不到预期结果,即使资源现在仍带有该标签。
为什么 NormalizedUsageAmount 不适合生成发票
NormalizedUsageAmount 是用量指标,不是货币成本。它主要用于比较可归一化的用量,而且并非所有 AWS 服务都能提供有意义的归一化用量。
Lambda、SES、API Gateway、SQS 和 Lightsail 的计费单位各不相同。把这些服务放在一起查询 NormalizedUsageAmount,无法得到可用于开票的总金额,返回单位也可能是 N/A。
常见的成本指标有:
UnblendedCost:未混合成本,适合直接查看资源产生的费用。AmortizedCost:将 Savings Plans、预留实例等预付费用摊销到使用周期。NetUnblendedCost:考虑适用折扣后的未混合成本,是否有数据取决于账户和账单情况。NetAmortizedCost:考虑适用折扣后的摊销成本,同样取决于账户情况。
建立简单的客户用量计费流程时,可以先使用 UnblendedCost。如果账户使用了 Savings Plans、预留实例或企业折扣,则要先确定发票采用哪种成本口径,不能只根据 API 返回值决定。
用分组查询检查标签是否匹配
排查时,可以先不按具体值过滤,而是按 client_id 分组,查看 Cost Explorer 实际识别到了哪些标签值:
response = client.get_cost_and_usage(
TimePeriod={
"Start": "2021-09-01",
"End": "2021-10-01",
},
Granularity="MONTHLY",
Metrics=["UnblendedCost"],
GroupBy=[
{
"Type": "TAG",
"Key": "client_id",
}
],
)
print(json.dumps(response, indent=2))
使用 GroupBy 后,金额通常位于 Groups,而不是 Total:
for period in response["ResultsByTime"]:
for group in period["Groups"]:
print(group["Keys"], group["Metrics"]["UnblendedCost"])
这种查询可以帮助发现以下问题:
client_id尚未激活;- 标签值的大小写或空格不一致;
- 部分资源没有标签;
- 费用产生时,资源尚未添加标签;
- 某些服务费用无法归属到资源标签;
- 查询时间早于标签激活时间。
还需检查的配置
Cost Explorer 是否已启用
付款账户或管理账户需要先启用 Cost Explorer,并等待成本数据可用。刚启用时,历史数据和标签维度不一定会立即出现。
IAM 权限是否完整
调用身份至少需要相应的 Cost Explorer 读取权限,例如:
{
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage",
"ce:GetTags"
],
"Resource": "*"
}
实际权限应根据组织的安全策略进一步收紧。权限不足通常会触发访问错误,而不是正常返回全零,因此它不是这个示例中最可能的原因。
标签必须在费用产生时有效
成本数据不会根据资源当前的标签重新计算。如果到月底才给一个已经运行整月的资源添加标签,月初产生的费用未必能归到该客户名下。
并非所有费用都能完整归属到资源标签
税费、支持费、部分抵扣、退款、共享费用以及某些非资源级费用可能没有 client_id。因此,按客户标签汇总的金额不一定等于 AWS 发票总额。
如果要用于正式开票,还需要明确以下项目的处理方式:
- 未标记成本;
- 共享基础设施成本;
- Credits 和 Refunds;
- Tax 和 Support;
- Savings Plans 或预留实例的摊销;
- 月末账单调整;
- 当前月份中
Estimated: true的数据。
建议的排查顺序
- 在 Cost Explorer 控制台确认同一时间范围内确实存在成本。
- 将 Metric 改为
UnblendedCost,暂时移除Filter,确认 API 能返回账户总成本。 - 确认 Billing 中的
client_id已作为用户定义成本分配标签激活。 - 使用
GroupBy按client_id分组,检查实际可用的标签值。 - 只查询标签激活且数据处理完成之后的时间范围。
- 最后添加
EQUALSFilter,并使用Decimal汇总金额。
当 client_id 已正确激活、费用产生时标签已经生效,并且 Metric 使用成本指标时,Cost Explorer 就能按客户标签汇总 AWS 成本。
备注:内容仅供参考。
