BMW 如何用 Prophet 与无服务器流水线监控 14,000 个云账户的成本异常

2026-09-22 12 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

BMW Group 的 FinOps 平台 CLEA 需要监控超过 14,000 个云账户。仅靠成本仪表盘,团队通常只能在费用已经增长后再去调查;BMW 因此加入了每日自动异常检测,利用 Prophet 预测成本基线,并通过 AWS Step Functions 编排无服务器处理流程,把被动查看报表变成主动告警。

更值得注意的是,这条流水线每天覆盖所有账户,月度运行成本约为 50 美元。它展示的并不只是一个预测模型,而是一种适合大规模 FinOps 的工程拆分方式:把账户分成独立任务,按需计算,集中汇总异常,只让工程师处理真正值得关注的变化。

仪表盘为什么不够

成本仪表盘擅长回答“上个月花了多少钱”,却不一定能及时回答这些问题:

  • 某个账户今天的支出是否偏离了自己的正常水平?
  • 周末下降、工作日回升,是周期规律还是异常?
  • 费用上涨 20%,对一个低成本测试账户和高成本生产账户是否同样重要?
  • 14,000 个账户中,哪些异常值得立即调查?

固定阈值很难处理这些差异。每天增加 100 美元,对月均数十万美元的账户可能只是噪声,对通常每天只花 20 美元的账户却可能意味着资源泄漏。

Prophet 的作用是根据历史序列建立动态基线,吸收趋势和周期性,再比较实际费用与预测区间。检测结果不应只包含一个“异常”布尔值,还应保留实际值、预测值、偏差金额和偏差比例,方便后续排序与告警聚合。

把 14,000 个账户变成可并行的日常任务

这种规模下,模型本身通常不是唯一难点。数据缺失、任务超时、API 限流和失败重试,都会影响检测结果。

可以把每日流程拆成五步:

  1. 获取待分析的账户清单。
  2. 为每个账户读取经过统一口径处理的每日成本序列。
  3. 使用历史数据训练或更新 Prophet 模型。
  4. 比较当天实际成本与预测基线,输出异常分数。
  5. 汇总异常,去重并发送告警。

AWS Step Functions 适合承担这里的控制面工作:维护任务状态、并行分发账户、限制并发量,并对暂时性失败执行重试。实际计算则可以交给按需启动的无服务器任务。这样没有长期空闲的检测集群,也不需要用单个长任务串行处理全部账户。

下面是一个可以改造的 Step Functions Map 状态示例。它不是 BMW 的内部配置;部署前需要把 Lambda ARN 和并发数替换为自己的值:

{
  "Comment": "Daily cloud cost anomaly detection",
  "StartAt": "DetectForAccounts",
  "States": {
    "DetectForAccounts": {
      "Type": "Map",
      "ItemsPath": "$.accounts",
      "MaxConcurrency": 100,
      "Iterator": {
        "StartAt": "DetectAnomaly",
        "States": {
          "DetectAnomaly": {
            "Type": "Task",
            "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:cost-anomaly-detector",
            "Retry": [
              {
                "ErrorEquals": ["Lambda.ServiceException", "Lambda.TooManyRequestsException"],
                "IntervalSeconds": 2,
                "MaxAttempts": 4,
                "BackoffRate": 2
              }
            ],
            "End": true
          }
        }
      },
      "Next": "PublishSummary"
    },
    "PublishSummary": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:publish-cost-alerts",
      "End": true
    }
  }
}

MaxConcurrency 不宜盲目调高。它应该同时考虑成本数据源的查询配额、函数并发额度、模型运行内存,以及告警下游能够承受的吞吐量。

动手实现一个最小 Prophet 检测器

下面的示例会生成 90 天模拟成本,其中最后一天被人为抬高。脚本只使用前 89 天训练模型,再判断最后一天是否异常,因此可以直接运行验证。

先准备环境:

python -m venv .venv
source .venv/bin/activate
python -m pip install prophet pandas numpy

