预算告警与通知
当支出超过预算限额的指定百分比时,AISIX Cloud 预算告警会通知你。你可以用它在阻断型预算开始拒绝流量前提醒运维人员,或监控仅告警预算。
前提条件
开始前,请准备以下内容:
- 至少一个现有预算。
- 用作组织通知渠道的 Webhook URL 或 Slack Incoming Webhook URL。
配置通知渠道
通知渠道是组织级告警目标,在 Notifications 视图中管理。组织内触发的每个告警都会发送到所有已启用的渠道。
支持两种渠道类型:
- Webhook:控制面会将每个告警作为 JSON
POST请求发送到你的 URL。可通过这种方式与事件处理工具、聊天系统或其它接受 HTTP 回调的系统集成。Webhook 渠道还可以自定义请求体和添加请求头。 - Slack:设置 Slack Incoming Webhook URL 后,告警会以易读的 Slack 消息发送。
创建并测试渠道:
- 打开 Notifications,然后选择 New channel。
- 在 Name 中输入名称,选择 Type,并填写 URL。
- 保持选中 Enabled,然后选择 Create channel。
- 为新渠道选择 Test,并确认目标已收到测试通知。
渠道 URL 是只写字段。创建渠道后,控制面仅显示类似 https://hooks.slack.com/*** 的脱敏形式。编辑渠道时将 URL 留空会保留已存储的 URL。
禁用渠道会停止后续投递。删除渠道会保留其历史投递记录,直至这些记录到达 90 天保留期;仍在队列中等待投递的通知会被标记为失败。
允许私有网络目标
On-Premises 控制面默认会投递到解析为私有、环回或链路本地地址的渠道 URL,因此位于内网的 Webhook 接收器或 Slack 代理无需额外配置即可使用。
如需拒绝此类目标,使操作人员提供的 URL 无法借助控制面访问内部端点,请在控制面 API 上设置 AISIX_CLOUD_SSRF_PROTECTION_ENABLED=true 开启 SSRF 防护。对应的 Helm Chart 配置项为 api.outbound.ssrfProtection.enabled。此时仍可保存使用 私有 URL 的渠道,但除非其主机列入允许的主机,对它的测试和告警投递会失败。该设置同样控制控制面的其他出站调用,参见私有网络访问。
配置告警阈值
每个预算都包含一个告警阈值列表,阈值以预算限额的百分比表示。按照以下步骤为每个预算配置阈值:
- 打开 Budgets 视图,并选择要编辑的预算。
- 在 Alert thresholds (%) 中输入一个或多个整数百分比,多个值之间用逗号分隔。例如,设置
80, 100可分别在接近限额和达到限额时收到告警。 - 保存预算。
最多可输入 20 个不重复的阈值,取值范围为 1 到 200。若留空,控制面将使用 80。
每个阈值在每个预算周期内只触发一次。当支出超过月度预算的 80% 时,控制面会向每个已启用的渠道发送一次告警。同一周期内不会重复发送 80% 告警,但其它已配置阈值可以分别触发告警。周期重置后,每个阈值都可以再次触发。对于按团队成员设置的预算(each member in a team),每个成员会独立触发告警。
对于阻断型预算,高于 100% 的阈值通常不会达到,因为后续请求会在达到限额时被拒绝。不过,低于限额时已准入的请求可能在完成时使支出超过限额,并发请求也可能造成超额。出现这类情况时,高于 100% 的阈值可以触发。
控制面会持续评估支出。即使之后没有更多流量,支出超过阈值后通常也会在 30 秒 内发出告警。
Webhook 载荷
默认情况下,Webhook 渠道会收到以下 JSON 文档形式的告警:
{
"event": "budget_threshold",
"dedup_key": "budget_threshold:6f6d…:…:1782864000:80",
"org_id": "1f0c…",
"budget_id": "6f6d…",
"budget_name": "payments-team monthly",
"scope": "team",
"scope_ref": "9a2b…",
"subject_name": "payments-team",
"threshold_pct": 80,
"percent": 82.3,
"spent_cents": 8230,
"limit_cents": 10000,
"period": "month",
"period_start": "2026-07-01T00:00:00Z",
"triggered_at": "2026-07-21T09:00:00Z",
"currency": "USD"
}
投递采用至少一次语义:重试或多渠道告警可能会多次到达同一接收器。同一次触发的所有投递都使用相同的 dedup_key。如果接收器必须对每个告警只处理一次,请使用该字段去重。Slack 渠道会以文本消息 的形式呈现相同信息。Slack 消息和下文的 .message 都用币种符号($ 或 ¥)书写金额。
请求的 Content-Type 始终为 application/json。
自定义请求体
在 Webhook 渠道上设置 Body template,即可发送你自定义结构的请求体,而不是默认文档。你可以借此把告警直接投递到要求特定消息格式的聊天平台。该字段留空则发送默认文档。
模板使用 Go text/template,变量写作 {{ .name }}。可用变量如下:
| 变量 | 说明 |
|---|---|
.event | 事件类型,取值为 budget_threshold。 |
.dedup_key | 同一次触发的所有投递共用的标识。 |
.org_id | 组织 ID。 |
.budget_id | 预算 ID。 |
.budget_name | 预算显示名称。 |
.scope | 预算作用域,例如 org、team 或 member。 |
.scope_ref | 作用域对象的 ID。 |
.subject_name | 作用域对象的名称。 |
.threshold_pct | 触发的阈值,整数百分比。 |
.percent | 支出占限额的百分比。 |
.spent_cents | 支出金额,单位为 currency 的最小单位:美元为美分,人民币为分。 |
.limit_cents | 预算限额,单位为 currency 的最小单位。 |
.period | 预算周期,例如 month。 |
.period_start | 当前周期的开始时间,RFC 3339 格式。 |
.triggered_at | 告警触发时间,RFC 3339 格式。 |
.currency | 组织币种,USD 或 CNY。 |
.message | 告警的单行可读摘要。 |
前 16 个变量就是默认文档中的字段;.message 是额外提供的便捷变量,例如 Budget 'payments-team monthly' (team 'payments-team') reached 82.3% of its month limit: spent $82.30 of $100.00。
模板中可以使用一个函数 json,它将值渲染为 JSON 字面量,并对字符串完成加引号和转义。嵌入任何值时都建议使用它:
{"msg": {{ .message | json }}}
在引号内直接插值——{"msg": "{{ .message }}"}——同样可以渲染,但不建议这样写:组织自己设置的预算名或团队名中可能包含引号或反斜杠,此时模板渲染出的内容将不是合法 JSON。这会使该次投递永久失败,告警随之丢失。
模板需遵守以下规则:
- 渲染结果必须是合法 JSON,且不超过 64 KiB。
- 引用表格之外的变量名会报错,而不是渲染为空字符串。
range、template和block会被拒绝。每个变量都是单个字符串或数字,既没有可遍历的内容,也没有可调用的第二个模板。if和with可以使用。- 保存渠道时,控制面会用一个示例告警渲染该模板;如果渲染失败或结果不是 JSON,保存会被拒绝。
- 如果模板在保存时能渲染、但在真实告警上渲染失败,该次投递会立即结束,不会重试。
- 与请求头的值不同,模板不是只写字段:读取渠道时会原样返回。请把凭据放在请求头中,不要放进模板正文。
- Body template 和 Headers 仅 Webhook 渠道支持。将已有的 Webhook 渠道改为
slack类型时,若这两个字段仍有值,请求会被拒绝——请在同一次请求中清空它们。
聊天平台示例
{"msg_type": "text", "content": {"text": {{ .message | json }}}}
{"msgtype": "text", "text": {"content": {{ .message | json }}}}
{"msgtype": "text", "text": {"content": {{ .message | json }}}}
如果不使用 .message,而要自行拼接文本:
{"msgtype": "text", "text": {"content": {{ printf "Budget %s reached %d%% (%.1f%%) of its %s limit" .budget_name .threshold_pct .percent .period | json }}}}
添加请求头
在 Webhook 渠道上设置 Headers,即可在每次投递时附带额外的请求头,例如 Bearer Token、共享签名密钥或租户标识。凭据应当放在这里,因为请求头的值是只写的。
请求头需遵守以下规则:
- 最多 16 个请求头。
- 名称必须是合法的 HTTP 请求头名称,长度不超过 128 个字符;任意两个名称不能仅大小写不同。
- 值的长度不超过 4096 个字符,且不能包含任何控制字符——不仅仅是换行符。
Content-Type、Content-Length、Host、Transfer-Encoding和Connection会被拒绝(忽略大小写匹配),这些请求头由控制面自行设置。
读取渠道时会返回已配置的请求头名称,但每个值都被替换为 ***,因此「读取后修改再写回」可以直接使用:为某个名称提交 *** 会保留该名称下已存储的值(名称匹配忽略大小写)。如果为一个没有已存储值的名称提交 ***,请求会被拒绝,否则该请求头的值就真的变成了 ***。提交空对象会清除所有已配置的请求头。
验证投递
Notifications 视图中的 Delivery log 会记录组织在最近 90 天内实际触发的告警。超过 90 天的投递记录会被自动删除。每条记录包含渠道、状态(pending、delivered 或 failed)、尝试次数、最后一次错误和完整载荷。
发送失败后会按逐步增加的间隔自动重试:首次失败后 1 分钟,然后依次为 5 分钟、15 分钟、1 小时和 6 小时。连续 6 次失败后,投递会被标记为 failed,并保留在日志中供检查。而重试无法解决的失败——例如请求体模板无法渲染——则会让该次投递立即结束。
如果失败的这次尝试收到了 HTTP 响应,记录中还会包含该响应的状态码和响应体。当状态码本身不足以说明被拒绝的原因时,响应体往往是唯一的线索。响应体按目标返回的原样保留,最多 1 MiB,但该渠道自己发出的凭据会被替换为 ***:渠道 URL(完整 URL,以及仅路径和查询参数的形式),以及任意长度不小于 8 个字符的已配置请求头的值——因为目标常常会把它们回显在错误信息里。长度更短的请求头值不做替换,否则会把无关内容也一并遮蔽。
投递列表中只携带响应体的前 8 KiB;在记录上选择 Load full response,即可通过 GET /notification_deliveries/{delivery_id} 获取完整内容。投递成功时不会记录响应,未收到响应的尝试(例如超时、地址被拒绝或域名无法解析)同样不会记录。
测试发送不会出现在投递日志中。它是同步操作,不会重试,其结果直接返回给控制台:包含实际发出的请求体,以及在收到响应时(无论成功还是失败)目标返回的状态码和响应体(按同样规则脱敏)。在正式依赖某个请求体模板前,可以用它确认模板渲染出的内容符合预期。
当部署允许私有网络目标时(这是 On-Premises 的默认行为),测试发送会把目标的响应体返回给任何有权编辑通知渠道的人。在操作人员不完全可信的控制面上,请设置 AISIX_CLOUD_SSRF_PROTECTION_ENABLED=true。
通过 AISIX Cloud Admin API 管理告警
通知渠道属于 AISIX Cloud Admin API:
GET /notification_channelsPOST /notification_channelsGET /notification_channels/{channel_id}PATCH /notification_channels/{channel_id}DELETE /notification_channels/{channel_id}POST /notification_channels/{channel_id}/test
预算告警阈值对应预算资源的 alert_thresholds 字段。有关请求和响应结构,请参阅 AISIX Cloud Admin API 参考。