云厂商集体涨价,我把多云成本监控工具链跑了一遍,踩了两个坑

73 阅读3分钟

最近这波云涨价让我不得不把尘封半年的多云成本脚本翻出来重新跑了一遍。本文记录整个过程,以及两个让我想摔键盘的坑。

背景

2026年Q1,AWS/阿里云/腾讯云/百度云集体上调AI算力价格,AWS GPU实例涨了约15%,阿里云高性能存储涨了30%,腾讯云混元API直接涨了463%。我们团队的月账单从上个季度的约4200飙到了预测4200飙到了预测5600——被迫开始认真看成本了。


技术方案:多云账单聚合 + 实时告警

我的方案分三层:

  1. 数据采集层:AWS Cost Explorer API + 阿里云费用API,每小时拉一次增量账单
  2. 聚合计算层:统一转成USD,按工作负载标签分摊
  3. 告警层:超过日预算80%触发企微Webhook

环境准备

# Python 3.11+,建议用 venv 隔离
python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate# 安装依赖
pip install boto3==1.34.0 aliyun-python-sdk-bssopenapi==1.0.0 httpx==0.27.0

💡 阿里云账单需要开通 BSS OpenAPI 权限,去RAM控制台给账号加 AliyunBSSReadOnlyAccess 策略。AWS侧需要开 Cost Explorer(每次API调用收费$0.01,注意别搞个循环死刷)。

如果嫌配多家API麻烦,可以先用聚合商平台过渡,比如 Ztopcloud 支持阿里云+AWS账单统一收口,对接一次就够,不用分别维护两套认证。


代码实现

1. AWS账单采集

import boto3
from datetime import datetime, timedelta
from typing import Optional
​
def get_aws_daily_cost(
    start_date: Optional[str] = None,
    end_date: Optional[str] = None
) -> dict:
    """
    获取AWS每日按服务分类的成本
    注意:Cost Explorer有约24小时延迟,当天数据不准确
    """
    ce = boto3.client("ce", region_name="us-east-1")
    
    if not start_date:
        start_date = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
    if not end_date:
        end_date = datetime.now().strftime("%Y-%m-%d")
    
    response = ce.get_cost_and_usage(
        TimePeriod={"Start": start_date, "End": end_date},
        Granularity="DAILY",
        Metrics=["UnblendedCost"],
        GroupBy=[
            {"Type": "DIMENSION", "Key": "SERVICE"},
        ]
    )
    
    result = {}
    for period in response["ResultsByTime"]:
        date = period["TimePeriod"]["Start"]
        result[date] = {
            group["Keys"][0]: float(group["Metrics"]["UnblendedCost"]["Amount"])
            for group in period["Groups"]
        }
    
    return result
​
​
# 使用
costs = get_aws_daily_cost()
for date, services in costs.items():
    ec2_cost = services.get("Amazon Elastic Compute Cloud - Compute", 0)
    bedrock_cost = services.get("Amazon Bedrock", 0)
    print(f"{date}: EC2=${ec2_cost:.2f}, Bedrock=${bedrock_cost:.2f}")

2. 阿里云账单采集

from aliyunsdkcore.client import AcsClient
from aliyunsdkbssopenapi.request.v20171214.QueryInstanceBillRequest import QueryInstanceBillRequest
import json
​
def get_aliyun_monthly_bill(
    access_key_id: str,
    access_key_secret: str,
    billing_cycle: str  # 格式: "2026-04"
) -> list:
    """获取阿里云月度账单明细"""
    client = AcsClient(access_key_id, access_key_secret, "cn-hangzhou")
    
    request = QueryInstanceBillRequest()
    request.set_BillingCycle(billing_cycle)
    request.set_ProductCode("ecs")  # 只查ECS,改成空字符串查全部
    request.set_PageNum(1)
    request.set_PageSize(100)
    
    response = client.do_action_with_exception(request)
    data = json.loads(response)
    
    items = data.get("Data", {}).get("Items", {}).get("Item", [])
    return [
        {
            "instance_id": item.get("InstanceID"),
            "product": item.get("ProductName"),
            "cost_cny": float(item.get("PretaxAmount", 0)),
            "billing_item": item.get("BillingItem"),
        }
        for item in items
    ]

3. 统一聚合与告警

import httpx
from dataclasses import dataclass
​
@dataclass
class DailyCostReport:
    date: str
    aws_usd: float
    aliyun_cny: float
    
    @property
    def total_cny(self) -> float:
        # 粗略汇率,实际请用实时汇率API
        USD_TO_CNY = 7.24
        return self.aws_usd * USD_TO_CNY + self.aliyun_cny
    
    def check_alert(self, threshold_cny: float = 2000.0) -> bool:
        return self.total_cny > threshold_cny
​
​
def send_wecom_alert(webhook_url: str, report: DailyCostReport):
    """发送企业微信告警"""
    msg = (
        f"⚠️ 云成本告警\n"
        f"日期:{report.date}\n"
        f"AWS:${report.aws_usd:.2f}\n"
        f"阿里云:¥{report.aliyun_cny:.2f}\n"
        f"折合总计:约¥{report.total_cny:.0f}"
    )
    
    httpx.post(webhook_url, json={"msgtype": "text", "text": {"content": msg}})

踩坑记录

坑1:Cost Explorer的"今日数据"延迟问题

这个问题害我白高兴了两天。

我设了一个今日实时告警,发现AWS的get_cost_and_usage接口返回今天的数据几乎一直是$0。以为代码写错了,反复调试。

后来才发现:Cost Explorer的成本数据有约24小时延迟,今天的数据最早要明天才能查到。官方文档里有写,但藏得很深,我在第三页才找到那句话。

解决方案:改成查"昨日-前日"差值作为当日估算,同时接CloudWatch的实时Billing Alarm(这个是真实时,超阈值立刻推送,但精度是月累计额)。

# 正确的今日成本估算:用昨天整天数据
from datetime import date, timedelta
​
yesterday = (date.today() - timedelta(days=1)).isoformat()
today = date.today().isoformat()
​
# 查昨天到今天(即昨天整天)
costs = get_aws_daily_cost(start_date=yesterday, end_date=today)

坑2:阿里云BSS API的分页没有"总页数"字段

阿里云的QueryInstanceBill接口响应里有TotalCount,但没有直接告诉你总页数。你需要自己算:total_pages = ceil(TotalCount / PageSize)

但更坑的是:当月正在进行中的账单,TotalCount会随时变化(因为资源在持续计费),导致你分页到一半,后面几页的数据已经和第一页对不上了。

目前我的处理方式是:月初拉上个月全量数据(稳定);当月数据只用来做趋势判断,不用来做精确报表。


小结

这套多云账单监控工具花了我大概两天,踩了上面两个坑之外,整体跑通了。每天早9点自动推送前一天的账单,比盯着控制台方便多了。

这一波涨价大概率是中长期趋势,既然降不回去了,那就把钱花在哪里搞清楚,能省的省,能迁的迁,省不了的也要知道原因。

有做多云成本优化的朋友,欢迎评论区聊聊你们的方案,我一直觉得这块最好的实践都藏在各家公司的内部文档里。