OneUptime 接入 Prometheus Alertmanager:把告警通知转化为带告警升级与自动恢复的 Incident 实战指南 📅 发布时间:2026/9/18 20:59:28 👁 浏览次数: OneUptime 接入 Prometheus Alertmanager把告警通知转化为带告警升级与自动恢复的 Incident 实战指南【免费下载链接】oneuptimeComplete open-source monitoring and observability platform.项目地址: https://gitcode.com/GitHub_Trending/on/oneuptime导读本文介绍如何将 Prometheus Alertmanager 的通知接入 OneUptime使其成为可跟踪、可升级、可自动关闭的 Incident。Prometheus 负责评估告警规则Alertmanager 负责路由而 OneUptime 负责记录、升级on-call 分页与恢复处理。读完本文你将掌握两种接入方式Incoming Request 监控器与 Webhook 工作流、一个告警一条 Incident 的分组配置、恢复事件的自动闭合机制以及针对“Prometheus 本身静默”的 dead mans switch 方案并能定位绝大多数接入故障。集成架构概览这是一个**入站inbound**集成数据从 Alertmanager 流向 OneUptime由 OneUptime 承担告警的记录、去重、升级与恢复判定。链路如下Prometheus rule fires ──► Alertmanager webhook receiver ──► OneUptime ──► Incident on-callOneUptime 为这种入站集成提供了两种构建方式适用于不同诉求方式适用场景Incoming Request 监控器推荐你希望告警变成带 on-call 升级的 Incident、一个告警对应一个 Incident、并在恢复时自动关闭且不想维护任何自定义逻辑。Webhook 触发的工作流你需要 OneUptime 原生不提供的路由逻辑——调用其他系统、重塑 payload、按条件分支。两种方式接收的是同一个 Alertmanager webhook payload因此接入链路、测试方法与大部分排障经验可以互相复用。Grafana 的 webhook 也是同构的 Alertmanager 形状本页的配置同样适用于 Grafana 集成。前提条件开始之前需要确认以下三点均已满足一套可编辑alertmanager.yml的 Prometheus Alertmanager 环境Alertmanager 所在网络可以通过 HTTPS 访问你的 OneUptime 实例自托管时即实例的入口地址一个拥有创建监控器或工作流权限的 OneUptime 项目。方式一Incoming Request 监控器推荐Incoming Request 监控器会生成一个专属 URL所有打到该 URL 的 HTTP 请求都会按你配置的 Criteria 被评估进而改变监控器状态、声明 Incident 并呼叫值班人员。它是把“外部告警源”变成“OneUptime Incident”的最直接通道。Step 1 — 创建监控器进入Monitors → Create Monitor选择Incoming Request类型。打开该监控器点击左侧菜单的Documentation复制其 URLhttps://oneuptime.com/heartbeat/YOUR_SECRET_KEY自托管时请将域名替换为你自己的 OneUptime 实例地址。路径中的 Secret Key 是唯一的凭证——不需要任何 Header 或 Token。关于这个 URL 的几点实现事实GET、POST 均可HEAD 被视为 GET其他方法返回 404URL 本身即秘密任何知道它的人都能让监控器保持健康请按密钥对待发往该端点的 Header 会被完整存储并可被有读取权限的人看到不要把 API Key 或 Token 放进 HeaderOneUptime 会在做任何校验之前立即返回空200并异步入队处理因此200不代表请求已被接受——错误的 Secret Key、已删除或已停用的监控器同样返回200确认请求落地要看监控器自身的时间线timeline请求体最大 50 MB且不要使用Content-Encoding: gzip压缩压缩体不会被解析路径引用将无法解析。Step 2 — 把 Alertmanager 指向监控器 URL在alertmanager.yml中增加一个 webhook receiver并把默认路由指向它receivers: - name: oneuptime webhook_configs: - url: https://oneuptime.com/heartbeat/YOUR_SECRET_KEY send_resolved: true route: receiver: oneuptime group_by: [alertname, instance] group_wait: 30s group_interval: 5m repeat_interval: 4hsend_resolved: true是必须的——它决定 Alertmanager 是否把恢复resolved通知也发给 OneUptime而恢复通知正是 OneUptime 判断“告警已恢复”并关闭 Incident 的依据。配置修改后用下面命令热重载或直接重启 Alertmanagercurl -X POST http://localhost:9093/-/reloadAlertmanager 发送的Content-Type: application/json也是必须的OneUptime 依赖它解析出 payload 中的字段。若使用其他 Content-Type或缺失requestBody下的所有引用都将无法解析。这一点与 Incoming Request 监控器文档 中的说明一致application/x-www-form-urlencoded虽会被解析但仅限扁平顶层字段其余类型完全不解析。Step 3 — 配置 Criteria条件打开监控器的Criteria编辑第一条 criteria。Filter过滤条件Filter Type类型JavaScript ExpressionFilter Condition条件Evaluates To TrueValue值{{requestBody.status}} firing占位符两侧的引号是必须的JavaScript Expression 的求值机制会先把{{var}}替换成实际值再执行脚本所以字符串比较必须带引号数字比较则不需要详见 JavaScript Expressions 文档。如果不习惯表达式也可以使用Request Body / Contains /status:firing的过滤器。注意 Request Body 的匹配是区分大小写的子串匹配且对象体会被序列化为无空格的紧凑 JSON——所以必须写成status:firing从格式化文档里复制的status: firing永远不会命中。Actions动作打开When filters match, change monitor status状态设为Offline或 Degraded打开When filters match, declare an incident设置Title、Severity以及需要被呼叫的On-Call Policies在该 Incident 表单的Advanced Options下打开Auto Resolve Incident。没有它恢复通知会被忽略Incident 将永远保持打开状态。Settings → Group incidents and alerts by a payload field打开此开关让一个端点可以同时容纳多个并发 Incident——每个告警一条而不是每封通知只有一条 Incident字段值Open a separate incident for each…requestBody.alerts[*].fingerprintField that signals recoveryrequestBody.alerts[*].statusValue that means recoveredresolvedMax incidents per request100[*]会在 Alertmanager 的alerts数组上扇出fan out对数组中的每个元素各提取一个值为每个不同的提取值各开一条 Incident。由于分组路径和恢复路径都用了[*]恢复判定是逐告警进行的——当一个 payload 里一条告警已 resolved、另外两条仍 firing 时只有已恢复的那条被关闭。警告分组必须选真正对每个告警唯一的字段。Alertmanager 的fingerprint是告警完整标签集的哈希天然唯一。普通标签只在“通知内部有差异”时才能用于分组——而路由group_by中列出的任何标签在通知内都不会有差异因为它正是聚合分组的依据。以上述group_by: [alertname, instance]为例若按requestBody.alerts[*].labels.alertname分组payload 中每个告警提取出的值都一样所有告警会塌缩成一条 Incident。更糟的是重复值只保留第一次出现——如果 payload 里第一条告警是resolved它会关闭那条 Incident而其余告警仍在 firing。分组底层实现可参考 IncomingRequestIncidentGrouping.ts分组配置由 IncidentGroupingConfig 定义groupByJSONPath、resolvedWhenJSONPath、resolvedWhenValue与maxKeysPerPayload默认 100即上面表格四个字段的模型映射每个提取出的键值会被哈希成seriesFingerprint与指标监控器 per-series Incident 相同的去重机制实现“创建 去重”复用extractItems在[*]前缀处把 payload 拆成数组逐元素处理元素后缀存在时从元素内深挖取值否则数组元素本身即键值同一 payload 内相同键只保留第一次出现超过maxKeysPerPayload后其余键被忽略并打 warn 日志恢复分类是事件驱动的只有当 payload 明确把某个键标记为resolved时才关闭对应 IncidentcollectResolvedFingerprints绝不因为键“缺席”而关闭——webhook 只描述当前 payload 内的告警不像快照模型那样代表全局状态。Step 4 — 编写 Incident 标题与描述分组键会以路径最后一段命名的变量形式暴露给模板requestBody.alerts[*].fingerprint对应{{fingerprint}}。但 fingerprint 是哈希值不适合展示给值班人员——标题应使用通知中共享的标签。commonLabels携带你路由group_by中的全部标签因此上面的配置里alertname和instance都可用Title标题{{requestBody.commonLabels.alertname}} on {{requestBody.commonLabels.instance}}Description描述{{requestBody.commonAnnotations.summary}} {{requestBody.commonAnnotations.description}} Severity: {{requestBody.commonLabels.severity}} Alertmanager: {{requestBody.externalURL}}commonLabels与commonAnnotations保存的是通知内共享的字段天然与分组键同义而requestBody.alerts[0].annotations.summary这类逐告警路径永远读取 payload 中的第一条告警不一定是你这条 Incident 所对应的那一条——所以想让每条 Incident 携带各自独立的注释文本就必须让group_by足够“紧”。另外解析不到的路径会原样打印连花括号一起而不是留空。完整的变量清单见 Incident Alert Dynamic Templating。从模板实现的源码角度看这类{{...}}引用与 JavaScript Expression 共用同一套占位符替换机制最终由VMUtil.deepFind在 payload 上做路径解析VMAPI.ts支持点分路径、[0]/[last]数组下标路径解析失败时静默返回 undefined模板层于是原样输出占位符。分组字段与模板字段还共享一个硬性约束——路径必须以字面量requestBody.开头否则一律解析为空静默失败。Step 5 — 把监控器送回 Operational可选Criteria 只在命中时生效。为了让一切恢复正常后监控器不一直停留在 Offline需要再补一条 criteriaFilter TypeJavaScript ExpressionValue{{requestBody.status}} resolved动作Change monitor status toOperational且不声明 Incident。Step 6 — 测试用一条模拟 Alertmanager 通知验证整条链路curl -X POST https://oneuptime.com/heartbeat/YOUR_SECRET_KEY \ -H Content-Type: application/json \ -d { version: 4, status: firing, commonLabels: { alertname: HighCPU, severity: critical }, commonAnnotations: { summary: CPU above 90% for 5m }, externalURL: http://alertmanager:9093, alerts: [ { status: firing, labels: { alertname: HighCPU, instance: web-1 }, fingerprint: a1b2c3d4e5f60001 }, { status: firing, labels: { alertname: HighCPU, instance: web-2 }, fingerprint: a1b2c3d4e5f60002 } ] }预期结果因为 payload 中有两个不同的fingerprint应产生两条 Incident。把两条告警的status都改为resolved重新发送两条 Incident 都应被自动关闭。也可以直接用amtool触发一条真实告警来测试amtool alert add test_alert severitywarning \ --annotationsummaryTest from Alertmanager \ --alertmanager.urlhttp://localhost:9093方式二Webhook 触发的工作流当需求超出“告警变成 Incident”时使用此方式——例如调用第三方系统、改造 payload、按条件分支。打开Workflows → Create Workflow命名为Alertmanager → Incidents进入Builder。添加Webhook触发器并复制其 URL把该块重命名为Alertmanager。Webhook 触发器的机制是OneUptime 生成唯一 URL任何打到该 URL 的请求都会启动工作流请求的 Headers、Query Params 与 Body 会被完整传入Webhook trigger。添加一个连接到触发器的Conditions块面板中叫 If / ElseLeft左值{{Alertmanager.Request Body.status}}Operator运算符Right右值firing从Yes分支连接Create Incident块Title标题{{Alertmanager.Request Body.commonAnnotations.summary}}Description描述{{Alertmanager.Request Body.commonAnnotations.description}}\nAlert: {{Alertmanager.Request Body.commonLabels.alertname}}Severity严重级别选择一个固定值或在前面先用 Conditions 按{{Alertmanager.Request Body.commonLabels.severity}}分支映射。保存后把 Step 2 中webhook_configs的 URL 替换为工作流的 URL。工作流版本的变量路径与监控器版本不同触发器输出被绑定到块名上因此是{{Alertmanager.Request Body.status}}而不是{{requestBody.status}}数据组件如 Create Incident的字段则按记录自身的列名column写入例如_id是 ID 列。详见 Workflow components。如果希望“一个告警一条 Incident”可以添加一个Custom Code块用 JavaScript 遍历Request Body.alerts配合send_resolved: true再添加第二个Conditions分支判断status resolved用Update Incident找到匹配的 Incident 并将其移动到已解决状态。Dead mans switch盯住 Prometheus 本身以上两种方式都无法告诉你“Prometheus 自己挂了”——没有告警到达看起来和“一切正常”完全一样。常规答案是设置一条永远在触发的告警路由到一个按节奏期待请求的监控器。官方 kube-prometheus-stack 内置了一条名为Watchdog的此类告警裸 Prometheus 上可以自己加一条表达式恒为真vector(1)的告警规则。做法再创建一个Incoming Request 监控器把Watchdog路由到它并配一个较短的repeat_interval这样一旦 Prometheus 静默能较快暴露给这个监控器配置Filter Type: Incoming Request / Filter Condition: Not Recieved In Minutes的 criteria——这是唯一一种“缺失请求”条件应该出现在告警接收器上的场景。之所以必须用独立监控器是因为接收告警的监控器没有规律节奏“Not Recieved In Minutes”条件放在它上面会频繁抖动dead mans switch 必须单独成器Incoming Request 监控器最佳实践。下面是 Step 2 配置合并了 watchdog 路由与 receiver 的完整版本——子路由先于父路由自身的 receiver 匹配因此Watchdog走第二个监控器其余告警仍走第一个receivers: - name: oneuptime webhook_configs: - url: https://oneuptime.com/heartbeat/YOUR_SECRET_KEY send_resolved: true - name: oneuptime-watchdog webhook_configs: - url: https://oneuptime.com/heartbeat/WATCHDOG_SECRET_KEY route: receiver: oneuptime group_by: [alertname, instance] group_wait: 30s group_interval: 5m repeat_interval: 4h routes: - receiver: oneuptime-watchdog matchers: - alertname Watchdog group_wait: 0s group_interval: 5m repeat_interval: 5m注意 watchdog 路由的group_wait: 0s与较短的repeat_interval: 5m前者让 Watchdog 触发后立即送达后者让它在静默后尽快被“漏报”暴露。Troubleshooting常见故障与处置没有任何请求到达—— 确认 Alertmanager 能访问该 URL检查其日志中的投递错误。注意 OneUptime 在做任何校验之前就对每个请求应答空200所以200并不代表 payload 已被接受——请以监控器的时间线timeline为准。Incident 打开后永不关闭—— 依次检查Alertmanager 侧send_resolved: true是否存在criteria 上的恢复字段与恢复值比较区分大小写IncidentAdvanced Options里的Auto Resolve Incident是否开启两个更隐蔽的原因其一payload 中不同键的数量超过Max incidents per request时超出上限的键对恢复同样不可见其二如果resolved通知恰好被 ingest 合并coalescing见下文丢弃Incident 会被永久困住——因为Alertmanager 会重复发送 firing 通知却不会重复发送 resolved 通知。这类 Incident 只能手动关闭。完全没有 Incident、监控器状态也不变—— 分组路径必须以字面量requestBody.开头且一条路径中只有第一个[*]是通配符。这两种错误都会静默失败源码层面normalizePath对非requestBody.前缀的路径直接返回空串extractItems对含多个[*]的路径只会处理第一个见 IncomingRequestIncidentGrouping.ts。Incident 文本显示原始{{...}}占位符—— 路径未解析成功而 OneUptime 的策略是保留未解析的占位符原样输出而非留空。不同告警规则设置的注释字段不同请引用你的规则中真实存在的字段commonAnnotations与逐告警的annotations是两回事。一个满是告警的 payload 只产生一条 Incident—— 你按一个在通知内部不变的标签分了组最常见的就是该标签同时出现在路由的group_by里。改用requestBody.alerts[*].fingerprint分组。Incident 过多—— 放宽 Alertmanager 的group_by/group_interval让相关告警被批量聚合调低Max incidents per request可以封顶但超出上限的键同样对恢复不可见属于“封顶即失明”需权衡。突发高峰时部分通知似乎被跳过—— 发往同一监控器的请求在 ingest 阶段会被合并coalesce以防单个发送方压垮监控器当通知背靠背到达时中间的某次 payload 可能被丢弃。增大group_wait与group_interval可以把它们拉开。该合并由应用容器的环境变量INCOMING_REQUEST_INGEST_COALESCE_ENABLED控制默认开启需要每次都评估所有 payload 的自托管运维人员可在该容器上将其设为false。源码佐证该开关定义于 App/FeatureSet/Telemetry/Config.tsprocess.env[INCOMING_REQUEST_INGEST_COALESCE_ENABLED] ! false即默认开启、只有显式设为false才关闭实际合并在 TelemetryQueueService.ts 中通过 BullMQ 的deduplicationid取incoming-request-${secretKey}keepLastIfActive: true实现——同一监控器至多保留“一个活跃 一个等待”的任务且保留最新 payload从而在入队阶段串行化同一监控器的处理避免大量并发monitorResource()争抢 per-monitor 的 Redis 锁。进一步阅读Incoming Request Monitor —— 该监控器类型的完整文档criteria、过滤条件、路径语法与 Incident 分组的全部细节Integrations Overview —— 入站与出站集成模式总览Grafana —— 同样的思路换成 Grafana alertingpayload 形状一致配置几乎完全复用Webhook trigger —— 工作流接收 URL 的工作原理Workflow components —— Conditions、Custom Code、数据组件Create/Update Incident的用法Incident Alert Dynamic Templating —— 标题与描述中可用的全部变量JavaScript Expressions —— 表达式语法与引号规则。【免费下载链接】oneuptimeComplete open-source monitoring and observability platform.项目地址: https://gitcode.com/GitHub_Trending/on/oneuptime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考