适用场景与故障现象

本文针对需要长期存在的固定采集端点,例如两个数据库 exporter、支付网关或边缘设备。示例只有一个 Prometheus、一个 payments job,端点为 pay-a:9100 和 pay-b:9100。主机名是演示值,接入前替换为实际地址。

典型现象是:服务停止时,up == 0 可以告警;但服务发现配置误删了目标后,告警反而恢复。Targets 页面只剩一台机器,另一台既不是红色,也没有出现在列表里。此时应检查“目标是否仍被发现”,不能据恢复通知认定业务恢复。

这是一套可复现的规则设计,不对应某次生产事故。临时 Pod、自动扩缩容实例不宜直接套用固定主机清单;它们应按 Deployment、工作负载或期望副本数建模。

原因:失败样本与不存在的序列不同

Prometheus 尝试抓取端点时会生成 up:成功为 1,失败为 0。若端点不再是采集目标,就不能期待它继续产生新的 0 样本。官方 job 与 instance 说明

目标删除后,旧样本也不会永远参与即时查询。序列被标记为陈旧后不再返回;没有陈旧标记时,还受查询回看窗口影响。因此,目标移除到查询变空之间可能存在延迟,不要把删除动作的时间当作缺失告警的起点。官方陈旧序列说明

在 Prometheus 查询页面选择 Table,依次执行:

up{job="payments"}
up{job="payments"} == 0

第一条用于列出当前可见实例,第二条只筛出失败实例。比较运算默认过滤序列;没有序列时,比较不会创造一个 0。官方比较运算说明

进一步核对 Targets 页面中的活动目标、服务发现原始标签,以及 relabel 后的 job、instance。若清单有目标而活动列表没有,先检查发现源、keep/drop 规则和配置变更;若活动列表有目标但标签变了,先修正标签合同。

为什么按 job 使用 absent 仍会漏掉一台机器

absent(up{job="payments"})

只要 pay-a 还在,输入就不是空向量,结果不会告诉你 pay-b 不见了。换成 absent_over_time(up{job="payments"}[5m]) 仍然不能列出部分丢失的实例:这个选择器在窗口内只要有任意匹配样本,就不满足整体缺失条件。官方 absent 函数说明

对少量固定端点,可以逐个写带精确 instance 的 absent 规则。端点较多时,更容易维护的方式是明确保存“应该有谁”,再与实际看到的端点做差集。

修复方案:独立的预期目标清单

预期清单应来自资产登记或配置仓库,不能由当前 up 动态生成,否则实际目标消失时预期目标也一起消失。以下 recording rule 将两个固定端点写成值为 1 的 monitoring_expected_target。在清单中使用 target_job 和 target_instance,最后通过规则标签赋给结果;它们不会受 exporter 自身抓取标签覆盖影响。

将下面内容保存为 target.rules.json。JSON 是 YAML 的兼容子集,使用 JSON 便于用标准库直接校验结构;Prometheus 的规则加载器仍按 YAML 读取。

{
  "groups": [{
    "name": "payments-targets",
    "interval": "1m",
    "rules": [
      {
        "record": "monitoring_expected_target",
        "expr": "vector(1)",
        "labels": {"target_job": "payments", "target_instance": "pay-a:9100"}
      },
      {
        "record": "monitoring_expected_target",
        "expr": "vector(1)",
        "labels": {"target_job": "payments", "target_instance": "pay-b:9100"}
      },
      {
        "record": "monitoring_observed_target",
        "expr": "max by (target_job, target_instance) (label_replace(label_replace(up{job=\"payments\"}, \"target_job\", \"$1\", \"job\", \"(.+)\"), \"target_instance\", \"$1\", \"instance\", \"(.+)\"))"
      },
      {
        "alert": "ExpectedTargetMissing",
        "expr": "(monitoring_expected_target == 1) unless on (target_job, target_instance) monitoring_observed_target",
        "for": "2m",
        "labels": {"severity": "warning"},
        "annotations": {"summary": "预期采集目标缺失"}
      },
      {
        "alert": "TargetScrapeFailed",
        "expr": "monitoring_observed_target == 0",
        "for": "2m",
        "labels": {"severity": "warning"},
        "annotations": {"summary": "采集目标抓取失败"}
      }
    ]
  }]
}

这套规则有三个关键约束:

  • label_replace 把实际 up 的身份复制到与预期清单一致的标签,随后去掉无关标签。若生产环境有同名 job 跨集群复用,必须把 cluster 等隔离维度同时加入两边的清单、聚合和匹配键。
  • unless 根据匹配标签判断右侧是否存在,不根据右侧样本是否等于 1 判断。up=0 仍代表目标被看到,所以只触发抓取失败,不触发目标缺失。官方集合运算说明
  • max 表示同一身份只要任一采集视角成功,就认为抓取成功。本例是一台 Prometheus;若要检测每个副本各自的故障,应保留采集副本标签,不能直接沿用此聚合方式。

同组规则顺序执行,先生成清单和观测记录,再计算告警;清单直接来自规则而不是业务 exporter。for: 2m 从表达式持续出现该标签组时开始计算,而不是从目标被删除时计算。官方告警规则说明