保存为 detect_cost.py

from datetime import date, timedelta

import numpy as np
import pandas as pd
from prophet import Prophet

np.random.seed(7)

# 生成带有每周周期的模拟日成本。
days = 90
start = date.today() - timedelta(days=days - 1)
dates = pd.date_range(start=start, periods=days, freq='D')
weekly = np.where(dates.dayofweek < 5, 25.0, -20.0)
trend = np.linspace(0, 18, days)
cost = 200 + weekly + trend + np.random.normal(0, 7, days)
cost[-1] += 140  # 模拟最后一天发生费用突增

series = pd.DataFrame({'ds': dates, 'y': cost})
train = series.iloc[:-1].copy()
actual = float(series.iloc[-1]['y'])
target_day = series.iloc[-1]['ds']

model = Prophet(
    weekly_seasonality=True,
    daily_seasonality=False,
    yearly_seasonality=False,
    interval_width=0.99,
)
model.fit(train)

future = pd.DataFrame({'ds': [target_day]})
prediction = model.predict(future).iloc[0]
expected = float(prediction['yhat'])
upper = float(prediction['yhat_upper'])

absolute_delta = actual - expected
relative_delta = absolute_delta / max(expected, 1.0)

# 同时要求突破预测区间,并满足金额或比例门槛,减少小额噪声。
is_anomaly = actual > upper and (
    absolute_delta >= 50 or relative_delta >= 0.30
)

print({
    'date': str(target_day.date()),
    'actual': round(actual, 2),
    'expected': round(expected, 2),
    'upper_bound': round(upper, 2),
    'delta': round(absolute_delta, 2),
    'delta_percent': round(relative_delta * 100, 1),
    'is_anomaly': bool(is_anomaly),
})

运行:

python detect_cost.py

接入真实系统时,将模拟数据替换成某个账户的每日摊销成本即可。建议至少保留以下输出字段:

account_id, billing_date, actual_cost, expected_cost,
upper_bound, absolute_delta, relative_delta, model_version

这能让告警系统按影响金额排序,也能在模型参数调整后回溯结果。

告警质量比异常数量更重要

把模型跑起来只是第一步。生产环境还需要处理几个边界条件:

  • 新账户缺少历史数据:可先使用简单移动平均或规则阈值,积累足够数据后再启用 Prophet。
  • 月底账单修正:退款、抵扣和延迟入账可能产生假异常,需要区分使用量增长与账务调整。
  • 零成本跳变:从 0 增长到 10 美元的比例无法正常解释,应同时设置绝对金额门槛。
  • 连续异常:同一问题持续多天时,不应每天创建一条新事件,可以按账户、服务和异常方向合并。
  • 业务已知变化:迁移、压测和大型发布可能是计划内支出,应支持静默窗口或事件标注。

Prophet 也不是所有序列的通用答案。历史数据太短、成本极度稀疏,或者单日费用主要由一次性采购驱动时,预测区间可能不稳定。此时应回退到规则检测,或者在服务、区域、标签等维度重新聚合数据。

落地时的检查清单

BMW 的案例说明,在 14,000 多个账户的规模上,主动成本检测并不必然意味着昂贵的常驻分析集群。通过每日批处理、Step Functions 编排和无服务器执行,整条流水线可以保持很低的运行费用。

落地类似方案时,可以按以下顺序推进:

  • 统一每日成本口径,先解决缺失、退款和币种问题。
  • 从账户级异常开始,避免一开始就产生海量服务级告警。
  • 同时设置预测区间、绝对金额和相对比例门槛。
  • 为每次检测保存输入日期、模型版本和预测结果。
  • 限制并发并配置重试,避免检测系统反过来压垮成本数据源。
  • 用“告警后节省了多少费用”衡量效果,而不是只统计模型发现了多少异常。

真正有价值的 FinOps 异常检测,不是画出更复杂的预测曲线,而是让一次不正常的成本增长在影响扩大之前,进入正确团队的处理队列。


相关推荐