# 指标规则

指标规则把阈值写在 PromQL 里，一条规则可带 S1 / S2 / S3 多档级别，查询返回的标签会落到事件上供路由使用。

最后校对于 2026-10-10

这页办完你会看得懂、也写得出 Prometheus 类规则的「告警条件」那一步——阈值写在哪儿、 一条规则怎么带多个级别、查询返回的标签里哪些最后会落到事件上。

这是 **Prometheus Like** 类数据源的规则类型：Prometheus 本身、VictoriaMetrics、Thanos、Mimir。 其他类型见[日志规则](https://n9e.github.io/zh/docs/alerting/log-rules.md)和 [SQL 规则](https://n9e.github.io/zh/docs/alerting/sql-rules.md)。

## 查询列表[​](#查询列表 "查询列表的直接链接")

第 3 步**告警条件**下面是一个叫**查询与阈值**的列表。每一项是一条 PromQL 加一个级别， 用列表下方的虚线按钮加新的一项。

```
查询      mem_used_percent > 80

级别      S2
```

每一项每个执行周期以**即时查询**的方式打到数据源上——和即时查询发的是同一种请求， 不是 range query。

## 阈值就写在 PromQL 里[​](#阈值就写在-promql-里 "阈值就写在 PromQL 里的直接链接")

没有单独的阈值输入框。比较写在表达式里，**查询返回几条序列就产生几条事件**：

```
mem_used_percent > 80
```

数据源只会返回大于 80 的那些序列，所以返回的每一条按定义就是异常点。 这和 Prometheus 的告警规则完全一样，也正是判定便宜的原因：过滤是在时序库里做的，不在夜莺里。

由此带来几个实际后果：

* **一条序列进，一条事件出。** 查询返回 30 台越线的机器，就产生 30 条事件， 每条带着那条序列自己的标签。想少一点，就用 `sum by (...)` 聚合。
* **查不到东西等于「正常」。** 「没有任何东西超阈值」和「exporter 不上报了」在结果上都是空， 区分不了。要对「数据消失」告警，就单写一条规则，用 `up == 0` 或 `target_up == 0`。
* **恢复时拿不到值。** 序列不再被返回时事件就恢复了，而恢复时刻的值没有—— 数据库压根什么都没返回，没地方读。

四则运算和条件筛选都能用，毕竟它就是 PromQL：

```
http_request_success{region="beijing"} / http_request_total{region="beijing"} < 0.995
```

## 一条规则里的多个级别[​](#一条规则里的多个级别 "一条规则里的多个级别的直接链接")

列表里加多条查询，每条各带自己的级别。常见写法：

```
disk_used_percent > 85     S2

disk_used_percent > 95     S1
```

加了第二条查询之后，列表标题旁边会出现**级别抑制**开关。打开它， 同一条序列同时命中多档时只有最严重的那档产生事件——磁盘到 97% 只按 S1 叫你一次，不会叫两次。

抑制比较的是序列，不是规则：只有指标名**和全部标签**都完全相同的事件之间才会互相抑制。 S1 压 S2，S2 压 S3。

## 执行频率与持续时长[​](#执行频率与持续时长 "执行频率与持续时长的直接链接")

查询列表下面：

| 字段         | 默认         | 含义                                                                        |
| ------------ | ------------ | --------------------------------------------------------------------------- |
| 执行频率     | `@every 60s` | 秒级精度的 cron 表达式。下拉里有常用间隔，`@every 15s` 和标准 cron 写法都收 |
| 持续时长 (s) | 60           | 条件要连续成立多久才产生事件。`0` 表示第一次命中就报                        |

持续时长是按序列判的：同一条序列要在连续几个周期里都被返回，直到累计时间覆盖了持续时长。 只出现过一次就消失的序列，永远不会产生事件——也就不会有恢复，因为它压根没产生过事件。

执行频率按你负担得起的值来设，不是按下拉里最小的那个。一条匹配十个数据源的规则设成 `@every 15s`，一小时就是 2400 次查询，还没算任何人打开仪表盘。 详见[判定周期与恢复](https://n9e.github.io/zh/docs/alerting/evaluation.md)。

## 保存之前先预览[​](#保存之前先预览 "保存之前先预览的直接链接")

每张查询卡片上都有**数据预览**，把表达式打到选中的数据源上并画成图。它有两个用途：

* 确认表达式到底返不返回东西——图是空的，那这条规则永远不会触发；
* 拿数据的真实形态核一下阈值，别在一个常年在 95 的指标上设 80。

预览需要一个具体的数据源，所以哪怕规则最终会匹配好几个，也先在第 2 步里选一个。

想要连标签、模板和通知路由一起验的更严格的检查，用[模拟触发](https://n9e.github.io/zh/docs/alerting/test-fire.md)。

## 事件上最后带着什么[​](#事件上最后带着什么 "事件上最后带着什么的直接链接")

事件的标签来自三个地方，按顺序：

1. **序列自己的标签**，就是查询返回的那些——所以 `sum by (service)` 和 `sum by (instance)` 产生的事件形态差别很大；
2. **规则的附加标签**（第 1 步配的），追加到这条规则产生的每一条事件上；
3. **附加信息**，逐事件渲染，那是文本不是标签。

事件的值是序列在触发时刻的值，在模板里是 `$value`，标签是 `$labels.<名字>`。 见[标签、附加信息与级别](https://n9e.github.io/zh/docs/alerting/labels-severity.md)。

关于聚合有一点要注意：`sum by (service) (...)` 会把 `instance` 和 `ident` 丢掉。 对服务级别的告警来说通常正是你要的，但也意味着事件没有一台可指的机器—— 而告警自愈是靠 `ident` 标签找机器的，那样它就无从下手了。

## 变量：一条规则，逐机器的不同阈值[​](#变量一条规则逐机器的不同阈值 "变量：一条规则，逐机器的不同阈值的直接链接")

一条查询可以带**变量**，让一条规则覆盖阈值各不相同的机器。在查询卡片上启用变量， 然后在 PromQL 里用 `$名字` 引用：

```
mem_used_percent{ident="$hosts"} > $threshold
```

变量分类型：

| 类型     | 提供什么                               |
| -------- | -------------------------------------- |
| 阈值     | 一个数字，直接替换进表达式             |
| 枚举值   | 一组标签取值，把查询限制在这些取值上   |
| 机器标识 | 一组机器，用和设备列表一样的筛选条件挑 |

变量集合可以嵌套，子集合对它覆盖的那些机器会**覆盖**父集合的取值—— 「全部 80%，这三台编译机 95%」就是这么写成一条规则的。

变量是有性能代价的：规则会按组合展开成多条查询。有少数几个例外、 否则就得写几条几乎一样的规则时才用它，别当默认写法。

## 下一步[​](#下一步 "下一步的直接链接")

* 把时间参数配对：[判定周期与恢复](https://n9e.github.io/zh/docs/alerting/evaluation.md)
* 让事件读得懂：[标签、附加信息与级别](https://n9e.github.io/zh/docs/alerting/labels-severity.md)
* 把贵的查询变便宜：[记录规则](https://n9e.github.io/zh/docs/query/recording-rules.md)
* 它就是不触发：[告警规则不触发](https://n9e.github.io/zh/docs/troubleshooting/alert-not-firing.md)