用规则单元测试固定预期行为

下面脚本仅生成本地测试文件,不发网络请求。保存为 make_target_tests.py,使用 Python 3.10 及以上运行。它覆盖正常抓取、抓取失败、部分目标消失、整个 job 消失、标签改名和先有样本后陈旧六种情形。

import json
from pathlib import Path


def make_series(instance: str, values: str) -> dict[str, str]:
    """构造实际抓取序列,不伪造预期清单。"""
    return {
        "series": f'up{{job="payments",instance="{instance}"}}',
        "values": values,
    }


def make_alert(instance: str, summary: str) -> dict[str, dict[str, str]]:
    """固定告警身份,避免标签变化后误合并目标。"""
    return {
        "exp_labels": {
            "target_job": "payments",
            "target_instance": instance,
            "severity": "warning",
        },
        "exp_annotations": {"summary": summary},
    }


def make_case(
    series: list[dict[str, str]],
    missing: list[str],
    failed: list[str],
) -> dict[str, object]:
    """同时检查两类告警,并验证缺失告警尚未达到等待时长。"""
    return {
        "interval": "1m",
        "input_series": series,
        "alert_rule_test": [
            {
                "eval_time": "1m",
                "alertname": "ExpectedTargetMissing",
                "exp_alerts": [],
            },
            {
                "eval_time": "4m",
                "alertname": "ExpectedTargetMissing",
                "exp_alerts": [
                    make_alert(item, "预期采集目标缺失") for item in missing
                ],
            },
            {
                "eval_time": "4m",
                "alertname": "TargetScrapeFailed",
                "exp_alerts": [
                    make_alert(item, "采集目标抓取失败") for item in failed
                ],
            },
        ],
    }


first = "pay-a:9100"
second = "pay-b:9100"
healthy = make_series(first, "1+0x6")
tests = {
    "rule_files": ["target.rules.json"],
    "evaluation_interval": "1m",
    "tests": [
        make_case([healthy, make_series(second, "1+0x6")], [], []),
        make_case([healthy, make_series(second, "0+0x6")], [], [second]),
        make_case([healthy], [second], []),
        make_case([], [first, second], []),
        make_case([healthy, make_series("pay-new:9100", "1+0x6")], [second], []),
        make_case([healthy, make_series(second, "1 stale _ _ _ _ _")], [second], []),
    ],
}
Path("target.tests.json").write_text(
    json.dumps(tests, ensure_ascii=False, indent=2), encoding="utf-8"
)
print("测试文件已生成")

在有与部署版本匹配的 promtool 的环境中运行,任一步非零退出都应停止上线:

python3 make_target_tests.py
promtool check rules target.rules.json
promtool test rules target.tests.json

1+0x6 表示从 1 开始,后续六个采样点增量为 0;stale 表示陈旧标记,_ 表示缺失样本。最后一个测试在一分钟处让目标陈旧,四分钟处应已触发缺失告警。只用 _ 模拟停采,可能因旧样本仍处于回看窗口而得到不同结果。官方规则测试格式

本文编写环境已验证 JSON 解析、生成脚本执行和六组测试数据结构,但未安装 promtool,没有执行 PromQL 引擎及规则时序测试;以上命令是接入环境必须补做的验证,不能用本地结构检查替代。

接入、验收与回滚

将 target.rules.json 加入现有 Prometheus 配置的 rule_files 列表,保留其他规则文件。先执行 promtool check config,再通过已有配置发布流程加载。不要为了加载规则临时开启未经保护的管理接口。

在预发布环境确认两台目标都存在时没有告警;停止 pay-b 的 exporter,应只出现抓取失败;恢复后再从服务发现中移除 pay-b,待序列陈旧且满足等待时间,应只出现目标缺失。最后恢复发现配置,检查记录规则、Alerts 页面和实际通知三处状态。

若通知不符合预期,先查询 monitoring_expected_target 与 monitoring_observed_target,比对身份标签,再检查规则评估错误、Alertmanager 路由、静默和抑制规则。规则 firing 不等于通知一定已经送达。回滚时移除新增规则文件的引用并按原发布流程加载,保留原有抓取失败规则。

维护清单时,把资产下线与预期清单删除放进同一变更流程。一次性维护可使用有结束时间的静默,不应永久删掉预期资产来压住告警。清单变更应做评审,避免“发现配置和预期清单同时误删”掩盖故障。

最后还要单独监控 Prometheus 自身和告警通知链路。本方案的清单由 recording rule 生成,只要规则评估还能运行就持续存在;Prometheus 整体停止时,它也无法在本机产生告警,需要独立探测或另一套监控发现这一故障。

总结

固定采集资产需要回答两件事:当前抓取是否成功,以及应该存在的目标是否还被发现。把预期清单与实际序列分开维护,用身份标签做差集,才能识别“少了一台机器但剩下的机器都正常”的监控盲区。接入前必须验证部分缺失、整体缺失和标签变化,并为目标正常下线建立明确流程。