如何为 Google App Engine 构建 Billing Monitor?
结论
Google App Engine 没有类似 getPercentBillingCeiling() 的运行时 API,应用也不适合自行读取“当前费用占每日 billing ceiling 的百分比”。
目前可采用以下方案:
- 在 Google Cloud Budgets 中设置 80%、85%、90%、95%、98% 和 100% 阈值。
- 使用原生邮件通知,或将预算事件发送到 Pub/Sub,再通过 Cloud Run / Cloud Functions 发送自定义邮件。
- 如果需要每天按
$50重新计算,可以启用 Cloud Billing Export to BigQuery,定期汇总当天费用,并记录已经触发的阈值。
Cloud Billing 预算只负责告警,不会自动停止消费。费用数据还可能存在延迟,因此无法保证在超过上限前阻止应用继续运行。旧版 App Engine 的每日 billing ceiling 和当前 Google Cloud 的预算机制也不是同一个概念。
优先使用 Google Cloud Budgets
可以在 Google Cloud Console 的 Billing 页面为指定项目创建预算,并添加多个实际支出阈值:
80%
85%
90%
95%
98%
100%
使用原生邮件通知时,通常不需要另外编写监控程序。如果通知需要发送给自定义收件人、聊天机器人或告警系统,可以配置 Pub/Sub 通知。
下面是一个 Terraform 示例:
resource "google_pubsub_topic" "billing_alerts" {
name = "billing-alerts"
}
resource "google_billing_budget" "gae_budget" {
billing_account = var.billing_account_id
display_name = "GAE cost alerts"
amount {
specified_amount {
currency_code = "USD"
units = "50"
}
}
filter {
projects = [
"projects/${var.project_number}"
]
}
threshold_rules {
threshold_percent = 0.80
spend_basis = "CURRENT_SPEND"
}
threshold_rules {
threshold_percent = 0.85
spend_basis = "CURRENT_SPEND"
}
threshold_rules {
threshold_percent = 0.90
spend_basis = "CURRENT_SPEND"
}
threshold_rules {
threshold_percent = 0.95
spend_basis = "CURRENT_SPEND"
}
threshold_rules {
threshold_percent = 0.98
spend_basis = "CURRENT_SPEND"
}
threshold_rules {
threshold_percent = 1.00
spend_basis = "CURRENT_SPEND"
}
all_updates_rule {
pubsub_topic = google_pubsub_topic.billing_alerts.id
schema_version = "1.0"
}
}
这里的 $50 是预算金额,不一定等同于旧版 App Engine 所说的“每日硬上限”。Google Cloud Budgets 可用的预算周期取决于当前产品能力和具体配置。如果控制台或 Budget API 不支持所需的每日周期,就要自行汇总每日费用。
创建预算、发布 Pub/Sub 消息和订阅消息所需的 IAM 权限需要分别配置。预算资源创建成功,并不表示 Pub/Sub 的服务代理已经具备完整权限。
Pub/Sub 通知的处理方式
预算事件可以发送到 Pub/Sub。消费程序收到消息后,应根据事件中的预算金额、当前费用和已越过的阈值判断是否发送邮件。
示意代码如下:
import base64
import json
from decimal import Decimal
def handle_budget_event(cloud_event):
message = cloud_event.data["message"]
payload = json.loads(
base64.b64decode(message["data"]).decode("utf-8")
)
cost = Decimal(str(payload["costAmount"]))
budget = Decimal(str(payload["budgetAmount"]))
threshold = Decimal(str(payload.get("alertThresholdExceeded", 0)))
percent = Decimal("0") if budget == 0 else cost / budget
subject = (
f"Billing alert: {percent:.1%} of budget used"
)
body = (
f"Budget: {budget}\n"
f"Current cost: {cost}\n"
f"Threshold: {threshold:.0%}\n"
f"Currency: {payload.get('currencyCode', '')}\n"
)
send_email(subject, body)
def send_email(subject: str, body: str) -> None:
# 调用已选定的邮件服务,例如 SMTP、SendGrid 或其他邮件 API。
# 密钥应放入 Secret Manager,不要写入源码。
raise NotImplementedError
预算通知可能重复投递,也可能随着费用增长多次更新,因此处理程序必须具备幂等性。可以使用 Firestore 保存类似下面的唯一键:
billing-account/project + budget-id + billing-period + threshold
发送邮件前先检查该键,发送成功后再记录状态。若要严格避免并发引起的重复邮件,应使用事务或带前置条件的原子写入。
实现真正的每日费用检查
如果费用需要每天零点重新按 $50 计算,而预算功能又无法直接提供所需周期,可以启用 Cloud Billing Export to BigQuery,再通过计划任务查询当天费用。
示例 SQL:
SELECT
COALESCE(
SUM(
cost
+ IFNULL(
(SELECT SUM(credit.amount) FROM UNNEST(credits) AS credit),
0
)
),
0
) AS net_cost
FROM
`billing_export_project.billing_dataset.gcp_billing_export_v1_*`
WHERE
DATE(usage_start_time, "Asia/Shanghai")
= CURRENT_DATE("Asia/Shanghai")
AND project.id = "your-gae-project-id";
这段查询会将 credits 计入净费用。如果业务中的“费用”指抵扣前金额,可以只汇总 cost。税费、调整项、舍入方式和不同导出表的字段也可能影响结果,具体应以实际账单口径为准。
查询结果可以按以下逻辑判断:
from decimal import Decimal
THRESHOLDS = tuple(
Decimal(value)
for value in ("0.80", "0.85", "0.90", "0.95", "0.98", "1.00")
)
def crossed_thresholds(
previous_cost: Decimal,
current_cost: Decimal,
daily_limit: Decimal,
) -> list[Decimal]:
if daily_limit <= 0:
return []
previous_ratio = previous_cost / daily_limit
current_ratio = current_cost / daily_limit
return [
threshold
for threshold in THRESHOLDS
if previous_ratio < threshold <= current_ratio
]
假设上次检查时费用为 $39,本次检查时已达到 $46,程序会同时识别 80%、85% 和 90% 三个阈值。可以分别发送三封邮件,也可以合并为一封,说明在两次检查之间越过了多个阈值。
常见的执行链路如下:
Cloud Scheduler
→ Cloud Run / Cloud Functions
→ BigQuery 查询当天费用
→ Firestore 检查阈值状态
→ 邮件服务发送通知
Cloud Scheduler 可以每隔几分钟或每小时执行一次,但提高检查频率无法消除计费数据本身的延迟。
为什么不能依靠应用内部的百分比 API
资源用量或配额 API 即使存在,也无法直接推导出准确账单,因为最终费用可能受到以下因素影响:
- 实例规格与运行时间
- 请求、带宽和存储用量
- 免费额度
- 折扣、合同价格和 credits
- 跨项目共享资源
- 计费数据入库延迟
- 税费及后续账单调整
Cloud Billing Budget API 主要用于创建、查询和管理预算配置,不是实时费用查询 API。Cloud Billing Catalog API 提供的是公开价格信息,也不代表某个账号实际适用的最终价格。用公开单价乘以应用指标,只能得到估算值。
防止应用因费用问题突然失效
费用邮件适合用作预警,不能成为唯一的保护措施。需要可靠 failover 的应用还应配置:
- Cloud Monitoring 的错误率、延迟和实例数告警
- 关键服务的 quota 告警
- 限流、降级和熔断逻辑
- 非关键任务的暂停开关
- 跨区域或独立系统的故障转移方案
- 独立于被监控项目的通知通道
不要只在同一个 App Engine 应用中运行 billing monitor。如果项目因配额、权限、账单或服务状态出现问题,监控程序可能同时停止。将监控部署到独立项目,并使用单独的 Pub/Sub、Cloud Run 和通知凭据,可靠性会更高。
还需要预留足够的缓冲。预算事件和 Billing Export 都不是实时数据,因此 98% 或 100% 阈值更适合确认费用状态和升级告警,不适合承担“阻止停机”的职责。如果需要提前处置,应把可操作阈值设在更低的位置,例如 70% 或 80%。
备注:内容仅供参考。
