# Prometheus 知识学习手册 · 第 4 篇:Alertmanager 与生态 > 基于 [prometheus.io 官方文档](https://prometheus.io/docs/alerting/latest/overview/) 整理 > 文档版本:Prometheus v3.15.0 + Alertmanager latest(2026-10)| 抓取日期:2026-10-08 > 数据来源:prometheus/prometheus@v3.15.0、prometheus/alertmanager、prometheus/docs 官方仓库源文件(与官网 latest 内容一致) > > **全册结构(共 4 篇)** > - 第 1 篇:入门与架构 > - 第 2 篇:PromQL 与查询 > - 第 3 篇:配置与运维 > - **第 4 篇:Alertmanager 与生态(告警、插桩、可视化、规范)** ← 本篇 > > 本篇共 10 章:第 1-4 章为 Alertmanager 告警体系,第 5-10 章为插桩、生态与规范。 ## 目录 **上半部分(本篇第 1-4 章:Alertmanager 专题)** - [第 1 章 Alertmanager 架构与核心概念](#第-1-章-alertmanager-架构与核心概念) - [1.1 告警体系的组成](#11-告警体系的组成) - [1.2 Alertmanager 的核心能力](#12-alertmanager-的核心能力) - [1.3 告警的生命周期](#13-告警的生命周期) - [1.4 核心概念详解](#14-核心概念详解) - [1.5 Alertmanager 的限制与注意事项](#15-alertmanager-的限制与注意事项) - [第 2 章 Alertmanager 配置全解](#第-2-章-alertmanager-配置全解) - [2.1 配置概览、重载与命令行限制](#21-配置概览重载与命令行限制) - [2.2 通用占位符定义](#22-通用占位符定义) - [2.3 global 全局设置](#23-global-全局设置) - [2.4 顶级配置段(File layout)](#24-顶级配置段file-layout) - [2.5 route 路由树](#25-route-路由树) - [2.6 time_intervals 时间区间](#26-time_intervals-时间区间) - [2.7 inhibit_rules 抑制规则](#27-inhibit_rules-抑制规则) - [2.8 Label matchers 标签匹配器](#28-label-matchers-标签匹配器) - [2.9 receiver 与共享配置块](#29-receiver-与共享配置块) - [2.10 全部 receiver 集成配置(逐类逐字段)](#210-全部-receiver-集成配置逐类逐字段) - [2.11 tracing_config 分布式追踪](#211-tracing_config-分布式追踪) - [2.12 event_recorder 事件记录器](#212-event_recorder-事件记录器) - [2.13 配置验证与 reload](#213-配置验证与-reload) - [第 3 章 通知路由与模板](#第-3-章-通知路由与模板) - [3.1 模板基础:Go template 语法要点](#31-模板基础go-template-语法要点) - [3.2 通知模板数据变量](#32-通知模板数据变量) - [3.3 模板函数](#33-模板函数) - [3.4 官方模板示例逐个解析](#34-官方模板示例逐个解析) - [3.5 各渠道通知样式与配置示例](#35-各渠道通知样式与配置示例) - [3.6 集成清单(Integrations)](#36-集成清单integrations) - [第 4 章 高可用与告警运维 API](#第-4-章-高可用与告警运维-api) - [4.1 HA 设计目标](#41-ha-设计目标) - [4.2 gossip / Memberlist 集群机制](#42-gossip--memberlist-集群机制) - [4.3 HA 模式下的通知流水线](#43-ha-模式下的通知流水线) - [4.4 去重与告警转发逻辑](#44-去重与告警转发逻辑) - [4.5 脑裂处理(Fail Open)](#45-脑裂处理fail-open) - [4.6 HA 中的 Silence 管理](#46-ha-中的-silence-管理) - [4.7 `--cluster.*` 全部命令行 flags](#47---cluster-全部命令行-flags) - [4.8 告警状态 API(Alerts API)](#48-告警状态-apialerts-api) - [4.9 管理 API(Management API)](#49-管理-apimanagement-api) - [4.10 HTTPS 与认证](#410-https-与认证) - [4.11 生产部署要点](#411-生产部署要点) **下半部分(本篇第 5-10 章:插桩、生态与规范,由续稿提供)** - [第 5 章 插桩与客户端库](#第-5-章-插桩与客户端库) - [第 6 章 导出器与集成生态](#第-6-章-导出器与集成生态) - [第 7 章 应用集成实战](#第-7-章-应用集成实战) - [第 8 章 可视化](#第-8-章-可视化) - [第 9 章 规范(OpenMetrics / Remote Write / Native Histograms)](#第-9-章-规范openmetrics--remote-write--native-histograms) - [第 10 章 参与社区](#第-10-章-参与社区) --- ## 第 1 章 Alertmanager 架构与核心概念 Prometheus 的告警能力并非由单一组件完成,而是「规则引擎 + 通知管理」两段式协作的结果。本章先建立整体认知:告警从何而来、经谁处理、如何送达,以及 Alertmanager 提供的分组、抑制、静默、去重、高可用等核心机制。 ### 1.1 告警体系的组成 Prometheus 的告警分为两部分: 1. **Prometheus server 中的 alerting rules**:Prometheus 依据时序数据(time series)周期性评估告警规则,条件成立时把告警**推送**给 Alertmanager。 2. **Alertmanager**:独立进程,负责接收这些告警,并对其进行管理——静默、抑制、聚合,最后通过多种方式发送通知。 Alertmanager 发送通知的方式,官方原文列举了三类:**email(邮件)**、**on-call notification systems(值班/待命通知系统)**、**chat platforms(聊天平台)**。实际落地上,这三类由「receiver integration」承载,官方目前提供了 18 种原生集成(详见第 2.10 节)。 搭建一套完整的告警与通知链路,官方给出三个主要步骤: 1. **Setup and configure the Alertmanager**——部署并配置 Alertmanager。 2. **Configure Prometheus 与 Alertmanager 通信**——在 Prometheus 配置中通过 `alertmanager_config` 指定 Alertmanager 地址。 3. **在 Prometheus 中创建 alerting rules**——通过 `alerting_rules` 定义告警规则。 整体数据流如下: ```text ┌──────────────────────────┐ ┌──────────────────────────┐ ┌────────────────────┐ │ Prometheus Server │ push │ Alertmanager │ notify │ Notification │ │ alerting rules 周期评估 │ ──────► │ dedup / group / route │ ─────► │ email / PagerDuty │ │ (expr / for / labels) │ POST │ silence / inhibit / HA │ │ Slack / Webhook… │ └──────────────────────────┘ api/v2 └──────────────────────────┘ └────────────────────┘ 时序数据 (任一实例独立接收,状态互通) 18 类 receiver ``` > 引自:[Alerting overview](https://prometheus.io/docs/alerting/latest/overview/) > 💡 补充:`alerting rules`、`alertmanager_config`、`alerting_rules` 三个键均定义在 **Prometheus** 的配置(`prometheus.yml`)中,不属于 Alertmanager 的配置文件。第 2 章讨论的 `alertmanager.yml` 只负责「收到告警之后怎么处理」,两个配置文件不要混淆。 ### 1.2 Alertmanager 的核心能力 Alertmanager 处理由客户端应用(例如 Prometheus server)发送来的告警,其职责可归纳为五点: | 能力 | 英文 | 一句话说明 | | --- | --- | --- | | 去重 | deduplicating | 把标签完全相同的重复告警合并为同一条实例 | | 分组 | grouping | 把性质相近的告警(如同一集群的批量故障)归并为单条通知 | | 路由 | routing | 依据标签把告警分发到「正确的」receiver 集成 | | 静默 | silencing | 在指定时间窗内,让匹配 matcher 的告警不发通知 | | 抑制 | inhibition | 当某条(更宏观的)告警已触发时,压制其它(派生的)告警通知 | - **去重(deduplication)**:`labels` 集合完全相同的告警被视为同一告警的重复上报,Alertmanager 只会保留一份。 - **分组(grouping)**:成百上千条告警可能源自同一个根因,分组后只发一条紧凑通知。 - **路由(routing)**:receiver integration 可以是 email、PagerDuty、OpsGenie 等,具体由配置文件中的 routing tree 决定。 - **静默(silencing)**:基于 matchers 配置,入站告警若匹配某条 active silence 的全部 matcher,则不发送任何通知。 - **抑制(inhibition)**:在特定其它告警已触发时,抑制某些告警的通知。 此外,Alertmanager 还负责 **高可用**:在 HA 部署中,每个 Alertmanager 实例独立接收告警,但 silences 与 notification state 会在 peers 之间复制(详见第 4 章)。 > 引自:[Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) ### 1.3 告警的生命周期 一条告警从产生到消亡,会横跨 Prometheus 与 Alertmanager 两端。下表按阶段梳理。 | 阶段 | 发生位置 | 说明 | | --- | --- | --- | | 规则评估 | Prometheus | 按 `evaluation_interval` 周期性执行 `expr`,条件为真则告警进入 pending | | 等待 `for` | Prometheus | 持续为真达 `for` 时长后,告警转为 firing;期间若恢复则不触发 | | 推送 | Prometheus → Alertmanager | firing 告警以 `api/v2/alerts` POST 给 Alertmanager,并**周期性重发** | | 接收与去重 | Alertmanager | 依据 labels 去重,更新 `startsAt`/`endsAt` | | 分组 | Alertmanager | 按 route 的 `group_by` 归入聚合组,等待 `group_wait` | | 抑制/静默过滤 | Alertmanager | 过滤掉被 inhibit 或匹配 silence 的告警 | | 路由与通知 | Alertmanager | 沿 routing tree 匹配 receiver,在 `group_interval`/`repeat_interval` 约束下发送 | | 解决 | Prometheus → Alertmanager | Prometheus 发出带 `endsAt` 的 resolved 告警;Alertmanager 视 `send_resolved` 决定是否发 resolved 通知 | 关于 Alertmanager 侧的告警时间语义(来自 Alerts API 规范): - `startsAt`:告警触发时间。若省略,Alertmanager 将其设为**当前时间**。 - `endsAt`:告警应被解决的时间。若省略,Alertmanager 将其设为**当前时间 + `resolve_timeout`**。 - `generatorURL`:唯一 URL,链接到告警来源(例如 Prometheus 中触发该告警的 rule)。 - 客户端应**定期重新发送 firing alerts 直到告警解决**;若省略 `endsAt`,Alertmanager 会把已存在告警的 `endsAt` 更新为当前时间 + `resolve_timeout`。firing 告警在其 `endsAt` 过去后即被解决。 - 为确保 resolved notification 能发出,客户端在告警解决后**最长 5 分钟内**仍应持续重发 resolved alerts;由于 Alertmanager 是无状态的,这保证了即使 Alertmanager 崩溃或重启,resolved notification 依然会发出。 > 引自:[Alerts API](https://prometheus.io/docs/alerting/latest/alerts_api/) > 💡 补充:这条「客户端持续重发」的约定是理解 Alertmanager「无状态」设计的关键——Alertmanager 自身不持久化 active alerts(只持久化 silences 与 notification log),告警的「记忆」由 Prometheus 的重复推送来维持。因此 Prometheus 与 Alertmanager 之间的网络短暂抖动通常不会丢告警。 ### 1.4 核心概念详解 #### 1.4.1 分组(Grouping) **是什么**:把性质相似的告警归类为一条通知。 **为什么**:在大量系统同时故障时,成百上千条告警可能同时涌入。例如网络分区导致半数服务实例无法连接数据库,如果每条实例各发一条告警,会产生数百条通知,淹没值班人员。 **原理/怎么用**:可配置按 `cluster` 与 `alertname` 分组,此时同一集群下同名的告警会被合并,只发一条紧凑通知。分组的告警、分组通知的时机、通知的 receivers,均由配置文件中的 **routing tree** 控制(route 的 `group_by`、`group_wait`、`group_interval`、`repeat_interval`)。 **注意事项**:`group_by` 使用特殊值 `'...'`(即 `group_by: ['...']`)作为唯一标签名时,会**完全禁用聚合**,把所有 alert 原样透传——即每个 alert 独立成组。这在与需要逐条告警的下游集成时有用。 #### 1.4.2 抑制(Inhibition) **是什么**:在特定其它告警已触发时,抑制某些告警的通知。 **为什么**:系统/服务之间存在依赖关系。当上游故障时,下游会连带告警,若全部发出会造成噪声。 **原理/怎么用**:典型示例是:一条「整个集群不可达」的告警触发时,可静音该集群的所有其它告警。Inhibition 通过配置文件中的 `inhibit_rules` 配置:当存在匹配 source matchers 的告警时,静音匹配 target matchers 的告警,且二者在 `equal` 列出的标签上取值相同。 **注意事项**:语义上「缺失标签」与「空值标签」相同,所以若 `equal` 中所有标签名在 source 与 target 中均缺失,抑制规则也会生效。要防止告警抑制自身,建议把 target/source matchers 设计成「任何告警都不会同时出现在两侧」。 #### 1.4.3 静默(Silences) **是什么**:在给定时间段内单纯地静音告警。 **为什么**:计划内维护、已知故障窗口等场景,需要临时屏蔽通知而不改动规则。 **原理/怎么用**:silence 基于 **matchers** 配置,语法与 routing tree 相同。入站告警会检查是否匹配某条 active silence 的**全部** equality 或 regular expression matcher;若匹配,则该告警不发送任何通知。Silences 在 Alertmanager 的 **web interface** 中配置(或经 v2 API)。 **注意事项**:与抑制的区别在于——抑制是「告警之间的条件关系」,静默是「时间维度上的主动屏蔽」。静默是 HA 集群中一等复制状态,创建后会自动传播到所有 peer。 #### 1.4.4 去重(Deduplication) **是什么**:识别「同一条告警」的多次上报。 **为什么**:Prometheus 会周期性重发 firing 告警(见 1.3),同时多个 Prometheus 实例、多个 Alertmanager 实例也可能上报同一告警。 **原理**:告警的**身份**由 `labels` 集合决定——`labels` 被用于「去重同一告警的相同实例」。此外,HA 集群中还有一层去重:通过 gossip 复制的 **notification log**,各实例共享「谁在什么时候发过哪条通知」的历史,从而避免同一通知被多个实例重复发出。 **注意事项**:`labels` 是去重的唯一依据,因此给告警加上会频繁变化的 label(如时间戳、随机 ID)会导致去重失效、通知风暴。 #### 1.4.5 高可用(HA gossip) **是什么**:多个 Alertmanager 实例组成集群,彼此通过 gossip 协议同步状态。 **为什么**:单点 Alertmanager 宕机会导致整个告警链路中断。 **原理/怎么用**:集群中每个实例独立接收告警,通过 gossip mesh 复制 silences 与 notification log,各自独立处理并发送通知;发送前用 peer position + notification log 去重(详见第 4 章)。通过 `--cluster.*` flags 组网。 **注意事项**:**不要**对 Prometheus 与 Alertmanager 之间的流量做负载均衡,而应让 Prometheus 指向**所有** Alertmanager 实例的地址列表。 > 引自:[Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) ### 1.5 Alertmanager 的限制与注意事项 #### 1.5.1 客户端行为约束 Alertmanager 对其客户端行为有特殊要求(详见 Alerts API,即第 4.8 节)。这一要求**仅在不用 Prometheus 发送告警的高级场景下才相关**——因为 Prometheus 已经替你处理了这些细节。 #### 1.5.2 建议由 Prometheus 发送告警 Prometheus 自行负责把告警发送给 Alertmanager。官方**建议在 Prometheus 中基于 time series 数据配置 alerting rules**,而非直接往 Alerts API 发告警。原因:Prometheus 处理了许多特殊情况,以确保即使 Alertmanager 崩溃或重启,告警也能送达。 #### 1.5.3 Alert limits(每 alertname 告警数量限制) Alertmanager 支持限制**每个 `alertname` 的 active alerts 数量**,通过 `--alerts.per-alertname-limit` flag 配置。 - 达到上限后,**新告警被丢弃**;已存在告警的 **heartbeats 仍会处理**(避免已有告警被误判为 resolved)。 - 已知告警(按 fingerprint)会**自动过期**以腾出空间。 - 该特性适用于「同一告警实例数异常激增」的场景,可防止可靠性问题、避免 receiver 被淹没。 相关指标: - `alertmanager_alerts_limited_total`:因 per-alertname limit 被丢弃的告警总数。 - 启用 `alert-names-in-metrics` feature flag 后,会给该 metric 增加 `alertname` label(便于定位是哪个告警名触顶)。 #### 1.5.4 集群相关提醒 - YAML 中**没有** cluster 配置,集群必须通过 CLI flags 组建。 - HA 下 **alerts 本身不持久化**——依赖 Prometheus 定期重发 firing alerts;只有 silences 与 notification log 会落盘。 - 默认集群通信**未加密**,跨 WAN 生产部署应启用 mutual TLS。 > 引自:[Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) | [High Availability](https://prometheus.io/docs/alerting/latest/high_availability/) ## 第 2 章 Alertmanager 配置全解 本章逐字段拆解 `alertmanager.yml`。这是 Alertmanager 最核心、体量最大的一份配置,涵盖全局默认值、路由树、抑制规则、时间区间、标签匹配器,以及 18 类 receiver 集成的完整字段。 ### 2.1 配置概览、重载与命令行限制 Alertmanager 通过两类输入配置: - **命令行 flag**:不可变的系统参数(监听地址、集群参数、数据目录、feature flag 等)。 - **配置文件**(YAML):定义 inhibition rules、notification routing、notification receivers。 所有 flag 可用 `alertmanager -h` 查看。官方还提供一个可视化编辑器(routing-tree-editor)辅助构建 routing tree。 **配置重载**:运行时可重载。若新配置格式不正确,则**变更不生效**,并记录错误日志。触发方式有两种: 1. 向 Alertmanager 进程发送 `SIGHUP`。 2. 向 `/-/reload` 端点发送 HTTP POST。 通过 `--config.file` 指定配置文件: ```bash ./alertmanager --config.file=alertmanager.yml ``` #### Limits(命令行限制) | Flag | 说明 | 默认 | | --- | --- | --- | | `--silences.max-silences` | 限制 silence 的最大数量(含已过期的) | 禁用 | | `--silences.max-silence-size-bytes` | 限制单个 silence 的最大字节数(单位 bytes) | 禁用 | > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.2 通用占位符定义 配置文档中用一组占位符标记参数类型: | 占位符 | 含义 | 说明 | | --- | --- | --- | | `` | 时长 | 匹配正则 `((([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?|0)`,如 `1d`、`1h30m`、`5m`、`10s` | | `` | 标签名 | 匹配 `[a-zA-Z_][a-zA-Z0-9_]*` 的字符串 | | `` | 标签值 | unicode 字符串 | | `` | 文件路径 | 当前工作目录下的有效路径 | | `` | 布尔值 | `true` 或 `false` | | `` | 字符串 | 普通字符串 | | `` | 秘密 | 作为秘密的普通字符串,如密码 | | `` | 模板字符串 | 使用前会做模板展开的字符串 | | `` | 模板秘密 | 使用前做模板展开的秘密字符串 | | `` | 整数 | 整数值 | | `` | 正则 | 任意合法 RE2 正则(**两端自动锚定**;要取消锚定用 `.*.*`) | 文件为 YAML 格式。方括号 `[]` 表示参数可选;非 list 参数取指定默认值。 > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.3 global 全局设置 `global` 指定对所有其他配置上下文有效的参数,同时作为其他配置段的**默认值**。 ```yaml global: [ smtp_from: ] # 默认 SMTP From 头 [ smtp_smarthost: ] # 发送邮件的 SMTP smarthost,含端口;常为 25,或 587(over TLS/STARTTLS),例 smtp.example.org:587 [ smtp_hello: | default = "localhost" ] [ smtp_auth_username: ] # SMTP Auth 用 CRAM-MD5/LOGIN/PLAIN;为空则不做认证;PLAIN 仅在 TLS 下支持 [ smtp_auth_password: ] # LOGIN 与 PLAIN [ smtp_auth_password_file: ] [ smtp_auth_identity: ] # PLAIN [ smtp_auth_secret: ] # CRAM-MD5 [ smtp_auth_secret_file: ] [ smtp_require_tls: | default = true ] # Go 不支持未加密连接到远端 SMTP 端点 [ smtp_tls_config: ] [ smtp_force_implicit_tls: ] # 不按端口强制隐式 TLS [ jira_api_url: ] [ slack_api_url: ] [ slack_api_url_file: ] [ slack_app_token: ] [ slack_app_token_file: ] [ slack_app_url: ] [ victorops_api_key: ] # 与 victorops_api_key_file 互斥 [ victorops_api_key_file: ] [ victorops_api_url: | default = "https://alert.victorops.com/integrations/generic/20131114/alert/" ] [ pagerduty_url: | default = "https://events.pagerduty.com/v2/enqueue" ] [ opsgenie_api_key: ] [ opsgenie_api_key_file: ] [ opsgenie_api_url: | default = "https://api.opsgenie.com/" ] [ rocketchat_api_url: | default = "https://open.rocket.chat/" ] [ rocketchat_token: ] # 与 rocketchat_token_file 互斥 [ rocketchat_token_file: ] [ rocketchat_token_id: ] # 与 rocketchat_token_id_file 互斥 [ rocketchat_token_id_file: ] [ wechat_api_url: | default = "https://qyapi.weixin.qq.com/cgi-bin/" ] [ wechat_api_secret: ] [ wechat_api_secret_file: ] [ wechat_api_corp_id: ] [ telegram_api_url: | default = "https://api.telegram.org" ] [ telegram_bot_token: ] # 与 telegram_bot_token_file 互斥 [ telegram_bot_token_file: ] [ webex_api_url: | default = "https://webexapis.com/v1/messages" ] [ mattermost_webhook_url: ] [ mattermost_webhook_url_file: ] [ http_config: ] # 默认 HTTP 客户端配置 [ resolve_timeout: | default = 5m ] # 若 alert 不含 EndsAt 则用此默认值;超时后若未更新即可判定为 resolved;对来自 Prometheus 的 alert 无影响(它们总含 EndsAt) ``` **关键字段解读**: | 字段 | 作用 | 注意 | | --- | --- | --- | | `smtp_smarthost` | SMTP 服务器地址,含端口 | `25`,或 `587`(STARTTLS) | | `smtp_require_tls` | 是否要求 TLS | 默认 `true`;Go 不支持未加密连接远端 SMTP 端点 | | `smtp_force_implicit_tls` | 强制隐式 TLS | 不按端口判断,直接 TLS | | `resolve_timeout` | 告警解析超时 | 若 alert 无 `EndsAt` 则以其为默认值;超时后若未更新即判为 resolved。**对来自 Prometheus 的告警无影响**,因为它们总带 `EndsAt` | | `slack_api_url` / `slack_app_token` | Slack 两种认证方式 | 分别有对应的 `_file` 变体 | | `http_config` | 全局默认 HTTP 客户端配置 | 各 receiver 的 `http_config` 默认取此值 | > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.4 顶级配置段(File layout) 除 `global` 外,配置文件的顶级段如下: | 顶级键 | 说明 | | --- | --- | | `global` | 全局默认值 | | `templates` | `[ - ... ]`——读取自定义通知模板定义的文件;最后一段可用通配符,如 `templates/*.tmpl` | | `route` | ``——routing tree 的根节点 | | `receivers` | `- ...`——通知接收者列表 | | `inhibit_rules` | `[ - ... ]`——抑制规则列表 | | `mute_time_intervals` | `[ - ... ]`——**DEPRECATED: 使用 `time_intervals`** | | `time_intervals` | `[ - ... ]`——用于 mute/activate route | | `event_recorder` | (可选)捕获重要 Alertmanager 事件并投递到 file/webhook/Kafka/stdout;受 `event-recorder` feature flag 控制,需 `--enable-feature=event-recorder` | | `tracing` | (可选)分布式追踪配置 | > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.5 route 路由树 #### 2.5.1 继承规则 定义一个 routing tree 节点及其子节点。多数可选参数若未设置则从父节点**继承**: | 参数 | 继承行为 | | --- | --- | | `receiver`、`group_by`、`group_wait`、`group_interval`、`repeat_interval` | 未设置则从父节点继承 | | `labels` | 从父到子**合并**(非替换) | | `mute_time_intervals`、`active_time_intervals` | **完全不继承**(省略即无,无论父节点如何) | #### 2.5.2 匹配与遍历逻辑 - 每个 alert 从**顶级 route**进入(顶级 route 必须匹配所有 alert,即不配置任何 matcher),再遍历子节点。 - 若 `continue` 为 `false`,在**第一个**匹配子节点后停止。 - 若匹配节点 `continue` 为 `true`,则**继续匹配后续兄弟节点**。 - 若 alert 不匹配任何子节点(无匹配或无子节点),则**按当前节点的配置参数处理**。 #### 2.5.3 字段全解 ```yaml [ receiver: ] [ group_by: '[' , ... ']' ] # 聚合标签;用特殊值 '...' 作为唯一标签名(group_by: ['...'])可完全禁用聚合,原样透传所有 alert [ continue: | default = false ] # 是否继续匹配后续兄弟节点 # DEPRECATED: 使用 matchers match: [ : , ... ] # DEPRECATED: 使用 matchers match_re: [ : , ... ] matchers: # 必须满足才匹配该节点 [ - ... ] labels: # 附加到 route 的标签,通知模板经 routeLabels 函数可用(也见于 /api/v2/alerts/groups 的 routeLabels 字段) [ : , ... ] [ group_wait: | default = 30s ] # 新组首条通知前等待;可由子路由继承 [ group_interval: | default = 5m ] # group_wait 后对同组后续通知间隔;同时设定每次发送的通知流水线 context 超时(发送耗时超过该值会被取消);可由子路由继承 [ repeat_interval: | default = 4h ] # 重复上次通知间隔;应为 group_interval 的倍数,否则向上取整到下一倍数;若比 --data.retention 更长,则在数据保留期结束时重复;可由子路由继承 mute_time_intervals: # 需匹配 time_intervals 中的名字;根节点不可有;不继承 [ - ...] active_time_intervals: # 空值表示始终 active;根节点不可有;不继承 [ - ...] routes: # 零或多个子路由 [ - ... ] ``` **三个 group 相关参数的语义细化**: - `group_wait`:新组首条通知前的等待时间。 - 太短 → 首条通知可能不含完整预期 alert 集合;且抑制 alert 若未及时到达,则本应被抑制的 alert 未被抑制。 - 太长 → firing 通知不及时。 - 若 alert 在 `group_wait` 结束前 resolved,则**不发送通知**(减少 flapping 噪声)。错过初始 `group_wait` 的 alert 通知将在下一 `group_interval` 发送。 - `group_interval`:`group_wait` 后,对**同组**后续通知的间隔。 - `repeat_interval`:重复上次通知的间隔。 - 应为 `group_interval` 的倍数,否则向上取整到下一倍数。 - 若自上次 `group_interval` 以来有新 alert fired 或 firing alert resolved,则**不重复**。 **labels 细节**:从父到子合并,子路由可重定义覆盖单个标签;与 `group_by` 不同,route labels **不影响分组**。标签值本身可为模板,按**每个 alert group 的通知数据**渲染(可引用组标签、其他 route labels 等)。route labels 按**组**渲染,与单个通知无关(也经 API 暴露)。仅对特定通知有意义的字段不可用,尤其 `.NotificationReason` 在 route label 模板中恒为 `"unknown"`。 #### 2.5.4 Route 示例 ```yaml route: receiver: 'default-receiver' group_wait: 30s group_interval: 5m repeat_interval: 4h group_by: [cluster, alertname] routes: - receiver: 'database-pager' group_wait: 10s matchers: - service=~"mysql|cassandra" - receiver: 'frontend-pager' group_by: [product, environment] matchers: - team="frontend" - receiver: 'dev-pager' matchers: - service="inhouse-service" mute_time_intervals: - offhours - holidays continue: true - receiver: 'on-call-pager' matchers: - service="inhouse-service" active_time_intervals: - offhours - holidays ``` 上例要点: - 根路由 `group_by: [cluster, alertname]`,默认 30s 聚合窗口,5m 组内间隔,4h 重复间隔。 - `database-pager`:仅缩短 `group_wait` 到 10s,其余继承根路由。 - `frontend-pager`:按 `product`、`environment` 重新分组。 - `dev-pager` 与 `on-call-pager` 共享同一 matcher(`service="inhouse-service"`),但前者在 `offhours`/`holidays` 时段**被静音**(`mute_time_intervals`),且设 `continue: true` 让匹配继续;后者只在 `offhours`/`holidays` 时段**激活**(`active_time_intervals`)。二者组合实现「工作时间与值班时间路由到不同 pager」。 > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.6 time_intervals 时间区间 ```yaml name: time_intervals: [ - ... ] ``` #### `` ```yaml - times: [ - ...] weekdays: [ - ...] days_of_month: [ - ...] months: [ - ...] years: [ - ...] location: ``` - 所有字段均为 list。每个非空 list 内**至少一个元素**满足即匹配该字段;字段未指定则任意值均匹配;一个时刻要匹配完整 time interval 需**所有字段都匹配**。 - 部分字段支持 range 与负索引。**未指定时区则按 UTC**。 | 子字段 | 语法 | 说明 | | --- | --- | --- | | `times` | `start_time: HH:MM` / `end_time: HH:MM` | **包含起始、不包含结束**(便于表示整点起止,如 17:00 到 24:00 前一刻) | | `weekdays` | 周日起、周六止,按名称(如 `Sunday`) | 支持 `:` 含两端;可跨周边界(如 `friday:monday` 覆盖周五到周一) | | `days_of_month` | 从 1 开始,支持负值 | `-1` 表示月末(1 月的 -1 即 1 月 31 日);越界会被 clamp(2 月指定 `1:31` 截到 28 或 29);含两端 | | `months` | 月份名(大小写不敏感,如 `January`)或数字(1 月=1) | 支持 range,含两端;例 `['1:3', 'may:august', 'december']` | | `years` | 数字年份 | 支持 range,含两端;例 `['2020:2022', '2030']` | | `location` | IANA 时区(如 `Australia/Sydney`) | 也可用 `Local`(Alertmanager 所在机器本地时间)或 `UTC`;未提供则按 UTC | **注意事项**:Windows 上仅支持 `Local` 或 `UTC`,除非用 `ZONEINFO` 环境变量提供自定义时区库。 > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.7 inhibit_rules 抑制规则 Inhibition 允许基于另一组 alert 的存在来静音某组 alert,从而建立系统/服务依赖,使中断期间只发出最相关的互连 alert。 #### `` 一条抑制规则:当存在匹配某组 matchers 的 alert(source)时,静音匹配另一组 matchers 的 alert(target)。target 与 source 的 alert 在 `equal` 列表中的标签名必须有**相同标签值**。 ```yaml name: # 可选规则名;允许重名但会影响 per-rule 指标 # DEPRECATED: 使用 target_matchers target_match: [ : , ... ] # DEPRECATED: 使用 target_matchers target_match_re: [ : , ... ] target_matchers: # 被静音 target alert 必须满足 [ - ... ] # DEPRECATED: 使用 source_matchers source_match: [ : , ... ] # DEPRECATED: 使用 source_matchers source_match_re: [ : , ... ] source_matchers: # 存在一个或多个 alert 满足才生效 [ - ... ] [ equal: '[' , ... ']' ] # source 与 target 必须有相同值的标签 ``` **语义与陷阱**: - 缺失标签与空值标签**相同**。因此若 `equal` 中所有标签名在 source 与 target 中均缺失,抑制规则会生效。 - 防止 alert 抑制自身:同时匹配规则 target 与 source 两侧的 alert,**不能被同样匹配两侧(含自身)的 alert 抑制**。建议选择 target/source matchers 使 alert 永不两侧都匹配。 > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.8 Label matchers 标签匹配器 Label matchers 用于将 alert 匹配到 route、silence 与 inhibition rule。 #### 2.8.1 UTF-8 过渡背景(重要) Prometheus 正在为 labels 与 metrics 增加 UTF-8 支持。为支持 UTF-8,Alertmanager 0.27 及以后版本引入了**新的 matcher parser**,含若干不向后兼容变更。多数 matcher 前向兼容,部分不兼容。Alertmanager 处于**同时支持 UTF-8 与 classic matchers 的过渡期**: - 新安装建议在创建配置文件前启用 UTF-8 strict mode; - 既有安装建议先在默认的 fallback mode 运行。 - Alertmanager 将在**接下来两个版本**把 UTF-8 strict mode 设为默认。 - 也可用 `amtool` 校验配置是否兼容 UTF-8 strict mode(无需运行 server)。 #### 2.8.2 三种运行模式 | 模式 | 启用方式 | 行为 | | --- | --- | --- | | **Fallback mode**(默认) | 无 | 先按 UTF-8 matchers 解析,若不兼容再按 classic 解析;不兼容时按 classic 解析并记 warning(含改进建议)。罕见的 "disagreement"(两 parser 均 valid 但结果不同)时用 classic parser 并记 warning | | **UTF-8 strict mode** | `alertmanager --config.file=config.yml --enable-feature="utf8-strict-mode"` | 禁用 classic matchers。新安装应启用;既有安装在所有不兼容 warning 解决后启用。未解决完之前不会启动 | | **Classic mode** | `alertmanager --config.file=config.yml --enable-feature="classic-mode"` | 等价于 Alertmanager 0.26.0 及更早版本。怀疑 fallback/UTF-8 strict 有问题时可用 | #### 2.8.3 校验(`amtool check-config`) ```bash amtool check-config config.yml amtool check-config config.yml --enable-feature="utf8-strict-mode" ``` - 第一条:与 server 类似,配置不兼容或含 disagreement 时输出 warning;**无 warning 即兼容 UTF-8 strict mode**。 - 第二条:附加一级校验;配置无效则命令失败,有效则成功。 - 输出末尾会列出 Found:global config / route / N inhibit rules / N receivers / N templates。 #### 2.8.4 ``(共享) **UTF-8 matchers** 由三个 token 组成: 1. 未加引号的 literal 或双引号字符串(**标签名**); 2. `=`、`!=`、`=~`、`!~` 之一——`=` 相等,`!=` 不等,`=~` 匹配正则,`!~` 不匹配正则; 3. 未加引号的 literal 或双引号字符串(**正则或标签值**)。 - **Unquoted literals** 可含除保留字符外的所有 UTF-8 字符。保留字符包括空白及 `{ } ! = ~ , \ " ' \``。例:`foo`、`[a-zA-Z]+`、`Προμηθεύς`(希腊语 Prometheus)合法;`foo!` 非法(`!` 为保留字符)。 - **Double-quoted strings** 可含任意 UTF-8 字符,无保留字符,但字面双引号与反斜杠须用单个反斜杠转义(如匹配正则 `\d+` 须写 `"\\d+"`),规则同 Go 字符串字面量;支持 UTF-8 code points(如 `"foo!"`、`"bar,baz"`、`"\"baz qux\""`、`"\xf0\x9f\x99\x82"`)。 **YAML quoting vs. matcher token quoting(易踩坑)** `matchers:` 列表中每个条目是**单个 YAML 字符串**,Alertmanager 在 YAML 处理后解析它。YAML quoting 作用于**整个 matcher 字符串**,不保护其中单个 token。三种 YAML 风格: | 风格 | 示例 | 说明 | | --- | --- | --- | | plain style | `env !~ preprod` | 无外层引号;matcher 不含 YAML 特殊字符 `{ } [ ] , # | > :` 时可用 | | single-quoted style | `'env =~ "prod|staging"'` | 防所有 YAML 特殊字符,允许内部字面双引号 | | double-quoted style | `"env !~ \"uat\""` | 支持 YAML 转义序列,内部字面双引号须写 `\"` | **关键提醒**:不带外层引号的 `env =~ "prod"` 是合法 plain-style YAML,内部双引号被 Alertmanager parser 剥离且**不**提供 YAML 特殊字符保护——值可能含特殊字符时应**始终在 YAML 层引用整个 matcher**。 **Classic matchers** 语法灵感来自 PromQL/OpenMetrics,三个 token: 1. 合法 Prometheus 标签名; 2. `=` / `!=` / `=~` / `!~` 之一; 3. UTF-8 字符串(可用双引号包裹)。每 token 前后可有任意空白,第 3 个 token 可为空字符串。 其内适用 OpenMetrics 转义规则:`\"` 双引号、`\n` 换行、`\\` 字面反斜杠;未转义的 `"` 不得出现在第 3 token 内部(仅可作首尾字符);允许字面换行符,以及未后跟 `\`、`n`、`"` 的单个 `\`(此时作字面反斜杠)。 **Matchers 组合** - 组合时**所有 matcher 都须匹配**。如 `{alertname="Watchdog", severity=~"warning|critical"}` 匹配 `alertname=Watchdog, severity=critical`,但不匹配 `severity=none`。 - 可用 YAML list 组合,或用 **PromQL 风格逗号分隔表达式**。 - 允许单个尾随逗号;`{` 与 `}` 可选但必须**同时存在或同时省略**(不可只有半边);不可重复括号;双引号外的空白无影响。 > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.9 receiver 与共享配置块 #### 2.9.1 `` Receiver 是一个或多个通知集成的**命名配置**。 > 注:解除「新 receiver 禁令」时约定,除既有要求外,新通知集成须有具备 push 权限的 committed maintainer。 ```yaml name: # 唯一名称 labels: # 附加到该 receiver 用于查询与过滤 [ : , ... ] discord_configs: [ - , ... ] email_configs: [ - , ... ] mattermost_configs: [ - , ... ] msteams_configs: [ - , ... ] msteamsv2_configs: [ - , ... ] jira_configs: [ - , ... ] opsgenie_configs: [ - , ... ] pagerduty_configs: [ - , ... ] incidentio_configs: [ - , ... ] pushover_configs: [ - , ... ] rocketchat_configs: [ - , ... ] slack_configs: [ - , ... ] sns_configs: [ - , ... ] telegram_configs: [ - , ... ] victorops_configs: [ - , ... ] webex_configs: [ - , ... ] webhook_configs: [ - , ... ] wechat_configs: [ - , ... ] ``` 一个 receiver 可同时挂载多种集成;多个 `*_configs` 条目会**同时触发**。 #### 2.9.2 ``(共享) `http_config` 配置 receiver 与 HTTP 类 API 服务通信所用的 HTTP 客户端。 ```yaml # basic_auth 与 authorization 互斥 basic_auth: # 用配置的用户名/密码设置 Authorization 头;password 与 password_file 互斥 [ username: ] [ password: ] [ password_file: ] authorization: # 可选 Authorization 头配置 [ type: | default: Bearer ] [ credentials: ] # 与 credentials_file 互斥 [ credentials_file: ] oauth2: # 可选 OAuth 2.0;不能与 basic_auth 或 authorization 同时用 [ ] [ enable_http2: | default: true ] [ proxy_url: ] # 可选代理 URL [ no_proxy: ] # 逗号分隔,可含 IP、CIDR、域名;IP 与域名可含端口 [ proxy_from_environment: | default: false ] # 使用 HTTP_PROXY, http_proxy, HTTPS_PROXY, https_proxy, NO_PROXY, no_proxy [ proxy_connect_header: [ : [, ...] ] ] # CONNECT 请求发给代理的 header [ follow_redirects: | default = true ] # 是否跟随 HTTP 3xx 重定向 tls_config: [ ] http_headers: # 每个请求携带的自定义 HTTP header;Prometheus 自身设置的 header 不可覆盖 [ ] ``` **``(共享)** ```yaml : [ values: [, ...] ] # Header 值 [ secrets: [, ...] ] # Header 值,配置页隐藏 [ files: [, ...] ] # 从文件读取 header 值 ``` **``(共享)** OAuth 2.0,使用 **client credentials grant type**。Alertmanager 用给定 client access/secret key 从指定 endpoint 获取 access token。 ```yaml client_id: [ client_secret: ] [ client_secret_file: ] # 与 client_secret 互斥 scopes: [ - ... ] token_url: # 获取 token 的 URL endpoint_params: [ : ... ] # 附加到 token URL 的可选参数 tls_config: [ ] [ proxy_url: ] [ no_proxy: ] [ proxy_from_environment: | default: false ] [ proxy_connect_header: [ : [, ...] ] ] ``` **``(共享)** ```yaml [ ca_file: ] # 校验服务器证书的 CA 证书 [ cert_file: ] # 客户端证书认证 [ key_file: ] [ server_name: ] # ServerName 扩展 [ insecure_skip_verify: | default = false ] [ min_version: ] # 接受值 TLS10/TLS11/TLS12/TLS13;未设则用 Go 默认最小版本 TLS 1.2 [ max_version: ] # 接受值同上;未设则用 Go 默认最大版本 TLS 1.3 ``` > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.10 全部 receiver 集成配置(逐类逐字段) 本节逐类给出字段与默认值。**说明**:官方 `` 配置块中列出的原生 receiver 集成共 **18 类**(discord、email、mattermost、msteams、msteamsv2、jira、opsgenie、pagerduty、incidentio、pushover、rocketchat、slack、sns、telegram、victorops、webex、webhook、wechat);另有 `tracing`(第 2.11 节)与 `event_recorder`(第 2.12 节,其内置 `kafka_output` 等输出机制)属于相邻功能。下文 18 类一个不少。 #### 2.10.1 `` Discord webhook API。 | 字段 | 类型/默认 | 说明 | | --- | --- | --- | | `send_resolved` | ` | default = true` | 是否发送 resolved 通知 | | `webhook_url` / `webhook_url_file` | `` / `` | **互斥** | | `title` | 默认 `{{ template "discord.default.title" . }}` | 消息标题模板 | | `message` | 默认 `discord.default.message` | 消息正文模板 | | `content` | 默认 `discord.default.content` | 限 2000 字符 | | `username` | 默认 `''` | 覆盖 webhook 用户名 | | `avatar_url` | 默认 `''` | 头像 URL | | `http_config` | 默认 `global.http_config` | HTTP 客户端配置 | #### 2.10.2 `` | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `false` | 是否发送 resolved | | `to` | — | ``,收件地址,允许逗号分隔的 rfc5322 合规列表 | | `from` | `global.smtp_from` | 发件人 | | `smarthost` | `global.smtp_smarthost` | SMTP 服务器 | | `hello` | `global.smtp_hello` | HELO 主机名 | | `auth_username` / `auth_password` / `auth_password_file` / `auth_secret` / `auth_secret_file` / `auth_identity` | 分别默认对应 `global.smtp_*` | `auth_password` 与 `auth_password_file` 互斥;`auth_secret` 与 `auth_secret_file` 互斥 | | `require_tls` | `global.smtp_require_tls` | 是否要求 TLS | | `force_implicit_tls` | `nil` | `true` 强制隐式 TLS(任意端口直接 TLS);`nil` 按端口自动检测(465=implicit,其他=explicit) | | `tls_config` | `global.smtp_tls_config` | TLS 配置 | | `html` | 默认 `{{ template "email.default.html" . }}` | HTML 正文 | | `text` | — | 纯文本正文 | | `headers` | `{ : , ... }` | 覆盖此前设置的 header | | `threading` | `enabled` 默认 `false`;`thread_by_date` 默认 `daily` | `thread_by_date` 接受 `daily`、`none`(`none` 表示按 alert group key 分组不带日期) | **Email TLS 示例**: ```yaml # 示例1:强制隐式 TLS receivers: - name: 'email-implicit' email_configs: - to: 'oncall@example.org' smarthost: 'smtp.example.com:8465' force_implicit_tls: true # 示例2:向后兼容——465 自动隐式、587 自动显式 receivers: - name: 'email-compat' email_configs: - to: 'oncall@example.org' smarthost: 'smtp.example.com:465' ``` #### 2.10.3 `` Mattermost webhook API。 | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `webhook_url` / `webhook_url_file` | — | **互斥** | | `channel` | `''` | 用**频道名**而非显示名,如 `town-square` | | `username` | `''` | 覆盖用户名 | | `icon_url` | `''` | 图标 URL | | `icon_emoji` | `''` | 图标 emoji | | `fallback` / `color` / `title` / `title_link` / `text` / `pretext` / `author_name` / `author_link` / `author_icon` / `fields`(`` 列表)/ `thumb_url` / `footer` / `footer_icon` / `image_url` | — | attachments 兼容 Slack 字段(顶级字段) | | `attachments` | — | **Deprecated: 使用顶级字段,未来将移除** | | `props` | `` | RHS 面板 Markdown 卡片 | | `priority` | `` | 优先级配置 | | `http_config` | — | HTTP 客户端 | **``(Mattermost)** `fallback` / `color` / `pretext` / `text` / `author_name` / `author_link` / `author_icon` / `title` / `title_link`(均默认 `''`)、`fields`(同 Slack fields)、`thumb_url` / `footer` / `footer_icon` / `image_url`(均默认 `''`)。 **``(Mattermost)** `[ card: | default = '' ]` —— Markdown 文本,仅在 RHS 面板显示。 **``(Mattermost)** | 字段 | 默认 | 说明 | | --- | --- | --- | | `priority` | `''` | 可选 `urgent`、`important`、`standard` | | `requested_ack` | `false` | 需 priority 为 Important/Urgent,仅企业版 | | `persistent_notifications` | `false` | 仅 Urgent,每 5 分钟持续通知直到确认,仅企业版 | #### 2.10.4 `` Microsoft Teams Incoming Webhooks。 > **DEPRECATION NOTICE**:Microsoft 正在弃用 Microsoft 365 connectors,建议迁移到 Workflows + `msteamsv2`。 | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `webhook_url` / `webhook_url_file` | — | **互斥** | | `title` | `msteams.default.title` | 标题 | | `summary` | `msteams.default.summary` | 摘要 | | `text` | `msteams.default.text` | 正文 | | `http_config` | — | HTTP 客户端 | #### 2.10.5 `` 使用新消息格式(adaptive cards),符合 flows 要求。 | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `webhook_url` / `webhook_url_file` | — | **互斥** | | `title` | `msteamsv2.default.title` | 标题 | | `text` | `msteamsv2.default.text` | 正文 | | `http_config` | — | HTTP 客户端 | #### 2.10.6 `` 经 JIRA Rest API v2 或 v3。仅针对 **Jira Cloud** 测试;Jira Data Center(on-premise)可能可用但不保证。V2 支持 Wiki Markup,V3 支持 ADF;默认 `jira.default.description` 模板**仅适用于 V2**。 | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `api_url` | `global.jira_api_url` | 须含完整 API 路径,例 `https://company.atlassian.net/rest/api/2/` | | `api_type` | `auto` | 可为 `auto`/`cloud`/`datacenter` | | `project` | — | `` | | `summary.template` | `jira.default.summary` | 摘要模板 | | `summary.enable_update` | `true` | `false` 则更新 issue 时不改 summary | | `description.template` | `jira.default.description` | 描述模板 | | `description.enable_update` | `true` | 是否更新描述 | | `labels` | `[ - ... ]` | 支持模板,如 `'{{ .CommonLabels.severity }}'` | | `priority` | `jira.default.priority` | 优先级 | | `issue_type` / `resolve_transition` / `reopen_transition` / `wont_fix_resolution` | — | 工作流字段 | | `reopen_duration` | — | 按分钟向下取整,用 resolutiondate 判断龄期 | | `fields` | `[ : ... ]` | 自定义字段 | | `http_config` | — | PAT 经 basic_auth——Jira Cloud 用邮箱作 username、PAT 作 password;或 authorization 字段——Jira Data Center 用 `credentials: `;也支持 OAuth2 client_credentials;Jira 自身 OAuth2 需交互式浏览器授权,不可用于自动通知 | **``** 示例: ```yaml fields: components: { name: "Monitoring" } # Component customfield_10001: "Random text" # TextField customfield_10002: {"value": "red"} # SelectList customfield_10003: [{"value": "red"}, {"value": "blue"}, {"value": "green"}] # MultiSelect ``` #### 2.10.7 `` | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `api_key` | `global.opsgenie_api_key` | 与 `api_key_file` 冲突 | | `api_key_file` | `global.opsgenie_api_key_file` | — | | `api_url` | `global.opsgenie_api_url` | — | | `message` | `opsgenie.default.message` | 限 130 字符 | | `description` | `opsgenie.default.description` | 描述 | | `source` | `opsgenie.default.source` | 来源 | | `details` | `{ : , ... }` | 默认含所有 common labels | | `responders` | `[ - ... ]` | 响应者列表 | | `tags` | — | 逗号分隔 | | `note` | — | 备注 | | `priority` | — | P1..P5 | | `update_alerts` | `false` | 默认从不更新,新消息仅现于 activity log | | `entity` | — | 实体 | | `actions` | — | 逗号分隔 | | `http_config` | — | HTTP 客户端 | **``(OpsGenie)** `id` / `name` / `username`(三者恰定义其一);`type: `(`team`/`teams`/`user`/`escalation`/`schedule` 之一;`teams` 用 `name` 字段,可逗号分隔;空列表则不配置 responder)。 #### 2.10.8 `` | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `routing_key` | `` | Events API v2 集成密钥,与 `routing_key_file` 互斥 | | `routing_key_file` | — | — | | `service_key` | `` | Prometheus 集成密钥,与 `service_key_file` 互斥 | | `service_key_file` | — | — | | — | — | `routing_key` 与 `service_key` **互斥** | | `url` | `global.pagerduty_url` | API URL | | `client` | `pagerduty.default.client` | 客户端名 | | `client_url` | `pagerduty.default.clientURL` | 客户端 URL | | `description` | `pagerduty.default.description` | 描述 | | `severity` | `'error'` | 严重级别 | | `source` | 默认 `client` | 来源 | | `details` | 默认含 firing/resolved JSON、num_firing/num_resolved | Events API v2 支持嵌套 k/v | | `images` | `[ ... ]` | 图片 | | `links` | `[ ... ]` | 链接 | | `component` / `group` / `class` | — | 分类字段 | | `http_config` | — | HTTP 客户端 | | `timeout` | `0s` | `0s` 表示无超时;高于 `group_interval` 时无效 | **``(PagerDuty)** `href: `、`src: `、`alt: `。 **``(PagerDuty)** `href: `、`text: `。 #### 2.10.9 `` | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `user_key` / `user_key_file` | — | **互斥** | | `token` / `token_file` | — | **互斥** | | `title` | `pushover.default.title` | 标题 | | `message` | `pushover.default.message` | 正文 | | `url` | `pushover.default.url` | 附带链接 | | `device` | — | 目标设备 | | `sound` | — | 提示音 | | `priority` | `{{ if eq .Status "firing" }}2{{ else }}0{{ end }}` | 优先级 | | `retry` | `1m` | 至少 30 秒 | | `expire` | `1h` | 过期 | | `ttl` | — | 生存时间 | | `html` | `false` | 与 `monospace` 互斥 | | `monospace` | `false` | 与 `html` 互斥 | | `http_config` | — | HTTP 客户端 | #### 2.10.10 `` | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `false` | 是否发送 resolved | | `api_url` | `global.rocketchat_api_url` | API URL | | `channel` | — | `` | | `token` / `token_file` | — | **互斥** | | `token_id` / `token_id_file` | — | **互斥** | | `color` | `{{ if eq .Status "firing" }}red{{ else }}green{{ end }}` | 颜色 | | `emoji` | `rocketchat.default.emoji` | emoji | | `icon_url` | `rocketchat.default.iconurl` | 图标 | | `text` | `rocketchat.default.text` | 正文 | | `title` | `rocketchat.default.title` | 标题 | | `title_link` | `rocketchat.default.titlelink` | 标题链接 | | `fields` | `[ ... ]` | 字段 | | `image_url` / `thumb_url` | — | 图片 | | `link_names` | `false` | 是否链接 @用户名 | | `short_fields` | `false` | 字段是否短显示 | | `actions` | `[ ... ]` | 动作按钮 | **``** `title`、`value`、`short`(默认 `rocketchat_config.short_fields`)。 **``** `type`(忽略,仅支持 `button`)、`text`、`url`、`msg`。 #### 2.10.11 `` 可经 Incoming webhooks 或 Bot tokens。用 incoming webhook 时 `api_url` 须为 webhook URL(或 `api_url_file`);用 Bot token 时 `api_url` 须为 `https://slack.com/api/chat.postMessage`,bot token 设为 `http_config` 的 authorization credentials,`channel` 含频道名(`#` 可选)或 Channel ID。 | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `false` | 是否发送 resolved | | `api_url` / `api_url_file` | 默认 `global.*` | 与 `app_token`/`app_token_file` 二者择一 | | `app_token` / `app_token_file` | 默认 `global.*` | 与 `api_url` **互斥** | | `app_url` | `global.slack_app_url` | 用 `app_token` 认证时**必需** | | `channel` | — | `` | | `icon_emoji` | `slack.default.iconemoji` | 图标 emoji | | `icon_url` | `slack.default.iconurl` | 图标 URL | | `link_names` | `false` | 是否链接 @用户名 | | `message_text` | — | 作为顶层 `text` 字段发送,便于兼容 Slack Workflow Webhooks | | `username` | `slack.default.username` | 用户名 | | `actions` | `[ ... ]` | 动作按钮 | | `callback_id` | — | 回调 ID | | `color` | — | 颜色 | | `fallback` | — | 回退文本 | | `fields` | `[ ... ]` | 字段 | | `footer` | — | 页脚 | | `mrkdwn_in` | 默认 `["fallback", "pretext", "text"]` | 启用 markdown 的字段 | | `pretext` | — | 前置文本 | | `short_fields` | `false` | 字段短显示 | | `text` | — | 正文 | | `title` / `title_link` | — | 标题与链接 | | `image_url` / `thumb_url` | — | 图片 | | `http_config` | — | HTTP 客户端 | | `timeout` | `0s` | `0s` 无超时;高于 `group_interval` 时无效 | | `update_message` | `false` | 更新既有消息而非新建;webhook URL 不支持 | | `post_updates_to_thread` | `false` | 后续通知作为 thread 回复;与 `update_message` 结合时原地更新并回复;webhook URL 不支持 thread | **``(Slack)** `text`、`type`(**必填**);`url` 或 `name`+`value` 二者之一;`confirm`(``)、`style`(默认 `''`)。 **``(Slack)** `text`(**必填**)、`dismiss_text`、`ok_text`、`title`(默认 `''`)。 **``(Slack)** `title`、`value`(**必填**)、`short`(默认 `slack_config.short_fields`)。 #### 2.10.12 `` | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `api_url` | `` | 未指定则用 SNS SDK 的 | | `sigv4` | `[ ]` | AWS 签名配置 | | `topic_arn` | — | 不指定则须给 `phone_number` 或 `target_arn`;FIFO topic 建议 message group interval > 5 分钟 | | `subject` | `sns.default.subject` | 主题 | | `phone_number` | — | E.164 格式 | | `target_arn` | — | 目标 ARN | | `message` | `sns.default.message` | 消息正文 | | `attributes` | `[ : ... ]` | 消息属性 | | `http_config` | — | HTTP 客户端 | | `use_aws_http_client` | `false` | 强制用 AWS SDK 的 BuildableClient 而非默认 tracing-wrapped client;`AWS_CA_BUNDLE` 环境变量设置时自动启用;启用后 SNS 请求禁用 tracing,仅 `tls_config` 与代理字段生效,其他 `http_config` 项被静默忽略 | **``(SNS)** `region`、`access_key`+`secret_key`(须同时提供或同时留空,留空则用 `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`)、`profile`、`role_arn`、`external_id`(仅与 `role_arn` 同用)。 #### 2.10.13 `` | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `api_url` | `global.telegram_api_url` | API URL | | `bot_token` / `bot_token_file` | — | **互斥** | | `chat_id`(``)/ `chat_id_file` | — | **互斥** | | `message_thread_id` | `` | 话题 ID | | `message` | `telegram.default.message` | 正文 | | `disable_notifications` | `false` | 静默发送 | | `parse_mode` | `"HTML"` | 支持 `MarkdownV2`/`Markdown`/`HTML`/空串;超字符上限会被截断,HTML 模式下替换为 fallback 消息 | | `http_config` | — | HTTP 客户端 | | `timeout` | `0s` | 超时 | #### 2.10.14 `` Splunk On-Call(原 VictorOps),经 Splunk On-Call REST endpoint 集成。名称与 `victorops_*` 全局字段保留以向后兼容;metrics/logs/traces/校验错误也继续用 VictorOps 标识。 | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `api_key` | `global.victorops_api_key` | 与 `api_key_file` 互斥 | | `api_key_file` | — | — | | `api_url` | `global.victorops_api_url` | API URL | | `routing_key` | `` | 映射 alert 到 team | | `message_type` | `'CRITICAL'` | 可 `CRITICAL`/`WARNING`/`INFO` | | `entity_display_name` | `victorops.default.entity_display_name` | 实体显示名 | | `state_message` | `victorops.default.state_message` | 状态消息 | | `monitoring_tool` | `victorops.default.monitoring_tool` | 监控工具名 | | `custom_fields` | `{ : , ... }` | 保留名不可作 key:`routing_key`、`message_type`、`state_message`、`entity_display_name`、`monitoring_tool`、`entity_id`、`entity_state` | | `http_config` | — | HTTP 客户端 | #### 2.10.15 `` | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `url` / `url_file` | `` / `` | **互斥** | | `http_config` | — | HTTP 客户端 | | `max_alerts` | `0` | 超出截断;`0` 表示全部包含 | | `timeout` | `0s` | 超时 | | `payload` | `{ : , ... }` | 高级选项,自定义 Go 模板 payload,须为合法 JSON,可用 `toJson`;Alertmanager 不做校验,官方不提供支持 | **Webhook JSON 载荷格式(version `"4"`)**: ```json { "version": "4", "groupKey": "", "truncatedAlerts": 0, "status": "firing", "receiver": "", "groupLabels": { "": "" }, "commonLabels": { "": "" }, "commonAnnotations": { "": "" }, "externalURL": "", "notification_reason": "", "alerts": [ { "status": "firing", "labels": { "": "" }, "annotations": { "": "" }, "startsAt": "", "endsAt": "", "generatorURL": "", "fingerprint": "" } ] } ``` > 💡 补充:`version` 字段用于载荷演进的兼容判断;`truncatedAlerts` 表示因 `max_alerts` 被截断的告警数量;`status` 取值 `resolved` 或 `firing`。自定义 `payload` 时建议保留 `version`,便于下游按版本解析。 #### 2.10.16 `` 经 incident.io Alert Sources API。可经 `http_config` 的 authorization,或用 `alert_source_token`/`alert_source_token_file`(后者优先于 `http_config`)。载荷超 512KB 时会自动截断为仅保留第一个 alert。 | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `http_config` | — | HTTP 客户端 | | `url` / `url_file` | — | **互斥** | | `alert_source_token` / `alert_source_token_file` | — | **互斥** | | `max_alerts` | `0` | `0` 不限;超 512KB 自动丢弃除首个外的所有 alert;截断后仍过大则收到 429 且 alert 不被摄取 | | `timeout` | `0s` | 超时 | | `metadata` | `{ : , ... }` | 元数据 | #### 2.10.17 `` | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `false` | 是否发送 resolved | | `api_secret` / `api_secret_file` | 默认 `global.wechat_api_secret*` | 二者择一 | | `api_url` | `global.wechat_api_url` | API URL | | `corp_id` | `global.wechat_api_corp_id` | 企业 ID | | `message` | `wechat.default.message` | 正文 | | `message_type` | `'text'` | 支持 `text`/`markdown` | | `agent_id` | 默认 `wechat.default.*` | 应用 ID | | `to_user` | 默认 `wechat.default.*` | 目标用户 | | `to_party` | 默认 `wechat.default.*` | 目标部门 | | `to_tag` | 默认 `wechat.default.*` | 目标标签 | #### 2.10.18 `` | 字段 | 默认 | 说明 | | --- | --- | --- | | `send_resolved` | `true` | 是否发送 resolved | | `api_url` | `global.webex_api_url` | API URL | | `room_id` | `` | **必填** | | `message` | `webex.default.message` | 正文 | | `http_config` | — | 须用它提供 bot token 作为 HTTP Authorization 头 | **receiver 字段速查表(按集成列关键字段)**: | 集成 | 配置键 | `send_resolved` 默认 | 核心必填/认证 | | --- | --- | --- | --- | | Email | `email_configs` | `false` | `to` + `smarthost` | | Webhook | `webhook_configs` | `true` | `url` | | PagerDuty | `pagerduty_configs` | `true` | `routing_key` 或 `service_key` | | Slack | `slack_configs` | `false` | `api_url`/`app_token` + `channel` | | OpsGenie | `opsgenie_configs` | `true` | `api_key` | | Splunk On-Call (VictorOps) | `victorops_configs` | `true` | `api_key` + `routing_key` | | Webhook (WeChat) | `wechat_configs` | `false` | `api_secret` + `corp_id` + `agent_id` + `to_*` | | Amazon SNS | `sns_configs` | `true` | `topic_arn` 或 `phone_number`/`target_arn` | | Telegram | `telegram_configs` | `true` | `bot_token` + `chat_id` | | Discord | `discord_configs` | `true` | `webhook_url` | | Microsoft Teams | `msteams_configs` | `true` | `webhook_url` | | Microsoft Teams v2 | `msteamsv2_configs` | `true` | `webhook_url` | | Webex | `webex_configs` | `true` | `room_id` + bot token | | Rocket.Chat | `rocketchat_configs` | `false` | `token` + `token_id` | | Pushover | `pushover_configs` | `true` | `user_key` + `token` | | Mattermost | `mattermost_configs` | `true` | `webhook_url` | | Jira | `jira_configs` | `true` | `project` + `api_url` | | incident.io | `incidentio_configs` | `true` | `url` + `alert_source_token` | > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.11 tracing_config 分布式追踪 ```yaml [ client_type: | default = "grpc" ] # 支持 http / grpc [ endpoint: | default = "" ] [ sampling_fraction: | default = 0.0 ] [ insecure: | default = false ] [ tls_config: ] [ http_headers: [ ] ] [ compression: | default = "gzip" ] [ timeout: | default = 0s ] ``` > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.12 event_recorder 事件记录器 捕获重要 Alertmanager 事件(进程启停、alert 生命周期转换、silence 创建、通知投递、mute/inhibit 抑制),扇出到一个或多个目的地。每个事件编码为结构化载荷,含时间戳、产生实例 hostname、cluster position(启用 HA 集群时)及事件特定数据。 - 受 **`event-recorder` feature flag** 控制(`--enable-feature=event-recorder`);**未设 flag 时静默丢弃所有事件**。 - 配置在顶级 `event_recorder` 键下。 #### `` 输出按类型分组,每种目的地一个 list(镜像 receiver 组织集成的方式)。每个记录事件会发往**所有 list 中的所有 output**。每个 output 需 `name`,与 type 一起作为 metrics 与 logs 中的标识(如 `webhook:primary`)。路径/URL/broker/topic 等目的地配置不进入 metric labels;URL/broker/topic 也从 logs 省略,文件路径保留在 file-output 错误日志中。`name` 须非空、合法 UTF-8 且在每种 output type 内唯一。 ```yaml file_outputs: [ - ... ] webhook_outputs: [ - ... ] kafka_outputs: [ - ... ] stdout_outputs: [ - ... ] ``` 每个 output 使用 `proto/eventrecorder/events/v2/events.proto` 的 schema。Alert labels、alert annotations、group labels、silence annotations、muted-alert labels 编码为 maps。Protobuf 消费者须用 Go 包 `github.com/prometheus/alertmanager/eventrecorder/events/v2` 或据该 schema 生成的绑定。 **``** 每个事件作为单行 JSON 写入文件;父目录观察到目标路径 rename/remove/create 时会重开文件(兼容 logrotate)。 | 字段 | 必填 | 说明 | | --- | --- | --- | | `name` | 是 | output 名称 | | `path` | 是 | 目标路径,不存在时创建 | **``** 将每个事件作为 JSON body POST 到 HTTP 端点。经有界 worker pool + 有界重试 + 指数退避投递。重试会重发整个事件或批次,**接收端应容忍重复**。多 worker 时请求可能乱序,需有序时设 `workers: 1`。 | 字段 | 默认 | 说明 | | --- | --- | --- | | `name` | 必填 | output 名称 | | `url` | 必填 | `` 目标 URL | | `http_config` | — | HTTP 客户端 | | `timeout` | `10s` | 超时 | | `workers` | `4` | 并发 worker 数 | | `max_retries` | `3` | 最大重试次数 | | `retry_backoff` | `500ms` | 后续退避为 `base * 2^attempt`,上限 30s | | `batch` | `false` | 以 JSON 数组发送,改变 payload 契约,仅在接收端接受数组时启用 | | `batch_max_events` | `100` | 批最大事件数 | | `batch_max_bytes` | `1048576` | 软上限;单个超限事件单独发送 | | `batch_flush_interval` | `100ms` | 批刷新间隔 | > 示例:Cloudflare Pipelines streams 接受 JSON 数组,可配置为批量 webhook output;event timestamp 字段名为 `@timestamp`,在 pipeline SQL 引用时需加引号;完整扇出示例见 `doc/examples/cloudflare-pipelines`。 **``** 将每个事件 produce 到 Kafka topic。记录以产生实例 hostname 作 message key(同一实例的事件落同一 partition 并保序)。投递**异步且有界**:本地 buffer 满时**丢事件**。启动时无法连 broker 记 warn 但不阻止 Alertmanager 启动(底层 client 后台重试)。**目标 topic 须已存在**(或 broker 配置了 auto-create),Alertmanager 不创建它。 | 字段 | 默认 | 说明 | | --- | --- | --- | | `name` | 必填 | output 名称 | | `brokers` | 必填(至少一项) | `[ - ... ]`,host:port | | `topic` | 必填 | 目标 topic | | `client_id` | `"alertmanager"` | 客户端 ID | | `format` | `"json"` | `json`(protojson)/ `protobuf`(二进制 proto) | | `acks` | `"leader"` | `none` / `leader` / `all` | | `compression` | 省略则不压缩 | `none` / `gzip` / `snappy` / `lz4` / `zstd` | | `buffer_size` | `1024` | 满则丢事件 | | `tls_config` | — | 未设则 PLAINTEXT | **``** 每个事件作为单行 JSON 写 stdout,容器部署推荐(运行时日志驱动自动捕获)。 | 字段 | 必填 | 说明 | | --- | --- | --- | | `name` | 是 | output 名称 | > 💡 补充:使用 `stdout_outputs` 时建议同时传 `--log.format=json`,否则 Alertmanager 自身日志用 logfmt 而事件记录为 JSON,同一流出现两种格式,可能增加下游解析复杂度。 > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 2.13 配置验证与 reload | 操作 | 方式 | 说明 | | --- | --- | --- | | 查看全部 flag | `alertmanager -h` | 命令行参数参考 | | 校验配置 | `amtool check-config config.yml` | 输出 warning 与 Found 统计;无 warning 即兼容 UTF-8 strict mode | | 严格校验 | `amtool check-config config.yml --enable-feature="utf8-strict-mode"` | 无效则命令失败 | | 触发重载 | `kill -HUP ` 或 `POST /-/reload` | 配置格式错误则变更不生效并记日志 | | 编辑器 | routing-tree-editor | 可视化构建 routing tree | 一个最小可用的 `alertmanager.yml` 结构: ```yaml global: resolve_timeout: 5m route: receiver: 'default-receiver' group_by: [alertname] group_wait: 30s group_interval: 5m repeat_interval: 4h receivers: - name: 'default-receiver' webhook_configs: - url: 'http://localhost:5001/' inhibit_rules: - source_matchers: - severity="critical" target_matchers: - severity="warning" equal: [alertname, cluster, service] templates: - 'templates/*.tmpl' time_intervals: - name: offhours time_intervals: - weekdays: ['saturday', 'sunday'] - times: - start_time: '00:00' end_time: '08:00' ``` (未启用的 `mute_time_intervals` 顶层键已废弃,应改用 `time_intervals`。) > 引自:[Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ## 第 3 章 通知路由与模板 Prometheus 创建并发送告警到 Alertmanager,Alertmanager 再**根据 labels** 把通知发送到不同 receivers。receiver 可以是多种集成之一:Slack、PagerDuty、email,或经通用 webhook 接口的自定义集成。 发送给 receiver 的通知由 **templates** 构造。Alertmanager 自带 default templates(仓库 `template/default.tmpl`),也可自定义。 > **重要提醒**:Alertmanager 模板**不同于** Prometheus 的 templating(后者也包含 alert rule 的 labels/annotations 模板)。Alertmanager 通知模板基于 **Go templating**;注意部分字段按 text 求值、部分按 HTML 求值,会影响转义。 > 引自:[Notification template reference](https://prometheus.io/docs/alerting/latest/notifications/) ### 3.1 模板基础:Go template 语法要点 Alertmanager 通知模板基于 Go 标准库 [`text/template`](https://golang.org/pkg/text/template/),在部分按 HTML 求值的字段中会走 `html/template`(影响转义)。 **常用语法速查**: | 语法 | 含义 | | --- | --- | | `{{ .Field }}` | 输出当前上下文的字段(`.` 为根数据) | | `{{ .Labels.alertname }}` | 访问 map 的具体 key(也可用 `index .Labels "alertname"`) | | `{{ range .Alerts }} ... {{ end }}` | 遍历列表,`.` 在循环体内指向当前元素 | | `{{ if eq .Status "firing" }}A{{ else }}B{{ end }}` | 条件判断;`eq`/`ne`/`lt`/`gt` 等为比较函数 | | `{{ template "name" . }}` | 引用已定义(define)的命名模板,`.` 为传入上下文 | | `{{ define "name" }} ... {{ end }}` | 定义命名模板(通常放外部模板文件) | | `{{ .Annotations.summary \| toUpper }}` | 管道,把左侧结果作为右侧函数的最后一个参数 | | `{{/* 注释 */}}` | 模板注释 | **要点与注意事项**: - **两种模板语境**:在 Prometheus 的 alert rule 中定义 labels/annotations 时用 **Prometheus 模板**(如 `{{ $labels.instance }}`);在 Alertmanager receiver 配置中引用通知内容时用 **Alertmanager 模板**(如 `{{ .CommonAnnotations.summary }}`)。原文强调:annotation/label 字段处应用 Prometheus 模板,receiver 处应用 Alertmanager 模板。 - **转义差异**:部分字段按 text、部分按 HTML 求值。在 HTML 邮件等场景,若不希望自动转义可用 `safeHtml` / `safeUrl`。 - **多行模板**:跨多行的复杂模板应放到**外部文件**并 `define`,再在配置中用 `{{ template "xxx" . }}` 引用,避免 YAML 中的可读性问题。 - **外部模板加载**:通过顶层 `templates:` 提供文件路径(最后一段可用通配符,如 `templates/*.tmpl`)。 > 引自:[Notification template reference](https://prometheus.io/docs/alerting/latest/notifications/) | [Notification template examples](https://prometheus.io/docs/alerting/latest/notification_examples/) ### 3.2 通知模板数据变量 #### 3.2.1 `Data` 结构 `Data` 是传给通知模板与 webhook push 的**根结构**: | 字段 | 类型 | 说明 | | --- | --- | --- | | `Receiver` | string | 通知将发送到的 receiver 名称(slack、email 等) | | `Status` | string | 至少一个告警 firing 则为 `firing`,否则为 `resolved` | | `Alerts` | [Alert] | 本组所有告警对象列表 | | `GroupLabels` | KV | 这些告警**被分组所用**的 labels | | `CommonLabels` | KV | 所有告警**共有**的 labels | | `CommonAnnotations` | KV | 所有告警共有的 annotations 集合,用于较长的附加信息 | | `RouteLabels` | KV | 附加到本告警组的 route `labels`,通常经 `routeLabels` 函数访问 | | `ExternalURL` | string | 发送该通知的 Alertmanager 回链 | `Alerts` 类型还暴露两个过滤函数: - `Alerts.Firing`:返回本组当前 firing 的告警对象列表; - `Alerts.Resolved`:返回本组 resolved 的告警对象列表。 > 模板中可写 `{{ range .Alerts.Firing }}` 只遍历 firing 告警,`{{ range .Alerts.Resolved }}` 只遍历 resolved 告警。 #### 3.2.2 `Alert` 结构 | 字段 | 类型 | 说明 | | --- | --- | --- | | `Status` | string | `resolved` 还是 `firing` | | `Labels` | KV | 标签集合 | | `Annotations` | KV | 注解集合 | | `StartsAt` | time.Time | 告警触发时间;若省略由 Alertmanager 赋当前时间 | | `EndsAt` | time.Time | 仅当结束时间已知才设置,否则设为「自最后收到告警起可配置的超时」 | | `GeneratorURL` | string | 标识告警致因实体的回链 | | `Fingerprint` | string | 可用于识别告警的指纹 | #### 3.2.3 `KV` `KV` 是 `type KV map[string]string`,表示 labels 与 annotations 的 key/value 字符串对。annotation 常见键含 `summary`、`description`。 **KV methods**: | 方法 | 签名 | 说明 | | --- | --- | --- | | `SortedPairs` | `→ Pairs` | 返回排序后的 key/value 对列表 | | `Remove` | `[]string → KV` | 返回去掉给定 keys 的 key/value map 副本 | | `Names` | `→ []string` | 返回 LabelSet 中 label 名的名字列表 | | `Values` | `→ []string` | 返回 LabelSet 中值的列表 | `Remove` 需要 string slice;模板中移除字面 key 需用 `stringSlice` 函数: ```gotemplate {{ .Annotations.Remove (stringSlice "summary" "description" "runbook_url") }} ``` > 引自:[Notification template reference](https://prometheus.io/docs/alerting/latest/notifications/) ### 3.3 模板函数 除下表外,Go 模板默认函数亦可使用。 | 函数 | 参数 | 说明 | | --- | --- | --- | | `append` | `(slice []any, args ...any)` | 返回把参数追加到 slice 后的新 slice | | `base64decode` | `(text string)` | `base64.URLEncoding.DecodeString`,解码 URL-safe base64 字符串 | | `base64encode` | `(text string)` | `base64.URLEncoding.EncodeToString`,返回 URL-safe base64 编码,如用于 query parameter | | `date` | `(string, time.Time)` | 按指定格式返回时间文本表示(格式见 pkg.go.dev/time) | | `dict` | `(values ...any)` | 返回 string→any 的 map,由变参 key-value 构成;参数个数须为偶数、key 须为 string | | `humanizeDuration` | `(number or string)` | 返回人类可读的时长字符串 | | `join` | `(sep string, v any)` | 用 `sep` 连接 slice/array 元素;元素可为任意类型;若第二个参数非 slice/array 则报错;参数顺序与 `strings.Join` 相反以支持管道 | | `list` | `(...any)` | 把传入参数作为 interface slice 返回 | | `match` | `(pattern, string)` | `Regexp.MatchString`,用 Regexp 匹配字符串 | | `now` | 无参 | `time.Now`,返回当前本地时间 | | `reReplaceAll` | `(pattern, replacement, text)` | `Regexp.ReplaceAllString`,非锚定的正则替换 | | `routeLabels` | `(name string)` | 返回本告警组指定名称的 route label 值,未设置则返回 `""` | | `safeHtml` | `(text string)` | `html/template.HTML`,标记字符串为无需自动转义的 HTML | | `safeUrl` | `(text string)` | `html/template.URL`,标记字符串为无需自动转义的 URL | | `since` | `(time.Time)` | `time.Since`,返回从给定时间到当前系统时间的时长 | | `stringSlice` | `(...string)` | 把传入字符串作为 string slice 返回 | | `title` | `(string)` | `strings.Title`,将每个单词首字母大写 | | `toJson` | `(any)` | `json.Marshal`,返回值的 JSON 编码 | | `toLower` | `(string)` | `strings.ToLower`,全部转小写 | | `toUpper` | `(string)` | `strings.ToUpper`,全部转大写 | | `trimSpace` | `(string)` | `strings.TrimSpace`,去除首尾空白 | | `tz` | `(string, time.Time)` | 返回指定时区(如 `Europe/Paris`)的时间 | | `urlUnescape` | `(text string)` | `url.QueryUnescape`,解转义含 % 编码的 URL | 常用组合示例: ```gotemplate {{ .Status | toUpper }} {{/* FIRING / RESOLVED */}} {{ .StartsAt | since | humanizeDuration }} {{/* 已持续多久 */}} {{ .StartsAt | tz "Asia/Shanghai" | date "2006-01-02 15:04:05" }} {{ .CommonLabels | toJson }} {{/* 便于排障 */}} {{ if eq .Status "firing" }}{{ .Alerts.Firing | len }}{{ end }} ``` > 引自:[Notification template reference](https://prometheus.io/docs/alerting/latest/notifications/) ### 3.4 官方模板示例逐个解析 以下示例均使用 Go templating,来自官方 notification_examples 页。 #### 3.4.1 Customizing Slack notifications 自定义 Slack 通知,发送指向组织 wiki 的 URL。 ```yaml global: slack_api_url: '' # 也可放入文件:slack_api_url_file: '/etc/alertmanager/slack_url' route: receiver: 'slack-notifications' group_by: [alertname, datacenter, app] receivers: - name: 'slack-notifications' slack_configs: - channel: '#alerts' text: 'https://internal.myorg.net/wiki/alerts/{{ .GroupLabels.app }}/{{ .GroupLabels.alertname }}' ``` **解析**:`slack_api_url` 可直填或经 `slack_api_url_file` 指向文件(避免明文密钥入库)。`text` 中通过 `.GroupLabels.app` / `.GroupLabels.alertname` 取到**分组标签**,拼出 wiki 链接。 #### 3.4.2 Accessing annotations in CommonAnnotations Alert 侧用 **Prometheus 模板**,Receiver 侧用 **Alertmanager 模板**。 Prometheus 侧告警规则: ```yaml groups: - name: example rules: - alert: InstanceDown expr: up == 0 for: 5m labels: severity: page annotations: description: '{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes.' summary: 'Instance {{ $labels.instance }} down' ``` Receiver 侧: ```yaml receivers: - name: 'default-receiver' slack_configs: - channel: '#alerts' text: " \nsummary: {{ .CommonAnnotations.summary }}\ndescription: {{ .CommonAnnotations.description }}" ``` **解析**:`.CommonAnnotations.summary` 取的是**所有告警共有**的注解。若各组告警的 summary 不同,则不会出现在 CommonAnnotations 中(需改用 `range .Alerts` 逐条渲染)。 #### 3.4.3 Ranging over all received Alerts 用 `{{ range .Alerts }}` 遍历所有收到的告警,分别打印各自 annotation。 ```yaml receivers: - name: 'default-receiver' slack_configs: - channel: '#alerts' title: "{{ range .Alerts }}{{ .Annotations.summary }}\n{{ end }}" text: "{{ range .Alerts }}{{ .Annotations.description }}\n{{ end }}" ``` **解析**:循环体内 `.` 指向单条 Alert,故用 `.Annotations.summary`(而非 `.CommonAnnotations`)。`\n` 用于每条之间换行。 #### 3.4.4 Defining reusable templates 把命名模板放外部文件,由 Alertmanager 加载,避免跨多行的复杂模板。 外部文件 `/alertmanager/template/myorg.tmpl`: ```gotemplate {{ define "slack.myorg.text" }} {{- range .Alerts }} {{ .Annotations.summary }} {{ end }} {{- end }} ``` 配置引用: ```yaml templates: - '/etc/alertmanager/templates/myorg.tmpl' receivers: - name: 'slack-notifications' slack_configs: - channel: '#alerts' text: '{{ template "slack.myorg.text" . }}' ``` **解析**:`define "slack.myorg.text"` 定义了一个命名模板,`{{ template "slack.myorg.text" . }}` 把当前 Data 作为上下文传入。 #### 3.4.5 Defining a reusable HTML email template 邮件通知的 HTML body 可用外部文件的命名模板。 外部文件 `/etc/alertmanager/templates/email.tmpl`: ```gotemplate {{ define "email.myorg.html" }} {{ range .Alerts }} {{ end }}
{{ .Status | toUpper }} {{ index .Labels "alertname" }} {{ index .Annotations "summary" }}
{{ end }} ``` 配置: ```yaml global: smtp_smarthost: 'smtp.example.org:587' smtp_from: 'alertmanager@example.org' templates: - '/etc/alertmanager/templates/*.tmpl' receivers: - name: 'email' email_configs: - to: 'oncall@example.org' html: '{{ template "email.myorg.html" . }}' ``` **解析**:`.Status | toUpper`、`range .Alerts`、`index .Labels "alertname"`、`index .Annotations "summary"` 组合渲染 HTML 表格。`templates` 用通配符 `*.tmpl` 一次加载目录下所有模板。SMTP 认证与 TLS 选项参见 ``。 #### 3.4.6 Sending notifications to Gotify with a custom webhook payload Gotify(`https://gotify.net`)无专用 receiver,但其 push message API 是简单 JSON POST,可直接用通用 `webhook_config` 及其 `payload` 字段驱动。 ```yaml receivers: - name: 'gotify' webhook_configs: - url: 'https://gotify.example.com/message' http_config: authorization: credentials_file: /etc/alertmanager/secrets/gotify-secret/token payload: title: '{{ template "gotify.default.title" . }}' priority: '{{ if eq .Status "firing" }}5{{ else }}0{{ end }}' message: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}' ``` **解析**:`payload` 为高级选项,须为合法 JSON,可用 `toJson`;Alertmanager 不做校验、官方不提供支持。`priority` 用 `if eq .Status "firing"` 在 firing/resolved 间切换。更完整版本(含 Markdown 格式与 firing/resolved 分区)见仓库 `examples/webhook/gotify.yml`。 > 引自:[Notification template examples](https://prometheus.io/docs/alerting/latest/notification_examples/) ### 3.5 各渠道通知样式与配置示例 各集成默认模板(`template/default.tmpl`)为通知提供开箱样式。下表汇总常见渠道的关键渲染字段与可定制点;具体字段见第 2.10 节对应 `<*_config>`。 | 渠道 | 主要渲染字段 | 样式特点 | 常用定制 | | --- | --- | --- | --- | | Email | `html`、`text`、`headers`、`threading` | 支持 HTML 表格、纯文本、线程化 | 用外部命名模板渲染 `email.myorg.html` | | Slack | `text`、`title`、`title_link`、`fields`、`actions`、`mrkdwn_in` | 支持 markdown、附件字段、交互按钮、thread | `update_message`、`post_updates_to_thread` | | PagerDuty | `description`、`details`、`severity`、`links`、`images` | 事件制 on-call | `routing_key`/`service_key`、`timeout` | | OpsGenie | `message`、`description`、`details`、`responders`、`priority` | 团队/值班路由 | `update_alerts`、`actions` | | Splunk On-Call | `message_type`、`entity_display_name`、`state_message`、`custom_fields` | 状态式告警 | `routing_key` 映射 team | | Discord | `title`、`message`、`content`、`username`、`avatar_url` | webhook 富文本 | `content` 限 2000 字符 | | Telegram | `message`、`parse_mode` | 支持 HTML/Markdown | `parse_mode` 超限截断 | | Rocket.Chat | `text`、`title`、`fields`、`actions`、`color` | 附件式卡片 | `short_fields` | | Mattermost | 顶级 attachment 字段、`props`、`priority` | 兼容 Slack 附件 | 企业版 priority | | Microsoft Teams | `title`、`summary`、`text` | Incoming Webhooks(将弃用) | 迁移 `msteamsv2` | | Microsoft Teams v2 | `title`、`text` | adaptive cards / Workflows | 新消息格式 | | Webex | `room_id`、`message` | bot token 认证 | 必填 `room_id` | | Pushover | `title`、`message`、`url`、`priority` | 移动推送 | `retry`/`expire`/`ttl` | | Webhook | `payload`(自定义 JSON) | 通用 JSON v4 载荷 | 任意下游对接 | | Amazon SNS | `subject`、`message`、`attributes` | AWS 消息 | `sigv4`、`topic_arn` | | Jira | `project`、`summary`、`description`、`labels` | 工单系统 | `resolve_transition` | | incident.io | `metadata`、`url` | 事件管理 | 512KB 自动截断 | | WeChat | `message`、`message_type`、`to_user`/`to_party`/`to_tag` | 企业微信 | `text`/`markdown` | **通用可复用片段(自定义模板)**: ```gotemplate {{ define "myorg.common.header" }} [{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{- range .Alerts }} - {{ .Labels.alertname }} ({{ .Labels.severity }}): {{ .Annotations.summary }} {{- end }} {{ end }} ``` > 引自:[Notification template reference](https://prometheus.io/docs/alerting/latest/notifications/) | [Configuration](https://prometheus.io/docs/alerting/latest/configuration/) ### 3.6 集成清单(Integrations) Alertmanager 通过配置文件支持多种通知集成。官方 Available Integrations 清单如下(集成名 → 对应配置字段): | # | 集成 | 配置字段 | | --- | --- | --- | | 1 | Amazon SNS | `sns_config` | | 2 | Discord | `discord_config` | | 3 | Email | `email_config` | | 4 | incident.io | `incidentio_config` | | 5 | Jira | `jira_config` | | 6 | Mattermost | `mattermost_config` | | 7 | Microsoft Teams | `msteams_config` | | 8 | Microsoft Teams v2 | `msteamsv2_config` | | 9 | OpsGenie | `opsgenie_config` | | 10 | PagerDuty | `pagerduty_config` | | 11 | Pushover | `pushover_config` | | 12 | Rocket.Chat | `rocketchat_config` | | 13 | Slack | `slack_config` | | 14 | Telegram | `telegram_config` | | 15 | Splunk On-Call(formerly VictorOps) | `victorops_config` | | 16 | Webex | `webex_config` | | 17 | Webhook | `webhook_config` | | 18 | WeChat(企业微信 / WeChat Work) | `wechat_config` | **对接方式(External Configuration / API Reference 摘要)**: | 集成 | 对接要点 | | --- | --- | | Discord | Discord Webhooks("Intro to Webhooks") | | Slack | Incoming Webhooks 或 Bot Tokens(`chat.postMessage`) | | Microsoft Teams | Incoming Webhooks(官方标注 **Deprecated**) | | Microsoft Teams v2 | Workflows for Teams / Power Automate Flows | | Email | SMTP(`smtp_*` 全局项 + `email_config`) | | PagerDuty | Events API v2(`routing_key`)或 Prometheus 集成(`service_key`) | | OpsGenie | Alert API(`api_key`) | | Splunk On-Call | Splunk On-Call REST endpoint(原 VictorOps) | | Webex | `webexapis.com/v1/messages`(bot token) | | Rocket.Chat | Rocket.Chat REST API(token + token_id) | | Pushover | Pushover API(user_key + token) | | Telegram | Telegram Bot API(bot_token + chat_id) | | Jira | JIRA REST API v2/v3(PAT 或 OAuth2 client_credentials) | | incident.io | Alert Sources API(alert_source_token) | | Amazon SNS | AWS SNS(topic_arn / phone_number / target_arn,SigV4) | | WeChat | 企业微信 API(corp_id + agent_id + api_secret) | **扩展方式**:对 Alertmanager 原生不支持的机制,可用 **webhook receiver** 集成;不完整列表见 Prometheus `docs/operating/integrations/#alertmanager-webhook-receiver`(例如 Gotify 即属此类)。 > 引自:[Notification Integrations](https://prometheus.io/docs/alerting/latest/integrations/) ## 第 4 章 高可用与告警运维 API 单实例 Alertmanager 是单点。本章讲清三件事:如何用 gossip 组建高可用集群、告警在集群中如何被去重与转发(含脑裂下的行为),以及运维所需的 API、HTTPS 认证与生产部署要点。 ### 4.1 HA 设计目标 Alertmanager 的高可用设计遵循三条核心原则: | 原则 | 英文 | 含义 | | --- | --- | --- | | 单窗视图与管理 | Single pane view and management | Silences 和 alerts 可从**任意**集群成员查看与管理,提供统一操作体验 | | 脑裂下「失败开放」 | Survive cluster split-brain with "fail open" | 网络分区期间,Alertmanager **宁可发送重复通知也不漏掉关键告警** | | 至少一次投递 | At-least-once delivery | 保证通知至少投递一次,与 fail-open 理念一致 | 这些目标让系统**优先保证运行可靠性与告警投递**,而非严格的 exactly-once 语义。 > 引自:[High Availability](https://prometheus.io/docs/alerting/latest/high_availability/) ### 4.2 gossip / Memberlist 集群机制 #### 4.2.1 架构总览 集群由多个 Alertmanager 实例组成,通过 **gossip protocol** 通信。每个实例: - 独立从 Prometheus servers 接收 alerts; - 参与 peer-to-peer gossip mesh; - 将状态(silences 与 notification log)复制到其他集群成员; - 独立处理并发送通知。 节点在集群中有 `position`(位置)概念,例如 AM-1 `(pos: 0)`、AM-2 `(pos: 1)`、AM-3 `(pos: 2)`。 #### 4.2.2 Gossip 协议 使用 [Hashicorp's Memberlist](https://github.com/hashicorp/memberlist) 库实现基于 gossip 的通信。 **成员管理(Membership Management)**: | 机制 | 说明 | | --- | --- | | Automatic peer discovery | 可配置已知 peer 列表,实例会自动发现其他集群成员 | | Health checking | 定期探针检测失效成员(默认:每 1 秒) | | Failure detection | 失效成员被标记,并可尝试重新加入 | **状态复制(State Replication)**:gossip 层复制三类状态: 1. **Silences** —— create/update/delete 操作广播到所有 peers; 2. **Notification log** —— 记录已发送过哪些通知以去重; 3. **Membership changes** —— join、leave、failure 事件。 状态为**最终一致(eventually consistent)**:给定足够时间与网络连通性,所有成员将收敛到同一状态。 **Gossip Settling(收敛等待)**:实例启动或重新加入集群时,会等待 gossip "settle" 之后再处理通知,避免基于不完整状态发通知。收敛算法等待条件(满足其一): - peer 数量连续 **3 次检查**保持稳定(`3 consecutive checks`,默认检查间隔 `push-pull interval`); - 或发生超时(可通过 context 配置)。 此期间实例**仍会接收并存储 alerts**,但推迟通知处理。 > 引自:[High Availability](https://prometheus.io/docs/alerting/latest/high_availability/) ### 4.3 HA 模式下的通知流水线 HA 模式下的通知流水线分两个阶段。 **Dispatcher Stage(分发阶段)**: 1. Find matching route(s) 2. Find/create aggregation group within route 3. Throttle by group wait or group interval **Notifier Stage(通知阶段)**: 1. Wait for HA gossip to settle(确保状态完整) 2. Filter inhibited alerts 3. Filter non-time-active alerts 4. Filter time-muted alerts 5. Filter silenced alerts(使用复制的 silences) 6. Wait according to HA cluster peer index(错峰通知) 7. Dedupe by repeat interval/HA state(使用 notification log) 8. Notify & retry intermittent failures 9. Update notification log(复制到 peers) ```text Alert ──► [Dispatcher] 匹配 route ─► 建/取聚合组 ─► group_wait/group_interval 节流 │ ▼ [Notifier] 等 gossip 收敛 ─► 过滤(抑制/时段/静默) ─► 按 peer position 错峰 ─► 用 nflog 去重 ─► 发送+重试 ─► 更新并复制 notification log ``` **1. Gossip Settling Wait**:某 group 的首次通知前,实例等待 gossip 收敛,以确保 silences 已完成复制、notification log 含其他实例的近期发送记录、集群成员关系稳定。实现:`peer.WaitReady(ctx)`。 **2. Peer Position-Based Wait(基于 peer 位置的等待)**:为防止所有成员同时发送通知,每个实例按其排序后 peer 列表中的位置等待: ```text wait_time = peer_position × peer_timeout ``` 例:3 个实例、`peer_timeout` 为 15 秒时: | 实例 | position | 等待 | | --- | --- | --- | | `am-1` | 0 | 0 秒 | | `am-2` | 1 | 15 秒 | | `am-3` | 2 | 30 秒 | 错峰的目的:让第一个实例先发送;后续实例能看到 notification log 条目;去重防止重复发送。实现:`clusterWait()` in `cmd/alertmanager/main.go:594`。 position 由对**所有 peer name 按字母序排序**决定: ```go func (p *Peer) Position() int { all := p.mlist.Members() sort.Slice(all, func(i, j int) bool { return all[i].Name < all[j].Name }) // Find position of self in sorted list } ``` > 引自:[High Availability](https://prometheus.io/docs/alerting/latest/high_availability/) ### 4.4 去重与告警转发逻辑 **3. 通过 Notification Log 去重**:`DedupStage` 查询 notification log 决定是否发送: ```go // Check notification log for recent sends entry := nflog.Query(receiver, groupKey) if entry.exists && !shouldNotify(entry, alerts, repeatInterval) { // Skip: already notified recently return nil } ``` 去重判断内容: | 判断 | 结果 | | --- | --- | | **Firing alerts changed?** | 是 → 通知 | | **Resolved alerts changed?** | 是 且 `send_resolved: true` → 通知 | | **Repeat interval elapsed?** | 是 → 通知 | | **Otherwise** | 跳过通知(被去重) | notification log 通过 gossip 复制,所有集群成员共享同一发送历史。 **转发逻辑一句话总结**:Prometheus 把同一条告警推给**每个** Alertmanager 实例;实例各自按 route 建组、过滤,然后靠「错峰等待 + 共享 nflog」做到**只有 position 最小的那个实例真正发通知**,其余实例跳过。 > 引自:[High Availability](https://prometheus.io/docs/alerting/latest/high_availability/) ### 4.5 脑裂处理(Fail Open) 网络分区时集群可能分裂为多个无法通信的分组;"fail open" 设计确保告警仍被投递。 分区期间的行为: | 分区 | 成员 | 行为 | | --- | --- | --- | | Partition A | 仅 AM-1 | AM-1 视自己为 position 0 → 等待 `0 × timeout = 0` 秒 → 发送通知(无来自 AM-2/AM-3 的去重) | | Partition B | AM-2、AM-3 | AM-2 为 position 0、AM-3 为 position 1;AM-2 等待 0 秒发送;AM-3 看到 AM-2 的 notification log 条目后去重 | **结果**:发送重复通知(Partition A 一条、Partition B 一条)——这是**有意为之**,Alertmanager 宁可重复也不漏告警。 **分区恢复后**: 1. gossip 协议重新发现所有 peers; 2. notification logs 合并(via **CRDT-like merge with timestamp**,按时间戳的类 CRDT 合并); 3. 后续通知在所有实例间正确去重; 4. 任一分区中创建的 silences 都会复制到所有 peers。 > 引自:[High Availability](https://prometheus.io/docs/alerting/latest/high_availability/) ### 4.6 HA 中的 Silence 管理 Silences 是集群中的**一等(first-class)复制状态**。 创建/更新 silence 时: 1. **Local storage** —— 存入本地 state map; 2. **Broadcast** —— 序列化(protobuf)后经 gossip 广播; 3. **Merge on receive** —— 其他实例接收并合并: ```go // Merge logic: last-write-wins based on UpdatedAt timestamp if !exists || incoming.UpdatedAt > existing.UpdatedAt { accept_update() } ``` 4. **Indexing** —— 更新 silence matcher cache 以加速 alert 匹配。 **Silence 的过期字段**: | 字段 | 含义 | | --- | --- | | `StartsAt`、`EndsAt` | 生效时间范围 | | `ExpiresAt` | 何时被垃圾回收(`EndsAt + retention period`) | | `UpdatedAt` | 合并时用于冲突解决 | 每个实例**独立**:按当前时间评估 silence 状态(pending/active/expired);对超过保留期的 silence 做 GC;GC 仅本地执行(不走 gossip),因为所有实例会收敛到同一决策。 **Single Pane of Glass**: - **View silences** —— 所有实例的 silence 状态相同(最终一致); - **Create/update silences** —— 任一实例上的修改传播到所有 peers; - **Delete silences** —— 实现为 "expire immediately" + gossip。 > 引自:[High Availability](https://prometheus.io/docs/alerting/latest/high_availability/) ### 4.7 `--cluster.*` 全部命令行 flags **YAML 中没有任何 cluster 配置**,必须使用 CLI flags: ```yaml # alertmanager.yml global: # ... other config ... # No cluster config in YAML - use CLI flags ``` 命令行示例: ```bash alertmanager \ --cluster.listen-address=0.0.0.0:9094 \ --cluster.peer=am-1.example.com:9094 \ --cluster.peer=am-2.example.com:9094 \ --cluster.peer=am-3.example.com:9094 \ --cluster.advertise-address=192.0.2.1:9094 \ --cluster.peer-timeout=15s \ --cluster.gossip-interval=200ms \ --cluster.pushpull-interval=60s ``` 关键 flags 及默认值: | Flag | 说明 | 默认 | | --- | --- | --- | | `--cluster.listen-address` | 集群通信绑定地址 | `0.0.0.0:9094` | | `--cluster.peer` | peer 地址列表(可重复) | — | | `--cluster.advertise-address` | 向 peers 通告的 IP 地址(**非 hostname**),格式 `:` | 省略时自动探测 | | `--cluster.peer-timeout` | 每个 peer position 的去重等待时间 | `15s` | | `--cluster.gossip-interval` | gossip 频率 | `200ms` | | `--cluster.pushpull-interval` | 全量状态同步间隔 | `60s` | | `--cluster.probe-interval` | peer 健康检查间隔 | `1s` | | `--cluster.settle-timeout` | 等待 gossip 收敛的最长时间 | context timeout | | `--cluster.tls-config` | 启用 mutual TLS 的集群 TLS 配置(见 https 页 Gossip Traffic) | — | > 💡 补充:以上为官方 HA 文档明确列出的 cluster flags。Alertmanager 二进制实际还提供若干更细粒度的集群调优 flag(如重连、TCP 超时等),可用 `alertmanager -h` 查看完整列表;生产调优前建议先核对本机版本帮助输出。 **Prometheus 侧配置**:应配置 Prometheus 将告警发往**所有** Alertmanager 实例,而非经由负载均衡器: ```yaml # prometheus.yml alerting: alertmanagers: - static_configs: - targets: - am-1.example.com:9093 - am-2.example.com:9093 - am-3.example.com:9093 ``` 好处: - **Redundancy**:某实例宕机其他仍收告警; - **Independent processing**:各实例独立做路由、分组、去重; - **No single point of failure**:LB 会引入单点故障。 > 引自:[High Availability](https://prometheus.io/docs/alerting/latest/high_availability/) ### 4.8 告警状态 API(Alerts API) #### 4.8.1 建议由 Prometheus 发送告警 Prometheus 自行负责把告警发送给 Alertmanager。官方**建议在 Prometheus 中基于 time series 数据配置 alerting rules**,而非直接往 Alerts API 发告警——因为 Prometheus 处理了许多特殊情况,以确保即使 Alertmanager 崩溃或重启,告警也能送达。 #### 4.8.2 API 版本 - 发送告警使用 **APIv2**。APIv2 以 OpenAPI 规范定义,规范文件:`https://github.com/prometheus/alertmanager/blob/master/api/v2/openapi.yaml`。 - **APIv1 在 Alertmanager 0.16.0 中废弃,并在 0.27.0 中移除。** #### 4.8.3 端点与请求 向 APIv2 发送告警需对 **`api/v2/alerts` 发起 POST 请求**;必须设置 `Content-Type` header 为 `application/json`,并发送包含告警数组的 JSON 数据: ```json [ { "labels": { "alertname": "", "": "" }, "annotations": { "": "" }, "startsAt": "", "endsAt": "", "generatorURL": "" } ] ``` `generatorURL` 为可选。send-resolved 相关行为见下。 #### 4.8.4 字段语义 | 字段 | 说明 | | --- | --- | | `labels` | 用于**去重**同一告警的相同实例;`alertname` 为必填 | | `annotations` | 附加信息,如 `summary`、`description`、runbook URL | | `startsAt` | 告警触发时间;若省略,Alertmanager 将其设为当前时间 | | `endsAt` | 告警应被解决的时间;若省略,Alertmanager 将其设为当前时间 + `resolve_timeout` | | `generatorURL` | 唯一 URL,链接到告警来源(例如 Prometheus 中触发该告警的 rule) | 所有时间戳均须 **RFC3339** 格式。 #### 4.8.5 客户端期望(Expectations from clients) - 客户端应**定期重新发送 firing alerts**,直到告警解决。确切间隔取决于多个变量,如 `endsAt` 时间戳,或(若省略)`resolve_timeout` 的值;若省略 `endsAt`,Alertmanager 会把已存在告警的 `endsAt` 更新为当前时间 + `resolve_timeout`。firing alerts 在其 `endsAt` 过去后即被解决。 - 为确保已解决告警能发出 **resolved notifications**,客户端还应在告警解决后**最长 5 分钟内持续重发 resolved alerts** 给 Alertmanager。由于 Alertmanager 是无状态的,这能保证即使 Alertmanager 崩溃或重启,resolved notification 仍会发出。 **curl 示例**: ```bash curl -X POST http://am-1:9093/api/v2/alerts \ -H 'Content-Type: application/json' \ -d '[{"labels":{"alertname":"TestAlert","instance":"host1"},"annotations":{"summary":"test"},"startsAt":"2026-10-08T12:00:00Z"}]' ``` > 引自:[Alerts API](https://prometheus.io/docs/alerting/latest/alerts_api/) ### 4.9 管理 API(Management API) Alertmanager 提供一组 management API,以便自动化与集成。 | 方法/路径 | 说明 | | --- | --- | | `GET /-/healthy`、`HEAD /-/healthy` | 该端点**始终返回 200**,用于检查 Alertmanager health | | `GET /-/ready`、`HEAD /-/ready` | 当 Alertmanager 已就绪可服务流量(即能响应查询)时返回 200 | | `POST /-/reload` | 触发 Alertmanager 配置文件重载;另一种方式是向进程发送 `SIGHUP` | > 引自:[Management API](https://prometheus.io/docs/alerting/latest/management_api/) > 💡 补充:`/-/healthy` 与 `/-/ready` 的差异在于——前者只表明进程存活(活着即 200),后者才表明已加载完配置与状态、可以对外服务。Kubernetes 中通常把前者用于 livenessProbe、后者用于 readinessProbe。 ### 4.10 HTTPS 与认证 Alertmanager 支持 basic authentication 与 TLS。该功能为 **experimental(实验性)**,未来可能变化。TLS 支持用于两类流量:**HTTP traffic** 与 **gossip traffic**。 #### 4.10.1 HTTP 流量(`--web.config.file`) 通过 `--web.config.file` flag 指定要加载的 web 配置文件。文件为 YAML 格式,**在每次 HTTP 请求时读取**,因此配置或证书的任何变更都会**立即生效**。 ```yaml tls_server_config: # Certificate and key files for server to use to authenticate to client. cert_file: key_file: # Server policy for client authentication. Maps to ClientAuth Policies. # NOTE: If you want to enable client authentication, you need to use # RequireAndVerifyClientCert. Other values are insecure. [ client_auth_type: | default = "NoClientCert" ] # CA certificate for client certificate authentication to the server. [ client_ca_file: ] # Verify that the client certificate has a Subject Alternate Name (SAN) # which is an exact match to an entry in this list, else terminate the connection. [ client_allowed_sans: [ - ] ] # Minimum TLS version that is acceptable. [ min_version: | default = "TLS12" ] # Maximum TLS version that is acceptable. [ max_version: | default = "TLS13" ] # List of supported cipher suites for TLS versions up to TLS 1.2. [ cipher_suites: [ - ] ] # prefer_server_cipher_suites controls whether the server selects the # client's most preferred ciphersuite, or the server's most preferred ciphersuite. [ prefer_server_cipher_suites: | default = true ] # Elliptic curves that will be used in an ECDHE handshake, in preference order. [ curve_preferences: [ - ] ] http_server_config: # Enable HTTP/2 support. Note that HTTP/2 is only supported with TLS. # This can not be changed on the fly. [ http2: | default = true ] # List of headers that can be added to HTTP responses. [ headers: [ Content-Security-Policy: ] [ X-Frame-Options: ] [ X-Content-Type-Options: ] [ X-XSS-Protection: ] [ Strict-Transport-Security: ] ] # Usernames and hashed passwords that have full access to the web # server via basic authentication. If empty, no basic authentication is # required. Passwords are hashed with bcrypt. basic_auth_users: [ : ... ] ``` **字段逐条说明**: | 字段 | 说明 | | --- | --- | | `tls_server_config.cert_file` / `key_file` | 服务端用于向客户端认证的证书与私钥文件 | | `client_auth_type` | 客户端认证的服务端策略,映射到 Go ClientAuth Policies;默认 `"NoClientCert"`。**注意**:要启用客户端认证必须用 `RequireAndVerifyClientCert`,其他取值不安全 | | `client_ca_file` | 用于客户端证书认证的 CA 证书 | | `client_allowed_sans` | 校验客户端证书的 SAN 是否与列表中某项**精确匹配**,不匹配则终止连接;SAN 匹配类型可为 DNS、IP、e-mail 或 URI 地址之一或多个 | | `min_version` | 可接受的最低 TLS 版本,默认 `"TLS12"` | | `max_version` | 可接受的最高 TLS 版本,默认 `"TLS13"` | | `cipher_suites` | 支持至 TLS 1.2 的密码套件列表;为空则用 Go 默认;仅支持 `crypto/tls#CipherSuites` 返回的套件 | | `prefer_server_cipher_suites` | 控制服务端选客户端最偏好还是服务端最偏好的套件;为 `true` 时按 `cipher_suites` 顺序采用服务端偏好;默认 `true` | | `curve_preferences` | ECDHE 握手使用的椭圆曲线,按偏好顺序 | | `http_server_config.http2` | 启用 HTTP/2,**仅 TLS 下受支持**,且**不可在线热改**;默认 `true` | | `http_server_config.headers` | 可加入 HTTP 响应的 header:`Content-Security-Policy`、`X-Frame-Options`(接受值 deny 与 sameorigin)、`X-Content-Type-Options`(接受值 nosniff)、`X-XSS-Protection`、`Strict-Transport-Security`;留空则不设置 | | `basic_auth_users` | 拥有通过 basic auth 访问 web server 完整权限的用户名与**哈希后**密码;为空则不要求 basic auth。**Passwords are hashed with bcrypt** | > 💡 补充:原文未给出生成 bcrypt 密码哈希的具体命令。实践上可用 `htpasswd -nBC 12 "" | tr -d ':\n'` 或 `amtool`/Python `bcrypt` 生成,再填入 `basic_auth_users`。此为实现细节,非官方文档内容。 #### 4.10.2 Gossip 流量(`--cluster.tls-config`) 通过 `--cluster.tls-config` flag 指定是否对 gossip 使用 **mutual TLS**。gossip 的 server 端与 client 端均可配置。 ```yaml tls_server_config: cert_file: key_file: [ client_auth_type: | default = "NoClientCert" ] [ client_ca_file: ] [ min_version: | default = "TLS12" ] [ max_version: | default = "TLS13" ] [ cipher_suites: [ - ] ] [ prefer_server_cipher_suites: | default = true ] [ curve_preferences: [ - ] ] tls_client_config: # Path to the CA certificate with which to validate the server certificate. [ ca_file: ] # Certificate and key files for client cert authentication to the server. [ cert_file: ] [ key_file: ] # Server name extension to indicate the name of the server. [ server_name: ] # Disable validation of the server certificate. [ insecure_skip_verify: | default = false] ``` **gossip TLS 与 HTTP 的差异点**: - `tls_server_config` 部分字段相同(`cert_file`、`key_file`、`client_auth_type` 默认 `"NoClientCert"`、`client_ca_file`、`min_version` 默认 `"TLS12"`、`max_version` 默认 `"TLS13"`、`cipher_suites`、`prefer_server_cipher_suites` 默认 `true`、`curve_preferences`),但**不含** `client_allowed_sans`。 - 额外提供 `tls_client_config`:`ca_file`(校验服务端证书的 CA 路径)、`cert_file`/`key_file`(客户端证书认证)、`server_name`(server name extension)、`insecure_skip_verify`(禁用服务端证书校验,默认 `false`)。 > 引自:[HTTPS and authentication](https://prometheus.io/docs/alerting/latest/https/) ### 4.11 生产部署要点 #### 4.11.1 集群规模考量 Alertmanager 使用 gossip,**无 quorum/投票**,因此**任意 N 个实例可容忍最多 N-1 个故障**——只要有一个存活,通知就会被发送。 | 维度 | 说明 | | --- | --- | | 实例更多的收益 | 更强的同时故障(硬件、网络、机房级中断)韧性;维护窗口期间仍可运行 | | 实例更多的代价 | 分区时重复通知增多;gossip 流量增加 | | 典型部署 | **2-3 实例**(单机房生产常见);**4-5 实例**(多机房或高关键环境) | | 与共识系统差异 | 与 etcd、Raft 不同,**奇数/偶数集群规模无差别**——没有投票或 quorum | #### 4.11.2 集群健康监控指标 ```text # Cluster size alertmanager_cluster_members # Peer health alertmanager_cluster_peer_info # Peer position (affects notification timing) alertmanager_peer_position # Failed peers alertmanager_cluster_failed_peers # State replication alertmanager_nflog_gossip_messages_propagated_total alertmanager_silences_gossip_messages_propagated_total ``` | 指标 | 用途 | | --- | --- | | `alertmanager_cluster_members` | 集群规模,应等于部署实例总数 | | `alertmanager_cluster_peer_info{state="alive"}` | 各 peer 健康状态 | | `alertmanager_peer_position` | 本实例 position,影响通知时机 | | `alertmanager_cluster_failed_peers` | 失败 peer 数 | | `alertmanager_nflog_gossip_messages_propagated_total` | notification log 复制量 | | `alertmanager_silences_gossip_messages_propagated_total` | silences 复制量 | | `alertmanager_alerts_limited_total` | 因 per-alertname limit 被丢弃的告警数(可选 `alertname` label) | #### 4.11.3 安全 默认集群通信**未加密**。生产部署(尤其跨 WAN)应使用 mutual TLS: ```bash alertmanager \ --cluster.tls-config=/etc/alertmanager/cluster-tls.yml ``` 详见 4.10.2 节 Gossip Traffic 的 mutual TLS 配置。 #### 4.11.4 持久化 每个实例持久化: | 状态 | 存储 | 默认路径 | | --- | --- | --- | | Silences | 快照文件 | `data/silences` | | Notification log | 快照文件 | `data/nflog` | 重启时:从磁盘加载 silences 与 notification log → 加入集群并与 peers gossip → 合并从 peers 收到的状态(**较新时间戳胜出**)→ gossip 收敛后开始处理通知。 > **注意**:**alerts 本身不持久化**——Prometheus 会定期重发 firing alerts。 #### 4.11.5 常见坑(Common Pitfalls) | # | 坑 | 正确做法 | | --- | --- | --- | | 1 | **Load balancing Prometheus → Alertmanager** | ❌ 不要用 LB;✅ 在 Prometheus 中配置所有实例 | | 2 | **Not waiting for gossip to settle** | 可能导致启动时漏 silence 或重复通知;由 `--cluster.settle-timeout` 控制 | | 3 | **Network ACLs blocking cluster port** | 确保所有实例间开放 9094(或自定义 `--cluster.listen-address` 端口);默认同时使用 TCP 和 UDP(仅在使用 TLS transport 时只用 TCP) | | 4 | **Unroutable advertise addresses** | 若未设 `--cluster.advertise-address`,Alertmanager 会尝试自动探测;该值必须是 `:` 格式的 **IP 地址——不支持 hostname**。使用 hostname 会记 warning,启动会继续但 advertise address 未设或错误,导致集群通信失败;云/NAT 环境应显式设置可路由 IP | | 5 | **Mismatched cluster configurations** | 所有实例应使用相同的 `--cluster.peer-timeout` 与 gossip 设置;不一致会导致不必要的重复或漏通知 | #### 4.11.6 故障排查(Troubleshooting) ```bash # View cluster members via API curl http://am-1:9093/api/v2/status # Check metrics curl http://am-1:9093/metrics | grep cluster # 开启 debug 日志 alertmanager --log.level=debug ``` **诊断脑裂**: 1. 检查各实例的 `alertmanager_cluster_members` —— 应等于集群总规模; 2. 检查 `alertmanager_cluster_peer_info{state="alive"}` —— 应显示所有 peer 为 alive; 3. 检查实例间的网络连通性。 **调试重复通知,可能原因**: | 原因 | 排查 | | --- | --- | | Network partitions | 预期内,fail-open | | Gossip not settled | 检查 `--cluster.settle-timeout` | | Clock skew | 确保所有实例配置 NTP | | Notification log not replicating | 检查 gossip 指标 | **关注日志关键字**:`"Waiting for gossip to settle..."`、`"gossip settled; proceeding"`,以及通知流水线中的去重决策。 #### 4.11.7 端到端示例(How It Works) **场景一:3 实例集群、新 alert group** 1. 告警经 Prometheus 到达全部 3 个实例; 2. Dispatcher 创建聚合组,等待 `group_wait`(如 30s); 3. `group_wait` 后各实例准备通知; 4. Notifier 阶段(若刚启动则都等待 gossip 收敛): - **AM-1**(position 0):等待 0s,查 notification log(空),发送通知并写入 nflog; - **AM-2**(position 1):等待 15s,查到 AM-1 的条目,**跳过**; - **AM-3**(position 2):等待 30s,查到 AM-1 的条目,**跳过**; 5. 结果:恰好发送**一条**通知(由 AM-1 发出)。 **场景二:AM-1 故障** 1. 告警仅到达 AM-2、AM-3; 2. Dispatcher 建组等待 `group_wait`; 3. AM-1 不在集群中(探针失败);**AM-2** 变为 position 0 等待 0s 并发送,**AM-3** 变为 position 1 等待 15s 后看到 AM-2 条目并跳过; 4. 结果:通知仍然发出(**fail-open**)。 **场景三:通知期间发生网络分区** 1. 告警到达所有实例; 2. 分区将 AM-1 与 AM-2/AM-3 隔开; 3. Partition A(AM-1)position 0 等待 0s 发送; 4. Partition B(AM-2、AM-3)AM-2 为 position 0 等待 0s 发送、AM-3 为 position 1 等待 15s 后去重; 5. 结果:发送**两条**通知(每个分区一条)——fail-open 行为。 > 引自:[High Availability](https://prometheus.io/docs/alerting/latest/high_availability/) --- ## 第 1–4 章小结(Alertmanager 部分) 本部分(第 1-4 章)覆盖了 Alertmanager 的完整告警链路: - **第 1 章**:告警体系组成、五大核心能力(去重/分组/路由/静默/抑制)+ HA、告警生命周期、限制与注意事项。 - **第 2 章**:`alertmanager.yml` 全字段——global、route 路由树、time_intervals、inhibit_rules、label matchers、18 类 receiver 集成、http_config 共享块、tracing 与 event_recorder、配置验证与 reload。 - **第 3 章**:Go 模板基础、Data/Alert/KV 数据结构、全部模板函数、6 组官方示例、各渠道样式与集成清单。 - **第 4 章**:HA 设计目标、gossip/Memberlist 机制、通知流水线与去重转发、脑裂 fail-open、silence 复制、`--cluster.*` flags、Alerts API、Management API、HTTPS 与认证、生产部署要点。 本篇后续章节(第 5-10 章):插桩与客户端库、导出器与集成生态、应用集成实战、可视化、规范(OpenMetrics / Remote Write / Native Histograms)、参与社区。 > 引自:[Alerting overview](https://prometheus.io/docs/alerting/latest/overview/) | [Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) | [Configuration](https://prometheus.io/docs/alerting/latest/configuration/) | [Notification template reference](https://prometheus.io/docs/alerting/latest/notifications/) | [Notification Integrations](https://prometheus.io/docs/alerting/latest/integrations/) | [High Availability](https://prometheus.io/docs/alerting/latest/high_availability/) | [Alerts API](https://prometheus.io/docs/alerting/latest/alerts_api/) | [Management API](https://prometheus.io/docs/alerting/latest/management_api/) | [HTTPS and authentication](https://prometheus.io/docs/alerting/latest/https/) ## 第 5 章 插桩与客户端库 第 4 篇的前四章解决了"告警怎么发出去",从本章开始转入**数据怎么进来**。要做监控,第一步永远是**插桩(instrumentation)**——在应用代码里把想观测的量记录下来,并以 Prometheus 能识别的格式暴露出来。这一章覆盖五件事: 1. **客户端库(client library)总览**:有哪些语言的库,谁在维护,状态如何; 2. **暴露格式(exposition format)**:指标最终长什么样——文本格式与 protobuf 格式的逐条规范; 3. **内容协商(content negotiation)**:抓取方(Prometheus)与被抓取方(target)如何谈定用哪种格式; 4. **UTF-8 名称转义方案(escaping schemes)**:当指标名/label 名超出传统字符集时如何降级; 5. **Pushgateway**:唯一的"推"通路,以及它为什么**只应在极少数场景**使用。 ### 5.1 客户端库总览 #### 5.1.1 是什么 / 为什么 **客户端库**是给应用代码做插桩的 SDK。它让你在应用内部定义并维护 Counter、Gauge、Histogram、Summary 等指标(这些库实现的是 Prometheus 的 [metric types](/docs/concepts/metric_types/)),然后通过一个 HTTP endpoint 把内部指标的**当前状态**暴露出去。当 Prometheus 抓取该 endpoint 时,客户端库会把所有被跟踪指标的当前值一次性发给服务端。 选型原则只有一条:**选择与你的应用编写语言匹配的 client library**。 #### 5.1.2 官方客户端库 以下为官方(official)维护的客户端库,逐项列出: | 语言 | 仓库地址 | | --- | --- | | Go | https://github.com/prometheus/client_golang | | Java or Scala | https://github.com/prometheus/client_java | | Node.js | https://github.com/prometheus/client_js | | Python | https://github.com/prometheus/client_python | | Ruby | https://github.com/prometheus/client_ruby | | Rust | https://github.com/prometheus/client_rust | #### 5.1.3 非官方第三方客户端库 以下为社区维护的第三方(Unofficial third-party)客户端库,逐项列出: | 语言 | 说明 / 仓库 | | --- | --- | | Bash | (Bash 实现) | | C | digitalocean/prometheus-client-c | | C++ | jupp0r/prometheus-cpp | | Common Lisp | (Common Lisp 实现) | | Dart | (Dart 实现) | | Delphi | (Delphi 实现) | | Elixir | (Elixir 实现) | | Erlang | (Erlang 实现) | | Haskell | prometheus-client(fimad/prometheus-haskell) | | Haskell | prometheus(bitnomial/prometheus) | | Julia | (Julia 实现) | | Lua | knyar/nginx-lua-prometheus(用于 Nginx) | | Lua | tarantool/metrics(用于 Tarantool) | | .NET / C# | (.NET / C# 实现) | | OCaml | (OCaml 实现) | | Perl | Metacpan Net::Prometheus | | PHP | promphp/prometheus_client_php | | R | cfmack/pRometheus | | Swift | swift-server/swift-prometheus | #### 5.1.4 状态划分与注意事项 - 本页**只区分 `official` 与 `unofficial third-party` 两类状态**,官方文档中**没有**"已弃用(deprecated)"分类。因此"官方/非官方"是唯一的权威状态标记。 - **无库时的替代方案**:若你的语言没有可用客户端库,或想避免引入依赖,也可以**自行实现一种受支持的 exposition format**(见 5.2 节)来暴露指标。 - **实现新库的指引**:实现新的 Prometheus 客户端库时,请遵循《writing client libraries》(见 6.5 节)。官方文档注明该指引仍是 work in progress,建议同时参考 development mailing list(groups.google.com/forum/#!forum/prometheus-developers);项目方乐于就"如何让库更实用、更一致"提供建议。 > 引自:[Client libraries](https://prometheus.io/docs/instrumenting/clientlibs/) > 引自:[Instrumenting](https://prometheus.io/docs/instrumenting/)(该页为左侧导航栏的章节索引页,仅有 frontmatter,无正文;正文由 clientlibs、exposition_formats、pushing、content_negotiation、escaping_schemes 等子页面承载) > 💡 补充:选择库时除"是否有官方实现"外,还应关注三件事——① 是否支持你需要的指标类型(部分小众库只实现了 Counter/Gauge);② 是否支持带标签的指标子项(`labels()` 类 API);③ 是否支持与 Prometheus 的 protobuf / OpenMetrics 格式对接(影响能否用 exemplar、native histogram 等新特性)。这三项决定了后续能不能平滑升级到新特性,而不是只把库引入就够了。 ### 5.2 文本与 protobuf 暴露格式详解 **总述**:自 Prometheus 2.0 起,所有向 Prometheus 暴露指标的进程**默认必须使用 text 格式**;此外可以在 HTTP 内容协商之后使用 **protobuf** 格式。多种客户端库已经实现了这些格式;若你的语言没有库,可以自行实现。本节只列**官方支持**的 exposition formats。 #### 5.2.1 Prometheus Text Format 0.0.4 | 项目 | 内容 | | --- | --- | | Inception(诞生时间) | April 2014 | | Supported in Prometheus | `>=0.4.0` | | Transmission(传输) | HTTP | | Encoding(编码) | UTF-8、`\n` 行结束 | | HTTP `Content-Type` | `text/plain`,带参数 `version=0.0.4`(缺失 `version` 会回退到最新 text 格式版本) | | 可选 HTTP `Content-Encoding` | `gzip` | | 支持的 metric primitives | Counter、Gauge、Histogram、Summary、Untyped | - **优点**:human-readable(人类可读)、易拼装(无需嵌套)、可逐行读(metadata 除外)。 - **局限**:Verbose(冗长)、类型与 docstring 非语法固有部分(导致指标契约校验几乎没有)、解析成本较高。 #### 5.2.2 文本格式的逐行细节 - 格式**面向行**,行以换行符 `\n` 分隔;**最后一行必须以换行符结尾**;空行被忽略。 - 行内 token 可用**任意数量的空格和/或 tab** 分隔;若不加分隔会与前一个 token 合并,则至少需要一个分隔符;首尾空白被忽略。 - **Comments / HELP / TYPE**: - 首个非空白字符为 `#` 的行是注释;除非 `#` 后第一个 token 是 `HELP` 或 `TYPE`,否则忽略。 - **HELP**:至少再有一个 token 为 metric name,其余 token 视为该指标名的 docstring。`HELP` 行在指标名之后可含任意 UTF-8 字符序列,但**反斜杠与换行符必须分别转义为 `\\` 和 `\n`**;同一指标名只允许一条 `HELP` 行。 - **TYPE**:恰好再有 2 个 token,第一个是 metric name,第二个是 `counter`、`gauge`、`histogram`、`summary` 或 `untyped`;同一指标名只允许一条 `TYPE` 行;**`TYPE` 行必须出现在该指标名第一个样本之前**;若无 `TYPE` 行,类型置为 `untyped`。 - 不符合 legacy Prometheus 指标名字符集的指标名**必须加引号并转义**。 #### 5.2.3 样本行语法(EBNF) ```text metric_name_or_labels value [ timestamp ] metric_name_or_labels = metric_name [ "{" labels "}" ] | "{" quoted_metric_name [ "," labels ] "}" metric_name = identifier quoted_metric_name = `"` escaped_string `"` labels = [ label_pairs ] label_pairs = label_pair { "," label_pair } [ "," ] label_pair = label_name "=" `"` escaped_string `"` label_name = identifier | `"` escaped_string `"` ``` 语法说明: - `identifier` 遵循 Prometheus 表达式语言的常规限制; - `escaped_string` 为任意 UTF-8 字符,但**反斜杠、双引号、换行必须转义**; - `metric_name` 用双引号时,**出现在大括号内而非外**; - `label_name` 可选用双引号包裹; - 不满足表达式语言常规限制的 metric / label 名**必须使用带引号语法**; - `label_value` 可为任意 UTF-8 字符序列,但 `\`、`"`、`\n` 必须分别转义为 `\\`、`\"`、`\n`; - `value` 是遵循 Go `ParseFloat()` 的 float,另外合法值有 `NaN`、`+Inf`、`-Inf`; - `timestamp` 是 `int64`(自 epoch 的**毫秒**,即 1970-01-01 00:00:00 UTC,不含闰秒),遵循 Go `ParseInt()`。 #### 5.2.4 Grouping and sorting(分组与排序) - 某指标的所有行必须作为**单个 group** 提供,可选的 `HELP` 与 `TYPE` 行放最前(顺序不限)。 - 除此之外,重复 exposition 中的**可复现排序是首选但非必需**(若计算代价过高可不排序)。 - **每行必须有唯一的 metric name 与 labels 组合**,否则 ingest 行为未定义。 #### 5.2.5 Histograms 与 summaries 的文本约定 - 名为 `x` 的 sum 以单独样本 `x_sum` 给出;count 以 `x_count` 给出; - summary 的每个 quantile 为同名 `x` 的单独样本行,带 label `{quantile="y"}`; - histogram 的每个 bucket count 为 `x_bucket` 样本行,带 label `{le="y"}`(y 为 bucket 上界); - histogram **必须**有一个 `{le="+Inf"}` 桶,其值**必须**等于 `x_count`; - histogram 的 bucket 与 summary 的 quantile 必须按 label 值(分别为 `le`、`quantile`)**升序出现**。 完整示例: ```text # HELP http_requests_total The total number of HTTP requests. # TYPE http_requests_total counter http_requests_total{method="post",code="200"} 1027 1395066363000 http_requests_total{method="post",code="400"} 3 1395066363000 # Escaping in label values: msdos_file_access_time_seconds{path="C:\\DIR\\FILE.TXT",error="Cannot find file:\n\"FILE.TXT\""} 1.458255915e9 # UTF-8 metric and label names: {"my.dotted.metric", "error.message"="Not Found"} # Minimalistic line: metric_without_timestamp_and_labels 12.47 # A weird metric from before the epoch: something_weird{problem="division by zero"} +Inf -3982045 # A histogram, which has a pretty complex representation in the text format: # HELP http_request_duration_seconds A histogram of the request duration. # TYPE http_request_duration_seconds histogram http_request_duration_seconds_bucket{le="0.05"} 24054 http_request_duration_seconds_bucket{le="0.1"} 33444 http_request_duration_seconds_bucket{le="0.2"} 100392 http_request_duration_seconds_bucket{le="0.5"} 129389 http_request_duration_seconds_bucket{le="1"} 133988 http_request_duration_seconds_bucket{le="+Inf"} 144320 http_request_duration_seconds_sum 53423 http_request_duration_seconds_count 144320 # Finally a summary, which has a complex representation, too: # HELP rpc_duration_seconds A summary of the RPC duration in seconds. # TYPE rpc_duration_seconds summary rpc_duration_seconds{quantile="0.01"} 3102 rpc_duration_seconds{quantile="0.05"} 3272 rpc_duration_seconds{quantile="0.5"} 4773 rpc_duration_seconds{quantile="0.9"} 9001 rpc_duration_seconds{quantile="0.99"} 76656 rpc_duration_seconds_sum 1.7560473e+07 rpc_duration_seconds_count 2693 ``` #### 5.2.6 OpenMetrics Text Format OpenMetrics 是基于 Prometheus text format 标准化 metric wire formatting 的努力;它可作为抓取 target 的格式,且**自 Prometheus v2.23.0 起也可用于 federation**。当前两个版本:[1.0](/docs/specs/om/open_metrics_spec/) 与 [2.0 (draft)](/docs/specs/om/open_metrics_spec_2_0/)(规范细节见第 9 章)。 | 项目 | 内容 | | --- | --- | | Inception | November 2020 | | Supported in Prometheus | `>=2.5.0` | | Encoding | UTF-8、`\n` 行结束、以 `# EOF` 结尾 | | HTTP `Content-Type` | `application/openmetrics-text`,带参数 `version=1.0.0`(缺失 `version` 会回退到最新 text OpenMetrics 版本) | | 可选 `Content-Encoding` | `gzip` | | primitives | Counter、Gauge、Histogram、GaugeHistogram、Summary、Info、StateSet、Untyped | | advanced features | Unit metadata、Exemplars(Counters、Histogram、GaugeHistogram)、Created/Start Timestamps(Counter、Histogram、GaugeHistogram、Summary)、UTF-8 metric names and label names | 优点与局限文字与 Prometheus text 格式相同。 **Exemplars(实验特性)**:使用 OpenMetrics 格式可以 exposition 与查询 Exemplars,它是对已汇总 MetricFamily 的时间点快照,可附上 Trace ID 与 tracing 系统联动。启用需至少 v2.26.0,并在启动参数中加 `--enable-feature=exemplar-storage`。 #### 5.2.7 Prometheus Protobuf Format 官方在文本表示之外还支持 protobuf exposition format。 - payload **必须**编码为一组表示 MetricFamily 的 protobuf 消息; - 消息**必须二进制编码**,并在前置其 **varint 编码的无符号整数长度**作为 delimitation; - 该 varint 长度分隔编码提供了**流式能力**,对大抓取目标尤其重要; - 所有 string 字段**必须 UTF-8 编码**。 **何时选用 protobuf**:Prometheus 3.0 **默认偏好文本协议**,除非满足以下之一: 1. 通过 Prometheus 配置中的 `scrape_protocols` 手动偏好设置; 2. 启用特定特性时(Protobuf format first),例如 `--enable-feature=created-timestamp-zero-ingestion` 或 `--enable-feature=st-storage`,或相应配置项(如 `scrape_native_histograms: true`)。 > 注:在 Prometheus 2.0 中 Protobuf 曾被标记为 deprecated,但该决定此后**被撤销**;自 Prometheus 3.0 起 Prometheus Proto 被**积极使用与维护**,作为文本格式的补充。 | 项目 | 内容 | | --- | --- | | Inception | April 2014 | | Supported in Prometheus | `>=0.4.0` | | Transmission | HTTP | | Encoding | 32-Bit Varint-Encoded Record Length-Delimited Protocol Buffer Messages of `io.prometheus.client.MetricFamily` | | HTTP `Content-Type` | `application/vnd.google.protobuf`,带参数 `proto=io.prometheus.client.MetricFamily`、`encoding=delimited` | | 可选 `Content-Encoding` | `gzip` | | 优点 | Cross-Platform、Size、Forward/Backward Compatibility、Strict Schema、Supports Concatenation and Streaming、Composite Values | | 局限 | Not human readable | | primitives | Counter、Gauge、Histogram、GaugeHistogram、Summary、Untyped | | advanced features | Unit metadata、Exemplars、Created/Start Timestamps、Native(sparse and exponential)Histogram/GaugeHistogram、UTF-8 metric names and label names | **Why Proto over Text(为何文本之外仍维护 protobuf)**:文本格式人可读、压缩好、对编程使用足够高效,但社区仍维护 protobuf,原因有三: 1. 提升新特性的质量与速度,可安全地以向后/向前兼容方式测试; 2. protobuf 借助代码生成与灵活性,有助于 exposer/ingestor 实现; 3. 在(意外的、罕见的)情况下二进制编码更高效。 **Versioning**:目前 Prometheus protobuf **稳定但明确不带版本号**,而是以 Prometheus 版本号作为参照。 **Schema**:标识为 `io.prometheus.client` 的 protobuf schema 维护在 Prometheus 仓库(`prompb/io/prometheus/client/metrics.proto`),也可在 buf registry 获取。 #### 5.2.8 HTTP Content-Type 要求(重要) **自 Prometheus 3.0 起,scrape target 必须为 metrics endpoint 返回合法的 `Content-Type`**;若缺失、不可解析或非受支持媒体类型,**scrape 将失败**。 #### 5.2.9 ScrapeProtocols vs Content-Type Prometheus 的 scrape config 通过 `scrape_protocols` 配置项基于 content-type 进行抓取协议协商;为方便用户,抓取协议以**唯一名称**引用并映射到具体 content-type。但 target 应以**绝对、单一的**响应 content-type(如 `application/openmetrics-text;version=1.0.0`)暴露指标。 **Historical versions**:历史格式版本见 legacy Client Data Exposition Format 文档;原始 Protobuf 格式的当前版本(含 native histograms 的近期扩展)维护在 `prometheus/client_model` 仓库。 > 引自:[Exposition formats](https://prometheus.io/docs/instrumenting/exposition_formats/) ### 5.3 内容协商(Scrape Protocol Content Negotiation) **Abstract**:本规范定义 Prometheus 抓取 target 时使用的协议协商机制,包括 **Accept header 格式**、受支持的 **Content Types**,以及**选定最佳 exposition 格式的协商流程**。 **Introduction**:Prometheus 支持多种抓取格式,含文本与二进制 protobuf;target 依据 Accept header 的值,挑选最佳可用 Content Type 回复。 #### 5.3.1 受支持协议(5 种) | 协议名 | 说明 | | --- | --- | | `PrometheusProto` | Binary protobuf format | | `PrometheusText0.0.4` | Prometheus text format version 0.0.4 | | `PrometheusText1.0.0` | Prometheus text format version 1.0.0 | | `OpenMetricsText0.0.1` | OpenMetrics text format version 0.0.1 | | `OpenMetricsText1.0.0` | OpenMetrics text format version 1.0.0 | #### 5.3.2 Protocol Headers(协议 ↔ MIME 类型 ↔ 参数) | 协议 | MIME 类型 | 参数 | | --- | --- | --- | | `PrometheusProto` | `application/vnd.google.protobuf` | `proto=io.prometheus.client.MetricFamily;encoding=delimited` | | `PrometheusText0.0.4` | `text/plain` | `version=0.0.4` | | `PrometheusText1.0.0` | `text/plain` | `version=1.0.0` | | `OpenMetricsText0.0.1` | `application/openmetrics-text` | `version=0.0.1` | | `OpenMetricsText1.0.0` | `application/openmetrics-text` | `version=1.0.0` | #### 5.3.3 Accept header 构造(Basic Format) 1. 对 target 支持的**每个协议**,**MUST** 指定该协议的 MIME type 与参数;protobuf 协议 **MUST** 指定编码 `delimited`;对 `PrometheusText1.0.0` 与 `OpenMetricsText1.0.0`,**SHOULD** 追加 escaping scheme 参数;**SHOULD** 追加质量值(q)参数。 2. **SHOULD** 追加一个最低质量值的 catch-all `*/*`。 **Quality Values**:**SHOULD** 按协议在 Accept header 中的**位置降序**赋 q —— 第一个协议 `q=0.{n+1}`,第二个 `q=0.{n}`,依此类推,n 为受支持协议数量。 **Escaping Scheme**:对 `PrometheusText1.0.0` 与 `OpenMetricsText1.0.0`,Accept header **SHOULD** 带 `escaping=`;`` **MUST** 为以下之一:`allow-utf-8`、`underscores`、`dots`、`values`。 **Compression**:Accept-Encoding **SHOULD** 设为 `gzip`(启用压缩)或 `identity`(禁用压缩)。 #### 5.3.4 格式选择(Selection of Format) 1. **MUST** 使用 Accept header 中**权重最高且被 Prometheus 支持**的协议; 2. 若无协议受支持,target **MAY** 使用用户配置的 fallback scrape protocol; 3. 若无 fallback,target **MUST** 以 `PrometheusText0.0.4` 作为最后兜底。 **Content-Type Response**:target **SHOULD** 回复与某个可接受格式匹配的 Content-Type,且 **MUST** 包含:① 恰当的 MIME type;② version 参数;③ 对 1.0.0 及以上文本格式,附 escaping scheme 参数。 #### 5.3.5 安全考量(Security Considerations) - target **MUST** 校验 Accept header 以防注入; - **MUST** 校验 escaping scheme 参数以防协议混淆; - Content-Type header **MUST** 正确净化以防 MIME type 混淆。 #### 5.3.6 示例 ```text # Default Accept Header Accept: application/openmetrics-text;version=1.0.0;escaping=allow-utf-8;q=0.5,application/openmetrics-text;version=0.0.1;q=0.4,text/plain;version=1.0.0;escaping=allow-utf-8;q=0.3,text/plain;version=0.0.4;q=0.2,/;q=0.1 # Protobuf-First Accept Header Accept: application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.5,application/ openmetrics-text;version=1.0.0;escaping=allow-utf-8;q=0.4,application/openmetrics-text;version=0.0.1;q=0.3,text/plain;version=1.0.0;escaping=allow-utf-8;q=0.2,text/plain;version=0.0.4;q=0.1,/;q=0.0 ``` > 引自:[Scrape protocol content negotiation](https://prometheus.io/docs/instrumenting/content_negotiation/) ### 5.4 UTF-8 指标名与 label 名转义方案(Escaping Schemes) **Abstract**:本规范定义 Prometheus 在生成文本 exposition 时,对含 **legacy 字符集之外字符**的 metric / label 名所使用的不同 escaping scheme;这些方案在抓取期间通过 Accept 与 Content-Type header 中的 `escaping` 参数协商。 **Introduction**:Prometheus 支持多种 escaping scheme 处理文本 exposition 中超出 legacy 字符集(`a-zA-Z0-9_:`)的 metric 与 label 名;escaping scheme 在抓取时协商,**影响 metric 生产者应如何格式化指标名**。 #### 5.4.1 四种方案 | 方案 | Header Value | 规则 | 示例 | | --- | --- | --- | --- | | No Escaping | `escaping=allow-utf-8` | 名称 **MUST** 是合法 UTF-8 字符串;在双引号内出现时,`\`、`\n`、`"` **MUST** 用反斜杠转义;不加引号出现时,`\` 与 `\n` **MUST** 用反斜杠转义;**MUST** 仅在 producer 与 consumer 都支持 UTF-8 名称时使用 | — | | Underscore Escaping | `escaping=underscores` | 任何不在 legacy 字符集(`a-zA-Z0-9_:`)内的字符 **MUST** 替换为下划线;首字符 **MUST** 为字母、下划线或冒号;后续字符 **MUST** 为字母、数字、下划线或冒号 | `metric.name/with/slashes` → `metric_name_with_slashes` | | Dots Escaping | `escaping=dots` | 点(`.`)**MUST** 替换为 `_dot_`;已有下划线 **MUST** 替换为双下划线(`__`);其他非 legacy 字符 **MUST** 替换为单个下划线;首字符 **MUST** 为字母、下划线或冒号;后续字符 **MUST** 为字母、数字、下划线或冒号 | `metric.name.with.dots` → `metric_dot_name_dot_with_dot_dots` | | Value Encoding Escaping | `escaping=values` | 名称 **MUST** 以 `U__` 为前缀;每个不属于 legacy 字符集(`a-zA-Z0-9_:`)的字符 **MUST** 替换为其 Unicode code point 的十六进制形式,并用下划线包围;单个下划线 **MUST** 替换为双下划线 | `metric.name` → `U__metric_2E_name`(`2E` 即 `.` 的十六进制 Unicode code point) | #### 5.4.2 默认行为与安全考量 - **Default Behavior**:若 Accept header 未指定 escaping scheme,**SHOULD** 使用 `underscores`。 - **Security Considerations**: 1. target 在应用 escaping 前 **MUST** 校验输入名称; 2. escaping scheme **MUST** 校验以防注入攻击; 3. `allow-utf-8` 方案 **MUST** 仅在 producer 与 consumer 都支持 UTF-8 名称时使用。 > 引自:[UTF-8 metric and label name escaping schemes](https://prometheus.io/docs/instrumenting/escaping_schemes/) > 💡 补充:实践中最容易踩坑的是"三方字符集不一致"。Prometheus 3.x 起自身支持 UTF-8 指标名,但**下游工具链(Grafana 面板变量、部分 exporter、旧版接收端)可能仍不支持**。因此当被监控系统(尤其 OpenTelemetry bridge 场景)产生点分命名(`process.cpu.seconds`)时,建议在协商中显式指定 `underscores` 而非 `allow-utf-8`,牺牲一点可读性换取全链路兼容;只有在确认消费端全部支持 UTF-8 后,才切到 `allow-utf-8`。 ### 5.5 Pushgateway #### 5.5.1 是什么 / 为什么 Pushgateway 是一个**中间服务**,允许把来自**无法被 scrape** 的 job 的指标推送给它,再由 Prometheus 以常规 scrape 方式抓取 Pushgateway。 它的价值在于:结合 Prometheus 简单的 text-based exposition format,**即使是没有客户端库的 shell 脚本也能轻松实现 instrumentation**(脚本生成文本指标 → POST 给 Pushgateway → 被 Prometheus 抓取)。 #### 5.5.2 适用场景 **通常唯一有效的用例**:捕获 **service-level batch job** 的结果。 - "service-level" 批处理指**语义上与特定机器或 job 实例无关**的 job(例如为整个服务删除一批用户)。 - 此类 job 的指标**不应包含 machine 或 instance 标签**,以将具体机器/实例的生命周期与推送指标解耦,从而减轻管理陈旧指标的负担。 #### 5.5.3 不适用场景与陷阱 官方明确:**仅推荐在有限的特定场景使用**。盲目用它替代 Prometheus 常规 pull 模型存在若干陷阱: 1. 通过单个 Pushgateway 监控多个实例时,Pushgateway 同时成为**单点故障**和潜在**瓶颈**; 2. 会**失去** Prometheus 基于 `up` 指标(每次 scrape 生成)的**自动实例健康监控**; 3. Pushgateway **永不忘记**推给它的 series,会**永久暴露**给 Prometheus,除非通过 Pushgateway API 手动删除这些 series。 第 3 点在多实例通过 `instance` 标签(或类似方式)区分指标时尤其重要:实例被改名或移除后,其指标仍留在 Pushgateway。原因是 **Pushgateway 作为 metrics cache 的生命周期与推送指标的进程生命周期本质上是分离的**。对比 pull 模型:实例消失(无论是否故意)时其指标会自动消失;用 Pushgateway 则不是,必须**手动删除陈旧指标**,或自行自动化生命周期同步。 #### 5.5.4 替代策略 - 若入站防火墙或 NAT 阻止 pull,考虑把 Prometheus server 也移到网络屏障之后;一般建议 Prometheus server 与被监控实例跑在**同一网络**。否则可考虑 **PushProx**,它让 Prometheus 能穿越防火墙/NAT。 - 对**与机器相关**的 batch job(如自动安全更新 cronjob、配置管理客户端运行),改用 **Node Exporter 的 textfile collector** 暴露指标,而非 Pushgateway。 #### 5.5.5 API 与各语言使用入口 官方 `pushing` 页面本身只是**入口导航**,各语言的具体用法指向对应库的文档: | 使用方式 | 文档入口 | | --- | --- | | Pushgateway 本身及从 Unix shell 使用 | 见项目 README(https://github.com/prometheus/pushgateway#readme) | | 从 Java 使用 | Pushgateway documentation(https://prometheus.github.io/client_java/exporters/pushgateway/) | | 从 Go 使用 | `Push` 与 `Add` 方法(https://pkg.go.dev/github.com/prometheus/client_golang/prometheus/push) | | 从 Python 使用 | Exporting to a Pushgateway(https://prometheus.github.io/client_python/exporting/pushgateway/) | | 从 Ruby 使用 | Pushgateway documentation(https://github.com/prometheus/client_ruby#pushgateway) | | 其他第三方客户端库 | 查阅各自文档(链接指向 /docs/instrumenting/clientlibs/) | > 注:具体的 push 语义与限制在 best practices 的 [short-lived service-level batch jobs](/docs/practices/pushing/) 中,`pushing` 页面未展开。 **删除 series**:如 5.5.3 所述,Pushgateway 会永久保留推入的 series,必须通过 **Pushgateway API 手动删除**。Pushgateway 还提供**管理 API**(`--web.enable-admin-api`,默认关闭,启用后位于 `/api/*/admin/`),其中包含清空所有 metric groups 的能力(见安全模型相关章节)。 > 💡 补充:Pushgateway 的常规推送路径形如 `POST /metrics/job/{//...}`(`POST` 为追加、`PUT` 为替换同组指标、`DELETE` 删除该组),根路径 `/` 提供带 landing page 的 Web UI,`/metrics` 暴露其自身指标供 Prometheus 抓取。这些端点细节以 Pushgateway README 为准,此处仅作定位提示,实际以所部署版本的项目文档核对。 #### 5.5.6 与 Native Histogram 的关系 Pushgateway **逐步支持** native histogram,**v1.9 起完整支持**;其内部基于 classic protobuf 模型,改动主要是 UI。混合 histogram(classic + native bucket)可以推送并原样暴露;但**查询 API 只能返回一种 bucket,优先 native**。 #### 5.5.7 注意事项汇总 | 维度 | 注意事项 | | --- | --- | | 实例标识 | service-level batch job 的指标不应带 machine/instance 标签;带则需自行做生命周期同步 | | 陈旧数据 | Pushgateway 不会自动过期;必须通过 API 删除,或用 `instance` 隔离 | | 健康监控 | 失去基于 `up` 的自动实例健康监控能力 | | 单点风险 | 单个 Pushgateway 监控多实例时既是 SPOF 又是瓶颈 | | 安全 | 任何可访问其 HTTP endpoint 者能创建/修改/删除其中指标;因其通常以 `honor_labels` 被 scrape,意味着能在 Prometheus 中创建任意时序 | | 抓取模型 | 仍是 pull:Prometheus 主动抓 Pushgateway,被推入的数据只是"缓存在中间站" | > 引自:[Pushing metrics](https://prometheus.io/docs/instrumenting/pushing/) > > 引自:[When to use the Pushgateway](https://prometheus.io/docs/practices/pushing/) ## 第 6 章 导出器与集成生态 客户端库解决"自己的代码怎么埋点",导出器(exporter)解决"别人的系统怎么变成指标"。本章先给出**完整的导出器与集成清单**,再给出**编写导出器与客户端库的规范**,最后落到三个部署实践:node_exporter、cAdvisor、multi-target exporter(blackbox 型)。 ### 6.1 导出器清单(Exporters) **定位**:有大量库与服务器用于把第三方系统的既有指标导出为 Prometheus 格式的指标。适用于**无法直接对目标系统做埋点**的场景(例如 HAProxy、Linux 系统统计)。 **官方 vs 第三方**: - 标记为 *official* 的导出器由官方 [Prometheus GitHub organization](https://github.com/prometheus) 维护;其余为外部贡献与维护。 - 官方**鼓励**创建更多导出器,但**无法为全部导出器审核其是否符合最佳实践**(见 6.4 节),因此这些导出器通常托管在 Prometheus GitHub 组织之外。 - [Default port allocations wiki](https://github.com/prometheus/prometheus/wiki/Default-port-allocations) 已成为另一个导出器目录,可能包含此处未列出的导出器(原因:功能重叠或仍在开发中)。 - [JMX exporter](https://github.com/prometheus/jmx_exporter) 可从各类 JVM 应用导出,例如 [Kafka](http://kafka.apache.org/)、[Cassandra](http://cassandra.apache.org/)。 #### 6.1.1 Databases(数据库) | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | Aerospike | aerospike/aerospike-prometheus-exporter | 社区 | | AWS RDS | qonto/prometheus-rds-exporter | 社区 | | BetterDB Monitor (Valkey/Redis/Dragonfly) | BetterDB-inc/monitor | 社区 | | ClickHouse | f1yegor/clickhouse_exporter | 社区 | | Consul | prometheus/consul_exporter | **official** | | Couchbase | couchbase/couchbase-exporter | 社区 | | CouchDB | gesellix/couchdb-exporter | 社区 | | Druid | opstree/druid-exporter | 社区 | | Elasticsearch | prometheus-community/elasticsearch_exporter | 社区 | | EventStore | marcinbudny/eventstore_exporter | 社区 | | IoTDB | fagnercarvalho/prometheus-iotdb-exporter | 社区 | | KDB+ | KxSystems/prometheus-kdb-exporter | 社区 | | Memcached | prometheus/memcached_exporter | **official** | | MongoDB | percona/mongodb_exporter | 社区 | | MongoDB query | raffis/mongodb-query-exporter | 社区 | | MongoDB Node.js Driver | christiangalsterer/mongodb-driver-prometheus-exporter | 社区 | | MSSQL server | awaragi/prometheus-mssql-exporter | 社区 | | MySQL router | rluisr/mysqlrouter_exporter | 社区 | | MySQL server | prometheus/mysqld_exporter | **official** | | OpenTSDB | cloudflare/opentsdb_exporter | 社区 | | Oracle DB | iamseth/oracledb_exporter | 社区 | | PgBouncer | prometheus-community/pgbouncer_exporter | 社区 | | PostgreSQL | prometheus-community/postgres_exporter | 社区 | | Presto | yahoojapan/presto_exporter | 社区 | | ProxySQL | percona/proxysql_exporter | 社区 | | RavenDB | marcinbudny/ravendb_exporter | 社区 | | Redis | oliver006/redis_exporter | 社区 | | RethinkDB | oliver006/rethinkdb_exporter | 社区 | | SQL | burningalchemist/sql_exporter | 社区 | | Tarantool metric library | tarantool/metrics | 社区 | | Twemproxy | stuartnelson3/twemproxy_exporter | 社区 | #### 6.1.2 Hardware related(硬件相关) | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | apcupsd | mdlayher/apcupsd_exporter | 社区 | | BIG-IP | ExpressenAB/bigip_exporter | 社区 | | Bosch Sensortec BMP/BME | David-Igou/bsbmp-exporter | 社区 | | Collins | soundcloud/collins_exporter | 社区 | | Dell Hardware OMSA | galexrt/dellhw_exporter | 社区 | | Disk usage | dundee/disk_usage_exporter | 社区 | | Fortigate | bluecmd/fortigate_exporter | 社区 | | IBM Z HMC | zhmcclient/zhmc-prometheus-exporter | 社区 | | IoT Edison | roman-vynar/edison_exporter | 社区 | | InfiniBand | treydock/infiniband_exporter | 社区 | | IPMI | soundcloud/ipmi_exporter | 社区 | | knxd | RichiH/knxd_exporter | 社区 | | Meinberg LANTIME OS | raphaelthomas/meinberg_ltos_exporter | 社区 | | Modbus | RichiH/modbus_exporter | 社区 | | Netgear Cable Modem | ickymettle/netgear_cm_exporter | 社区 | | Netgear Router | DRuggeri/netgear_exporter | 社区 | | Network UPS Tools (NUT) | DRuggeri/nut_exporter | 社区 | | Node/system metrics | prometheus/node_exporter | **official** | | NVIDIA DCGM (GPU) | NVIDIA/dcgm-exporter | 社区 | | ProSAFE | dalance/prosafe_exporter | 社区 | | Redfish | comcast/fishymetrics | 社区 | | SmartRAID | gitlab.com/calestyo/prometheus-smartraid-exporter | 社区 | | Waveplus Radon Sensor | jeremybz/waveplus_exporter | 社区 | | Weathergoose Climate Monitor | branttaylor/watchdog-prometheus-exporter | 社区 | | Windows | prometheus-community/windows_exporter | 社区 | | Intel® Optane™ Persistent Memory Controller | intel/ipmctl-exporter | 社区 | #### 6.1.3 Issue trackers and continuous integration(工单与 CI) | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | Bamboo | AndreyVMarkelov/bamboo-prometheus-exporter | 社区 | | Bitbucket | AndreyVMarkelov/prom-bitbucket-exporter | 社区 | | Confluence | AndreyVMarkelov/prom-confluence-exporter | 社区 | | Jenkins | lovoo/jenkins_exporter | 社区 | | JIRA | AndreyVMarkelov/jira-prometheus-exporter | 社区 | #### 6.1.4 Messaging systems(消息系统) | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | Beanstalkd | messagebird/beanstalkd_exporter | 社区 | | EMQ | nuvo/emq_exporter | 社区 | | Gearman | bakins/gearman-exporter | 社区 | | IBM MQ | ibm-messaging/mq-metric-samples (cmd/mq_prometheus) | 社区 | | Kafka | danielqsj/kafka_exporter | 社区 | | KafkaJS | christiangalsterer/kafkajs-prometheus-exporter | 社区 | | NATS | nats-io/prometheus-nats-exporter | 社区 | | NSQ | lovoo/nsq_exporter | 社区 | | Mirth Connect | vynca/mirth_exporter | 社区 | | MQTT blackbox | inovex/mqtt_blackbox_exporter | 社区 | | MQTT2Prometheus | hikhvar/mqtt2prometheus | 社区 | | RabbitMQ | kbudde/rabbitmq_exporter | 社区 | | RabbitMQ Management Plugin | deadtrickster/prometheus_rabbitmq_exporter | 社区 | | RocketMQ | apache/rocketmq-exporter | 社区 | | Solace | solacecommunity/solace-prometheus-exporter | 社区 | #### 6.1.5 Storage(存储) | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | Ceph | digitalocean/ceph_exporter | 社区 | | Ceph RADOSGW | blemmenes/radosgw_usage_exporter | 社区 | | Gluster | ofesseler/gluster_exporter | 社区 | | GPFS | treydock/gpfs_exporter | 社区 | | Hadoop HDFS FSImage | marcelmay/hadoop-hdfs-fsimage-exporter | 社区 | | HPE CSI info metrics provider | scod.hpedev.io/csi_driver/metrics.html | 社区 | | HPE storage array | hpe-storage.github.io/array-exporter/ | 社区 | | Lustre | HewlettPackard/lustre_exporter | 社区 | | NetApp E-Series | treydock/eseries_exporter | 社区 | | Pure Storage | PureStorage-OpenConnect/pure-exporter | 社区 | | ScaleIO | syepes/sio2prom | 社区 | | Tivoli Storage Manager / IBM Spectrum Protect | treydock/tsm_exporter | 社区 | | IBM Storage Scale metrics | IBM/ibm-spectrum-scale-bridge-for-grafana | 社区 | #### 6.1.6 HTTP | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | Apache | Lusitaniae/apache_exporter | 社区 | | HAProxy | prometheus/haproxy_exporter | **official** | | Nginx metric library | knyar/nginx-lua-prometheus | 社区 | | Nginx VTS | sysulq/nginx-vts-exporter | 社区 | | Passenger | stuartnelson3/passenger_exporter | 社区 | | Squid | boynux/squid-exporter | 社区 | | Tinyproxy | gmm42/tinyproxy_exporter | 社区 | | Varnish | jonnenauha/prometheus_varnish_exporter | 社区 | | WebDriver | mattbostock/webdriver_exporter | 社区 | #### 6.1.7 IoT | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | FairCom Edge | FairCom-Official/faircom_exporter | 社区 | #### 6.1.8 APIs | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | AWS ECS | slok/ecs-exporter | 社区 | | AWS Health | Jimdo/aws-health-exporter | 社区 | | AWS SQS | jmal98/sqs_exporter | 社区 | | AWS SQS Prometheus | jmriebold/sqs-prometheus-exporter | 社区 | | Azure Health | matzefriedrich/az-health-exporter | 社区 | | BigBlueButton | greenstatic/bigbluebutton-exporter | 社区 | | Cloudflare | gitlab.com/gitlab-org/cloudflare_exporter | 社区 | | Cryptowat | nbarrientos/cryptowat_exporter | 社区 | | DigitalOcean | metalmatze/digitalocean_exporter | 社区 | | Docker Cloud | infinityworks/docker-cloud-exporter | 社区 | | Docker Hub | infinityworks/docker-hub-exporter | 社区 | | Fastly | peterbourgon/fastly-exporter | 社区 | | Garmin Connect | barnes-c/garmin_exporter | 社区 | | GitHub | githubexporter/github-exporter | 社区 | | Gmail | jamesread/prometheus-gmail-exporter | 社区 | | GraphQL | ricardbejarano/graphql_exporter | 社区 | | InstaClustr | fcgravalos/instaclustr_exporter | 社区 | | IO River | ioriver/ioriver-exporter | 社区 | | Mozilla Observatory | Jimdo/observatory-exporter | 社区 | | OpenWeatherMap | RichiH/openweathermap_exporter | 社区 | | Pagespeed | foomo/pagespeed_exporter | 社区 | | Rancher | infinityworks/prometheus-rancher-exporter | 社区 | | Speedtest | nlamirault/speedtest_exporter | 社区 | | Tankerkönig API | lukasmalkmus/tankerkoenig_exporter | 社区 | #### 6.1.9 Logging(日志) | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | Fluentd | V3ckt0r/fluentd_exporter | 社区 | | Google mtail log data extractor | google/mtail | 社区 | | Grok | fstab/grok_exporter | 社区 | #### 6.1.10 FinOps | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | AWS Cost | electrolux-oss/aws-cost-exporter | 社区 | | Azure Cost | electrolux-oss/azure-cost-exporter | 社区 | | Kubernetes Cost | electrolux-oss/kubernetes-cost-exporter | 社区 | #### 6.1.11 Other monitoring systems(其他监控系统) | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | Akamai Cloudmonitor | ExpressenAB/cloudmonitor_exporter | 社区 | | Alibaba Cloudmonitor | aylei/aliyun-exporter | 社区 | | AWS CloudWatch | prometheus/cloudwatch_exporter | **official** | | Azure Monitor | RobustPerception/azure_metrics_exporter | 社区 | | CCF HuaTuo | ccfos/huatuo | 社区 | | Cloud Foundry Firehose | cloudfoundry-community/firehose_exporter | 社区 | | Collectd | prometheus/collectd_exporter | **official** | | Google Stackdriver | frodenas/stackdriver_exporter | 社区 | | Graphite | prometheus/graphite_exporter | **official** | | Heka dashboard | docker-infra/heka_exporter | 社区 | | Heka | imgix/heka_exporter | 社区 | | Huawei Cloudeye | huaweicloud/cloudeye-exporter | 社区 | | InfluxDB | prometheus/influxdb_exporter | **official** | | ITM | rafal-szypulka/itm_exporter | 社区 | | Java GC | loyispa/jgc_exporter | 社区 | | JavaMelody | fschlag/javamelody-prometheus-exporter | 社区 | | JMX | prometheus/jmx_exporter | **official** | | Munin | pvdh/munin_exporter | 社区 | | Nagios / Naemon | Griesbacher/Iapetos | 社区 | | Neptune Apex | dl-romero/neptune_exporter | 社区 | | New Relic | mrf/newrelic_exporter | 社区 | | NRPE | robustperception/nrpe_exporter | 社区 | | Osquery | zwopir/osquery_exporter | 社区 | | OTC CloudEye | tiagoReichert/otc-cloudeye-prometheus-exporter | 社区 | | Pingdom | giantswarm/prometheus-pingdom-exporter | 社区 | | Promitor (Azure Monitor) | promitor.io | 社区 | | scollector | tgulacsi/prometheus_scollector | 社区 | | Sensu | reachlin/sensu_exporter | 社区 | | site24x7 | svenstaro/site24x7_exporter | 社区 | | SNMP | prometheus/snmp_exporter | **official** | | StatsD | prometheus/statsd_exporter | **official** | | TencentCloud monitor | tencentyun/tencentcloud-exporter | 社区 | | ThousandEyes | sapcc/1000eyes_exporter | 社区 | | StatusPage | sergeyshevch/statuspage-exporter | 社区 | #### 6.1.12 Miscellaneous(杂项) | 名称 | 项目 / 仓库 | 维护状态 | | --- | --- | --- | | ACT Fibernet | git.captnemo.in/nemo/prometheus-act-exporter | 社区 | | BIND | prometheus-community/bind_exporter | 社区 | | BIND query | DRuggeri/bind_query_exporter | 社区 | | Bitcoind | LePetitBloc/bitcoind-exporter | 社区 | | Blackbox | prometheus/blackbox_exporter | **official** | | Bungeecord | weihao/bungeecord-prometheus-exporter | 社区 | | BOSH | cloudfoundry-community/bosh_exporter | 社区 | | cAdvisor | google/cadvisor | 社区 | | Cachet | ContaAzul/cachet_exporter | 社区 | | ccache | virtualtam/ccache_exporter | 社区 | | c-lightning | lightningd/plugins (prometheus) | 社区 | | DHCPD leases | DRuggeri/dhcpd_leases_exporter | 社区 | | Dovecot | kumina/dovecot_exporter | 社区 | | Dnsmasq | google/dnsmasq_exporter | 社区 | | eBPF | cloudflare/ebpf_exporter | 社区 | | eBPF network traffic | kasd/texporter | 社区 | | Ethereum Client | 31z4/ethereum-prometheus-exporter | 社区 | | FFmpeg | domcyrus/ffmpeg_exporter | 社区 | | File statistics | michael-doubez/filestat_exporter | 社区 | | JFrog Artifactory | peimanja/artifactory_exporter | 社区 | | Hostapd | Fundacio-i2CAT/hostapd_prometheus_exporter | 社区 | | IBM Security Verify Access / Security Access Manager | gitlab.com/zeblawson/isva-prometheus-exporter | 社区 | | IPsec | torilabs/ipsec-prometheus-exporter | 社区 | | ipset | hatamiarash7/ipset-exporter | 社区 | | IRCd | dgl/ircd_exporter | 社区 | | Linux HA ClusterLabs | ClusterLabs/ha_cluster_exporter | 社区 | | JMeter plugin | johrstrom/jmeter-prometheus-plugin | 社区 | | JSON | prometheus-community/json_exporter | 社区 | | Kannel | apostvav/kannel_exporter | 社区 | | Kemp LoadBalancer | giantswarm/prometheus-kemp-exporter | 社区 | | Kibana | pjhampton/kibana-prometheus-exporter | 社区 | | kube-state-metrics | kubernetes/kube-state-metrics | 社区 | | Locust | ContainerSolutions/locust_exporter | 社区 | | Meteor JS web framework | atmospherejs.com/sevki/prometheus-exporter | 社区 | | Minecraft exporter module | Baughn/PrometheusIntegration | 社区 | | Minecraft | dirien/minecraft-prometheus-exporter | 社区 | | NetBird | matanbaruch/netbird-api-exporter | 社区 | | Nomad | gitlab.com/yakshaving.art/nomad-exporter | 社区 | | nftables | Intrinsec/nftables_exporter | 社区 | | Open OnDemand | OSC/ondemand_exporter | 社区 | | OpenClaw | SammyLin/openclaw-exporter | 社区 | | OpenStack | openstack-exporter/openstack-exporter | 社区 | | OpenStack blackbox | infraly/openstack_client_exporter | 社区 | | OpenVPN | Fadi-hamwi/OpenVPN-Metrics-Exporter | 社区 | | oVirt | czerwonk/ovirt_exporter | 社区 | | Pact Broker | ContainerSolutions/pactbroker_exporter | 社区 | | PHP-FPM | bakins/php-fpm-exporter | 社区 | | Pi-Hole | eko/pihole-exporter | 社区 | | Podman | containers/prometheus-podman-exporter | 社区 | | Postfix | sarab97/postfix_exporter | 社区 | | Prefect2 | pathfinder177/prefect2-prometheus-exporter | 社区 | | Process | ncabatoff/process-exporter | 社区 | | rTorrent | mdlayher/rtorrent_exporter | 社区 | | Rundeck | phsmith/rundeck_exporter | 社区 | | SABnzbd | msroest/sabnzbd_exporter | 社区 | | SAML | DoodleScheduling/saml-exporter | 社区 | | Scraparr | thecfu/scraparr | 社区 | | Script | adhocteam/script_exporter | 社区 | | Shield | cloudfoundry-community/shield_exporter | 社区 | | Slurm | sckyzo/slurm_exporter | 社区 | | Smokeping prober | SuperQ/smokeping_prober | 社区 | | SMTP/Maildir MDA blackbox prober | cherti/mailexporter | 社区 | | SoftEther | dalance/softether_exporter | 社区 | | SSH | treydock/ssh_exporter | 社区 | | Tailscale | adinhodovic/tailscale-exporter | 社区 | | Teamspeak3 | hikhvar/ts3exporter | 社区 | | Transmission | metalmatze/transmission-exporter | 社区 | | Unbound | kumina/unbound_exporter | 社区 | | WireGuard | MindFlavor/prometheus_wireguard_exporter | 社区 | | Xen | lovoo/xenstats_exporter | 社区 | | ZLMediaKit | guohuachan/ZLMediaKit_exporter | 社区 | #### 6.1.13 实现新导出器的建议 实现新 Prometheus 导出器时,请遵循《guidelines on writing exporters》(见 6.4 节);也建议咨询 development mailing list(https://groups.google.com/forum/#!forum/prometheus-developers)。官方乐于就"如何让导出器尽可能有用、一致"提供建议。 > 引自:[Exporters and integrations](https://prometheus.io/docs/instrumenting/exporters/) ### 6.2 Software exposing Prometheus metrics(自身直接暴露指标) 以下软件**自身直接暴露指标、无需独立导出器**。标记 *direct* 的软件同时也用 Prometheus 客户端库做了直接埋点。 | 软件 | 备注 | | --- | --- | | Ansible Automation Platform Automation Controller (AWX) | | | Apache APISIX | | | App Connect Enterprise | | | Ballerina | | | BFE | | | Caddy | **direct** | | Ceph | | | ClickHouse | | | CockroachDB | | | Collectd | | | Concourse | | | CRG Roller Derby Scoreboard | **direct** | | Diffusion | | | Docker Daemon | | | Doorman | **direct** | | Dovecot | | | Envoy | | | Etcd | **direct** | | Flink | | | FreeBSD Kernel | | | GitLab | | | Grafana | | | JavaMelody | | | Kong | | | Kubernetes | **direct** | | LavinMQ | | | Linkerd | | | mgmt | | | MidoNet | | | midonet-kubernetes | **direct** | | MinIO | | | PATROL with Monitoring Studio X | | | Netdata | | | OpenZiti | | | Pomerium | | | Pretix | | | Quobyte | **direct** | | RabbitMQ | | | RobustIRC | | | RTPEngine | | | ScyllaDB | | | Skipper | | | SkyDNS | **direct** | | Telegraf | | | Traefik | | | Triton Inference Server | | | Vector | | | VerneMQ | | | Flux | | | Xandikos | **direct** | | Zipkin | | **Other third-party utilities(其他第三方工具/库,非客户端库本身)**:这些库/工具用于辅助在特定语言中埋点,**本身不是 Prometheus 客户端库**,而是底层使用某个标准 Prometheus 客户端库;与所有独立维护的软件一样,官方无法为其审核最佳实践。 | 语言 | 工具 / 库 | | --- | --- | | Clojure | iapetos(https://github.com/clj-commons/iapetos) | | Go | go-metrics instrumentation library(https://github.com/armon/go-metrics) | | Go | gokit(https://github.com/peterbourgon/gokit) | | Go | prombolt(https://github.com/mdlayher/prombolt) | | Java/JVM | EclipseLink metrics collector(https://github.com/VitaNuova/eclipselinkexporter) | | Java/JVM | Hystrix metrics publisher(https://github.com/ahus1/prometheus-hystrix) | | Java/JVM | Jersey metrics collector(https://github.com/VitaNuova/jerseyexporter) | | Java/JVM | Micrometer Prometheus Registry(https://micrometer.io/docs/registry/prometheus) | | Python-Django | django-prometheus(https://github.com/korfuri/django-prometheus) | | Node.js | swagger-stats(https://github.com/slanatech/swagger-stats) | ### 6.3 如何编写导出器(Writing exporters) **适用前提**:如果你是在为自己的代码做埋点,应遵循客户端库埋点的通用规则;但**从其他监控/埋点系统取指标时,事情往往不那么黑白分明**。本节给出编写导出器或自定义 collector 时应考虑的事项;其中理论对做直接埋点的人也很有价值。 #### 6.3.1 Maintainability and purity(可维护性与纯粹性) 编写导出器的**主要决策**是:**你愿意投入多少工作以从系统中得到"完美"的指标**。 | 系统形态 | 建议 | 示例 | | --- | --- | --- | | 指标数量少且极少变化 | 追求完美是容易的选择 | HAProxy exporter | | 有数百个指标且随新版本频繁变化 | 追求完美等于签下大量持续性工作 | MySQL exporter 处于光谱另一端 | | 混合 | 复杂度按 module 而异 | node exporter:`mdadm` collector 手工解析一个文件并暴露专为该 collector 创建的指标,不妨把指标做对;`meminfo` collector 的结果随内核版本变化,因此只做"刚好够"的转换以产出合法指标 | #### 6.3.2 Configuration(配置) - 处理应用类目标时,应追求**用户除了告诉它应用在哪里之外无需任何自定义配置**。 - 若某些指标过于细粒度、在大型环境中开销太大,可能需要提供**过滤掉某些指标**的能力,例如 [HAProxy exporter](https://github.com/prometheus/haproxy_exporter) 允许过滤 per-server 统计;同样,可能有开销很大的指标默认被禁用。 - 处理其他监控系统、框架与协议时,往往需要提供额外配置或定制才能产出适合 Prometheus 的指标: - **最好的情况**:监控系统的数据模型与 Prometheus 足够相似,可以自动决定如何转换——[Cloudwatch](https://github.com/prometheus/cloudwatch_exporter)、[SNMP](https://github.com/prometheus/snmp_exporter)、[collectd](https://github.com/prometheus/collectd_exporter) 属于此类。这里最多只需要让用户选择想抽取哪些指标。 - **其他情况**:指标完全非标准,取决于系统与底层应用的用法,此时须由用户告知如何转换。[JMX exporter](https://github.com/prometheus/jmx_exporter) 是这方面最典型的(worst offender),[Graphite](https://github.com/prometheus/graphite_exporter) 与 [StatsD](https://github.com/prometheus/statsd_exporter) 导出器也需要配置来提取标签。 - 建议:**确保导出器开箱即用(无需配置)**,并在需要转换时提供一批**示例配置**。 - **YAML 是 Prometheus 的标准配置格式,所有配置默认都应使用 YAML。** #### 6.3.3 Metrics · Naming(指标命名) - 遵循指标命名最佳实践。 - 一般来说,指标名应让**熟悉 Prometheus 但不熟悉该特定系统**的人也能较好地猜出指标含义。`http_requests_total` 就不是很有用——这些请求是在进入时、在某个过滤器中、还是到达用户代码时被计量的?`requests_total` 更糟——是什么类型的请求? - 直接埋点时,一个给定指标应**恰好存在于一个文件**中。相应地,在导出器与 collector 中,一个指标**应恰好适用于一个子系统**并据此命名。 - **指标名绝不应程序化生成(procedurally generated)**,除非是在编写自定义 collector 或导出器时。 - 应用的指标名一般应以**导出器名作为前缀**,例如 `haproxy_up`。 - 指标**必须使用基本单位**(例如秒、字节),把转换为更易读单位的工作留给绘图工具。 - 无论最终使用什么单位,**指标名中的单位必须与实际使用的单位一致**。 - 同理,**暴露比率(ratios),而不是百分比**。更好的做法是为比率两个组成部分各自指定一个 counter。 - 指标名**不应包含其导出时所用的标签**,例如 `by_type`——因为如果该标签被聚合掉,名字就不成立了。 - 唯一例外:当你通过多个指标以不同标签导出同一份数据时,这通常是最合理的区分方式。对直接埋点而言,这只会出现在"带全部标签导出一个指标会导致基数过高"的情况下。 - Prometheus 的指标名与标签名使用 `snake_case`。把 `camelCase` 转成 `snake_case` 是可取的,但自动转换对 `myTCPExample`、`isNaN` 之类不一定产生好结果,所以有时最好保持原样。 - **暴露的指标不应包含冒号**,冒号保留给用户自定义的 recording rules 在做聚合时使用。 - 指标名中**只有 `[a-zA-Z0-9:_]` 是合法的**。 - `_sum`、`_count`、`_bucket`、`_total` 后缀被 Summaries、Histograms、Counters 使用。**除非你产出的就是这几种,否则应避免这些后缀。** - `_total` 是 counter 的约定;如果你使用 COUNTER 类型就应当使用它。 - **`process_` 与 `scrape_` 前缀是保留的**;如果加上自己的前缀后仍符合匹配语义,是可以的。例如 Prometheus 有 `scrape_duration_seconds` 表示刮取耗时,良好实践是**同时提供一个以导出器为中心的指标**,例如 `jmx_scrape_duration_seconds`,表示该导出器自身做事花了多久。对于有 PID 的进程统计,Go 与 Python 都提供了替你处理此事的 collector,好例子是 [HAProxy exporter](https://github.com/prometheus/haproxy_exporter)。 - 当同时有成功请求数与失败请求数时,最好的暴露方式是**一个总请求数指标 + 一个失败请求数指标**,这样易于计算失败率。**不要用一个带 failed/success 标签的指标。** 同理,缓存的 hit/miss 最好是"总量一个指标 + 命中一个指标"。 - 要考虑使用者做代码/网页搜索指标名的可能性。如果名称非常成熟且不太可能在这些名称圈子之外被使用(例如 SNMP 与网络工程师),那么保持原样可能是好主意;这个逻辑并不适用于所有导出器(例如 MySQL exporter 的指标可能被各种人使用,不只是 DBA)。**在 `HELP` 字符串中写入原始名称**可以提供与使用原始名称大体相当的好处。 #### 6.3.4 Metrics · Labels(标签) - **避免把 `type` 用作标签名**,它太泛且常常没有意义。 - 应尽可能避免那些**可能与 target labels 冲突**的名字,例如 `region`、`zone`、`cluster`、`availability_zone`、`az`、`datacenter`、`dc`、`owner`、`customer`、`stage`、`service`、`environment`、`env`。 - 但是,如果这就是应用对某资源的称呼,最好不要为了改名而造成混淆。 - 避免仅仅因为共享前缀就把东西塞进一个指标。**除非你确信某组东西作为单个指标是合理的,否则多个指标更安全。** - 标签 `le` 对 Histogram 有特殊含义,`quantile` 对 Summary 有特殊含义,**一般应避免这些标签**。 - **读/写 与 发送/接收 最好是各自独立的指标,而不是作为标签。** 这通常是因为你一次只关心其中之一,分开使用更容易。 - 经验法则:**一个指标在被求和或求平均时应当是有意义的。** - 导出器中还会出现另一种情况:数据本质上是**表格型(tabular)**的,若不这样做用户就得对指标名做正则才能使用。比如主板上的电压传感器,对它们做数学运算没有意义,但把它们放在一个指标里比每个传感器一个指标更合理。**一个指标内的所有值(几乎)总是应当具有相同单位**——设想风扇转速和电压混在一起,而你没有自动区分它们的办法。 - **不要这样**(错误示范): ```text my_metric{label="a"} 1 my_metric{label="b"} 6 my_metric{label="total"} 7 ``` 或这样: ```text my_metric{label="a"} 1 my_metric{label="b"} 6 my_metric{} 7 ``` 前者会破坏对它做 `sum()` 的人;后者既破坏 sum 又非常难用。某些客户端库(例如 Go)会在自定义 collector 中主动阻止后一种做法,所有客户端库在直接埋点中都应阻止后一种做法。**两种都绝不要做,要依赖 Prometheus 的聚合。** - 如果你的监控这样暴露了一个 total,**就去掉这个 total**。若因某些原因必须保留(例如该 total 包含未被单独计数的东西),**请使用不同的指标名**。 - **埋点标签应尽量少**:每多一个标签,用户在写 PromQL 时就多需要考虑一个。因此应避免那些"移除后不影响时间序列唯一性"的埋点标签。指标周边的附加信息可通过 **info 指标**来添加。 - 但也有这样的情形:几乎该指标的所有使用者都想要该附加信息。此时**添加一个非唯一(non-unique)标签,而不是 info 指标,才是正确的解法**。例如 [mysqld_exporter](https://github.com/prometheus/mysqld_exporter) 的 `mysqld_perf_schema_events_statements_total` 的 `digest` 标签是完整查询模式的哈希,足以保证唯一性;但仅有它用处不大,还需要人类可读的 `digest_text` 标签,而它对长查询只包含查询模式的开头、因此不唯一。于是最终同时保留供人阅读的 `digest_text` 标签和用于唯一性的 `digest` 标签。 #### 6.3.5 Target labels, not static scraped labels - 如果你发现自己想给**所有**指标加同一个标签,**停下来**。 - 这通常出现在两种情形: 1. 某个标签在指标上会很有用,例如软件版本号。此时应改用 "How to have labels for machine roles" 中描述的方法。 2. 该标签实际上是一个 **target label**。这类东西例如 region、cluster 名称等,来自你的基础设施设置而非应用本身。应用不应自行宣称自己在你的标签分类体系中的位置——那是运行 Prometheus 服务器的人来配置的,而且不同的人监控同一个应用可能会给它起不同的名字。 - 相应地,这些标签属于 Prometheus 的刮取配置(通过你所用的服务发现)。把 machine roles 的概念也用在这里是可以的,因为对至少一部分刮取者来说这很可能是有用信息。 #### 6.3.6 Types(类型) - 应尽量把指标类型对应到 Prometheus 类型。通常意味着 counters 与 gauges;summaries 的 `_count` 与 `_sum` 也相对常见,偶尔会见到 quantiles;Histograms 很少见,若遇到,请记住暴露格式中暴露的是**累积值(cumulative)**。 - 常常不容易判断指标的类型,尤其是在自动处理一批指标时。一般来说 **`UNTYPED` 是安全的默认值**。 - **Counter 不能下降**,所以如果你从其他埋点系统(例如 Dropwizard metrics)拿到一个可被递减的 counter 类型,那它**不是 counter,而是 gauge**。此时 `UNTYPED` 可能是最好的类型,因为若它被当作 counter 使用,标为 `GAUGE` 会有误导。 #### 6.3.7 Help strings(帮助字符串) - 在转换指标时,用户能够回溯原始指标是什么、以及当时应用了哪些规则,会很有用。把 **collector 或 exporter 的名称、所应用规则的 ID、以及原始指标的名称与细节**写进 help 字符串,会极大地帮助用户。 - **Prometheus 不喜欢一个指标有不同的 help 字符串。** 如果你从许多指标合成一个指标,请选择其中一个放进 help 字符串。 - 示例:SNMP exporter 使用 OID;JMX exporter 写入一个示例 mBean 名称;[HAProxy exporter](https://github.com/prometheus/haproxy_exporter) 使用手写字符串;[node exporter](https://github.com/prometheus/node_exporter) 也有丰富多样的示例。 #### 6.3.8 Drop less useful statistics(丢弃不太有用的统计量) - 某些埋点系统会暴露 1m/5m/15m 速率、自应用启动以来的平均速率(在 Dropwizard metrics 中称为 `mean`),以及最小值、最大值和标准差。 - **这些都应丢弃**,因为它们不太有用且增加杂乱。Prometheus 可以自己计算速率,而且通常更准确——因为暴露出来的平均值通常是**指数衰减**的。你不知道 min/max 是在哪个时间区间上计算的;标准差在统计上没有用处,而且如果你真需要计算它,随时可以暴露平方和、`_sum` 和 `_count`。 - **分位数(quantiles)有类似问题**,可以选择丢弃它们,或者放进一个 Summary。 #### 6.3.9 Dotted strings(点分字符串) - 许多监控系统没有标签,而是用类似 `my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3` 的形式。 - [Graphite](https://github.com/prometheus/graphite_exporter) 与 [StatsD](https://github.com/prometheus/statsd_exporter) 导出器**共用一套小型配置语言**来转换这些。**其他导出器也应实现相同的方式。** 该转换目前**仅在 Go 中实现**,若能抽取为独立库会更好。 #### 6.3.10 Collectors(采集器) - 实现导出器的 collector 时,**绝不应使用常见的直接埋点方式、然后在每次刮取时更新指标**。 - 而应**每次创建新的指标**。在 Go 中这通过 `Collect()` 方法里的 [MustNewConstMetric](https://godoc.org/github.com/prometheus/client_golang/prometheus#MustNewConstMetric) 完成;Python 参见自定义 collector 文档;Java 则是在 collect 方法中生成 `List`。 - **这样做的原因有两点**: 1. 两次刮取可能同时发生,而直接埋点使用的是**事实上的文件级全局变量**,会产生竞态条件(race conditions); 2. 如果某个标签值消失了,它仍然会被导出。 - **对导出器自身做直接埋点是没问题的**,例如导出器在所有刮取中传输的总字节数或总调用次数。 - 对于 [blackbox exporter](https://github.com/prometheus/blackbox_exporter) 和 [SNMP exporter](https://github.com/prometheus/snmp_exporter) 这类**不绑定单个 target** 的导出器,这些自身指标**应只在一个普通的 `/metrics` 调用中暴露,而不是在对某个特定 target 的刮取中暴露**。 #### 6.3.11 Metrics about the scrape itself - 有时你想导出关于刮取本身的指标,例如耗时多久或处理了多少条记录。 - 这些**应作为 gauges 暴露**(因为它们关乎一个事件——那次刮取),且**指标名以导出器名作前缀**,例如 `jmx_scrape_duration_seconds`。通常**去掉 `_exporter`**;如果该导出器同时也可作为单纯的 collector 使用,那**务必去掉**。 - **其他刮取"元"指标应当避免**,例如刮取次数的 counter、或刮取耗时的 histogram。让导出器跟踪这些指标会与 Prometheus 自动生成的指标重复,并增加每个导出器实例的存储成本。 #### 6.3.12 Machine and process metrics(机器与进程指标) - 许多系统(例如 Elasticsearch)会暴露 CPU、内存、文件系统等机器指标。由于 [node exporter](https://github.com/prometheus/node_exporter) 已在 Prometheus 生态中提供这些,此类指标**应当被丢弃**。 - 在 Java 世界,许多埋点框架会暴露进程级与 JVM 级统计(如 CPU、GC)。Java 客户端与 JMX exporter 已通过 `DefaultExports.java` 以推荐形式包含这些,因此**这些也应被丢弃**。 - 其他语言与框架同理。 #### 6.3.13 Deployment(部署) - **每个导出器应只监控恰好一个应用实例**,最好与它同机紧邻运行。这意味着:每运行一个 HAProxy,就运行一个 `haproxy_exporter` 进程;每台跑 Mesos worker 的机器上都运行 Mesos exporter,如果一台机器同时有 master,则再跑一个。 - 背后的理论是:对直接埋点而言这就是你会做的事,我们只是尽量在其他架构中贴近它。这意味着**所有服务发现都在 Prometheus 中完成,而不是在导出器中完成**。这还有个好处:Prometheus 拥有它所需的目标信息,可让用户用 [blackbox exporter](https://github.com/prometheus/blackbox_exporter) 探测你的服务。 - **有两个例外**: 1. 在你所监控的应用旁边运行完全说不通的情况。**SNMP、blackbox、IPMI 三类导出器**是主要例子。IPMI 与 SNMP 的目标设备往往是黑盒、无法在其上运行代码(不过如果能在其上跑一个 node exporter 会更好);blackbox exporter 则是在监控诸如 DNS 名称之类的东西,那里同样没有东西可运行。此时 Prometheus 仍应做服务发现,并把要刮取的 target 传递过去。 - 注意:**目前只有 Go、Python、Java 客户端库能编写这类导出器。** 2. 你从某个系统的随机实例拉取一些统计、且不关心具体在与哪个实例通信。设想有一组 MySQL 副本,你想对数据跑一些业务查询然后导出。让导出器用你平常的负载均衡方式去和某一个副本通信,是最合理的做法。 - 当监控的系统存在 **master-election** 时这不适用;那种情况下应**逐个实例监控**,并在 Prometheus 中处理 "masterness"。因为并不总是恰好存在一个 master,而且在 Prometheus 脚下改变 target 的含义会引起怪异现象。 #### 6.3.14 Scheduling(调度) - **指标应当只在 Prometheus 刮取它们时才从应用拉取**;导出器**不应基于自己的定时器**进行刮取。也就是说,**所有刮取都应是同步的**。 - 相应地,**不要**给你暴露的指标设置时间戳,让 Prometheus 去处理。如果你认为自己需要时间戳,那你可能需要的是 [Pushgateway](https://prometheus.io/docs/instrumenting/pushing/)。 - 如果某个指标获取特别昂贵(例如耗时超过一分钟),**可以接受缓存它**,但**必须在 `HELP` 字符串中注明**。 - Prometheus 的**默认刮取超时是 10 秒**。如果你的导出器预计会超过这个值,应在用户文档中明确指出。 #### 6.3.15 Pushes(推送型系统) - 有些应用与监控系统只会推送指标,例如 StatsD、Graphite、collectd。 - 两个考量: 1. **何时让指标过期?** Collectd 与对接 Graphite 的东西都是定期导出的,当它们停止时我们就应停止暴露指标。Collectd 自带过期时间,所以用它;Graphite 没有,所以在导出器上做成一个 flag。StatsD 稍有不同,因为它处理的是**事件**而非指标;最好的模型是**在每个应用旁运行一个导出器,并在应用重启时同时重启它们**,以清除状态。 2. 这类系统往往允许用户发送 **delta 或原始 counter**。**应尽可能依赖原始 counter**,因为那才是 Prometheus 的通用模型。 - 对于**服务级指标**(例如服务级批处理作业),应让导出器把数据推入 **Pushgateway** 并在事件结束后退出,而不是自己维护状态。 - 对于**实例级批处理指标**,目前还没有清晰的模式。可选方案:滥用 node exporter 的 textfile collector、依赖内存状态(若不需要跨重启持久化可能最好)、或实现与 textfile collector 类似的功能。 #### 6.3.16 Failed scrapes(刮取失败) - 当所通信的应用不响应或存在其他问题时,目前**有两种模式**: 1. 返回 **5xx 错误**; 2. 提供一个类似 `myexporter_up`(例如 `haproxy_up`)的变量,**取值 0 或 1**,取决于刮取是否成功。 - 后一种在**刮取失败时仍能获取一些有用指标**的情况下更好,例如 HAProxy exporter 仍能提供进程统计。 - 前一种对用户来说略微更容易处理,因为 `up` 按平常方式工作,**但你无法区分是导出器挂了还是应用挂了**。 #### 6.3.17 Landing page & Port numbers - **着陆页**:用户访问 `http://yourexporter/` 时看到一个简单的 HTML 页面(含导出器名称和指向 `/metrics` 页面的链接)会更好。 - **端口号**:用户可能在同一台机器上有许多导出器与 Prometheus 组件,为便于处理,每个都应有一个唯一端口号。 - [https://github.com/prometheus/prometheus/wiki/Default-port-allocations](https://github.com/prometheus/prometheus/wiki/Default-port-allocations) 是我们记录它们的地方,**该页面公开可编辑**。 - 开发导出器时**可自由取用下一个空闲端口号**,最好在公开宣布之前就取好。如果还没准备好发布,写上你的用户名和 WIP 也可以。 - 这是一个为了让用户生活更方便的注册表,**不是对开发特定导出器的承诺**。对于内部应用的导出器,**建议使用默认端口分配范围之外的端口**。 #### 6.3.18 Announcing(对外发布) 准备好向世界宣布你的导出器后,**给邮件列表发邮件**,并**提交一个 PR** 把它加入可用导出器列表(具体是编辑 https://github.com/prometheus/docs/blob/main/docs/instrumenting/exporters.md)。 > 引自:[Writing exporters](https://prometheus.io/docs/instrumenting/writing_exporters/) ### 6.4 客户端库编写规范(Writing client libraries) **文档目的**:规定 Prometheus 客户端库**应提供哪些功能与 API**,目标是各库之间保持一致,让常见用例变简单,并避免提供可能把用户引向错误道路的功能。撰写时已有 **10 种语言**受支持,因此对"如何写客户端库"已有较成熟认识;这些准则用于帮助新客户端库作者写出好的库。 #### 6.4.1 Conventions(约定) - MUST / MUST NOT / SHOULD / SHOULD NOT / MAY 的含义依 [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)。 - 额外定义 **ENCOURAGED**:表示某特性是库应当具备的(desirable),但即使没有也可以——即 "nice to have"。 - 需牢记的原则: - 充分利用各语言自身的特性; - 常见用例应当简单易用; - 正确的做法应当就是容易的做法; - 更复杂的用例应当是可实现的(possible)。 - 常见用例(按重要性排序): 1. 不带标签的 Counter,在库/应用中大量散布使用; 2. 在 Summaries/Histograms 中对函数/代码块计时; 3. Gauge 用于跟踪事物的当前状态(及其上限); 4. 批处理作业(batch jobs)的监控。 #### 6.4.2 Overall structure(整体结构) - 客户端**内部必须(MUST)是回调式(callback based)**。客户端**应当(SHOULD)**大体遵循本节描述的结构。 - 关键类是 **Collector**。它有一个方法(通常叫 `collect`),返回零个或多个指标及其样本(samples)。Collector 注册到 **CollectorRegistry**。 - 数据暴露方式:把 CollectorRegistry 传给某个类/方法/函数的 "bridge",由 bridge 以 Prometheus 支持的格式返回指标。**每次 CollectorRegistry 被刮取时,都必须回调每个 Collector 的 collect 方法。** - 大多数用户直接交互的接口是 **Counter、Gauge、Summary、Histogram** 这四个 Collector。它们代表单个指标,应覆盖用户为自己的代码埋点时的绝大多数用例。 - 更高级的用例(例如从其他监控/埋点系统代理指标)需要编写**自定义 Collector**。也可能需要写一个 "bridge",接收 CollectorRegistry 并产出其他监控/埋点系统能理解的格式,使用户只需关心一套埋点系统。 - CollectorRegistry **应当(SHOULD)**提供 `register()` / `unregister()` 函数;一个 Collector **应当**允许被注册到多个 CollectorRegistry。 - **客户端库必须是线程安全的(MUST be thread safe)。** - 对于 C 这类非面向对象语言,客户端库应在**切实可行的范围内遵循此结构的精神**。 #### 6.4.3 Naming(命名) - 客户端库**应当(SHOULD)**沿用本文档中提到的函数/方法/类名,同时遵循所用语言的命名规范。示例:`set_to_current_time()` 适合 Python 方法名;Go 里更适合 `SetToCurrentTime()`;Java 约定是 `setToCurrentTime()`。 - 若因技术原因导致名称不同(例如不允许函数重载),**文档/帮助字符串应当(SHOULD)**把用户引导到其他名称。 - 库**绝不能(MUST NOT)**提供与本文档给出的函数/方法/类同名或近似名、但语义不同的成员。 #### 6.4.4 Metrics(指标) - Counter、Gauge、Summary、Histogram 是用户的主要接口。 - **Counter 与 Gauge 必须(MUST)包含在客户端库中。Summary 与 Histogram 至少必须提供其中一个(MUST)。** - 这些指标应主要作为 **file-static 变量**使用,即与其所埋点的代码定义在同一文件中的全局变量。客户端库**应当(SHOULD)**支持这一点。常见用例是整体地对一段代码埋点,而不是在某个对象实例的上下文中对代码埋点。用户不应被迫在代码各处传递(plumbing)指标对象,客户端库应替他们处理。 - **必须(MUST)有一个默认的 CollectorRegistry**;标准指标**必须**默认隐式注册到其中,用户无需做任何额外工作。 - **必须(MUST)提供一种方式让指标不注册到默认 CollectorRegistry**,用于批处理作业和单元测试。自定义 Collector **应当(SHOULD)**也遵循这一点。 - 指标具体如何创建因语言而异:有些语言(Java、Go)最好用 builder 方式;其他语言(Python)函数参数足够丰富,一次调用即可。 - Java Simpleclient 示例: ```java class YourClass { static final Counter requests = Counter.build() .name("requests_total") .help("Requests.").register(); } ``` - 这会用默认 CollectorRegistry 注册 `requests`;调用 `build()` 而非 `register()` 则不会注册(方便单元测试);也可以向 `register()` 传入 CollectorRegistry(方便批处理作业)。 #### 6.4.5 Counter - Counter 是单调递增计数器。**绝不能允许值下降,但可以重置为 0**(例如服务器重启)。 - Counter **必须(MUST)**有以下方法: - `inc()`:加 1; - `inc(double v)`:加指定数量;**必须检查 v >= 0**。 - Counter 是 **ENCOURAGED** 具备:统计某段代码中抛出/引发的异常数量,并可选只统计特定异常类型。即 Python 中的 `count_exceptions`。 - **Counter 必须从 0 开始。** #### 6.4.6 Gauge - Gauge 表示可升可降的值。 - Gauge **必须(MUST)**有以下方法: - `inc()`:加 1; - `inc(double v)`:加指定数量; - `dec()`:减 1; - `dec(double v)`:减指定数量; - `set(double v)`:设为指定值。 - **Gauge 必须从 0 开始**;**可以(MAY)**提供让某 Gauge 从其他数值开始的方式。 - Gauge **应当(SHOULD)**有以下方法:`set_to_current_time()`:把 Gauge 设为当前 unixtime(秒)。 - Gauge 是 **ENCOURAGED** 具备: - 跟踪某段代码/函数中"进行中"的请求数(Python 中为 `track_inprogress`); - 对一段代码计时并把 Gauge 设为其耗时秒数(对批处理作业有用)。Java 中为 startTimer/setDuration,Python 中为 `time()` 装饰器/上下文管理器。**这应当与 Summary/Histogram 中的模式一致(只是用 `set()` 而非 `observe()`)。** #### 6.4.7 Summary - Summary 在滑动时间窗口上采样观测值(通常是请求耗时之类),提供对其分布、频率与总和的即时洞察。 - **绝不能允许用户把 `quantile` 用作标签名**,因为该名被内部用于标识 summary 的分位数。 - Summary 是 **ENCOURAGED** 提供分位数作为导出项,但这些**无法聚合且往往很慢**。 - **Summary 必须允许不设分位数**,因为仅 `_count`/`_sum` 就已经相当有用,并且**这必须是默认行为**。 - Summary **必须(MUST)**有以下方法:`observe(double v)`:观测给定数值。 - Summary **应当(SHOULD)**有以下方法:为使用者提供按秒计时的方式(Python 中为 `time()` 装饰器/上下文管理器;Java 中为 startTimer/observeDuration)。**绝不能提供秒以外的单位**(若用户想要其他单位,可自行手工处理)。应与 Gauge/Histogram 的模式一致。 - **Summary 的 `_count`/`_sum` 必须从 0 开始。** #### 6.4.8 Histogram - Histogram 允许可聚合的事件分布(例如请求延迟),其本质是"每个 bucket 一个 counter"。 - **绝不能(MUST NOT)允许 `le` 作为用户设置的标签**,因为 `le` 被内部用于标识 bucket。 - **必须(MUST)**提供手动选择 buckets 的方式。**应当(SHOULD)**提供按 `linear(start, width, count)` 与 `exponential(start, factor, count)` 方式设置 buckets 的途径。**count 必须包含 `+Inf` bucket。** - Histogram **应当(SHOULD)**使用与其他客户端库相同的默认 buckets。 - **buckets 一旦指标创建后绝不可更改(MUST NOT)。** - Histogram **必须(MUST)**有以下方法:`observe(double v)`:观测给定数值。 - Histogram **应当(SHOULD)**有以下方法:为使用者提供按秒计时的方式(Python `time()` 装饰器/上下文管理器;Java `startTimer`/`observeDuration`)。**绝不能提供秒以外的单位。** 应与 Gauge/Summary 的模式一致。 - **Histogram 的 `_count`/`_sum` 以及 buckets 必须从 0 开始。** **Further metrics considerations**: - 在本文档所述内容之外,按语言合理性地提供额外功能是 **ENCOURAGED** 的。 - 如果存在可以让常见用例更简单的做法,那就去做——前提是它不会鼓励不良行为(例如次优的指标/标签布局,或在客户端做计算)。 #### 6.4.9 Labels(标签) - 标签是 Prometheus 最强大的方面之一,但也容易被滥用。因此客户端库向用户提供标签的方式必须非常谨慎。 - 客户端库**不应当(SHOULD NOT)**允许用户对同一个 Gauge/Counter/Summary/Histogram 或库提供的任何其他 Collector 使用不同的标签名。 - 来自自定义 collector 的指标几乎总是应有一致的标签名。由于仍存在罕见但有效的例外,客户端库**不应**对此做校验。 - 虽然标签很强大,但**大多数指标不会有标签**。因此 API 应允许标签,但不应让标签主导 API。 - **必须(MUST)**允许在 Gauge/Counter/Summary/Histogram 创建时可选地指定标签名列表。**应当(SHOULD)**支持任意数量的标签名。**必须(MUST)**校验标签名符合文档要求。 - 访问指标带标签维度的通用方式:通过 `labels()` 方法,它接受标签值列表或标签名→标签值的 map,返回一个 "Child"。随后可在 Child 上调用常用的 `.inc()`/`.dec()`/`.observe()` 等方法。 - `labels()` 返回的 Child **应当(SHOULD)**可被用户缓存,避免重复查找——这在延迟敏感的代码中很重要。 - 带标签的指标**应当(SHOULD)**支持 `remove()` 方法(签名与 `labels()` 相同),用于从指标中移除某个 Child 从而不再导出它;并支持 `clear()` 方法,移除指标的所有 Child。这两者都会使 Child 的缓存失效。 - **应当(SHOULD)**有一种用默认值初始化指定 Child 的方式,通常就是调用 `labels()`。 - **不带标签的指标必须始终被初始化**,以避免指标缺失问题。 #### 6.4.10 Metric names(指标名) - 指标名必须遵循规范。与标签名一样,这**必须(MUST)**在 Gauge/Counter/Summary/Histogram 以及库提供的任何其他 Collector 的使用中得到满足。 - 许多客户端库以三部分设置名称:`namespace_subsystem_name`,其中**只有 `name` 是必填的**。 - **必须(MUST)劝阻动态/生成的指标名或指标名的子部分**,除非是自定义 Collector 从其他埋点/监控系统代理指标时。生成/动态的指标名说明你应该改用标签。 #### 6.4.11 Metric description and help - Gauge/Counter/Summary/Histogram **必须(MUST)**要求提供指标描述/help。 - 客户端库自带的所有自定义 Collector 的指标**必须(MUST)**有描述/help。 - 建议把它做成必填参数,但**不要**校验其长度——因为如果某人真的不想写文档,我们也没法说服他。库自带的 Collector(以及生态内所有可能的地方)**应当(SHOULD)**有良好的指标描述,以树立榜样。 #### 6.4.12 Exposition(暴露格式) - 客户端**必须(MUST)**实现 exposition formats 文档中所述基于文本的暴露格式。 - 暴露指标的可复现顺序(reproducible order)是 **ENCOURAGED** 的(尤其是对人类可读格式),前提是能做到而不带来显著资源开销。 #### 6.4.13 Standard and runtime collectors - 客户端库**应当(SHOULD)**尽可能提供下述 Standard exports。 - 这些**应当(SHOULD)**实现为自定义 Collector,并默认注册到默认 CollectorRegistry 上。 - **应当(SHOULD)**有一种禁用它们的方式,因为存在一些非常小众的用例中它们会碍事。 **Process metrics(进程指标)**:这些指标以 `process_` 为前缀。若在所使用语言/运行时中获取某个必要值有问题甚至不可能,客户端库**应当(SHOULD)优先省略对应指标**,而不是导出伪造、不准确或特殊值(如 `NaN`)。**所有内存值单位为字节,所有时间单位为 unixtime/秒。** | Metric name | Help string | Unit | | --- | --- | --- | | `process_cpu_seconds_total` | Total user and system CPU time spent in seconds. | seconds | | `process_open_fds` | Number of open file descriptors. | file descriptors | | `process_max_fds` | Maximum number of open file descriptors. | file descriptors | | `process_virtual_memory_bytes` | Virtual memory size in bytes. | bytes | | `process_virtual_memory_max_bytes` | Maximum amount of virtual memory available in bytes. | bytes | | `process_resident_memory_bytes` | Resident memory size in bytes. | bytes | | `process_heap_bytes` | Process heap size in bytes. | bytes | | `process_start_time_seconds` | Start time of the process since unix epoch in seconds. | seconds | | `process_threads` | Number of OS threads in the process. | threads | **Runtime metrics(运行时指标)**:此外,客户端库 **ENCOURAGED** 也提供对各自语言运行时合理的指标(如垃圾回收统计),并使用合适的语言前缀,例如 `go_`、`hotspot_` 等。 #### 6.4.14 Unit tests(单元测试) - 客户端库**应当(SHOULD)**有覆盖核心埋点库与暴露格式的单元测试。 - 客户端库 **ENCOURAGED** 提供让用户易于对自己使用埋点代码进行单元测试的方式,例如 Python 中的 `CollectorRegistry.get_sample_value`。 #### 6.4.15 Packaging and dependencies(打包与依赖) - 理想情况下,客户端库可以被引入任意应用以添加一些埋点,而**不破坏该应用**。 - 因此给客户端库添加依赖时需要谨慎。例如:如果你添加的库使用了 Prometheus 客户端并要求某个库的版本 x.y,而应用在别处使用 x.z,这是否会对应用造成不利影响? - 建议在可能出现该问题的场景下,**把核心埋点与各格式的 bridge/暴露分离**。例如 Java simpleclient 的 `simpleclient` 模块无依赖,而 `simpleclient_servlet` 承载 HTTP 相关部分。 #### 6.4.16 Performance considerations(性能考量) - 由于客户端库必须线程安全,需要某种并发控制,并且必须考虑在多核机器与应用上的性能。 - 经验上**性能最差的是互斥锁(mutexes)**。 - 处理器原子指令(atomic instructions)通常居中,一般可接受。 - **避免不同 CPU 修改同一块 RAM 的做法效果最好**,例如 Java simpleclient 中的 DoubleAdder——但有内存成本。 - 如前所述,`labels()` 的结果应可缓存。支撑带标签指标的并发 map 往往相对较慢;**对不带标签的指标做特化处理以避免 `labels()` 式查找能带来很大帮助**。 - 指标在 inc/dec/set 等操作时**应当(SHOULD)避免阻塞**,因为在刮取进行中把整个应用卡住是不可取的。 - **ENCOURAGED**:为主要埋点操作(包括带标签的情况)建立基准测试(benchmarks)。 - 在做暴露时应留意资源消耗,尤其是 RAM。可考虑通过流式输出结果来降低内存占用,并可能对并发刮取数量设上限。 > 引自:[Writing client libraries](https://prometheus.io/docs/instrumenting/writing_clientlibs/) ### 6.5 集成生态清单(Operating integrations) 除 client libraries 与 exporters/相关库外,Prometheus 还有许多其他通用集成点。本页列出部分集成;**并非全部列出**(功能重叠或仍在开发中)。exporter 默认端口 wiki 页也含少量非 exporter 的集成。 #### 6.5.1 File Service Discovery 对 Prometheus 原生不支持的服务发现机制,file-based service discovery 提供集成接口(自行产出 target 文件)。 | 集成 | 说明 | | --- | --- | | ElastiCache | AWS ElastiCache 的 file SD | | Kuma | Kuma 服务网格 | | Lightsail | AWS Lightsail | | Netbox | NetBox | | Packet | Packet(Equinix Metal) | | Scaleway | Scaleway | #### 6.5.2 HTTP Service Discovery 允许从 HTTP endpoint 获取 targets(由外部服务动态提供目标列表)。 | 集成 | 说明 | | --- | --- | | fastly-exporter | 发现 Fastly 服务 | | ns1_exporter | 发现 NS1 zones | | NetBox via plugin | 通过插件 | | NetBox via export templates | 通过导出模板 | #### 6.5.3 Remote Endpoints and Storage remote write / remote read 可透明地发送与接收样本,主要面向**长期存储**;建议仔细评估方案能否处理你的数据量。下表括号内 w=仅写、rw=读写。 | 远程存储 / 端点 | 能力 | | --- | --- | | AppOptics | w | | AWS Timestream | rw | | Azure Data Explorer | rw | | Azure Event Hubs | w | | Chronix | w | | Cortex | rw | | CrateDB | rw | | Elasticsearch | w | | Gnocchi | w | | Google BigQuery | rw | | Google Cloud Spanner | rw | | Grafana Mimir | rw | | Graphite | w | | GreptimeDB | rw | | InfluxDB | rw | | Instana | w | | IRONdb | rw | | Kafka | w | | Last9 | w | | M3DB | rw | | Mezmo | w | | New Relic | w | | Oodle | w | | OpenObserve | w | | OpenTSDB | w | | QuasarDB | rw | | SignalFx | w | | Splunk | rw | | Sysdig Monitor | w | | TiKV | rw | | Thanos | rw | | VictoriaMetrics | w | | Wavefront | w | - 另有 **Prom-migrator**:在远程存储系统之间迁移数据的工具。 #### 6.5.4 Alertmanager Webhook Receiver 对 Alertmanager 原生不支持的通知机制,webhook receiver 提供集成(涵盖值班/事件管理、IM 聊天、工单、短信/语音、日志与告警收敛分析等渠道)。以下为条目(部分): AlertINT、alertmanager-webhook-logger、Alertsnitch、All Quiet、Asana、AWS SNS、Better Uptime、Canopsis、DingTalk、Discord、GitLab、Gotify、GELF、Grafana OnCall、HeyOnCall、Icinga2、iLert、IRC Bot、ITOC360、JIRAlert、Matrix(两个)、Notion、Parny、Phabricator/Maniphest、prom2teams、Rootly、Runframe、Ansible Tower(prom2tower)、Robusta、Signal、SIGNL4、Simplepush、SMS(sachet)、SMSEagle、SNMP traps、Squadcast、STOMP、Telegram bot、xMatters、XMPP Bot、Zenduty、Zoom、Zulip。 #### 6.5.5 Management Prometheus 不包含配置管理功能,可与现有系统集成或在其上构建。 | 集成 | 说明 | | --- | --- | | Prometheus Operator | 在 Kubernetes 上管理 Prometheus | | Promgen | Prometheus 与 Alertmanager 的 Web UI 与配置生成器 | #### 6.5.6 Other | 集成 | 说明 | | --- | --- | | Alert analysis(am2ch) | 告警存 ClickHouse 并提供分析 dashboard | | karma | 告警 dashboard | | PushProx | 穿越 NAT 等网络环境的代理 | | Promdump | kubectl 插件,导出与恢复数据块 | | Promregator | Cloud Foundry 应用的发现与抓取 | | pint | Prometheus 规则 linter | > 引自:[Integrations](https://prometheus.io/docs/operating/integrations/) > > 引自:[Operating](https://prometheus.io/docs/operating/)(该页仅有 frontmatter,无正文;为 Operating 章节的导航/索引页) ### 6.6 node_exporter 部署实践 node_exporter 是官方维护的**机器指标导出器**(`prometheus/node_exporter`,**official**),用于暴露大量硬件与内核相关的系统级指标,供 Prometheus 抓取。 > ⚠️ node_exporter **面向 *nix 系统**(Linux/Unix);**Windows** 有对应的 Windows exporter(`prometheus-community/windows_exporter`),流程与本节一致,仅二进制不同。 #### 6.6.1 下载与运行 node_exporter 是**单个静态二进制**(以 tarball 分发),下载解压后直接运行即可: ```bash # NOTE: Replace the URL with one from the "downloads" page. # Node Exporter is available for multiple OS targets and architectures. # Downloads are available at: # https://github.com/prometheus/node_exporter/releases/download/v/node_exporter-.-.tar.gz # 、、 为占位符(如 1.10.2 / linux / amd64) # 本例使用 Node Exporter 1.10.2,Linux amd64 wget https://github.com/prometheus/node_exporter/releases/download/v1.10.2/node_exporter-1.10.2.linux-amd64.tar.gz tar xvfz node_exporter-1.10.2.linux-amd64.tar.gz cd node_exporter-1.10.2.linux-amd64 ./node_exporter ``` 运行后日志会表明其监听 **9100** 端口(日志含 `Listening on :9100`)。验证 metrics endpoint: ```bash curl http://localhost:9100/metrics # 输出全部指标 curl http://localhost:9100/metrics | grep "node_" # 只看系统指标(前缀 node_) ``` #### 6.6.2 配置 Prometheus 抓取 在 `prometheus.yml` 中新增 job(全局 `scrape_interval` 设为 15s): ```yaml global: scrape_interval: 15s scrape_configs: - job_name: node static_configs: - targets: ['localhost:9100'] ``` 安装并启动 Prometheus,用 `--config.file` 指向上述配置: ```bash wget https://github.com/prometheus/prometheus/releases/download/v*/prometheus-*.*-amd64.tar.gz tar xvf prometheus-*.*-amd64.tar.gz cd prometheus-*.* ./prometheus --config.file=./prometheus.yml ``` #### 6.6.3 expression browser 探索与示例指标 在 expression browser(`http://localhost:9090/graph`)的表达式栏输入 PromQL 探索。Node Exporter 专属指标前缀为 `node_`,如 `node_cpu_seconds_total`、`node_exporter_build_info`。常用示例: | Metric | Meaning | |:------|:-------| | `rate(node_cpu_seconds_total{mode="system"}[1m])` | 最后一分钟每秒处于 system mode 的平均 CPU 时间(秒) | | `node_filesystem_avail_bytes` | 非 root 用户可用的文件系统空间(bytes) | | `rate(node_network_receive_bytes_total[1m])` | 最后一分钟每秒平均接收网络流量(bytes) | #### 6.6.4 注意事项 - node_exporter 属于"**在目标机器上运行**"的导出器,符合"每个导出器只监控恰好一个应用实例、与它同机紧邻运行"的部署原则(node_exporter 处理的是整机系统指标)。 - 它**只应被 Prometheus 抓取时才拉取指标**,不应自己起定时器(同步抓取原则)。 - node_exporter 的 collector 复杂度按 module 而异:`mdadm` 之类手工解析单文件的 collector 可以把指标做"对";`meminfo` 之类随内核版本变化的 collector 只做"刚好够"的转换。 - 对**与机器相关的 batch job**,应使用 **node_exporter 的 textfile collector** 暴露指标,而不要用 Pushgateway(见 5.5 节)。 > 引自:[Getting started with Prometheus](https://prometheus.io/docs/tutorials/getting_started/) > > 引自:[Exporters and integrations](https://prometheus.io/docs/instrumenting/exporters/) > > 引自:[node-exporter guide](https://prometheus.io/docs/guides/node-exporter/) > 💡 补充:生产部署还应在 systemd 中托管 node_exporter 并加 `--collector.` 白/黑名单裁剪掉不需要的 collector,减少抓取体积;同时注意 textfile collector 目录的权限与原子写入(先写临时文件再 `mv`),否则会出现读取到半截文件导致抓取失败。 ### 6.7 cAdvisor 实践 cAdvisor(**container Advisor**,`google/cadvisor`,**社区**维护,列于 Miscellaneous 类)分析并暴露**运行中容器的资源使用与性能数据**,开箱即用提供 Prometheus metrics。在容器/编排场景中它与 node_exporter 互补:node_exporter 看整机,cAdvisor 看每个容器。 本节指南目标:用 **Docker Compose** 建立一个含 Prometheus、cAdvisor、Redis 的多容器环境,并查看 Redis 容器的容器级指标。 #### 6.7.1 prometheus.yml 抓取配置 cAdvisor 自身即提供 Prometheus 可抓取的 `/metrics` endpoint,只需在 `prometheus.yml` 中加 job: ```yaml scrape_configs: - job_name: cadvisor scrape_interval: 5s static_configs: - targets: - cadvisor:8080 ``` > 💡 注意 `cadvisor:8080` 用的是 compose 网络中的**服务名**,而非 `localhost`。 #### 6.7.2 Using Bind Mounts:完整 docker-compose.yml ```yaml version: '3.2' services: prometheus: image: prom/prometheus:latest container_name: prometheus ports: - 9090:9090 command: - --config.file=/etc/prometheus/prometheus.yml volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro depends_on: - cadvisor cadvisor: image: gcr.io/cadvisor/cadvisor:latest container_name: cadvisor ports: - 8080:8080 volumes: - /:/rootfs:ro - /var/run:/var/run:rw - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro depends_on: - redis redis: image: redis:latest container_name: redis ports: - 6379:6379 ``` 三个 service 的分工: | Service | 作用 | |:------|:------| | prometheus | 使用本地 `prometheus.yml`(经 `volumes` 挂载进容器 `/etc/prometheus/`) | | cadvisor | 暴露 8080(cAdvisor metrics 默认端口),依赖多个宿主 volumes(`/`、`/var/run`、`/sys`、`/var/lib/docker/`)读取容器与宿主数据 | | redis | 标准 Redis server,cAdvisor 会自动采集其容器指标,**无需额外配置** | > ⚠️ cAdvisor 的 volumes 挂载与权限模式(`ro`/`rw`)是它能否读到容器指标的关键,缺项会导致容器指标为空。 #### 6.7.3 启动与验证 ```bash docker-compose up ``` 启动成功后可见日志 `msg="Server is ready to receive web requests."`;用 `docker-compose ps` 确认三个容器均处于运行状态。 #### 6.7.4 cAdvisor web UI cAdvisor 自带 Web UI,可直接查看容器资源使用: - 总览:`http://localhost:8080` - 特定容器统计:`http://localhost:8080/docker/`,如 `http://localhost:8080/docker/redis`、`http://localhost:8080/docker/prometheus` #### 6.7.5 expression browser 示例 在 Prometheus 的 expression browser(`http://localhost:9090/graph`)中查询容器指标。例:`container_start_time_seconds` 记录容器启动时间(秒),用 `name` 标签选择容器(对应 compose 的 `container_name`): ```text container_start_time_seconds{name="redis"} ``` #### 6.7.6 替代方案:Inline Docker Configs 上述方案把 `prometheus.yml` 作为独立文件放在宿主上管理。**Inline Docker Configs(Remote Deployments)** 方案改用 Docker 的 `configs` 特性,把配置**内联**进 `docker-compose.yml`(`version: '3.8'`),无需在宿主上维护独立配置文件。要点:用 `configs: prometheus_config: content: |` 内联 scrape 配置;prometheus 服务下用 `configs: - source: prometheus_config / target: /etc/prometheus/prometheus.yml` 引用。 > ⚠️ Required Fields:使用 Docker `configs` 时**必须显式给出数值形式**的 `uid`、`gid`、`mode`: > - `uid: "65534"`——65534 是 Prometheus 镜像中的 `nobody` 用户 > - `gid: "65534"`——nobody 组 > - `mode: 0400`——owner 只读 > > **省略这些字段**,或写成 `"nobody"` 之类**字符串**,会报错:`strconv.Atoi: parsing "nobody": invalid syntax`。 > 引自:[Exporters and integrations](https://prometheus.io/docs/instrumenting/exporters/) > > 引自:[cadvisor guide](https://prometheus.io/docs/guides/cadvisor/) > 💡 补充:采用 Kubernetes 时,通常不单独部署 cAdvisor——kubelet 已内置 cAdvisor 并在其 `/metrics/cadvisor` 路径暴露容器指标(该路径由 kubelet 服务发现或 kube-state-metrics 体系覆盖)。独立部署 cAdvisor 的常见难点是宿主 `--volume` 挂载与 cgroup v2 下的指标差异,跨内核版本时容器级指标命名可能变化,需在 dashboard 中做兼容。以上为经验补充,具体 endpoint 与挂载参数请以 cAdvisor 项目 README 与你所用版本为准。 ### 6.8 multi-target exporter 模式(blackbox 型) #### 6.8.1 模式定义 **multi-target exporter(多目标 exporter)** 是指:exporter 通过网络协议获取 target 的指标;**exporter 不必运行在被采集机器上**;exporter 把 **targets 与 query config string 作为 Prometheus GET 请求的参数**;收到 GET 请求后才开始 scrape;可查询多个 target。 **仅用于** blackbox、SNMP exporter 等场景——当目标上**无法运行 exporter**,或关心**从网络外某点看网站的距离/延迟/可达性**时。 #### 6.8.2 运行 blackbox exporter 可**作为普通程序、容器、后台服务、裸机、虚拟机运行**;因通过网络查询与收发,需开放合适端口。用 Docker 启动: ```bash docker run -p 9115:9115 prom/blackbox-exporter ``` (视系统配置可能需要 `sudo`。)成功日志最后一行应报 `msg="Listening on address"`,如 `address=:9115`。 #### 6.8.3 两种查询方式 1. **查 exporter 自身指标**(通常 `/metrics`):这是 **whitebox monitoring**。 ```bash curl 'localhost:9115/metrics' ``` 返回 `blackbox_exporter_build_info`、`go_gc_duration_seconds`、`go_goroutines`、`process_cpu_seconds_total`、`process_max_fds`、`process_open_fds`、`process_resident_memory_bytes`、`process_start_time_seconds`、`process_virtual_memory_bytes` 等。 2. **让 exporter 去 scrape 另一个 target**("descriptive" endpoint,如 `/probe`):这是使用 multi-target exporter 时**主要关心**的方式。需在 HTTP GET 中提供 `target` 与 `module` 参数(`target` 是 URI 或 IP,`module` 必须在 exporter 配置中定义): ```bash curl 'localhost:9115/probe?target=prometheus.io&module=http_2xx' ``` 返回 `probe_dns_lookup_time_seconds`、`probe_duration_seconds`、`probe_failed_due_to_regex`、`probe_http_content_length`、`probe_http_duration_seconds{phase="connect|processing|resolve|tls|transfer"}`、`probe_http_redirects`、`probe_http_ssl`、`probe_http_status_code`、`probe_http_version`、`probe_ip_protocol`、`probe_success` 等。首次使用 Docker 时可能看到大多为 0、`probe_success 0`、`probe_ip_protocol 6`——**Docker daemon 默认阻止 IPv6**,故容器内 exporter 无法经 IPv6 连接。 #### 6.8.4 配置 modules 容器内配置文件 `config.yml` 是 GitHub 仓库中 `blackbox.yml` 的副本,下载: ```bash curl -o blackbox.yml https://raw.githubusercontent.com/prometheus/blackbox_exporter/master/blackbox.yml ``` 原始片段定义 `http_2xx`(prober: http)与 `http_post_2xx`(prober: http,http.method: POST)。为 `http_2xx` 增加 `preferred_ip_protocol: "ip4"`: ```yaml modules: http_2xx: prober: http http: preferred_ip_protocol: "ip4" http_post_2xx: prober: http http: method: POST ``` 用 `--config.file="blackbox.yml"` 指定配置;Docker 下先用 `--mount` 把文件挂进容器(macOS 需先在 Preferences→File Sharing 允许目录,再 Apply & Restart)。停止旧容器用 `ctrl+c`: ```bash docker \ run -p 9115:9115 \ --mount type=bind,source="$(pwd)"/blackbox.yml,target=/blackbox.yml,readonly \ prom/blackbox-exporter \ --config.file="/blackbox.yml" ``` 该命令含义:`run` 容器并把宿主 9115 映射到容器 9115;`mount` 当前目录 `$(pwd)` 下 blackbox.yml 到容器 `/blackbox.yml`(readonly);使用镜像 `prom/blackbox-exporter`;以 `--config.file` 指定 `/blackbox.yml`。成功日志含 "Starting blackbox_exporter"、"Loaded config file"、"Listening on address"。 再次探测可见 `probe_success 1`、`probe_http_status_code 200`、`probe_ip_protocol 4`、`probe_http_ssl 1`、`probe_http_redirects 1`、`probe_ssl_earliest_cert_expiry`、`probe_tls_version_info{version="TLS 1.3"}`、`probe_http_uncompressed_body_length` 等。exporter 还提供小型 Web UI(localhost:9115)查看最近探测、已加载配置与调试信息。 #### 6.8.5 让 Prometheus 抓取 exporter 自身 最小配置让 Prometheus 抓取 exporter 自身(`metrics_path: /metrics`,target `localhost:9115`)。Docker for Mac/Windows 需用 `host.docker.internal:9115`(**不建议用于生产**)。 ```bash docker \ run --network="host"\ --mount type=bind,source="$(pwd)"/prometheus.yml,target=/prometheus.yml,readonly \ prom/prometheus \ --config.file="/prometheus.yml" ``` macOS/Windows 版本改为 `run -p 9090:9090` 加同样的 `--mount` 与 `prom/prometheus --config.file="/prometheus.yml"`。检查 `localhost:9090/targets` 中 `blackbox` 为绿色 UP(DOWN 说明 exporter 未运行,UNKNOWN 说明再等几秒)。 #### 6.8.6 让 Prometheus 查询 `/probe` 新增 job `blackbox-http`,设 `metrics_path: /probe`,并在 `params:` 下指定 `module: [http_2xx]` 与 `target: [prometheus.io]`;保存后 `ctrl+C` 停止并重启容器。终端应出现 "Server is ready to receive web requests."。 **该做法缺点**:真实 targets 放在 param config 中不直观;`instance` label 值是 exporter 地址而非关心的目标;看不出探测的是哪个 URL,多个 URL 会混在一条指标里。 #### 6.8.7 用 relabeling 修正 三条关键事实: 1. 所有以 `__` 开头的 label 在 scrape 后**被丢弃**; 2. 可设置 `__param_` 内部 label 来设置 scrape 请求的 **URL 参数**; 3. `__address__` 由 `static_configs` 的 `targets` 设置,是 scrape 请求的 hostname,默认随后用于设置每指标附带的 `instance` label。 **改进配置**:`params` 只留 `module: [http_2xx]`,targets 移到 static_configs(`http://prometheus.io`、`https://prometheus.io`、`http://example.com:8080`),`relabel_configs` 三条: ```yaml relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: localhost:9115 # The blackbox exporter’s real hostname:port ``` **效果**:relabel 前请求为 `"http://prometheus.io/probe?module=http_2xx"`,relabel 后为 `"http://localhost:9115/probe?target=http://prometheus.io&module=http_2xx"`。 逐条解释: - 从 `__address__` 取值写入 `__param_target`(加 target 参数); - 从 `__param_target` 取值创建 `instance` label(返回指标带 `instance="http://prometheus.io"`); - 把 `__address__` 写为 `localhost:9115`,使请求发给 exporter 而非直接发给 target URI。 这些规则可与 service discovery 组合使用(服务发现同样写入 `__address__`)。 #### 6.8.8 多 target、多 module 单 job job-level `module` 是 list 但 blackbox 只用第一个;改为在每个 `static_config` 内用 `labels.module` 指定(如 `module: http_2xx`、`module: icmp`),并新增 relabel 规则注入 `__param_module`: ```yaml relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - source_labels: [module] target_label: __param_module - target_label: __address__ replacement: localhost:9115 ``` #### 6.8.9 小结 - 理解多目标 exporter 模式及其适用范围; - 运行定制 module 的 blackbox exporter; - 用 relabeling 让 Prometheus 抓取带 prober label 的指标。 > 引自:[Understanding and using the multi-target exporter pattern](https://prometheus.io/docs/guides/multi-target-exporter/) ## 第 7 章 应用集成实战 本章把第 5、6 章的规则落到可运行的代码上:先用官方 Go 指南做一次完整的"最小可用"埋点(7.1),再做一次"HTTP 服务埋点"端到端教程(7.2),最后补上 OpenTelemetry 接入(7.3)。 ### 7.1 Go 应用埋点教程 Prometheus 官方提供 Go 客户端库 `github.com/prometheus/client_golang`;本教程创建一个通过 HTTP 暴露 Prometheus 指标的简单 Go 应用。完整 API 文档见 GoDoc。 #### 7.1.1 安装依赖 ```bash go get github.com/prometheus/client_golang/prometheus go get github.com/prometheus/client_golang/prometheus/promauto go get github.com/prometheus/client_golang/prometheus/promhttp ``` #### 7.1.2 Go 暴露机制 需提供 `/metrics` HTTP endpoint,使用 `prometheus/promhttp` 库的 `Handler` 作为 handler 函数。 #### 7.1.3 最小示例:只暴露 Go 默认指标 通过 `http://localhost:2112/metrics` 暴露 Go 默认指标。引入包路径为 `net/http`、`github.com/prometheus/client_golang/prometheus`、`github.com/prometheus/client_golang/prometheus/collectors`、`github.com/prometheus/client_golang/prometheus/promhttp`: ```go func main() { reg := prometheus.NewRegistry() reg.MustRegister( collectors.NewGoCollector(), collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), ) http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) http.ListenAndServe(":2112", nil) } ``` 启动与访问: ```bash go run main.go curl http://localhost:2112/metrics ``` #### 7.1.4 添加自定义指标 注册一个 `myapp_processed_ops_total` counter,每 2 秒自增一次。引入包路径 `net/http`、`time`、`github.com/prometheus/client_golang/prometheus`、`.../promauto`、`.../promhttp`: ```go type metrics struct { opsProcessed prometheus.Counter } func newMetrics(reg prometheus.Registerer) *metrics { m := &metrics{ opsProcessed: promauto.With(reg).NewCounter(prometheus.CounterOpts{ Name: "myapp_processed_ops_total", Help: "The total number of processed events", }), } return m } func recordMetrics(m *metrics) { go func() { for { m.opsProcessed.Inc() time.Sleep(2 * time.Second) } }() } ``` 在 `main` 中组装:`reg := prometheus.NewRegistry()`、`m := newMetrics(reg)`、`recordMetrics(m)`,再 `http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))` 与 `http.ListenAndServe(":2112", nil)`。 输出中可见 help、type 与当前值: ```text # HELP myapp_processed_ops_total ... # TYPE myapp_processed_ops_total counter myapp_processed_ops_total 5 ``` #### 7.1.5 抓取配置 ```yaml scrape_configs: - job_name: myapp scrape_interval: 10s static_configs: - targets: - localhost:2112 ``` #### 7.1.6 其他 Go 客户端特性 gauges、histograms、non-global registries(`prometheus.Registry`)、推送指标到 PushGateway 的 `prometheus/push`、Prometheus 与 Graphite 的桥接等。 **小结**:共创建两个示例应用(仅默认 Go 指标 / 含自定义 counter),并配置 Prometheus 抓取。 > 引自:[Instrumenting a Go application for Prometheus](https://prometheus.io/docs/guides/go-application/) ### 7.2 Go HTTP 服务埋点完整教程 **目标**:创建简单 Go HTTP server,通过添加 counter 指标来插桩,统计服务器处理的总请求数。 #### 7.2.1 初始服务器 含 `/ping` 端点,返回 `pong`: ```go package main import ( "fmt" "net/http" ) func ping(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w,"pong") } func main() { http.HandleFunc("/ping", ping) http.ListenAndServe(":8090", nil) } ``` 编译运行: ```bash go build server.go ./server ``` **检查点**:浏览器打开 `http://localhost:8090/ping`,**必须看到 `pong`**。 #### 7.2.2 选择指标类型 统计 ping 端点请求数,**counter 合适**,因为请求数只增不减。 #### 7.2.3 创建 Prometheus counter ```go type metrics struct { pingCounter prometheus.Counter } func newMetrics(reg prometheus.Registerer) *metrics { m := &metrics{ pingCounter: promauto.With(reg).NewCounter( prometheus.CounterOpts { Name: "ping_request_count", Help: "No of requests handled by Ping handler", }), } return m } ``` #### 7.2.4 更新 ping Handler 用 `metrics.pingCounter.Inc()` 增加计数: ```go func ping(m *metrics) func(w http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, req *http.Request) { m.pingCounter.Inc() fmt.Fprintf(w, "pong") } } ``` #### 7.2.5 注册指标并暴露 ```go func main() { reg := prometheus.NewRegistry() m := newMetrics(reg) http.HandleFunc("/ping", ping(m)) http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) http.ListenAndServe(":8090", nil) } ``` **promhttp 相关要点**: - `prometheus.MustRegister` 函数将 pingCounter 注册到 default registry; - 暴露指标需 Go Prometheus client library 的 `promhttp` 包; - `promhttp.Handler()` 提供一个 `http.Handler`,暴露注册在 default registry 中的指标。 **完整示例代码(main.go 结构)**:import 包含 `"fmt"`、`"net/http"`、`github.com/prometheus/client_golang/prometheus`、`github.com/prometheus/client_golang/prometheus/promauto`、`github.com/prometheus/client_golang/prometheus/promhttp`;定义 `type metrics struct{ pingCounter prometheus.Counter }`;`newMetrics(reg prometheus.Registerer) *metrics`;`ping(m *metrics) func(...)`;`main()` 中 `reg := prometheus.NewRegistry()`、`m := newMetrics(reg)`、`http.HandleFunc("/ping", ping(m))`、`http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))`、`http.ListenAndServe(":8090", nil)`。 #### 7.2.6 运行与验证 ```bash go mod init prom_example go mod tidy go run server.go ``` 多次访问 `localhost:8090/ping`,再请求 `localhost:8090/metrics` 查看指标。`ping_request_count` 显示 `/ping` 端点被调用了 3 次。 **说明**:default registry 自带 Go runtime metrics 的 collector,因此会看到 `go_threads`、`go_goroutines` 等其他指标。 #### 7.2.7 接入 Prometheus 抓取 ```yaml global: scrape_interval: 15s scrape_configs: - job_name: prometheus static_configs: - targets: ["localhost:9090"] - job_name: simple_server static_configs: - targets: ["localhost:8090"] ``` 启动:`prometheus --config.file=prometheus.yml` > 引自:[Instrumenting HTTP server written in Go](https://prometheus.io/docs/tutorials/instrumenting_http_server_in_go/) #### 7.2.8 配套告警与可视化 官方教程延续此例,用 `ping_request_count > 5` 作为告警条件(教程简化场景,真实最佳实践另见 practices/alerting),配置 Alertmanager 的 webhook receiver,并通过 `rules.yml` 定义 `CountGreaterThan5` 规则(`for: 10s`);再用 Grafana 建 `Ping Request Count` 面板。这两条链路分别是第 4 篇第 4 章(Alertmanager)与本章 8.2 节(Grafana)的直接应用,此处不再重复配置细节。 > 引自:[Alerting based on metrics](https://prometheus.io/docs/tutorials/alerting_based_on_metrics/) ### 7.3 OpenTelemetry 接入(OTLP) Prometheus 支持**通过 HTTP 方式接收 OTLP(OpenTelemetry Protocol)数据**,可作为 OTel 指标的后端。 #### 7.3.1 启用 OTLP receiver 默认**关闭**(与 Remote Write receiver 类似),因为 Prometheus 可无认证运行,未显式配置就接收来流量不安全。开启 CLI flag: ```shell $ prometheus --web.enable-otlp-receiver ``` 之后 Prometheus 在 HTTP 路径 **`/api/v1/otlp/v1/metrics`** 接收 OTLP 指标。 #### 7.3.2 发送端配置 发送端需告知 Prometheus endpoint 并使用 **OTLP 的 HTTP 模式**(gRPC 通常是默认)。OTel SDK/插桩库可经标准环境变量配置: ```shell export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:9090/api/v1/otlp ``` **注意**:OpenTelemetry 规范要求 `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` 作为 **base URL**,`/v1/metrics` 会**自动追加**(参见 opentelemetry-python #2443)。 关闭 traces 与 logs: ```shell export OTEL_TRACES_EXPORTER=none export OTEL_LOGS_EXPORTER=none ``` OTLP 指标默认推送间隔 60 秒,设为 15 秒: ```shell export OTEL_METRIC_EXPORT_INTERVAL=15000 ``` 若插桩库未默认提供 `service.name` 与 `service.instance.id`,**强烈建议设置**: ```shell export OTEL_SERVICE_NAME="my-example-service" export OTEL_RESOURCE_ATTRIBUTES="service.instance.id=$(uuidgen)" ``` **约定**:`service.instance.id` 每个实例唯一;资源属性变化时须生成新的 `service.instance.id`;推荐每次实例启动生成新 UUID。 配置示例文件:`documentation/examples/prometheus-otlp.yml`。 #### 7.3.3 启用 out-of-order ingestion OTel collector 鼓励 batching,多个 collector 副本发送数据且样本无序,可能乱序: ```shell storage: tsdb: out_of_order_time_window: 30m ``` 30 分钟乱序窗口对多数场景够用,可按需调整。 #### 7.3.4 提升(promote)resource attributes 默认不提升任何属性。推荐提升集合(写到 `otlp.promote_resource_attributes`): `service.instance.id`、`service.name`、`service.namespace`、`service.version`、`cloud.availability_zone`、`cloud.region`、`container.name`、`deployment.environment`、`deployment.environment.name`、`k8s.cluster.name`、`k8s.container.name`、`k8s.cronjob.name`、`k8s.daemonset.name`、`k8s.deployment.name`、`k8s.job.name`、`k8s.namespace.name`、`k8s.pod.name`、`k8s.replicaset.name`、`k8s.statefulset.name`。 #### 7.3.5 查询时纳入 resource attributes 所有 OTel resource attributes 默认(除 `service.instance.id`、`service.namespace`、`service.name` 外)都转成特殊 **`target_info`** 指标的 label,可通过 join `target_info` 在查询中使用**未提升**的属性。推荐用实验性 PromQL 函数 `info`,需开启: ```shell --enable-feature=promql-experimental-functions ``` 示例查询: ```promql info(rate(http_server_request_duration_seconds_count[2m]), {k8s_cluster_name=~".+"}) ``` 等价原始 join 查询: ```promql rate(http_server_request_duration_seconds_count[2m]) * on (job, instance) group_left (k8s_cluster_name) target_info ``` **原理**:`rate(...)` 结果被 `target_info` 中共享相同 `job`/`instance` 的 `k8s_cluster_name` label 增强,`job`/`instance` 类似 SQL 外键;`k8s_cluster_name` 对应 OTel 资源属性 `k8s.cluster.name`(Prometheus 默认把点转为下划线)。 `info` 函数通常比原始 join 更快,因为它只选择 `job`/`instance` 匹配的 `target_info` 序列;更重要的是它解决 join 的老问题:当 identifying labels 之外的 label 值发生 churn 时,旧 `target_info` 未被标记 stale,会在 PromQL lookback delta(默认 5 分钟)内新旧重叠,导致 join 查询因存在两条匹配序列而失败;**`info` 总是选最新样本的序列**,不受影响。 #### 7.3.6 `target_info` 与资源属性的关系 处理 OTLP 写请求时,若 resource 含 `service.instance.id` 和/或 `service.name`,Prometheus 为每个 resource 生成 info 指标 `target_info`: - `instance` label 取 `service.instance.id`; - `job` label 取 `service.name`; - 若存在 `service.namespace`,会加前缀到 `job`(`/`)。 默认 `service.name`、`service.namespace`、`service.instance.id` 本身**不加入** `target_info`(已转为 `job`/`instance`)。可启用以下参数在转换之外再直接加入: ```yaml otlp: keep_identifying_resource_attributes: true ``` 其余资源属性也作为 label 加入 `target_info`,名称按 Prometheus 格式转换(默认点转下划线)。**若 resource 既无 `service.instance.id` 也无 `service.name`,则不生成 `target_info`。** 每个 resource 的 OTel 指标都被转成对应 Prometheus 时间序列,并在生成 `target_info` 时附加正确的 `instance`/`job` label。 #### 7.3.7 UTF-8 与 translation_strategy 从 3.x 起 Prometheus 支持指标名与 label 的 UTF-8,可省略 OpenTelemetry 的 Prometheus normalization translator package。内容协商允许 UTF-8 并不要求指标名必须含新字符;OTLP 指标转换方式取决于 endpoint 配置。UTF-8 在存储与 UI 中默认启用,但 **OTLP receiver 的 `translation_strategy` 默认仍为旧的归一化 `UnderscoreEscapingWithSuffixes`**。 四种 translation strategy(其中两种需启用 UTF-8): | 策略 | 行为 | 是否需要 UTF-8 | | --- | --- | --- | | `UnderscoreEscapingWithSuffixes`(默认) | 完全转义以兼容经典 Prometheus 指标名,并追加 type/unit 后缀 | 否 | | `UnderscoreEscapingWithoutSuffixes` | 同样完全转义但不追加 type/unit 后缀,可能造成指标名冲突,需谨慎测试 | 否 | | `NoUTF8EscapingWithSuffixes` | 不把特殊字符转 `_`,原生使用 OTel 格式,尤配合语义约定;会附加单位与 counter 的 `_total` 等后缀以防同名不同型/单位冲突 | 是 | | `NoTranslation` | 完全绕过指标与 label 名翻译,原样透传;可能因缺少后缀导致同名不同型/单位冲突 | 是 | 配置示例: ```yaml otlp: # Ingest OTLP data keeping UTF-8 characters in metric/label names. translation_strategy: NoTranslation ``` #### 7.3.8 Delta Temporality OTel 规范同时支持 Delta 与 Cumulative temporality;delta 常见于 statsd/graphite,而 cumulative 是 Prometheus 默认。当前 Prometheus 内嵌 OpenTelemetry-Collector-contrib 的 delta to cumulative processor,可摄入 delta 并在存入 TSDB 前转成等价 cumulative。**该特性为 experimental**,需以 feature-flag `otlp-deltatocumulative` 启动 Prometheus 才能使用。团队仍在研究更高效的 OTLP delta 处理方式。 > 引自:[Using Prometheus as your OpenTelemetry backend](https://prometheus.io/docs/guides/opentelemetry/) > 💡 补充:接入 OTLP 时最容易踩的两个坑——① 忘记 `--web.enable-otlp-receiver`,客户端一直报 404;② endpoint 写成 `http://localhost:9090/api/v1/otlp/v1/metrics`(把规范自动追加的 `/v1/metrics` 又写了一遍),导致路径变成 `.../otlp/v1/metrics/v1/metrics`。正确做法是只写 base `http://localhost:9090/api/v1/otlp`。 ## 第 8 章 可视化 指标存下来之后,下一步是"看见它"。Prometheus 官方文档给出四种可视化路径:Server 自带的**表达式浏览器**(ad-hoc 查询)、**Grafana**(推荐的生产级 dashboard)、**Perses**(新兴的 dashboard-as-code 平台)、以及 **Console 模板**(Go 模板驱动的自定义 console,但 3.0 起不再随包分发)。本章逐一说明,最后给出选型建议。 ### 8.1 表达式浏览器(Expression browser) - **位置**:expression browser 位于 Prometheus server 的 **`/graph`**,可输入任意 expression 并以 **table** 形式或随时间绘制的 **graph** 查看结果。 - **定位**:主要用于 **ad-hoc queries 与 debugging**。 - **绘制 graph 的推荐工具**:使用 [Grafana](/docs/visualization/grafana/) 或 [Console templates](/docs/visualization/consoles/)。 > 引自:[Expression browser](https://prometheus.io/docs/visualization/browser/) ### 8.2 Grafana 集成 #### 8.2.1 是什么 / 为什么 [Grafana](http://grafana.com/) 是开源的分析与可视化平台,用于监控和分析来自多种数据源的指标,支持创建、探索、分享交互式 dashboard,可集成 Prometheus、InfluxDB、Elasticsearch 等数据库;广泛用于 observability,提供告警、插件扩展性与灵活查询编辑器。 **版本说明**:Grafana 自 **2.5.0(2015-10-28)** 起**内置** Prometheus 数据源。 #### 8.2.2 安装与访问 - 安装见官方 Grafana 文档(https://grafana.com/grafana/download/)。 - 默认 Grafana 监听 **http://localhost:3000**,默认登录 `admin` / `admin`。 #### 8.2.3 创建 Prometheus 数据源步骤 1. 点击侧边栏 "cogwheel"(齿轮)打开 Configuration 菜单; 2. 点击 "Data Sources"; 3. 点击 "Add data source"; 4. 类型选择 "Prometheus"; 5. 设置合适的 Prometheus server URL(例如 `http://localhost:9090/`); 6. 按需调整其他数据源设置(如选择正确的 Access method); 7. 点击 "Save & Test" 保存新数据源。 #### 8.2.4 创建 Prometheus graph 步骤 按标准方式新增 Grafana graph,然后: 1. 点击 graph 标题,再点 "Edit"; 2. 在 "Metrics" tab 下选择你的 Prometheus 数据源(右下角); 3. 在 "Query" 字段输入任意 Prometheus expression,同时用 "Metric" 字段通过自动补全查找指标; 4. 用 "Legend format" 输入格式化时间序列图例名,例如仅显示返回结果的 `method` 与 `status` label 并以短横分隔,可用图例格式串 `{{method}} - {{status}}`; 5. 调整其他 graph 设置直至可用。 **版本建议**:在 Grafana 7.2 及之后,推荐在 `rate` 与 `increase` 函数中使用 **`$__rate_interval`** 变量。 #### 8.2.5 从 Grafana.com 导入预建 dashboard Grafana.com 维护共享 dashboard 集合(https://grafana.com/grafana/dashboards/),可下载用于独立 Grafana 实例;用 Grafana.com 的 "Filter" 选项仅浏览 "Prometheus" 数据源的 dashboard。当前必须**手动编辑**下载的 JSON 文件,把 `datasource:` 条目改为你为 Prometheus server 所选的 Grafana 数据源名;再用 "Dashboards" → "Home" → "Import" 导入编辑后的 dashboard 文件。 > 引自:[Grafana support for Prometheus](https://prometheus.io/docs/visualization/grafana/) #### 8.2.6 可视化实战(教程) **目标**:用 Grafana 创建简单 dashboard,可视化 `ping_request_count` 指标(来自 7.2 节的 Go HTTP 服务埋点教程)。 **为何用 Grafana**:在 Prometheus 上跑查询看到的图是用于 ad-hoc 查询的。Grafana 与 Console Templates 是推荐的两种建图方式。 **安装与设置**:按 https://grafana.com/docs/grafana/latest/installation/requirements/#supported-operating-systems 中对应操作系统的步骤安装并运行。 1. 浏览器打开 http://localhost:3000; 2. 使用默认凭据登录:用户名 `admin`、密码 `admin`,登录后设置新凭据。 **添加 Prometheus 数据源**: 1. 点击侧边栏齿轮图标,选择 `Data Sources`(⚙ > Data Sources); 2. Data Sources 界面可见 Grafana 支持多种数据源(如 Graphite、PostgreSQL 等),选择 Prometheus 设置; 3. 在 HTTP 段输入 URL `http://localhost:9090`,点击 `Save and Test`。 **创建第一个 dashboard**: 1. 点击侧边栏 `+` 图标,选择 `Dashboard`; 2. 下一屏点击 `Add new panel`; 3. 在 `Query` 标签输入 PromQL 查询,此处直接输入 `ping_request_count`; 4. 访问 `ping` 端点几次并刷新 Graph,验证是否正常工作; 5. 右侧 `Panel Options` 下将 `Title` 设为 `Ping Request Count`; 6. 点击右上角 Save 图标保存 dashboard。 > 引自:[Visualizing metrics using Grafana](https://prometheus.io/docs/tutorials/visualizing_metrics_using_grafana/) ### 8.3 Perses - [Perses](https://perses.dev) 是开源 dashboard 与可视化平台,面向 observability,**原生支持 Prometheus 作为数据源**;可让用户创建、管理、分享 dashboard 以监控指标与可视化数据。 - **定位**:Perses 旨在提供简单、灵活、可扩展的替代方案,聚焦**易用性、社区驱动开发、GitOps 能力与 dashboard as code 方法**。 - **安装**:见官方 Perses 文档(安装于容器:https://perses.dev/perses/docs/installation/in-a-container/)。 - **使用**:默认 Perses 监听端口 **`8080`**,web UI 在 `http://localhost:8080`;**默认无登录**。 - **创建 Prometheus 数据源**:在 Perses 中设置数据源的方式参见 Perses 文档 datasources 概念页;与 Prometheus 实例的连接配置完成后,即可从 **Dashboard 与 Explore 视图**查询。 - **导入预建 dashboard**:Perses 提供一组预建 dashboard 可导入实例;这些 dashboard 由社区维护,位于 Perses dashboard 仓库(https://github.com/perses/community-dashboards)。 > 引自:[Perses support for Prometheus](https://prometheus.io/docs/visualization/perses/) ### 8.4 Console 模板(Console templates) #### 8.4.1 重要变更(3.0 起不再打包) > **CAUTION**:自 **Prometheus 3.0** 起,console templates 与 libraries **不再随 Prometheus 打包**。若要使用,必须通过 `--web.console.templates` 与 `--web.console.libraries` 命令行参数**自行提供** templates 与 libraries。官方页面仅为历史参考并演示 console templates 能力;请知悉,任何引自 Prometheus 2.x 分支的 console libraries **已不再维护**,且**可能含已知安全漏洞(CVEs)**。 能力与取舍: - console templates 使用 [Go templating language](http://golang.org/pkg/text/template/) 创建任意 console,由 Prometheus server 提供。 - 这是**最强大的模板方式**,易于源码管理;但有学习曲线,新手建议先用 [Grafana](/docs/visualization/grafana/)。 #### 8.4.2 Getting started Prometheus 自带一套示例 console,运行中的 Prometheus 在 `/consoles/index.html.example`;若 Prometheus 抓取带 `job="node"` label 的 Node Exporter,会显示 Node Exporter consoles。 示例 console 的 5 个部分:① 顶部 navigation bar;② 左侧 menu;③ 底部 time controls;④ 中间 main content(通常是 graphs);⑤ 右侧 table。 - navigation bar 用于链接到其他系统(如其他 Prometheis、文档等); - menu 用于同一 Prometheus server 内部导航(便于在新标签页打开 console 关联信息); - 两者均在 `console_libraries/menu.lib` 中配置; - time controls 可改变 graph 的 duration 与 range;console URL **可分享**且他人看到相同 graphs; - 主内容通常是 graphs,提供**可配置的 JavaScript 绘图库**,负责向 Prometheus 请求数据并通过 [Rickshaw](https://shutterstock.github.io/rickshaw/) 渲染; - 右侧 table 以比 graphs 更紧凑的形式展示统计。 示例 console 代码(展示 tasks 数、up 数、平均 CPU、平均内存,以及 queries-per-second 图): ```text {{template "head" .}} {{template "prom_right_table_head"}} MyJob {{ template "prom_query_drilldown" (args "sum(up{job='myjob'})") }} / {{ template "prom_query_drilldown" (args "count(up{job='myjob'})") }} CPU {{ template "prom_query_drilldown" (args "avg by(job)(rate(process_cpu_seconds_total{job='myjob'}[5m]))" "s/s" "humanizeNoSmallPrefix") }} Memory {{ template "prom_query_drilldown" (args "avg by(job)(process_resident_memory_bytes{job='myjob'})" "B" "humanize1024") }} {{template "prom_right_table_tail"}} {{template "prom_content_head" .}}

MyJob

Queries

{{template "prom_content_tail" .}} {{template "tail"}} ``` - `prom_right_table_head` 与 `prom_right_table_tail` 模板承载右侧表格,**可选**。 - `prom_query_drilldown` 会求值传入的 expression、格式化,并链接到 [expression browser](/docs/visualization/browser/) 中的表达式;**第 1 个参数是表达式,第 2 个是用单位,第 3 个是输出格式;仅第 1 个必需**。 - `prom_query_drilldown` 第 3 参数的合法输出格式: | 格式 | 含义 | | --- | --- | | 未指定 | 默认 Go 显示输出 | | `humanize` | 用 metric prefixes 显示 | | `humanizeNoSmallPrefix` | 绝对值 >1 用 metric prefixes,绝对值 <1 显示 3 位有效数字(避免 `humanize` 产生的 milliqueries/s 之类单位) | | `humanize1024` | 以 1024 而非 1000 为基做 humanize(常与第二参数 `B` 搭配产生 `KiB`、`MiB`) | | `printf.3g` | 显示 3 位有效数字 | 自定义格式可定义,示例见 prom.lib(release-2.55 分支)。 #### 8.4.3 Graph Library 调用方式 ```text
``` `head` 模板加载所需的 Javascript 与 CSS。 **Graph library 参数表(逐项)**: | 参数 | 必需 | 含义 | 默认值 | | --- | --- | --- | --- | | `expr` | 必需 | 要绘制的表达式,可为列表 | — | | `node` | 必需 | 渲染进的 DOM 节点 | — | | `duration` | 可选 | 图的时间跨度 | 1 小时 | | `endTime` | 可选 | 图结束的 Unixtime | now | | `width` | 可选 | 图宽度(不含标题) | 自动检测 | | `height` | 可选 | 图高度(不含标题与图例) | 200 像素 | | `min` | 可选 | x 轴最小值 | 最低数据值 | | `max` | 可选 | y 轴最大值 | 最高数据值 | | `renderer` | 可选 | 图类型 `line` 或 `area`(堆叠图) | `line` | | `name` | 可选 | 图例与 hover detail 中的标题;传字符串时 `[[ label ]]` 会被替换为 label 值,传函数时会被传入 labels map 且应返回字符串,可为列表 | — | | `xTitle` | 可选 | x 轴标题 | `Time` | | `yUnits` | 可选 | y 轴单位 | 空 | | `yTitle` | 可选 | y 轴标题 | 空 | | `yAxisFormatter` | 可选 | y 轴数字格式化器 | `PromConsole.NumberFormatter.humanize` | | `yHoverFormatter` | 可选 | hover detail 数字格式化器 | `PromConsole.NumberFormatter.humanizeExact` | | `colorScheme` | 可选 | 绘图配色,可为十六进制色码列表或 Rickshaw 支持的 color scheme 名之一 | `'colorwheel'` | - 若 `expr` 与 `name` 均为列表,**长度必须相同**,name 会应用于对应 expression 的 plots。 - `yAxisFormatter` 与 `yHoverFormatter` 的合法选项:`PromConsole.NumberFormatter.humanize`(用 metric prefixes)、`PromConsole.NumberFormatter.humanizeNoSmallPrefix`(绝对值 >1 用 metric prefixes,<1 用 3 位有效数字)、`PromConsole.NumberFormatter.humanize1024`(以 1024 而非 1000 为基)。 > 引自:[Console templates](https://prometheus.io/docs/visualization/consoles/) #### 8.4.4 Console 设计实践(Consoles and dashboards) > **重要警示(版本变更)**:自 Prometheus 3.0 起,console templates 与 console libraries **不再随 Prometheus 一起打包**。若要使用 console templates,必须自备模板与库,并通过命令行参数 `--web.console.templates` 与 `--web.console.libraries` 指定。官方页面仅为历史参考,用于演示 console templates 的能力;2.x 分支中引用的 console libraries **已不再维护**,可能包含已知安全漏洞(CVE)。 **设计理念**:**不要试图在一张 dashboard 上展示尽可能多的数据**,否则会因信息过载而变得难以理解,连系统专家也难以提炼含义。 应改为思考**最可能的失败模式**,以及如何用 console 区分它们;善用服务的结构。例如在线服务系统是一棵服务树时,下层服务延迟是典型问题——不要把所有服务信息放进一张大 dashboard,而应**为每个服务建独立 dashboard**,包含它所对话的每个服务的延迟与错误,从而从顶层逐层向下定位到出问题的服务。 **有效指南**: - 一个 console 上的图**不超过 5 个**; - 每个图上**不超过 5 条线(plots)**;若是堆叠/面积图可以多些; - 使用官方模板示例时,右侧表格不超过 **20–30 行**。 超出上述限制时,应**降低次要信息的可见性**:改画聚合而非细分数据、移到右侧表格、或在很少用到时直接删除(随时可在 expression browser 中查看)。 **多主人问题**:一组 console 很难同时服务多个"主人"——oncall 时想知道"什么坏了",开发时想知道"多少人命中了 corner case X",两者需求差异很大;此时可维护**两套独立**的 console。 > 引自:[Consoles and dashboards](https://prometheus.io/docs/practices/consoles/) ### 8.5 可视化选型建议 > 引自:[Visualization](https://prometheus.io/docs/visualization/)(该页仅有 frontmatter,无正文;为 Visualization 章节的导航/索引页,正文由 browser、grafana、perses、consoles 子页面承载) | 场景 | 推荐方案 | 理由 | | --- | --- | --- | | 临时排查、验证一条 PromQL 对不对 | **表达式浏览器(`/graph`)** | 无需额外部署,table/graph 两种视图,官方定位就是 ad-hoc 与 debugging | | 生产级 dashboard、团队共享、告警联动 | **Grafana** | 自 2.5.0 起内置 Prometheus 数据源;生态成熟、面板丰富、支持 `$__rate_interval` 等实用变量;有 Grafana.com 预建 dashboard 库 | | dashboard as code / GitOps 流水线 | **Perses** | 原生支持 Prometheus,聚焦 GitOps 与 dashboard as code,默认无登录、轻量(端口 8080) | | 极客式的自定义 console(Go 模板、可拼接任意 HTML/JS) | **Console 模板** | 最强大、易源码管理,但有学习曲线;**3.0 起不再随包分发,需自备模板并通过 `--web.console.templates` / `--web.console.libraries` 指定,且官方不再维护、可能存在 CVE** | | 新手 / 快速上手 | **Grafana**(而非 Console 模板) | 官方明确建议新手先用 Grafana | **通用建图原则**(来自 console 设计实践,对 Grafana 同样适用): - 一张图不要塞太多信息,思考"最可能的失败模式"而非"能放多少数据"; - 图数量、线条数、表格行数都应有上限(5 / 5 / 20–30 的经验值); - 按服务/子系统拆分 dashboard,而不是做一张超级大盘; - 长期维护的 dashboard 应以代码方式管理(Perses / Grafana 的 dashboard JSON / Mixins)。 ## 第 9 章 规范(OpenMetrics / Remote Write / Native Histograms) 本章是全篇最"硬"的一章,逐份梳理 Prometheus 生态的四套规范性文档:**OpenMetrics 1.0 / 2.0**(暴露格式)、**Remote Write 1.0 / 2.0**(远程写)、**Native Histograms**(原生直方图数据模型与编码),最后给出 **OpenMetrics 2.0 的迁移指南**。 > 导航说明:`/docs/specs/`、`/docs/specs/om/`、`/docs/specs/prw/` 三个 URL 均为**仅含 frontmatter 的章节索引页,正文为空**,实质内容全部在同目录的规范文件中。以下各节分别以对应规范文件为源。 > 引自:[Specifications](https://prometheus.io/docs/specs/)(导航页) > > 引自:[OpenMetrics](https://prometheus.io/docs/specs/om/)(导航页) > > 引自:[Remote Write](https://prometheus.io/docs/specs/prw/)(导航页) ### 9.1 OpenMetrics 1.0 规范 **元信息**:Version 1.0,Status: **Published**,Date: November 2020;作者/编辑 Richard Hartmann (Grafana Labs)、Ben Kochie (GitLab)、Brian Brazil (Robust Perception)、Rob Skillington (Chronosphere)。定位为 Prometheus exposition format 0.0.4(2014 起稳定)的**清理收紧版**,目标提交 IETF。明确声明:对 0.0.4 或 OpenMetrics 1.0 的**颠覆性改动属 out of scope**;未来版本将**始终要求**语法与语义上支持 1.0。 > **关于 2.0 的两处原文注记**:① NOTE:OpenMetrics 2.0 开发进行中,加入方式见 github.com/prometheus/OpenMetrics/issues/276;② NOTE:Prometheus 生态**不支持** OpenMetrics protobuf schema,改用相近的 `io.prometheus.client` 格式;protobuf schema 在 2.0 的前景讨论见 issues/296。全文**未**把 gaugehistogram / UTF-8 / 单位处理描述为"2.0 新增"——**gaugehistogram 与 UTF-8 label value 在 1.0 中即已存在**。 #### 9.1.1 Overview 与数据模型 - Metrics 是一类遥测数据,代表当前状态快照,区别于 logs/events(面向单事件)。主要是 **wire format**,与 transport 无关,期望周期消费且**跨多次 exposition 有意义**。 - Implementers **MUST** 响应简单 HTTP GET 到文档化 URL 暴露文本格式;该 endpoint **SHOULD** 名为 `/metrics`;**MAY** 以其他方式(如 HTTP push)暴露。 **Data Types**: - Values **MUST** 为浮点或整数;ingestor **MAY** 仅支持 float64。NaN、+Inf、-Inf **MUST** 支持;NaN **MUST NOT** 视为缺失值,**MAY** 表示除零。 - Booleans **MUST** `1==true`、`0==false`。Timestamps **MUST** 为 Unix Epoch 秒,负值 **MAY**。 - Strings **MUST** 仅合法 UTF-8,**MAY** 零长度;NULL(0x0) **MUST** 支持。 - **Label**:key-value 字符串对;下划线开头的 label name 为 RESERVED,**MUST NOT** 使用(规范指明者除外);空 label value **SHOULD** 视同不存在。LabelSet **MUST** 由 Label 组成、**MAY** 空,label name **MUST** 唯一。 - **Exemplars**:**MUST** 由一个 LabelSet + 一个 value 构成,**MAY** 带 timestamp,其 LabelSet/timestamp **MAY** 与 MetricPoint 不同;LabelSet 的 name+value 总长 **MUST NOT** 超过 **128 UTF-8 code point**(`",=` 等不计入)。Ingestor **MAY** 丢弃。 - **Metric**:在 MetricFamily 内由唯一 LabelSet 定义,**MUST** 含一个或多个 MetricPoint;同名 Metric **SHOULD** 有相同 label name 集;MetricPoint **SHOULD NOT** 带显式 timestamp;多个 MetricPoint 时 timestamp **MUST** 单调递增。 - **MetricFamily**:**MAY** 含零或多个 Metric;**MUST** 有 name、HELP、TYPE、UNIT;每 Metric **MUST** 唯一 LabelSet。Name **MUST** 在 MetricSet 内唯一、**SHOULD** 为 snake_case;冒号 RESERVED(表示计算/聚合而来);下划线开头 RESERVED。 - **Suffixes 冲突**:MetricFamily 名 **MUST NOT** 与另一 MetricFamily 产生样本名冲突(如 gauge `foo_created` 撞 counter `foo` 生成的 `foo_created`)。各类型后缀见下表。 - **Type** 合法值(8):`unknown`、`gauge`、`counter`、`stateset`、`info`、`histogram`、`gaugehistogram`、`summary`。 - **Unit**:非空时 **MUST** 是 name 以 underscore 分隔的后缀;后续规则可能使其在文本格式中成 infix。Help **SHOULD** 非空且简短。 - **MetricSet**:顶层对象,**MUST** 由 MetricFamily 组成、**MAY** 空;name **MUST** 唯一;同一 label name/value **SHOULD NOT** 出现在每个 Metric 上。无强制排序;名为 `target` 的 Info MetricFamily **SHOULD** 排最前。 各 MetricFamily 类型后缀: | 类型 | 后缀 | | --- | --- | | Counter | `_total`、`_created` | | Summary | `_count`、`_sum`、`_created`、``(空) | | Histogram | `_count`、`_sum`、`_bucket`、`_created` | | GaugeHistogram | `_gcount`、`_gsum`、`_bucket` | | Info | `_info` | | Gauge / StateSet / Unknown | ``(空) | #### 9.1.2 Metric Types(全部 8 类) | 类型 | 定义与关键约束 | | --- | --- | | **Gauge** | 当前测量值,关注绝对值;MetricPoint **MUST** 单值;**MAY** 增/减/不变;**MAY** 编码多状态枚举 | | **Counter** | 度量离散事件,关注增速;MetricPoint **MUST** 有一个 Total;Total 非 NaN 且 **MUST** 随时间单调非减、从 0 起。**SHOULD** 有名为 Created 的 Timestamp。Total **MAY** 重置为 0,此时 Created **MUST** 同步设为重置时刻。Total **MAY** 带 exemplar | | **StateSet** | 一组相关布尔值(bitset),可编码 ENUM;MetricPoint **MAY** 多状态、**MUST** 每 State 一个布尔;State 有 String 名。Metric 的 LabelSet **MUST NOT** 含与 MetricFamily 同名的 label。ENUM 编码时 MetricPoint 内 **MUST** 恰一个布尔为 true。Unit **MUST** 为空 | | **Info** | 暴露进程生命周期内 **SHOULD NOT** 变化的文本信息;MetricPoint 含一 LabelSet,其 LabelSet **MUST NOT** 含与其 Metric 的 LabelSet 中某 label 同名的 label。**MAY** 编码不变 ENUM。Unit **MUST** 为空 | | **Histogram** | 度量离散事件分布;MetricPoint **MUST** 至少一个 bucket,**SHOULD** 含 Sum 与 Created;每 bucket **MUST** 有 threshold 与 value。**MUST** 有 +Inf bucket;bucket **MUST** 累积。+Inf 计全部请求;若含 Sum,Sum **MUST** 等于被测事件值之和;同一 MetricPoint 内 threshold **MUST** 唯一。Sum 与 bucket value 语义上是 counter,**MUST NOT** 为 NaN 或负数。**MAY** 用负 threshold bucket,但此时 **MUST NOT** 含 sum。threshold **MUST NOT** 为 NaN;Count 与 bucket value **MUST** 为整数。**SHOULD** 有 Created。Metric 的 LabelSet **MUST NOT** 含 `le`。Bucket value **MAY** 带 exemplar,每 bucket **MUST NOT** 多于一个;exemplar value **MUST** 落在该 bucket 范围(≤该值),**SHOULD** 放入最高值 bucket | | **GaugeHistogram** | 度量当前分布;MetricPoint **MUST** 有一个 +Inf bucket,**SHOULD** 含 Gsum;每 bucket **MUST** 有 threshold 与 value,bucket 规则同 Histogram。bucket 与 Gsum 概念上是 gauge,但 bucket value **MUST NOT** 为负或 NaN;若有负 threshold bucket 则 sum **MAY** 为负;Gsum **MUST NOT** 为 NaN;bucket value **MUST** 为整数。Metric LabelSet **MUST NOT** 含 `le`。bucket value 可有 exemplar,规则同上 | | **Summary** | 度量离散事件分布,Histogram 过贵或均值足够时 **MAY** 用;**MAY** 用于向后兼容(旧库暴露预计算 quantile,**SHOULD NOT** 使用,因不可聚合、时间跨度难定)。MetricPoint **MAY** 含 Count、Sum、Created 及一组 quantile。Count/Sum 语义上是 counter,**MUST NOT** 为 NaN 或负数;Count **MUST** 为整数。含 Count/Sum 时 **SHOULD** 有 Created,且 Created **MUST NOT** 关联 quantile 采集周期。quantile 是 quantile→value 映射;无事件时值 **MUST** 为 NaN。Metric LabelSet **MUST NOT** 含 `quantile`。quantile **MUST** 在 [0,1];quantile value **MUST NOT** 为负;**SHOULD** 反映近期值(通常最近 5–10 分钟) | | **Unknown** | **SHOULD NOT** 使用,仅无法判断第三方系统类型时 **MAY** 用;MetricPoint **MUST** 单值 | #### 9.1.3 Wire formats 与协商 - 文本格式 **MUST** 支持且为默认;protobuf **MAY** 支持且 **MUST** 仅在协商后使用。两者均为 **Regular Chomsky Grammar**。**Partial 或 invalid exposition MUST 整体视为错误。** - 所有 ingestor **MUST** 能接收 TLS 1.2+ 数据;exposer **SHOULD** 能发出 TLS 1.2+;ingestor **SHOULD** 能接收无 TLS 的 HTTP;所有实现 **SHOULD** 用 TLS。版本协商 out-of-band;pull 用 HTTP content-type negotiation,**无更新版本请求时 MUST 默认最旧版本(1.0.0)**;push 场景 producer **MUST** 用 1.0.0,除非 ingestor 另有要求。 #### 9.1.4 ABNF(RFC 5234,`exposition` 为顶层 token) ```abnf exposition = metricset HASH SP eof [ LF ] metricset = *metricfamily metricfamily = *metric-descriptor *metric metric-descriptor = HASH SP type SP metricname SP metric-type LF metric-descriptor =/ HASH SP help SP metricname SP escaped-string LF metric-descriptor =/ HASH SP unit SP metricname SP *metricname-char LF metric = *sample metric-type = counter / gauge / histogram / gaugehistogram / stateset metric-type =/ info / summary / unknown sample = metricname [labels] SP number [SP timestamp] [exemplar] LF exemplar = SP HASH SP labels SP number [SP timestamp] labels = "{" [label *(COMMA label)] "}" label = label-name EQ DQUOTE escaped-string DQUOTE number = realnumber number =/ [SIGN] ("inf" / "infinity") ; 大小写不敏感 number =/ "nan" timestamp = realnumber realnumber = [SIGN] 1*DIGIT realnumber =/ [SIGN] 1*DIGIT ["." *DIGIT] [ "e" [SIGN] 1*DIGIT ] realnumber =/ [SIGN] *DIGIT "." 1*DIGIT [ "e" [SIGN] 1*DIGIT ] eof = %d69.79.70 ; 大写;type/help/unit 同为字面大写 counter = %d99.111.117.110.116.101.114 ; 及 gauge/histogram/stateset/info/summary/unknown 小写字面量 gaugehistogram = gauge histogram BS="\"; EQ="="; COMMA=","; HASH="#"; SIGN="-" / "+" metricname = metricname-initial-char 0*metricname-char metricname-char = metricname-initial-char / DIGIT metricname-initial-char = ALPHA / "_" / ":" label-name = label-name-initial-char *label-name-char label-name-char = label-name-initial-char / DIGIT label-name-initial-char = ALPHA / "_" escaped-string = *escaped-char escaped-char = normal-char escaped-char =/ BS ("n" / DQUOTE / BS) escaped-char =/ BS normal-char normal-char = %x00-09 / %x0B-21 / %x23-5B / %x5D-D7FF / %xE000-10FFFF ; 除换行/双引号/反斜杠外任意 Unicode ``` #### 9.1.5 Overall Structure 与 Escaping - **MUST** 用 UTF-8;**BOM MUST NOT 使用**(byte 0 合法、byte 255 非法)。 - Content type **MUST** 为 `application/openmetrics-text; version=1.0.0; charset=utf-8`。 - 行尾 **MUST** 用 LF(\n)、**MUST NOT** 含 CR(\r)。Exposition **MUST** 以 EOF 结束,**SHOULD** 以 `EOF\n` 结束。 - **Escaping(ABNF 标注处 MUST 应用)**:换行 `\n`(0x0A)→`\\n`(0x5c 0x6e);双引号→`\\"`(0x5c 0x22);反斜杠→`\\\\`(0x5c 0x5c)。表示反斜杠 **SHOULD** 用双反斜杠;未定义转义序列 **SHOULD NOT** 用单反斜杠。 #### 9.1.6 Numbers / Canonical Numbers - 整数 **MUST NOT** 含小数点(`23`、`0042`);浮点 **MUST** 用小数点或科学计数法(`8903.123421`、`1.89e-7`),**MUST** 落在 IEEE 754 64 位范围内(可丢精度,可用于纳秒时间戳)。 - `quantile`/`le` label value **MUST NOT** 用任意渲染,其他位置 **MAY**。 - 目标渲染等价 Go float64 的 `%g` 并在无小数点/指数时补 `.0`;Exposer **MUST** 将正无穷输出为 `+Inf`;**SHOULD** 对 0.0–10.0 按 0.001 步进、对 1e-10–1e+10 按 10 的幂输出。示例:`0.0 0.001 0.002 0.01 0.1 0.9 0.95 0.99 0.999 1.0 1.7 10.0`;`1e-10 1e-09 1e-05 0.0001 0.1 1.0 100000.0 1e+06 1e+10`。 - Parser **MUST NOT** 仅因不符合 canonical 而拒收(如 `1.1e-4`)。C 语言 printf 需 17 位有效数字(`%.17g`)。纳秒时间戳 **SHOULD NOT** 用指数浮点渲染。 #### 9.1.7 结构约束 - MetricFamily 间 **MUST NOT** 有显式分隔,下一 MetricFamily **MUST** 以元数据或不可能属于前者的新样本名标示;MetricFamily **MUST NOT** 交错。 - 元数据四要素 name/TYPE/UNIT/HELP;无 TYPE 则 **MUST** 为 Unknown。 - 有 unit 则 **MUST** 提供 UNIT 行且 underscore+unit **MUST** 为 name 后缀(`foo_seconds`+`seconds` 合法;`foo`+`seconds` 非法);已知 unit **SHOULD** 提供。 - UNIT/HELP 值 **MAY** 为空,等同无该行。每类元数据 **MUST NOT** 多于一条,顺序 **SHOULD** 为 TYPE、UNIT、HELP。 - 除元数据与 EOF 外 **MUST NOT** 暴露 `#` 开头行。Metric **MUST NOT** 交错;MetricPoint **MUST NOT** 交错。 #### 9.1.8 各类型文本行格式 | 类型 | 文本行规则 | | --- | --- | | Gauge | 样本名 **MUST** 无后缀(`foo 17.0`) | | Counter | Total 样本名 **MUST** 带 `_total`;Created 若存在 **MUST** 带 `_created`;exemplar **MAY** 挂 Total | | StateSet | 样本名 **MUST** 无后缀;每 State 一个样本,样本 **MUST** 有以 MetricFamily 名为 label name、State 名为 label value 的 label;值 true→1、false→0(`foo{foo="bb"} 1`) | | Info | 样本名 **MUST** 带 `_info`;样本值 **MUST** 恒为 1;Metric label 与 value label **MAY** 任意顺序 | | Summary | Sum→`_sum`、Count→`_count`、Created→`_created`;quantile 用 label name `quantile`;quantile **MAY** 任意顺序 | | Histogram | bucket 样本名 **MUST** 带 `_bucket`;Sum→`_sum`;Created→`_created`;**当且仅当**存在 Sum 时,+Inf bucket 值 **MUST** 同时以 `_count` 后缀样本出现。bucket **MUST** 按 `le` 数值升序,`le` **MUST** 遵循 Canonical Numbers | | GaugeHistogram | bucket 样本名 **MUST** 带 `_bucket`;Sum→`_gsum`;当且仅当存在 Sum 时 +Inf bucket 值 **MUST** 同时以 `_gcount` 后缀样本出现 | | Unknown | 样本名 **MUST** 无后缀 | Exemplar 示例(Histogram): ```text foo_bucket{le="0.1"} 8 # {} 0.054 foo_bucket{le="1"} 11 # {trace_id="KOO5S4vxi0o"} 0.67 foo_bucket{le="10"} 17 # {trace_id="oHg5SJYRHA0"} 9.8 1520879607.789 ``` 无 label 的 exemplar **MUST** 表示为 `{}`。 #### 9.1.9 Protobuf format(1.0) - 消息 **MUST** 二进制编码,content type **MUST** 为 `application/openmetrics-protobuf; version=1.0.0`;payload **MUST** 是单个 MetricSet 消息。 - **MUST** 遵循 proto3。字符串字段 **MUST** UTF-8。 - Timestamp **MUST** 遵循 `google.protobuf.Timestamp`:epoch 秒 int64 + 非负纳秒小数 int32,**MUST** 在 0–999,999,999 闭区间。 - schema 见 OM 仓库 `proto/openmetrics_data_model.proto`。 #### 9.1.10 Design Considerations(关键条目) | 主题 | 要点 | | --- | --- | | Scope | 无实时保证;ingestor 与 exposer 的互相发现属 out of scope | | Extensions | 基于 0.0.4 刻意不加大改;无非透明二进制类型(sketch/t-digest);unit 在 name 中重复 | | Units | 基于 SI base unit(seconds、bytes、joules、grams、meters、ratios、volts、amperes、celsius);bytes 而非 bits;grams 而非 kilograms;ratio 而非百分比;decibels 非 base;非 base 无法避免时仍应把单位写进 name | | Statelessness | 跨 exposition 无状态,先前暴露 **MUST NOT** 影响后续;同一自包含 exposition **MUST** 提供给新老 ingestor;exposer **MUST NOT** 剔除近期无变化的指标 | | Metric Evolution | **SHOULD** 避免改变 label 顺序;Metrics/samples **SHOULD NOT** 忽现忽隐;counter 应持续暴露到进程终止。改 type 或增删 label 通常破坏性。非破坏性:给 Info value 加 label、改 Help、float/int 互切、给 stateset 加状态。Histogram bucket **SHOULD NOT** 跨 exposition 变化 | | NaN | 无特殊含义,**MUST NOT** 用作缺失/坏数据标记。Missing Data:无特殊标记,后续 exposition 直接不含该 Metric | | Performance | **SHOULD** 不超过 1 秒;**SHOULD** 暴露最新状态、**SHOULD NOT** 依赖缓存。Concurrency:并发 exposition **MUST** 支持,遵循并发 BCP | | Naming | snake_case;建议公司前缀(`acme_`);`scrape_` 前缀由 ingestor 用于 exposition 信息,应用不应直接暴露;生态有 `process_` 系列(`process_open_fds`、`process_max_fds`);避免 metric/timer/stats/counter/total/float64 等冗余子串;不把 label name 写进 metric name;不把 "openmetrics"/"prometheus" 等实现细节写进名字 | | Label Namespacing | label name 不推荐显式命名空间;避免易冲突名:region、zone、cluster、availability_zone、az、datacenter、dc、owner、customer、stage、service、team、job、instance、environment、env;`type` 过于泛化应避免(HTTP 场景用 `method`)。label name 无元数据 | | Metric Names vs Labels | 求和/平均应仍有意义;把 total 与成员并列暴露是错的(会双计);另宜分开 total/failure 及 read/write、send/receive | | Name Characters | 向后兼容是核心目标,字符集不扩不缩;label value 支持完整 UTF-8 | | Metadata 两类 | Target metadata(来自服务发现/CMDB,优先由 ingestor 加 label)与 Exposer metadata(软件版本、编译器版本、Git SHA)。提供 target metadata 首选方案是名为 `target` 的 Info MetricFamily(`target_info{env="prod",...} 1`),**SHOULD** 排最前;exposer **MUST NOT** 给所有 Metric 加 target metadata label(除非为特定 ingestor 显式配置),**MUST NOT** 因 target metadata 前缀化/变更 MetricFamily 名;通用监控系统 exposition 中 metric 名 **MUST NOT** 因 target metadata 变化(如不得全局加 `staging_` 前缀) | | Client Calculations | 计算应留给 ingestor(Summary quantile 为兼容例外);不应暴露 counter 平均增速 gauge、histogram/summary 平均事件大小、标准差(应暴露 sum of squares counter,因 64 位精度不足未纳入)、请求失败率(应分别暴露失败与总数 counter) | | Number Types | counter 每秒百万次需一世纪才丢精度,100 Gbps 满吞吐约 20 小时即丢精度;高吞吐整数可选 int64;Summary quantile **MUST** 为 float64 | | Exposing Timestamps | 一般 MetricPoint timestamp 不应暴露,应由 ingestor 自加;建议把最后变更时刻做成独立 Gauge(如 `my_counter_last_increment_timestamp_seconds`);绝对时间戳(epoch)比 elapsed 稳健(如 `my_boot_time_seconds`)。exemplar timestamp 无最佳实践限制;截断到秒可能重复,此时 **MUST** 用最早 timestamp 的 MetricPoint | | Size Limits | 不规定样本/label/state 数上限;ingestor **MAY** 自设限制(防攻击),任一 value/metric/exposition 超限则整个 exposition **必须被拒**。性能三要素:唯一时间序列数、随时间样本数、唯一字符串数。2020 年单实例 ingestor 约 **1000 万时间序列**为大;单个 exposition **SHOULD NOT** 超 **10k 时间序列**;singleton target 几十万可接受。exemplar 硬上限 **128 UTF-8 字符** | #### 9.1.11 Security 与 IANA - **Security**:实现 **MAY** 提供认证/授权/计费,**SHOULD** 在 OpenMetrics 之外处理;exposer **SHOULD** 能 TLS 1.2+ 保护 HTTP;不支持加密时 **SHOULD** 用反向代理/防火墙/ACL;`/metrics` 不宜置于 TCP/80、443、8080、8443(面向公开服务)。 - **IANA**:IANA 为 OpenMetrics 客户端暴露数据分配的端口为 **9099**。多 endpoint 共用 IP:port 时建议反向代理,endpoint **SHOULD** 在路径带自己名字(如 `/node_exporter/metrics`)。拟注册两个 MIME 类型:`application/openmetrics-text` 与 `application/openmetrics-proto`(编辑注:前者自 2018 年已在用,后者尚未投入使用)。 > 引自:[OpenMetrics 1.0](https://prometheus.io/docs/specs/om/open_metrics_spec/) ### 9.2 OpenMetrics 2.0 规范 #### 9.2.1 版本与状态 - Version: **2.0.0-rc0**,Status: **Experimental**,Date: March 2026。 - 作者:Arthur Silva Sens、Bartłomiej Płotka、David Ashpole、György Krajcsovits、Owen Williams、Richard Hartmann;Emeritus:Ben Kochie、Brian Brazil、Rob Skillington。 - 定位:以 OpenMetrics 1.0 为基础,**对齐现代 Prometheus 数据模型与 OpenTelemetry 数据模型/命名约定**;本文件为 **standalone spec**。 - 明确注释:RC 版本,不预期大改动,但保留基于早期采用者反馈**破坏兼容性**的权利;反馈走 `prometheus/openmetrics` issue。 - 历史脉络:2012 Prometheus 诞生;Prometheus text exposition 0.0.4 自 2014 稳定;2020 发布 OpenMetrics 1.0;约 2024 OpenMetrics 项目并入 CNCF Prometheus 项目伞下。 #### 9.2.2 Overview 与规范性语言 - OpenMetrics 首要是一个 **wire format**(线格式),不绑定任何特定传输;期望被周期性消费,跨多次 exposition 有意义。 - 实现 **SHOULD** 以 HTTP GET 响应形式在文档化 URL 暴露,该端点 **SHOULD** 叫 `/metrics`;**MAY** 以 push 等其它方式暴露。 - 指标表达为数值:counts、current values、distributions、enumerations、boolean states。Time series 示例:网络接口计数器、设备温度、BGP 连接状态、延迟分布、告警状态。 - 规范性关键词按 **RFC 2119 / RFC 8174**,**仅当全大写时生效**。 - `RESERVED` 一词表示保留给未来或本标准自用;被标 RESERVED 的值/名/字段 **MUST NOT** 使用,除非本标准或未来版本明确允许。 - Data Model 一节 **MUST** 与 ABNF 一节一起读;两者冲突时**以 ABNF 的限制优先**。 #### 9.2.3 Data Model(数据模型) **Sample Values 与数据类型**: - Metric 值 **MUST** 为 Number 或 CompositeValue。 - **Number** **MUST** 为浮点或整数。Ingestor **MAY** 仅支持 float64(IEEE 754-2008 binary64,约 15–17 位有效十进制数字)。非实数值 **NaN、+Inf、-Inf MUST 被支持**;NaN **MUST NOT** 视为缺失值,但 **MAY** 用于表示除零或其它未定义/不定结果。 - **Boolean** **MUST** 用 Number 表示:`1` = true,`0` = false。 - **CompositeValue** **MUST** 包含在 MetricFamily 内重建该 sample 值所需的全部信息。**MUST** 使用 CompositeValue 的类型:**Histogram、GaugeHistogram、Summary**;其它 MetricFamily 类型 **MUST** 用 Number。 **Timestamps**:**MUST** 为 Unix Epoch 秒;**SHOULD** 用浮点表示亚秒精度(毫秒/微秒);负时间戳 **MAY** 使用。出现位置:Exemplar 的 Timestamp、Sample 的 Timestamp、Sample 的 Start Timestamp。 **Strings / Label / LabelSet**: - String **MUST** 仅含合法 UTF-8,**MAY** 为零长度;**NULL (ASCII 0x0) MUST 被支持**。 - Label 为字符串键值对。label name 以一个或多个下划线开头的 **RESERVED,MUST NOT 使用**(除非本标准规定);此类名 **MAY** 在 MetricFamily 元数据可能冲突时(如 metric federation)替代 TYPE/UNIT 元数据。 - Label names **SHOULD** 遵循 ABNF 的 `label-name`;**MAY** 为任意带引号转义 UTF-8 字符串(注意可降低可用性)。空 label 值 **SHOULD** 视作该 label 不存在。 - LabelSet **MUST** 由 Label 组成,**MAY** 为空;同一 LabelSet 内 label name **MUST** 唯一。 **Exemplars**: - Exemplar 是 MetricSet 外数据的引用(常见用途:trace ID)。**MUST** 含 LabelSet + Number 值,**MUST 有 timestamp**。 - 其 LabelSet **SHOULD NOT** 含 Metric LabelSet 中已有的 label name;timestamp **SHOULD** ≤ Sample timestamp(若存在),且 **SHOULD** ≥ Sample start timestamp(若存在)。 - 同一 Sample 的 exemplar **SHOULD** 有相同 label names。timestamp **SHOULD** 接近观测时刻但不必精确。 - 引用 W3C Trace Context 时 **SHOULD** 用 key `trace_id`(对应 trace-id)与 `span_id`(对应 parent-id)。 - Ingestor **MAY** 截断 exemplar LabelSet 或丢弃 exemplar;截断时 **SHOULD** 保留 `trace_id` 与 `span_id`。 **Sample / Metric / MetricFamily**: - **Sample**:Metric 内单个数据点;**MUST** 有 Value,**MAY** 有 Timestamp、**MAY** 有 Exemplars、**MAY** 有 Start Timestamp(视类型)。Sample **SHOULD NOT** 带 Timestamp。Start Timestamp 存在时 **SHOULD** 表示测量周期开始时间(帮助区分新/长期指标、检测 counter reset)。 - **Metric**:由 MetricFamily 内唯一 LabelSet 定义;**MUST** 含 1 个或多个 Sample;多于一个 Sample 时其 timestamp **MUST** 单调递增。同名 Metric **SHOULD** 有相同 label name 集合。 - **MetricFamily**:**MAY** 含 0 或多个 Metric;每个 Metric 的 LabelSet **MUST** 唯一;**MUST** 有 name,**SHOULD** 有 Help、Type、Unit。 - **Name**:**MUST** 为 string;**MUST** 在 MetricSet 内唯一;**MUST 与族内每个 Metric 的 Name 相同**。**SHOULD** 用 snake_case;**SHOULD** 遵循 ABNF `metricname`;**MAY** 为带引号转义 UTF-8。名字中冒号 RESERVED,表示该族是通用监控系统计算/聚合的结果;以一个或多个下划线开头的名字 RESERVED 且 **MUST NOT** 使用。 - **Discouraged Suffixes**:族名 **SHOULD NOT** 以 `_count`、`_sum`、`_gcount`、`_gsum`、`_bucket` 结尾;**SHOULD NOT** 在转换为 1.0 文本时产生 MetricName 碰撞。Ingestor **MAY** 拒收此类 MetricSet。反例:gauge `foo_bucket` 与 histogram `foo` 冲突。 - **Type**:合法值 `unknown`、`gauge`、`counter`、`stateset`、`info`、`histogram`、`gaugehistogram`、`summary`。 - **Unit**:非空时 **SHOULD** 是族名的后缀(下划线分隔),类型特定后缀在 unit 后缀之后。单位取值见 Units and Base Units。 - **Help**:string,**SHOULD** 非空,简短、可作 tooltip。 - **MetricSet**:顶层对象;**MUST** 由 MetricFamily 组成,**MAY** 为空;每个族名 **MUST** 唯一;同一 label name+value **SHOULD NOT** 出现在集合内每个 Metric 上。族间无强制排序(**MAY** 为可读性排序)。若存在符合 target metadata 约定的 Info 族 `target_info`,**SHOULD** 放在最前。 #### 9.2.4 MetricFamily Types(八种类型) - **Gauge**:当前测量(内存字节、队列项数),关注绝对值。Sample **MUST** 为 Number;**MAY** 增/减/恒定(仅单向也仍可能是 gauge)。 - **Counter**:测量离散事件(HTTP 请求数、CPU 秒、发送字节),关注增长率。族名 **SHOULD** 以 `_total` 结尾。Sample **SHOULD** 有 Start Timestamp;**MUST** 为**非 NaN** Number,**MUST** 单调非递减(除非重置为 0),从 0 开始;**MAY** 重置为 0,此时 Start Timestamp **MUST** 设为近似重置时间。**MAY** 有 exemplars。 - **StateSet**:一组相关 boolean(bitset),ENUM 可经此编码。结构为每个 state 一个 Metric(StateSet **MetricGroup**)。MetricGroup 含 1+ 个 state,每个 state **MUST** 有一个 boolean Metric。若作 ENUM 编码,**MUST** 在单个 Timestamp 下恰好一个 Sample 为 `1`。**Unit MUST 为空字符串**。 - **与 1.0 差异**:1.0 的 Metric 由 MetricPoint 组成;**1.0 Metric ≈ 2.0 MetricGroup,1.0 MetricPoint ≈ 2.0 Metric**。 - **Info**:暴露进程生命周期内 **SHOULD NOT** 变化的文本信息(版本、commit、编译器版本)。族名 **MUST** 以 `_info` 结尾;Unit **MUST** 为空字符串。 - **Histogram**:测量离散事件分布。Sample **MUST** 含 Count 与 Sum。 - Count **MUST** = 测量次数(语义上是 counter),**SHOULD** 为整数,**MUST NOT** 负,**SHOULD NOT** 为 +Inf/NaN。允许 float Count(因直方图算术运算可能超整数范围)。 - Sum **MUST** = 所有被测量值之和;仅当无负测量值时语义上才是 counter。 - **MUST** 测量非 NaN 值(在 Classic 或 Native bucket 中,或两者)。bucket 边界 **MUST NOT** 为 NaN。 - Bucket **MUST** 有良定义边界与值;bucket 值是 counter 语义,**SHOULD** 为整数,**MUST NOT** 负,**SHOULD NOT** 为 +Inf/NaN;允许 float bucket 值。 - **SHOULD NOT** 含 NaN 测量;若含,NaN **MUST** 计入 Count 且 Sum **MUST** 为 NaN。含 +Inf/-Inf 时 **MUST** 计入 Count 并加到 Sum(可能得 +Inf/-Inf/NaN)。 - Sample **SHOULD** 有 Start Timestamp。若用 Classic Buckets,Metric LabelSet **MUST NOT** 含 `le` label name(与 `_bucket` 生成的 `le` 冲突)。 - 类型随时间累积,**MAY** 重置;重置时 Sum/Count/Classic/Native buckets **MUST** 归零,若有 Start Timestamp **MUST** 设为近似重置时间。 - **MAY** 有 exemplars;exemplar 值 **SHOULD** 均匀分布(如每个 Classic Bucket 保留一个)。 - **Classic Buckets**:每个 **MUST** 有 threshold,同 Sample 内 threshold **MUST** 唯一,**MAY** 为负。**MUST** 统计 ≤ threshold 的测量值(含被更低 bucket 统计的),即**累积**。含 Classic Buckets 的 Sample **MUST** 有一个 +Inf threshold bucket;+Inf bucket 统计所有测量;**Count MUST 等于 +Inf bucket 的值**。阈值 **SHOULD** 随时间与目标保持恒定。若允许 NaN,NaN **MUST** 计入 +Inf bucket 且 **MUST NOT** 计入其它 bucket。 - **Native Buckets**:**MUST** 有 Schema 值,为 8-bit 有符号整数,范围 **-4 到 8(含)**,称 Standard (exponential) schema;范围外 RESERVED 且 **MUST NOT** 使用。对 Standard Schema `n`:**MAY** 含正/负 Native Buckets,**MUST** 含 zero Native Bucket;空的正/负 bucket **SHOULD NOT** 出现。 - 边界公式(Python 语法):正 bucket 上含限 `(2**2**-n)**i`,正 bucket 下开限 `(2**2**-n)**(i-1)`;负 bucket 下含限 `-((2**2**-n)**i)`,负 bucket 上开限 `-((2**2**-n)**(i-1))`;`i` 为整数,**MAY** 为负。 - 例外:含 MaxFloat64 的正 bucket 上含限取 MaxFloat64(避免 overflow);下一个正 bucket 下开限 MaxFloat64、上含限 +Inf(正 overflow bucket);含 MinFloat64 的负 bucket 下含限取 MinFloat64(避免 underflow);下一个负 bucket 上开限 MinFloat64、下含限 -Inf(负 overflow bucket)。超出 ±Inf bucket 的 Native Buckets **MUST NOT** 使用。 - zero Native Bucket 边界为 `[-threshold, threshold]`(含);Zero threshold **MUST** 为非负 float64。若 threshold > 0,落入 zero bucket 的值 **MUST** 只计入 zero bucket。Zero threshold **SHOULD** 等于某 Native Bucket 的下限。 - 若不允许 NaN:Count **MUST** = 负+正+zero 三 bucket 之和。若允许 NaN:NaN **MUST NOT** 计入任何 bucket 而 **MUST** 计入 Count,Count 与三 bucket 和之差 **MUST** 为 NaN 观测数。 - **GaugeHistogram**:测量当前分布。Sample **MUST** 含 Gcount、Gsum。Gcount = 当前测量数(gauge 语义),**SHOULD** 为整数,**SHOULD NOT** 为 -Inf/+Inf/NAN/负(允许 float 与负值以支持对直方图做算术,如变化率)。Gsum = 当前所有被测量值之和(gauge 语义)。 - 若从 Classic 或 Native 之一停止测量而继续另一个,**MUST** 清除且不再暴露停止的那类 bucket。Bucket 值 **SHOULD** 为整数、gauge 语义、**SHOULD NOT** 为 -Inf/+Inf/NaN/负。**SHOULD NOT** 含 NaN;若含,NaN **MUST** 计入 Gcount 且 Gsum **MUST** 为 NaN;含 ±Inf 时 **MUST** 计入 Gcount 并加入 Gsum。 - 用 Classic Buckets 时 Metric LabelSet **MUST NOT** 含 `le`。Classic/Native buckets 与 exemplars 规则同 Histogram,Gcount/Gsum 对应 Count/Sum。**无 Start Timestamp**。 - **Summary**:测量离散事件分布,用于 Histogram 太贵且少量预计算分位数够用时。**SHOULD NOT 使用**(分位数不可聚合、用户常无法推出覆盖时间窗),仅为向后兼容 **MAY** 使用。Sample **MUST** 含 Count、Sum、一组 quantiles。Count/Sum 语义为 counter,**MUST NOT** 为 NaN 或负;Count **MUST** 为整数。**SHOULD** 有 Start Timestamp,且 Start Timestamp **MUST NOT** 基于分位数值的采集周期。 - Quantile 是从 quantile 到 value 的映射(如 0.95→0.2 表示 95th 百分位 200ms、时间窗未知)。无事件时量化值 **MUST** 为 NaN。Quantile Metric 的 LabelSet **MUST NOT** 含 `quantile` label name。Quantile **MUST** 在 [0,1] 含端点内;值 **MUST NOT** 为负;**SHOULD** 表示近值(通常最近 5–10 分钟)。 - **Unknown**:**SHOULD NOT** 使用;仅在无法从第三方系统判定类型时 **MAY** 使用。Sample **MUST** 为 Number 或 CompositeValue。 #### 9.2.5 Text Format - OpenMetrics 格式是 **Regular Chomsky Grammars**(允许快速小型 parser)。**部分或无效的 exposition MUST 整体视为错误**。 - 注:早期版本曾定义 protobuf 格式,**2.0 不含 protobuf 表示**。 **Protocol Negotiation**:Ingestor **MUST** 能摄取 TLS 1.2+ 保护的数据,**SHOULD** 支持 TLS 1.3+;Exposer **SHOULD** 能发出 TLS 1.3+ 保护的数据。Ingestor **SHOULD** 能摄取无 TLS 的 HTTP 数据。所有实现 **SHOULD** 使用 TLS。版本协商 out-of-band:HTTP pull 用标准 content type negotiation,**无更新版本请求时 MUST 默认最老版本(1.0.0)**。Push 场景出题方通常发起连接,Producers **MUST** 用最老版本(1.0.0)除非 ingestor 另有请求。 **ABNF(RFC 7405;`%s`=区分大小写,`%i`=不区分)**: ```abnf exposition = metricset HASH SP %s"EOF" [ LF ] metricset = *metricfamily metricfamily = *metric-descriptor *sample metric-descriptor = HASH SP %s"TYPE" SP (metricname / metricname-utf8) SP metric-type LF metric-descriptor =/ HASH SP %s"HELP" SP (metricname / metricname-utf8) SP escaped-string LF metric-descriptor =/ HASH SP %s"UNIT" SP (metricname / metricname-utf8) SP *metricname-char LF metric-type = %s"counter"/%s"gauge"/%s"histogram"/%s"gaugehistogram"/%s"stateset"/%s"info"/%s"summary"/%s"unknown" sample = metricname-and-labels SP value [SP timestamp] [SP start-timestamp] *exemplar LF value = number / "{" composite-value "}" timestamp = realnumber start-timestamp = %s"st" "@" timestamp exemplar = SP HASH SP labels-in-braces SP number SP timestamp metricname-and-labels = metricname [labels-in-braces] / name-and-labels-in-braces labels-in-braces = "{" [label *(COMMA label)] "}" name-and-labels-in-braces = "{" metricname-utf8 *(COMMA label) "}" label = label-key "=" DQUOTE escaped-string DQUOTE number = realnumber number =/ [SIGN] (%i"inf"/%i"infinity") number =/ %i"nan" realnumber = [SIGN] 1*DIGIT ["." *DIGIT] ["e" [SIGN] 1*DIGIT] realnumber =/ [SIGN] *DIGIT "." 1*DIGIT ["e" [SIGN] 1*DIGIT] integer = [SIGN] 1*"0" / [SIGN] positive-integer positive-integer = *"0" positive-digit *DIGIT positive-digit = "1".."9" ; 注:non-negative-integer 产生式同属该 ABNF(UPPER/lower 未展开,以规范原文为准) BS="\"; COMMA=","; HASH="#"; SIGN="-" / "+" metricname = metricname-initial-char 0*metricname-char metricname-char = metricname-initial-char / DIGIT metricname-initial-char = ALPHA / "_" / ":" metricname-utf8 = DQUOTE escaped-string-non-empty DQUOTE label-key = label-name / DQUOTE escaped-string-non-empty DQUOTE label-name-initial-char = ALPHA / "_" label-name-char = label-name-initial-char / DIGIT escaped-string = *escaped-char escaped-string-non-empty = 1*escaped-char escaped-char = normal-char / BS("n"/DQUOTE/BS) / BS normal-char normal-char = %x00-09 / %x0B-21 / %x23-5B / %x5D-D7FF / %xE000-10FFFF ``` Composite 相关产生式: ```abnf composite-value = histogram-value / gauge-histogram-value / summary-value histogram-value = h-count "," h-sum "," histogram-buckets gauge-histogram-value = %s"g" h-count "," %s"g" h-sum "," histogram-buckets h-count = %s"count" ":" number h-sum = %s"sum" ":" number histogram-buckets = classic-buckets / native-buckets [ "," classic-buckets ] classic-buckets = %s"bucket" ":" "[" [ch-le-counts ","] ch-pos-inf-bucket "]" ch-le-counts = (ch-neg-inf-bucket / ch-le-bucket) *("," ch-le-bucket) ch-pos-inf-bucket = "+" %s"Inf" ":" number ch-neg-inf-bucket = "-" %s"Inf" ":" number ch-le-bucket = realnumber ":" number native-buckets = nh-schema "," nh-zero-threshold "," nh-zero-count ["," nh-negative-spans "," nh-negative-buckets] ["," nh-positive-spans "," nh-positive-buckets] nh-schema = %s"schema" ":" integer nh-zero-threshold = %s"zero_threshold" ":" realnumber nh-zero-count = %s"zero_count" ":" number nh-negative-spans / nh-positive-spans = %s"negative_spans"/%s"positive_spans" ":" "[" [nh-spans] "]" nh-spans = nh-start-span *("," nh-span) nh-start-span = integer ":" non-negative-integer nh-span = non-negative-integer ":" non-negative-integer nh-negative-buckets / nh-positive-buckets = %s"negative_buckets"/%s"positive_buckets" ":" "[" [nh-buckets] "]" nh-buckets = number *("," number) summary-value = cs-count "," cs-sum "," cs-quantile cs-count = %s"count" ":" number cs-sum = %s"sum" ":" number cs-quantile = %s"quantile" ":" "[" [cs-q-counts] "]" cs-q-counts = cs-q-count *("," cs-q-count) cs-q-count = realnumber ":" number ``` **Overall Structure**: - **MUST** 使用 UTF-8;**BOM MUST NOT 使用**(NULL 0x00 合法,0xFF 非法)。 - Content type **MUST** 为:`application/openmetrics-text; version=2.0.0; charset=utf-8`。 - 行结束 **MUST** 用 LF (`\n`),**MUST NOT** 含 CR (`\r`)。Exposition **MUST** 以 `EOF` 结束,**SHOULD** 以 `EOF\n` 结束。 - 完整示例含:summary 族(`count/sum/quantile` 复合值 + `st@`)、gauge 族、counter 族(`process_cpu_seconds_total 4.20072246e+06`)、histogram 族(同时含 native 与 classic bucket)、gaugehistogram 族(`acme_http_request_seconds:rate5m`)、带引号 UTF-8 名族(`"foodb.read.errors"` → `{"foodb.read.errors","service.name"="my_service"} 3482`)。 **UTF-8 Quoting**: - 不符合 ABNF `metricname` 的指标名 **MUST** 用双引号包裹并采用 UTF-8 替代语法:引号名 **MUST** 移到括号内作为第一项、无 label name 与等号;TYPE/UNIT/HELP 行中 **MUST** 用双引号。引号语法 **MAY** 用于任何名字。label name 不符合 `label-name` 时 **MUST** 双引号,任何 label name **MAY** 双引号。 - 正则:无需引号的指标名 `^[a-zA-Z_:][a-zA-Z0-9_:]*$`;label name `^[a-zA-Z_][a-zA-Z0-9_]*$`。 **Escaping(转义 MUST 应用于 ABNF 标注处,含引号 UTF-8 串)**: | 原字符 | 转义序列 | | --- | --- | | Line feed `\n` (0x0A) | 字面 `\n`(0x5c 0x6e) | | 双引号 | `\"`(0x5c 0x22) | | 反斜杠 | `\\`(0x5c 0x5c) | 双反斜杠 **SHOULD** 表示反斜杠字符;单反斜杠 **SHOULD NOT** 用于未定义转义序列(如 `\\a` 优于 `\a`)。 **Numbers / CompositeValues / Timestamps / Exemplars**: - 整数 **MUST NOT** 有小数点(`23`、`0042`、`1341298465647914`)。浮点 **MUST** 用小数点或科学计数法(`8903.123421`、`1.89e-7`),**MUST** 落在 IEEE 754 64-bit 范围内(可因尾数丢精度,可用于纳秒时间戳)。 - CompositeValue 为结构化字段;字段周围 **MUST NOT** 有空白。 - 时间戳若需纳秒精度 **SHOULD NOT** 用指数浮点渲染(float64 精度不足,如 `1604676851.123456789`)。 - 无 Label 的 Exemplar **MUST** 表示为 `{}`。 #### 9.2.6 MetricFamily 与文本行规则 - MetricFamily 间 **MUST NOT** 有显式分隔符;下一个族 **MUST** 用元数据或新 Metric 名标识。**族 MUST NOT 交错(interleaved)**。同族的 Name 与 Metric Name **SHOULD** 使用相同引号方式。 - **元数据四要素**:MetricFamily name、TYPE、UNIT、HELP。 - 无 TYPE 时族 **MUST** 按 **Unknown** 解释。 - 指定 unit 时 **MUST** 出现在 UNIT 行;下划线+单位 **SHOULD** 是族名后缀(Counter 则在 `_total` 前作中缀)。已知单位时 **SHOULD** 提供。 - UNIT/HELP 行的值 **MAY** 为空 → **MUST** 视作该行不存在。 - 同一族每类元数据行 **MUST NOT** 多于一行;顺序 **SHOULD** 为 TYPE、UNIT、HELP。 - 除元数据与末尾 EOF 行外,**MUST NOT** 暴露以 `#` 开头的行。 - Ingestor **MUST** 支持无元数据行的族。 - **Metric**:**MUST NOT** 交错。无标签无时间戳且值为 0 的 Sample 应渲染为 `bar_seconds_count 0` 或 `bar_seconds_count{} 0`。label 值可为任意 UTF-8,故 **MUST** 按 ABNF 转义(示例 `bar_seconds_count{a="x",b="escaping\" example \n "} 0`)。指标名与 label name 亦可为 UTF-8,特定情况 **MUST** 引号转义(如 `{"\"bar\".seconds.count","b\\"="escaping\" example \n "} 0`)。 #### 9.2.7 各类型文本行细则 **st@ 通用规则**:Sample 的 Start Timestamp 若存在,**MUST** 以 `st@` 前缀与 Sample 内联;若 value 的 timestamp 存在,Start Timestamp **MUST** 紧随其后;若 exemplar 存在,Start Timestamp **MUST** 放在 exemplar 之前。**GaugeHistogram 除外(无 Start Timestamp)**。 | 类型 | 文本行规则 | | --- | --- | | Gauge | Sample 值 **MUST** 为 Number;无推荐后缀 | | Counter | 值 **MUST** 为 Number;st@ 内联规则同上;**MAY** 有 Exemplars。示例:`foo_total 17.0 1520879607.789 st@1520430000.123 # {trace_id="KOO5S4vxi0o"} 0.67 1520879606.1`;无 `_total` 后缀合法但降低可用性 | | StateSet | 无推荐后缀。**MUST** 每 state 一个 Metric;每 state Metric **MUST** 有以族名作 label name、state 名作 label value 的 label;Sample 值 **MUST** 为 1(true)/ 0(false)。示例 `foo{foo="bb"} 1`。StateSet MetricGroup **MUST NOT** 交错 | | Info | Sample 值 **MUST** 恒为 1。示例 `foo_info{name="pretty name",version="8.2.7"} 1`;Metric label 与 Sample value label **MAY** 任意顺序 | | Summary | 值 **MUST** 为 CompositeValue,**MUST** 含字段 `count`、`sum`、`quantile`(按此顺序)。quantile **MUST** 按分位数递增排序。示例 `foo {count:17,sum:324789.3,quantile:[]} st@1520430000.123` | | Histogram with Classic Buckets | 值 **MUST** 为 CompositeValue,字段为 `count`、`sum`、`bucket`(按序)。Classic Buckets **MUST** 按 threshold 数值递增排序;**所有 Classic Buckets MUST 存在,即使值为 0** | | Histogram with Native Buckets | 字段 **MUST** 为 `count`、`sum`、`schema`、`zero_threshold`、`zero_count`(按序)。无负 Native Buckets 时 `negative_spans`/`negative_buckets` **SHOULD** 省略;无正 Native Buckets 时 `positive_spans`/`positive_buckets` **SHOULD** 省略;有则 **MUST** 按此顺序出现在 `zero_count` 之后。bucket 值 **MUST** 按 index 排序。值 **MUST** 为**绝对计数**(非相对前一个 bucket 的 delta)。值为 0 的 Native Bucket **SHOULD NOT** 出现 | | Histogram with both Classic and Native Buckets | 字段 **MUST** 含 `count`、`sum`(按序),之后先 Native 字段、再 Classic(`bucket`)字段;顺序便于实现跳过 Classic | | GaugeHistogram(Classic / Native / 两者) | 语法同 Histogram,但 Count/Sum 用字段 `gcount`/`gsum`,且无 Start Timestamp | | Unknown | 值 **MUST** 为 Number 或 CompositeValue;无推荐后缀;示例含无元数据行的 `foo 42.23` | **Native bucket spans 构造**:每 span 为 `(offset:length)`;仅列表首 span 可负 offset,定义对应 buckets 列表首个 bucket 的 index;length 为连续 bucket 数;后续 span 的 offset 表示被排除(未填充)的 bucket 数。各 span list 的 length 之和 **MUST** 等于对应 bucket 列表长度。保留空 bucket 可减少 span 数(当两 span offset 差恰为 1 时)。 示例: ```text {count:59,sum:1.2e2,schema:7,zero_threshold:1e-4,zero_count:0,negative_spans:[1:2],negative_buckets:[5,7],positive_spans:[-1:2,3:4],positive_buckets:[5,7,10,9,8,8]} ``` 无 bucket 时: ```text {count:0,sum:0,schema:3,zero_threshold:1e-4,zero_count:0} ``` **Exemplars(Histogram)**:**MAY** 附加;若 Classic 与 Native 各存一套,**MAY** 只附一套以兼顾性能与兼容,**SHOULD** 附 Classic 关联的那套。 #### 9.2.8 Design Considerations(设计考量) - **Scope**:为在线系统提供遥测;无硬/软实时保证;足够用于聚合决策但不反映单个事件。支持各种规模(每小时几请求 → 400Gb 网络口)。以固定节奏传输状态快照。Ingestor 与 exposer 的相互发现**不在范围**。 - **Failure Modes**:主张事务性处理——任何编码/解码/校验错误 **MUST** 拒收整个 MetricSet(失败抓取优于不准确/部分视图)。**唯一例外**:exemplar 专属失败不应导致整个 exposition 失败,malformed/invalid exemplar 应被丢弃或忽略。 - **Extensions and Improvements**:基于 Prometheus text 0.0.4、Prometheus Protobuf、OpenMetrics 1.0。未来 minor 版本将**始终要求同时支持 2.0 的语法与语义**。去掉元数据与结构后,作为无序 sample 集合仍应可用。 - **与 1.0 的显著差异汇总**: | # | 差异 | | --- | --- | | 1 | Start Timestamp 改为内联 `st@`,取代单独 `_created` sample | | 2 | Metric name 必须与 MetricFamily name 完全一致,移除隐式后缀剥离 | | 3 | Counter 的 `_total` 后缀与单位后缀由 MUST 改为 SHOULD | | 4 | Histogram/GaugeHistogram/Summary 值合并为单行 CompositeValue,Count 与 Sum 变为必需 | | 5 | 引入带指数 bucket schema 的 Native Histograms | | 6 | 允许带引号的 UTF-8 指标名与 label 名 | | 7 | Exemplar 时间戳变为强制,每 sample 允许多个 exemplar | | 8 | 移除 Protobuf 格式规范 | - **Units and Base Units**:以 SI 基本单位为准(seconds、bytes、joules、grams、meters、ratios、volts、amperes、celsius)。ratio 为基本单位而非百分比;优先暴露原始分子/分母 gauge 或 counter。分贝不是基本单位。非基本单位不可避时仍应在名中体现。并非所有族都有单位(如 HTTP 请求计数)。 - **Statelessness**:wire format 跨 exposition 无状态;先前暴露信息 **MUST** 对未来无影响。同样的自包含 exposition **MUST** 提供给新老 ingestor。Exposer **MUST NOT** 因某指标近期无变化/无观测就排除它。 - **Exposition Across Time and Metric Evolution**:跨 exposition 应保持标签暴露顺序(避免破坏 parser 缓存)。Metric/Sample **SHOULD NOT** 忽现忽隐。**打破兼容**:改变族 Type、增删 label。**不打破**:Info Metric 增 label、改 Help、float/int 互换、stateset 增 state、在不改变名的情况下增 unit 元数据。Histogram buckets **SHOULD NOT** 跨 exposition 变化。 - **NaN**:NaN 是普通数字(常源自除零),无特殊含义,**MUST NOT** 用作缺失/坏数据标记。 - **Missing Data**:数据消失无特殊标记/信号,后续 exposition 不含该 Metric 即可。 - **Exposition Performance**:**SHOULD** 不超过 1 秒;**SHOULD** 为最新状态(避免依赖缓存值)。 - **Concurrency**:**MUST** 支持并发 exposition;遵循并发 BCP,避免死锁、竞态、过粗锁。 - **Metric Naming and Namespaces**:snake_case;以公司/组织→库/子系统/应用分形命名(如 `acme_http_router_request_seconds`)。`scrape_` 前缀供 ingestor 使用,应用不应直接暴露(可用 `myexposer_scrape_` 前缀,如 `myexposer_scrape_duration_seconds`)。`process_` 为生态一致前缀(如 `process_open_fds`、`process_max_fds`)。避免冗余子串 `metric`/`timer`/`stats`/`counter`/`total`/`float64`;避免把 label 名、其它层实现细节(如 `openmetrics`/`prometheus`)写进指标名。 - **Label Namespacing**:不建议对 label 名做公司/库命名空间;避免易与目标标签冲突的名(region、zone、cluster、availability_zone、az、datacenter、dc、owner、customer、stage、service、team、job、instance、environment、env);`type` 过于泛化应避免(HTTP 场景用 `method`)。label 名无元数据(元数据仅 HELP/TYPE/UNIT)。 - **Metric Names versus Labels**:对族求和/平均应有意义(电压与风扇转速混合无意义)。**错误**:同时暴露 total 汇总与各项会造成下游重复计数(`wrong_metric{label="total"}` 反例)。label 应最少化;可跨多族的 label 宜移入 `_info` 指标。经验上分离 total/failure、read/write、send/receive 族优于用 `{result=...}` label。 - **Metric and Label Name Characters**:向后兼容是核心目标;字符集扩展/收缩会与兼容性冲突;查询语言依赖这些精确字符集;label 值支持完整 UTF-8。 - **Types of Metadata**:Target metadata(外部,如服务发现/CMDB,优选由 ingestor 添加以更灵活、开销更低)vs Exposer metadata(如软件版本、编译器版本、Git SHA)。 - **Supporting Target Metadata in Push/Pull Systems**:push 场景常由 exposer 提供目标元数据,pull 场景常由 ingestor 预先知道。OpenMetrics 无状态、对所有 ingestor 提供相同 exposition,与 push 风格冲突。首选方案:用 Info MetricFamily `target_info`(单 Metric 无 label)承载目标元数据;该族 **SHOULD** 放在 exposition 最前(供 ingestor 免缓冲)。Exposer **MUST NOT** 除非显式配置否则给 exposition 所有 Metric 加目标元数据 label;**MUST NOT** 根据目标元数据给族名加前缀或变体。通用监控系统的 exposition 可能含多个 `target_info`;**MUST NOT** 因目标元数据改变指标名(如不得统一加 `staging_` 前缀)。 - **Client Calculations and Derived Metrics**:计算应交由 ingestor;唯一例外是 Summary quantile(向后兼容所需)。不要暴露 5 分钟平均增长率 gauge、平均事件大小、request failure ratio;改用独立 counter。标准偏差未纳入 Histogram 值(float64 精度不足,53 位尾数平方后仅剩半数,10k 事件/秒时约 2 小时即丢精度;int64 因无浮点小数点、纳秒精度下约 19 次观测即溢出);可待 128-bit 浮点普及时重议。 - **Number Types**:float64 有 53 位尾数,每秒百万次递增的 counter 需一个多世纪才开始丢精度,但 100Gbps 网络接口在约 20 小时内可能开始丢精度;高吞吐整型数据可考虑 int64。Summary quantiles **必须** float64。 - **Exposing Timestamps**:一般 Sample 时间戳不应暴露,由 ingestor 自行打时间戳。已带时间戳的数据(尤其被通用系统摄取过的)可携带时间戳;实时/原始数据不应带。同一 Sample 值+同一时间戳跨 exposition 暴露合法,但底层指标已缺失时则不合法。Ingestor 应能附加自身视角的当前时间戳。 - Tracking When Metrics Last Changed:正确写法是不带时间戳 `my_counter_total 1`;带时间戳 `my_counter_total 1 123` 错误。若需记录最后变化时间,应另建 gauge(如 `my_counter_last_increment_timestamp_seconds 123`)。暴露绝对时间戳(epoch)优于 elapsed/since(`my_boot_time_seconds 1256060124` 优于 `my_time_since_boot_seconds 123`)。 - Exemplar 时间戳无 best-practice 限制;可能因竞态/时钟不同步略超前。当降采样到秒导致两个 Sample 时间戳看似重复时,**MUST 使用时间戳最早的那个 Sample**。 - **Thresholds**:通用真值可发 gauge(如 HVAC 的 setpoints);随规模/部署/时间变化的阈值暴露可能适得其反。示例 `acme_notifications_queue_capacity 10000` 与 `acme_notifications_queue_length 42`。 - **Size Limits**:本标准不规定 sample 数、label 数、stateset state 数、info label 数、各类字符长度上限。Ingestor 可自设限制(防攻击/防中断),但须顾及合理用例;**超限则整个 exposition MUST 被拒收**。影响性能的三要素:唯一时间序列数、跨时间 sample 数、唯一字符串数(名/label name/label value/HELP)。唯一时间序列数 ≈ 文本格式非注释行数;截至 2020,**1000 万条时间序列**被视为很大,常为单实例 ingestor 上界量级;单次 exposition 未经审慎评估不应超 **1 万条时间序列**。若目标是单例(暴露整集群),几十万条可能合理。关键是时间序列总量的数量级,而非族数或单 label/bucket/stateset 基数(1000 个各含 1 Metric 的 gauge 与 1 个含 1000 Metric 的 gauge 等价)。所有目标暴露相同时间序列集时,新增目标字符串几乎无增量成本;各自唯一字符串则有成本(1000 个目标各暴露唯一 36 字符 UUID 比单个 10k 字符指标名贵三倍以上)。1000 万时间序列下每小时 100MB 唯一字符串可能更接近事件日志而非指标。 #### 9.2.9 Security 与 IANA - 实现 **MAY** 提供认证/授权/计费;若提供 **SHOULD** 在 OpenMetrics 之外处理。 - 所有 exposer 实现 **SHOULD** 能用 TLS 1.3+ 保护 HTTP;不支持加密时运维 **SHOULD** 用反代/防火墙/ACL。 - 指标暴露应与面向终端用户的生产服务独立;对公网暴露服务,不建议在 TCP/80、TCP/443、TCP/8080、TCP/8443 上放 `/metrics`。 - 多数 Prometheus exposition 实现仍用非 IANA 注册端口;OpenMetrics 有明确定义的端口。IANA 为暴露数据的客户端分配端口为 `<9099 requested for historical consistency>`。 - 多端点共用同一 IP:port 时可用反代经 localhost 通信;为便于多路复用,端点 **SHOULD** 在路径中带自身名(如 `/node_exporter/metrics`);exposition **SHOULD NOT** 合并为一个(允许独立摄取、避免单点故障)。 - OpenMetrics 希望注册 MIME 类型 `application/openmetrics-text`。 > 引自:[OpenMetrics 2.0](https://prometheus.io/docs/specs/om/open_metrics_spec_2_0/) ### 9.3 Remote Write 1.0 规范 **元信息**:Version `1.0`;Status `Published`;Date `April 2023`。定位为**记录并标准化现有已被广泛采用的事实协议**,不提出任何新东西。关键词按 **RFC 2119** 解读。文首 NOTE 指向存在 2.0 版本。 #### 9.3.1 背景与设计取舍 - **目标**:把样本实时、**无损**地从 Sender 可靠传播到 Receiver。 - 协议**无状态**,消息间严格无通信,因此不算"流式";要产生流式效果应在同一连接上连发多条消息(HTTP/1.1 或 HTTP/2)。 - 曾考虑 gRPC,但当时未被广泛采用,且把 gRPC 服务暴露到 AWS EC2 ELB 这类负载均衡器后面很困难。 - 协议支持批处理(单个请求里放多个不同 series 的样本);同一 series 的多样本同请求并不常见,但协议支持。 - **协议不是给应用直接 push 指标用的**;设计意图是兼容"发送方抓取(scrape)已插桩的应用/exporter"再发 remote write 消息给服务端。 - 一致性测试套件:`https://github.com/prometheus/compliance/tree/main/remotewrite/sender`。 #### 9.3.2 术语(Glossary,MUST 遵守) - `Sender`:发送 Prometheus Remote Write 数据的东西。 - `Receiver`:接收 remote write 数据的东西。 - `Sample`:`(timestamp, value)` 二元组。 - `Label`:`(key, value)` 二元组。 - `Series`:由唯一标签集标识的一个 sample 列表。 #### 9.3.3 协议(Protocol) 协议 **MUST** 由如下签名的 RPC 构成(1.0 的 protobuf schema): ```protobuf func Send(WriteRequest) message WriteRequest { repeated TimeSeries timeseries = 1; // Cortex uses this field to determine the source of the write request. // We reserve it to avoid any compatibility issues. reserved 2; // Prometheus uses this field to send metadata, but this is // omitted from v1 of the spec as it is experimental. reserved 3; } message TimeSeries { repeated Label labels = 1; repeated Sample samples = 2; } message Label { string name = 1; string value = 2; } message Sample { double value = 1; int64 timestamp = 2; } ``` - Sender **MUST** 把 WriteRequest 编码进 **HTTP POST 的 body**,按给出的 URL 路径发给 Receiver;Receiver **MAY** 指定任意 HTTP URL 路径收指标。 - 时间戳 **MUST** 为 `int64`,以 Unix epoch 起算的**毫秒**;值 **MUST** 为 `float64`。 - HTTP 请求 **MUST** 带以下头: ```text Content-Encoding: snappy Content-Type: application/x-protobuf User-Agent: X-Prometheus-Remote-Write-Version: 0.1.0 ``` - 客户端 **MAY** 允许用户自定义 HTTP 头,但 **MUST NOT** 允许以能发送**保留头**的方式配置(参见 prometheus/prometheus#8416)。 - body **MUST** 用 **Google's Snappy** 压缩,且 **MUST** 用 **block format**,**MUST NOT 用 framed format**。 - 请求 **MUST** 用 **Google Protobuf 3** 编码,且 **MUST** 使用上面定义的 schema。注:Prometheus 实现使用了 gogoproto 优化,非 Golang 的接收方 **MAY** 用等价的行级类型替换 gogoproto 类型。 - Receiver 的响应体 **SHOULD** 为空;客户端 **MUST** 忽略响应体(响应体为未来预留)。 #### 9.3.4 前后向兼容与版本协商 - 协议遵循 **SemVer 2.0**:任意 1.x 兼容 Receiver **MUST** 能读任意 1.x 兼容 Sender,以此类推;破坏性/不兼容变更将产生 2.x 版本。 - proto 格式自身的兼容性:**删除字段 = major bump**;**新增(可选)字段 = minor bump**。 - 协商规则: - Sender **MUST** 在 header 中发送版本号; - Receiver **MAY** 在响应头 `X-Prometheus-Remote-Write-Version` 中返回其支持的最高版本号; - 想用 >1.x 格式的 Sender **MUST** 先发一个空的 1.x,看响应是否声明支持别的版本;Sender **MAY** 使用任意被支持的版本。**若响应中无版本头,Sender MUST 假定仅 1.x 兼容**。 #### 9.3.5 标签(Labels)规则 - **MUST** 随每个 sample 发送**完整标签集**;并且与样本关联的标签集: - **SHOULD** 含 `__name__` 标签; - **MUST NOT** 含重复标签名; - **MUST** 按**字典序**排列标签名; - **MUST NOT** 含空标签名或空标签值。 - Sender **MUST** 只发送合法的 metric name / label name / label value: - Metric name **MUST** 匹配正则 `[a-zA-Z_:]([a-zA-Z0-9_:])*`; - Label name **MUST** 匹配正则 `[a-zA-Z_]([a-zA-Z0-9_])*`; - Label value **MAY** 为任意 UTF-8 字符序列。 - Receiver **MAY** 对标签数量与长度设限(receiver 特定,超出本文范围)。 - 以 `__` 开头的标签名 **RESERVED** 给系统用,**SHOULD NOT** 使用。 - 含非法样本的写请求:Receiver **MAY** 只写入其中合法样本,但 **MUST** 返回 **HTTP 400 Bad Request**;**SHOULD** 在响应体给人类可读错误信息。Sender **MUST NOT** 尝试解析错误信息,**SHOULD** 原样记日志。 #### 9.3.6 顺序(Ordering) Sender **MUST** 对**任一给定 series** 按时间戳顺序发送样本;**MAY** 对不同 series 并行发多个请求。 #### 9.3.7 重试与退避(Retries & Backoff) - Sender **MUST** 在 **HTTP 5xx** 上重试,且 **MUST** 使用退避算法以免压垮服务端。 - Sender **MUST NOT** 在 **HTTP 2xx、4xx(429 除外)** 上重试;**MAY** 在 **429** 上重试(若服务端跟不上,可能导致 Sender "falling behind")。这样设计是为:服务端错误时不丢数据、客户端错误时仍能推进。 - Receiver:写入成功 **MUST** 返回 **HTTP 2xx**;写入失败且应重试 **MUST** 返回 **5xx**;请求非法、永不可能成功、不应重试 **MUST** 返回 **4xx**。 #### 9.3.8 过期标记(Stale Markers) - Sender **MUST** 在某个时间序列不再被追加时发送 stale marker。 - **MUST** 用特殊 NaN 值 `0x7ff0000000000002` 表示;该值 **MUST NOT** 挪作他用。 - 常见探测手段:① 服务发现发现目标消失;② 相邻两次抓取间发现目标不再暴露该序列;③ 抓取原暴露该序列的目标失败;④ 跟踪 recording/alerting rule 的配置与求值。 #### 9.3.9 范围之外(Out of Scope) | 主题 | 说明 | | --- | --- | | `up` 指标 | 定义与语义不在协议范围,应单独记录 | | HTTP Path | 路径可为任意值,**MUST** 由 Sender 提供,通常在配置里给完整 URL | | 持久化 | 建议 Sender 在 Receiver 故障时持久缓冲样本数据 | | 认证与加密 | 因走 HTTP,视为传输层问题;收发双方应支持常规方案(Basic auth、TLS 等),可自定义认证;不应假定 Sender/agent 支持自定义认证 | | Remote Read | 是另一套已迭代的接口,使用较少 | | 分片(Sharding) | Prometheus 现有的 remote write 并行分片是实现细节、不属于规范;一旦实现并行,**MUST** 保持**每 series 的样本顺序** | | 回填(Backfill) | 规范不限制能推多老的序列,但实现可能有约束 | | 限制(Limits) | 标签数量/长度、batch size 等不在本文范围,但实现应设合理上限 | | Push-based Prometheus | 应用直接推指标不是本系统设计目标,应另文讨论 | | Labels | 每个 series **MAY** 含 `job` 和/或 `instance` 标签(通常由 Sender 的服务发现添加),非强制 | #### 9.3.10 未来计划(非规范内容) - **事务性**:希望像 scrape 那样"对齐"remote write,例如把单次抓取的 samples、metadata、exemplars 放进单个 remote write 请求;尚未设计。 - **Metadata 与 Exemplars**:Prometheus 当前对发送 metadata(type/help)与 exemplars 是**实验性**支持;未来版本可能强制打包进单请求。 - **优化**:希望通过消除 label 名/值的重复来减小消息体积。 #### 9.3.11 相关与 FAQ 兼容 Sender/Receiver 清单(截至 2023-04):Prometheus(双向)、Avalanche(sender)、Cortex(receiver)、Elastic Agent(receiver)、Grafana Agent(双向)、GreptimeDB(receiver)、InfluxData Telegraf(双向)、M3(receiver)、Mimir(receiver)、Oodle(receiver)、OpenTelemetry Collector(sender,最终也做 receiver)、Thanos(receiver)、Vector(双向)、VictoriaMetrics(receiver)。 FAQ 要点: - **为何不用 gRPC**:最初确实用过,2016 年难穿过 ELB(prometheus/prometheus#1982),改用 HTTP 上的 protobuf。 - **为何不做流式 protobuf**:持久 HTTP/1.1 连接已接近流式;头需重发但比新建 TCP 便宜。 - **为何按序发样本**:来源于 Prometheus 内部 append-only 的时序编码;未来版本可研究缓冲重排以解除约束。 - **如何并行**:样本只需**同 series 内有序**;按标签分片到不同队列、队列内串行,从而同 series 有序、不同 series 并行。这是为生态完整性,避免出现"能写 Prometheus、不能写 Cortex/Thanos"的 agent 分叉。 > 引自:[Prometheus Remote-Write 1.0 specification](https://prometheus.io/docs/specs/prw/remote_write_spec/) ### 9.4 Remote Write 2.0 规范 [EXPERIMENTAL] **元信息**:Version `2.0-rc.5`;Status **`Experimental`**;Date `May 2024`。关键词同按 RFC 2119 解读。 **定位**:对 1.0 做 **protocol 与 semantics 的小幅修改**,新增一个 Protobuf Message 承载新特性(更多用例、更广采用、性能与成本更优);**弃用 1.0 的 Protobuf Message**;新增**强制**的 `X-Prometheus-Remote-Write-*-Written` HTTP 响应头以保证可靠性;并给出用基础 content negotiation 请求头实现**向后兼容收发双方(甚至共用单个 endpoint)**的做法。更高级的自动协商可能留给未来 minor 版本。 > 免责 NOTE:这是 RC,处于实验态;预计不会有大改,但保留基于早期采用者反馈**破坏兼容性**的权利;反馈应提到 proposals PR #35。 #### 9.4.1 新增/变更术语 - `Remote-Write`:本 Prometheus 协议名。 - `Protocol`:使 client 与 server 传输 metrics 的通信规范。 - `Protobuf Message`:本协议数据结构的 content type 定义;只用 Google Protocol Buffers,schema 定义在 `.proto` 文件中。 - `Wire Format`:数据在网络上的形式;对 Remote-Write **永远是压缩后的二进制 protobuf**。 - `Sender` / `Receiver`:发送方 / 接收(写入)方。`Written` 的含义由 Receiver 决定——通常指存入数据库,也包括仅校验、拆分或增强。`Written` 指 Receiver **已接收并接受**的数据,是否落持久存储/WAL 由 Receiver 决定;与"显式以错误拒绝"相对。 - `Sample`:**(start timestamp, timestamp, value) 三元组**(1.0 是二元组)。 - `Histogram`:**(start timestamp, timestamp, histogram value) 三元组**。 - `Label`:`(key, value)`;`Series`:由唯一标签集标识的 sample(或 histogram)列表。 #### 9.4.2 协议(Protocol)—— 1.0 vs 2.0 消息选择 - 协议 **MUST** 由 RPC 构成,请求体 **MUST** 用 Google Protocol Buffers 序列化后再压缩。 - 序列化 **MUST** 使用以下两种消息之一: - `prometheus.WriteRequest`(1.0 引入)——**自 2.0 起弃用**,**SHOULD** 仅因兼容而使用;收发双方 **MAY NOT** 支持它; - `io.prometheus.write.v2.Request`(本规范引入)——收发双方 **SHOULD** 尽可能使用,且 **MUST 支持**。 - Protobuf Message **MUST** 用**二进制 Wire Format**,再 **MUST** 用 Google's Snappy 压缩;**MUST 用 block format,MUST NOT 用 framed format**。 - Sender **MUST** 把序列化压缩后的消息放进 **HTTP POST body** 发给 Receiver 给出的 URL 路径;Receiver **MAY** 指定任意路径。 - Sender **MUST** 发送以下**保留头**:`Content-Encoding`、`Content-Type`、`X-Prometheus-Remote-Write-Version`、`User-Agent`。Sender **MAY** 允许用户加自定义头,但 **MUST NOT** 允许配置出这些保留头。 #### 9.4.3 四个头的具体要求 - **Content-Encoding**:**MUST** 遵守 RFC 9110;Sender **MUST** 用 `snappy`;Receiver **MUST** 支持 `snappy`。新压缩算法可能出现在 2.x 及以后。 - **Content-Type**:**MUST** 遵守 RFC 9110。Sender **MUST** 仅用 `application/x-protobuf` 作为媒体类型,**MAY** 追加 `;proto=`。因此 Sender **MUST** 发送以下三种头值之一: ```text Content-Type: application/x-protobuf Content-Type: application/x-protobuf;proto=prometheus.WriteRequest Content-Type: application/x-protobuf;proto=io.prometheus.write.v2.Request ``` - 面向 1.x Receiver 时 **SHOULD** 用不带 `proto=` 的 `application/x-protobuf` 以求向后兼容;否则 **SHOULD** 用带 `proto=io.prometheus.write.v2.Request` 的形式。 - Receiver **MUST** 依据 content type 判定用哪个 schema;选错 schema 可能导致非确定性行为(如数据损坏)。 - NOTE:得益于 v2 消息的 reserved 字段,Receiver 若误用 `prometheus.WriteRequest` schema 解 v2 载荷,会得到**空消息**——便利但**不可依赖**,未来消息未必有此特性。 - **X-Prometheus-Remote-Write-Version**:面向 1.x Receiver 时 **MUST** 用 `0.1.0`;否则 **SHOULD** 用自身兼容的最新版本,如 `2.0.0`。 - **User-Agent**:**MUST** 带,且 **SHOULD** 遵守 RFC 9110 的 User-Agent 格式。 #### 9.4.4 响应语义(Response) - 全部数据成功写入的 Receiver **MUST** 返回 **2xx**;此时响应体 **SHOULD** 为空、状态码 **SHOULD** 为 **204 No Content**;Sender **MUST** 忽略响应体。 - 异步处理(如入消息队列/管道延迟处理)的 Receiver **MAY** 返回 **202 Accepted**,表示已受理但未处理完。 - 若 Receiver 已知的任一部分数据(Samples/Histograms/Exemplars)**未同步成功写入**(无论 partial write 还是整体拒绝)或**未送入异步处理**(如队列满),Receiver **MUST NOT** 返回 2xx;此时 **MUST** 在响应体给出人类可读错误信息,错误 **SHOULD** 含被拒样本数量与原因。Sender **MUST NOT** 解析错误信息,**SHOULD** 原样记日志。 #### 9.4.5 强制 `Written` 响应头(2.0 核心新增) 内容协商成功后 Receiver 处理该批次;对每类重要数据(目前 Samples、Histograms、Exemplars)处理完毕(无论成败)后,Receiver **MUST** 发送对应的 `X-Prometheus-Remote-Write-*-Written` 响应头,值为**成功写入元素的精确数量**。 返回 202 的异步 Receiver 若无法同步计数 **MAY** 省略这些头(校验被推迟);此类 Receiver **SHOULD** 通过其他渠道把错误传播给客户端。 每个头值 **MUST** 是单个 **64-bit 整数**;头名固定为: ```text X-Prometheus-Remote-Write-Samples-Written X-Prometheus-Remote-Write-Histograms-Written X-Prometheus-Remote-Write-Exemplars-Written ``` - 收到 **2xx 或 4xx** 时,Sender 可**假定**缺失的头意味着该类元素写入数为 `0`。 - 但 Sender **MUST NOT** 在以下情况如此假定:收到 **202**(异步可能推迟校验);使用弃用的 `prometheus.WriteRequest`(可能撞上没此特性的 1.0 Receiver)。 - **用途**:更精细处理 Partial Write(更准确的客户端埋点与错误处理);识别损坏的 1.0 Receiver 实现(用 v2 请求却返回非 202 的 2xx 且无任何 `*-Written` 头时,**SHOULD** 假定为 **415 Unsupported Media Type**——这类 Receiver 不检查 `Content-Type`,用 1.0 schema 解 v2 载荷得到空结果且不报错);以及检测其他收发实现缺陷。 - Sender **MUST NOT** 依据 remote write 响应头去推断 Receiver 实现了哪个规范版本。未来可能有更多(可选)头。 #### 9.4.6 Partial Write / 不支持的内容 / 非法样本 / 重试 - **Partial Write**:Sender **SHOULD** 单请求发多 series 样本,因此 Receiver **MAY** 只写入部分合法样本(partial write);此时 Receiver **MUST** 依 Invalid Samples 与 Retries on Partial Writes 返回**非 2xx**。 - **Unsupported Request Content**:Receiver 若不支持给定 content type 或 encoding,**MUST 返回 415**;Sender 对 1.x Receiver **SHOULD** 预期是 **400**(向后兼容)。 - **Invalid Samples**:哪些样本非法由 Receiver 决定(有 Receiver 可能拒无 metadata type 或无 start timestamp 的样本,另一些接受)。含非法样本的请求,Receiver **MUST** 返回 **400**(除非发生可重试的部分写)。Sender **MUST NOT** 对 4xx(429 除外)重试;429 **MUST** 用于表示永不会成功、不应重试;Sender **MAY** 用不同 content type/encoding 对 **415** 重试以试探 Receiver 支持什么。 - **Retries & Backoff**: - Receiver **MAY** 返回 **429**(过载)并可带 **Retry-After** 头指示下次尝试时间;**MAY** 返回 5xx 表示内部错误; - Sender **MAY** 在 429 上重试;**MUST** 在 5xx 上重试;**MUST** 用退避算法;**MAY** 处理 `Retry-After` 以估算下次重试时间; - 429 与 5xx 的差异原因:Receiver 跟不上或主动限流时,Sender 可选择不重试 429,从而在 Sender 侧错误时仍能推进,同时 5xx(Receiver 侧错误)不丢数据。 - **Retries on Partial Writes**:部分写或部分非法样本时,若期望 Sender 重试整个请求,Receiver **MAY** 返回 5xx 或 429;此时 **Receiver MUST 支持幂等性**,因为 Sender **MAY** 用同一请求重试。 #### 9.4.7 前后向兼容 - 遵循 SemVer 2.0:任意 2.x 兼容 Receiver **MUST** 能读任意 2.x 兼容 Sender,反之亦然;破坏性变更将产生 **3.x**。 - Protobuf Message 自身:**删字段 = major bump**;**加(可选)字段 = minor bump**。故 2.x 的未来 minor 版本 **MAY** 新增可选字段、新压缩、新 Protobuf Message 与协商机制,只要向后兼容(对收发双方均为可选)。 - **2.x vs 1.x**:2.x 通过引入**强制**的 `io.prometheus.write.v2.Request` 并弃用 `prometheus.WriteRequest`,与 1.x **破坏兼容**。2.x Sender **MAY** 通过让用户配置 content type 来支持 1.x Receiver;也 **MAY** 在收到 **415** 时自动回退到其他 content type。 #### 9.4.8 Protobuf Message:`io.prometheus.write.v2.Request` 权威 schema 在 Prometheus 仓库 `prompb/io/prometheus/write/v2/types.proto`;`gogo` 依赖与选项可忽略(将被移除),不属规范、不影响序列化格式。简化 schema: ```protobuf message Request { reserved 1 to 3; // symbols contains a de-duplicated array of string elements used for various // items in a Request message, like labels and metadata items. For the sender's convenience // around empty values for optional fields like unit_ref, symbols array MUST start with // empty string. repeated string symbols = 4; repeated TimeSeries timeseries = 5; } message TimeSeries { reserved 6; repeated uint32 labels_refs = 1; repeated Sample samples = 2; repeated Histogram histograms = 3; repeated Exemplar exemplars = 4; Metadata metadata = 5; } message Exemplar { repeated uint32 labels_refs = 1; double value = 2; int64 timestamp = 3; } message Sample { double value = 1; int64 timestamp = 2; int64 start_timestamp = 3; } message Metadata { enum MetricType { METRIC_TYPE_UNSPECIFIED = 0; METRIC_TYPE_COUNTER = 1; METRIC_TYPE_GAUGE = 2; METRIC_TYPE_HISTOGRAM = 3; METRIC_TYPE_GAUGEHISTOGRAM = 4; METRIC_TYPE_SUMMARY = 5; METRIC_TYPE_INFO = 6; METRIC_TYPE_STATESET = 7; } MetricType type = 1; uint32 help_ref = 3; uint32 unit_ref = 4; } message Histogram { ... } // 支持稀疏指数分桶/自定义分桶;float 或 integer 直方图 ``` 所有时间戳 **MUST** 为 `int64` 毫秒(Unix epoch);sample 值 **MUST** 为 `float64`。 对每个 `TimeSeries`: - `labels_refs` **MUST** 提供; - `samples`、`histograms`、`exemplars` 中**至少一个元素** **MUST** 提供;只有 `metadata` 与 `labels_refs` 的 TimeSeries 非法; - **MUST NOT** 同时包含 `samples` 和 `histograms`;混用 float 与 histogram 的序列 **MUST** 拆成独立 TimeSeries; - `metadata` 子字段 **SHOULD** 提供;Receiver **MAY** 拒绝未指定 `Metadata.type` 的序列; - 系列若有 exemplars,**SHOULD** 提供。 **Symbols(字符串驻留 / 去重)**: - v2 消息对所有字符串做 **string interning**,在标准压缩之外带来额外的压缩与内存收益。 - `symbols` 表 **MUST** 提供,且 **MUST** 含 series、exemplar 标签、metadata 字符串所去重后的字符串。 - `symbols` 表**第一个元素 MUST 是空字符串**,用于表示空/未指定值(如 `Metadata.unit_ref`、`Metadata.help_ref` 未提供时)。 - 引用 **MUST** 指向 `symbols` 数组中的**已存在下标**;字符串顺序由 Sender 决定,Receiver 不应假设任何特定编码。 **Series Labels**: - **MUST** 随每个 `Sample`/`Histogram` 发送完整标签集,且标签集:**SHOULD** 含 `__name__`;**MUST NOT** 含重复标签名;**MUST** 按字典序排列;**MUST NOT** 含空名或空值。 - Metric name、label name、label value **MUST 为任意 UTF-8 字符序列**(相对 1.0 的放宽)。 - Metric name **SHOULD** 匹配 `[a-zA-Z_:]([a-zA-Z0-9_:])*`;label name **SHOULD** 匹配 `[a-zA-Z_]([a-zA-Z0-9_])*`。不合规的名字对 PromQL 用户更难用。 - 以 `__` 开头的标签名 RESERVED,**SHOULD NOT** 使用。Receiver **MAY** 对标签数量/长度设限。 **Samples 与 Histogram Samples(created/start timestamp)**: - Sender **MUST** 对给定 TimeSeries 按时间戳顺序发送 `samples`(或 `histograms`);**MAY** 对不同 series 并行发多请求。 - Sample/Histogram 的 `start_timestamp` **SHOULD** 为**具 counter 语义**的类型(如 counter 与 counter histogram)提供;Receiver **MAY** 拒无 `start_timestamp` 的此类序列。 - 因可选性,**0 值 MUST 被 Receiver 视为未设置**;要表达极罕见的 0 毫秒时间戳,**MUST** 用 `"1"` 或 `"-1"`。 - 过期标记: - Sender **SHOULD** 在时间序列不再被追加时发 stale marker;若序列停用**可被探测到**,则 **MUST** 发送,例如:被拉取(scraped)的序列(除非用了显式时间戳);由 recording rule 求值产生的序列。 - 一般地,不发送会引来 Receiver 侧**非平凡的查询时间对齐问题**(staleness)。 - **MUST** 用特殊 NaN `0x7ff0000000000002`;**MUST NOT** 挪作他用。 - 探测手法同 1.0,另加第 5 条:对非抓取来源跟踪指标停用(如 k6 中某个 benchmark 结束后为其 series 发 stale marker)。 **Metadata**:**SHOULD** 遵循 Prometheus 官方的 Type 与 Help 指南;**MAY** 遵循 OpenMetrics 官方的 Unit 指南。 **Exemplars**:挂在 TimeSeries 上的每个 exemplar: - **MUST** 含 value; - **MAY** 含标签(如引用 trace/request ID);若引用 trace,**SHOULD** 用 `trace_id` 标签名作为最佳实践; - **MUST** 含 timestamp(Prometheus/OpenMetrics 暴露格式中 exemplar 时间戳可选,但此处假定抓取时分配;Receiver 需靠它可靠处理如去重); - **MAY** 出现在既无 samples 也无 histograms 的 TimeSeries 中,此时 exemplars 关联到由 `labels_refs` 标识的 series,而非某个具体 sample/histogram。 #### 9.4.9 范围之外 / 未来计划 / 相关 - **Out of Scope**:同 1.0。 - **Future Plans**(2.0 已完成 1.0 三项未来计划中的两项): - **事务性**:2.0 仍未定义事务性(会让可扩展 Sender 实现变难);但 2.0 借 native histogram 的**自定义分桶**解决了经典直方图桶的事务性问题;Sender 可把经典直方图翻译成 native histogram,但这不属本规范强制;因此 Receiver **MAY** 忽略某些指标类型(如经典直方图)。 - **替代 wire format**:OpenTelemetry 的 OTLP 已验证 Apache Arrow 等列式格式的可行性;希望实验并纳入基准测试,可能长期同时维护 protobuf 与列式格式并通过内容协商加新消息。 - **全局 symbols**:预定义静态 ref→symbol 字典(如 `namespace`、`le`、`job`、`seconds`、`bytes`),Sender 可引用而无需放进请求的 symbols 表;字典可随 minor 版本增长。 - **FAQ**: - 不用 gRPC:1.0 没用,破坏它会增加采用摩擦。 - 为何按序:约束来自 Prometheus 为 append-only 优化的时序编码,也是生态众多数据库共有的要求;开启 OOO 特性时允许乱序但有性能代价,故仅应急。规范不允许乱序(Receiver 可支持),未来 2.x 可用 content type 协商乱序写。 - 如何并行:同 series 内必须有序;不同 series 可并行(Prometheus 按标签分片到独立队列、队列内串行)。 - **与 OTLP 的区别**:OTLP 是从零设计、覆盖 metrics/logs/traces/profiles 的多信号协议(推荐 gRPC+protobuf,也支持 HTTP+protobuf/JSON),metrics 侧含非标识标签、flags、时态聚合类型、resource/scoped metrics、schema URLs 等,并要求语义约定。Remote-Write 为简单、效率与有机增长而设计,2023 年正式发布时已有大量 CNCF 采用者用了多年;2.0 在旧协议上加 metadata、exemplars、start timestamp、native histograms 与字符串驻留;始终无状态、只聚焦 metrics、有主见,目标是比同类更便宜简单的稳定协议。 > 引自:[Prometheus Remote-Write 2.0 specification](https://prometheus.io/docs/specs/prw/remote_write_spec_2_0/) ### 9.5 Native Histograms 规范 #### 9.5.1 版本与状态 - **2022-11** 作为**实验特性**引入;Prometheus server **v2.40.0** 首次支持,需 `--enable-feature=native-histograms`。 - **v3.8.0 起为 stable**;但仍需**显式开启抓取**:配置项 `scrape_native_histograms`。v3.8 中 feature flag 的唯一残留作用是默认把 `scrape_native_histograms` 设为 `true`;**v3.9 起 feature flag 完全 no-op,必须显式设置该配置**。远程写需 `send_native_histograms`。**v4 起上述两项默认 `true`**。 - 声明按 **RFC 2119** 使用 MUST/MUST NOT/SHOULD/MAY/OPTIONAL 等关键词。 #### 9.5.2 术语表(Glossary) - **native histogram**:新的复合 sample type,表示完整直方图(下文常简称 histogram)。 - **classic histogram**:旧的固定 bucket 直方图类型,在 exposition 格式中以整体存在,摄入时被拆成多个 float sample。 - **Sparse histogram**:**已废弃的旧称**(等同 native histogram);**sparse buckets** 仍是描述 native histogram bucket 的有效术语。 #### 9.5.3 核心概念与设计目标 - 把直方图提升为 **data model 的一等公民**("native" 命名由来)。单个 sample 表示 sum + count + 动态 bucket 集合;PromQL 表达式也可返回该类型。 - **6 大特性**: 1. 稀疏 bucket 表示,空 bucket(近)零成本; 2. 覆盖完整 float64 范围; 3. 埋点无需配置 bucket 边界; 4. 由简单配置参数决定动态分辨率; 5. 指数分桶 schema,同 schema 间可合并; 6. exposition 与存储均高效表示。 - 早期名 "sparse histograms" 强调稀疏,但未涵盖指数分桶与动态分辨率,故改名。 #### 9.5.4 数据模型(Data model) **总体结构**:字段含 `count`(观测数)与 `sum`(观测和,任意 float64)。另含:`schema`、正/负观测各一份的稀疏索引 bucket 表示、`zero bucket`、`custom values` 列表(可空)、`Exemplars`。 **Flavors(两个独立维度)**: 1. **counter vs. gauge**:counter-like(bucket 为计数器)或 gauge-like(bucket 在某一时刻表示任意分布)。gauge histogram 概念沿用 OpenMetrics gaugehistogram。 2. **integer vs. float**:integer histogram 的 bucket 数、zero bucket、total count 均为 uint64;float histogram 同些值为 float64("weighted/scaled" 场景)。**sum 始终为 float64**。 - PromQL 只处理 float(float histogram 通用):TSDB 取出后引擎先转 float histogram;recording rule 写回的也是 float histogram。若其非 sum 字段可精确表示为 uint64,TSDB 实现 **MAY** 转回 integer histogram 以省空间(v3.00 的 TSDB 未启用)。 - **negative histogram**(如乘 -1)**仅允许作为中间结果**,在 PromQL 内始终视为 gauge;无法用任何交换格式(exposition/remote-write/OTLP)表示,也不能存入 TSDB。 - 引入 integer/float 区分的主要理由:protobuf varint 对小整数编码更短,配合 integer bucket 的 delta encoding 使多数整数 1–2 字节,多 bucket 时数据量接近 **8x 缩减**(float 恒 8 字节)。 - counter histogram 的 total count 与各 bucket 单独表现为 Prometheus counter(仅在 counter reset 时下降);但 sum 可因观测负值而下降。PromQL 实现 **MUST** 基于**整个 histogram** 检测 counter reset。 **Schema**: - int8 有符号整数,定义 bucket 边界计算方式。合法值:`-53`,以及 `-4` 至 `+8`(闭区间);`-9` 至 `+52` 为保留区。 - `-53` = custom bucket boundaries(custom buckets / NHCB);其余为标准指数 schema(standard schemas)。 - 标准 schema 相互可合并,**RECOMMENDED** 一般使用。schema 数字越大分辨率越高;schema `n` 分辨率是 `n+1` 的一半,故 `n+1` 可合并相邻 bucket 转成 `n`。 - 标准 schema `n` 下索引 `i` 的 bucket 边界(Python 语法):正 bucket 上界(含)`(2**2**-n)**i`,下界(不含)`(2**2**-n)**(i-1)`;负 bucket 下界(含)`-((2**2**-n)**i)`,上界(不含)`-((2**2**-n)**(i-1))`。`i` 可为负整数。 - `MaxFloat64`/`MinFloat64` 与 `±Inf` 例外:含 `MaxFloat64` 的正 bucket 上界(含)取 `MaxFloat64`(避免溢出);其下一个正 bucket 下界(不含)为 `MaxFloat64`、上界(含)为 `+Inf`(正溢出桶)。负向对称(含 `MinFloat64` 的 bucket 下界取 `MinFloat64`;下一个负 bucket 上界不含为 `MinFloat64`、下界含为 `-Inf`,负溢出桶)。`+Inf`/`-Inf` 桶之外 **MUST NOT** 使用。 - 取值范围依据:>52 时相邻 bucket 增长因子小于 float64 可表示差;<-9 时增长因子超过 float64 最大值。故 `-9..+52` 保留给未来标准 schema,**MUST NOT** 他用。 - 接收方 **MAY** 在摄入时降低 schema(合并 bucket);**MAY** 接受 9..52 的 schema 并在摄入时降到合法值。若仍未知:**scrape(含 federation)中任何一个未知 schema 的 histogram 存在则整个 scrape MUST 失败**(避免不完整抓取);其他摄入路径(含 WAL/WBL 重放)**MAY** 忽略并 **SHOULD** 通知用户。TSDB 从永久存储读取(不含 WAL/WBL 重放):9..52 **MAY** 转换;否则未知 schema **MUST** 在检索时报错,触发它的 PromQL 查询 **MUST** 失败。 - `-53` 边界由 custom values 显式设定,可表示 classic histogram 或指数分桶不匹配的分布;不同 custom 边界的 histogram 一般不可相互合并,**SHOULD** 仅在明确场景审慎使用。 **Buckets**: - 标准 schema 用两个 list(正、负);custom buckets 只用正 bucket list(复用)。 - 空 bucket **MAY** 从 list 中省略("sparse buckets" 由来)。 - **float histogram**:list 元素为 float64,直接表示 population(一般非负,仅 PromQL 中间结果可为负)。 - **integer histogram**:元素为 int64,每个元素是相对前一 bucket 的 delta(首 bucket 为绝对量,等价于相对 0 的 delta)。delta **MUST NOT** 使绝对 population 变负。 - **Spans**:映射 list 中 bucket 到索引。每对为 `offset`(int32)与 `length`(uint32)。**仅每个 list 的首 span 可负 offset**,定义该 bucket list 首 bucket 的索引(NHCB 索引恒为正)。length 定义紧随其后的连续 bucket 数;后续 span 的 offset 定义被排除(未填充)的 bucket 数,length 定义随后连续 bucket 数。 - 每个 span list 的 length 之和 **MUST** 等于对应 bucket list 长度。 - length=0 的空 span 有效且 **MAY** 使用,但一般无用,**SHOULD** 通过把其 offset 并入下一 span 消除;非首 span 可 offset=0,**SHOULD** 通过把其 length 并入上一 span 消除。两者允许以给生产者择优选表示的灵活性。 - 索引溢出:int32 最大 2147483647;schema 8 中含 `MaxFloat64` 的 bucket 索引 262144、`+Inf` 溢出桶索引 262145;仍可用 int32 的最高标准 schema 为 **20**(相邻增长因子约 1.000000661)。 - **示例(integer histogram,正 bucket `-2→3, -1→5, 0→0, 1→0, 2→1, 3→0, 4→3, 5→2`)**: - list `[3, 2, -4, 2, -1]` + spans `[[-2,2],[2,1],[1,2]]`; - 显式表示索引 3 一个空 bucket 后合并第二、三 span:list `[3, 2, -4, -1, 3, -1]` + spans `[[-2,2],[2,4]]`; - 全显式则合并为单 span:list `[3, 2, -5, 0, 1, -1, 3, -1]` + spans `[[-2,8]]`。 **Zero bucket**: - 恰好为 0 的观测不入标准 bucket,进 zero bucket;计数用单个 uint64(integer)或 float64(float),一般非负。 - `zero threshold`:float64 ≥ 0。为 0 时仅恰好 0 进入;为正时 `[-threshold, +threshold]` 闭区间内观测全部进入 zero bucket。用于:抑制零附近噪声;或对远离 0 的长尾分布避免大量高分辨率 bucket。 - threshold **SHOULD** 与某个常规 bucket 边界重合;若重叠,计在重叠常规 bucket 中的观测 **MUST** 在 `[-threshold, +threshold]` 之外。 - 合并规则:同 threshold 时两 zero bucket 直接相加;不同时取源中最大 threshold,若其落在其他源的已填充 bucket 内则增加 threshold 直到(对每个源)新 threshold 与已填充 bucket 边界重合、或不在任何已填充 bucket 内;随后把各源 zero bucket 及现落入新 threshold 内的 bucket 相加。 - schema `-53` 不使用 zero bucket。 **Custom values**: - 标准 schema 下未用;仅 `-53` 使用。表示 custom bucket 的上界(含),升序排列。custom bucket 存于正 bucket list 与正 span list(边界可为负);其索引即上界在 custom values 中的零基位置。 - 下界(不含)为前一个 custom value;首个(位置 0)无前值,下界视为 `-Inf`(含)。故索引 0 的 bucket 统计 `[-Inf, 第一个custom value]`。仅观测正值时,索引 0 的 bucket **SHOULD** 上界为 0,以标记是否出现 0 或以下观测(若只有正值,该 bucket 保持未填充、不显式表示)。 - custom values **MUST NOT** 为 `+Inf`;大于最后一个 custom value 的观测进入上界 `+Inf` 的溢出桶,其索引等于 custom values 长度。故 classic histogram 常见的 `+Inf` 上界通常不在 custom values 中显式出现。 - custom values **MUST NOT** 为 `NaN`;此类 classic histogram **MUST** 被拒绝,不能转为 NHCB。 **Exemplars**:native histogram sample 可有 0/1/多个 exemplar,组织为列表且 **MUST** 带时间戳。classic histogram 的 exemplar 若带时间戳 **MAY** 被 native histogram 复用。 **特殊观测值**: - 埋点 **SHOULD** 避免观测 `NaN`/`±Inf`,但 **MUST** 正确处理。 - sum 按常规浮点加法(`NaN` 观测使 sum 为 `NaN`;`+Inf` 使 sum 为 `+Inf`,除非已是 `NaN`/`-Inf` 则为 `NaN`)。 - `NaN` 观测不入任何 bucket,但使 count 自增;故 count 可大于所有 bucket 之和,差额即 `NaN` 观测数。 - `±Inf` 观测使 count 自增并递增:标准 schema 下 `+Inf`→正溢出桶、`-Inf`→负溢出桶;schema `-53` 下 `+Inf`→索引等于 custom values 长度的 bucket、`-Inf`→索引 0 的 bucket。 **OpenTelemetry 互操作**: - 标准 schema 可与 OTel exponential histogram 互转:Prom `schema` = OTel `scale`(OTel 允许低于 -4/高于 +8);Prom 索引 `n` 对应 OTel `n-1`。OTel 为稠密表示(可看作"只有一个 span 的 Prom")。Prom `zero bucket` = OTel `zero count`,均含 `zero threshold`(OTel 未给时隐含 0)。 - NHCB(及其他未来 schema)无法干净转为 OTel exponential histogram,但可转常规固定 bucket 的 OTel histogram。OTel 的 min/max 字段在 Prom 无对应概念(counter histogram 长期累积、随时可抓取)。 #### 9.5.5 编码格式(Exposition formats) - 背景:classic exposition 以字符串为主,故曾弃用 protobuf;native histogram 数值数据量大,故复活 **protobuf-based exposition**。 - 经典 Prometheus 文本格式**未**扩展、也不计划扩展;OpenMetrics 截至 2024-11-03 不支持 native histogram(protobuf 版提案 PR #256 审核中;文本版设计文档在推进)。两者 wire format 相同:proto2(client_model/metrics.proto)与 proto3(prompb/io/prometheus/client/metrics.proto)。 **Protobuf 消息结构(proto3 关键部分)**: ```protobuf message Histogram { uint64 sample_count = 1; double sample_count_float = 4; // > 0 时覆盖 sample_count double sample_sum = 2; repeated Bucket bucket = 3; // classic 用,按 upper_bound 递增,+Inf 桶可选 google.protobuf.Timestamp start_timestamp = 15; sint32 schema = 5; // 目前 -4 <= n <= 8 double zero_threshold = 6; // 零桶宽度 uint64 zero_count = 7; // 零桶计数 double zero_count_float = 8; // > 0 时覆盖 zero_count repeated BucketSpan negative_span = 9; repeated sint64 negative_delta = 10; // integer:相对前一桶的增量(首桶相对 0) repeated double negative_count = 11; // float:每桶绝对计数 repeated BucketSpan positive_span = 12; // 无观测且 zero_threshold=0 时用 no-op span(0,0) repeated sint64 positive_delta = 13; repeated double positive_count = 14; repeated Exemplar exemplars = 16; // MUST 带时间戳 } message BucketSpan { sint32 offset = 1; uint32 length = 2; } ``` 要点: - native 与 classic **共用同一 `Histogram` 消息**(在旧消息上扩展字段);sum/count/`start_timestamp` 两者共享且行为一致;扩展时顺带加入 classic float histogram 支持(`sample_count_float`、`cumulative_count_float`)。 - 桶 population:float 用绝对值,integer 用相对前一桶(首桶相对 0)的 delta → 值更小、varint 编码更短(`sint64`)。 - **未观测且 classic 无 bucket 的 native histogram 与 classic histogram 的 protobuf 完全相同**,故欲被解析为 native 的消息 **MUST** 在 `positive_span` 中放 no-op span(offset=0、length=0)。 - native exemplar **MAY** 放入重复 `Exemplar` 字段但每个 **MUST** 带时间戳;若未提供,parser **MAY** 用 classic bucket 上带时间戳的 exemplar(每 bucket 最多一个)。exemplar payload **SHOULD NOT** 远大于消息其余部分,且 **SHOULD** 分布在不同 bucket、近似均匀覆盖 bucket 范围(优于按观测分布比例放置,后者罕见取得长尾 exemplar)。 - **无 custom values 的表示**:protobuf 不表示 NHCB 的 custom values;NHCB 从不直接暴露,而是以 classic histogram 呈现,摄入时再转回 NHCB(federation 同理)。 #### 9.5.6 埋点库(Instrumentation libraries) - 官方支持 native histogram 的库(2026-06-15):**Go client_golang、Java client_java、Rust client_rust**。 - 埋点 API 不变;库可同时维护 classic 与 native 版本并并行暴露,由用户配置选择。 - **SHOULD** 提供的配置参数(括号为默认值):`NativeHistogramBucketFactor`(1.1,>1 的 float,决定初始分辨率,库选一个使相邻 bucket 宽度增长不超过该因子的起始 schema);`NativeHistogramZeroThreshold`(2^-128)。 - 分辨率用增长因子而非直接给 schema(用户不易理解 schema 数学)。**因子 → schema 对照表**: | 增长因子 | schema | | --- | --- | | 65536 | -4 | | 256 | -3 | | 16 | -2 | | 4 | -1 | | 2 | 0 | | 1.5 | 1 | | 1.2 | 2 | | 1.1 | 3 | | 1.05 | 4 | | 1.03 | 5 | | 1.02 | 6 | | 1.01 | 7 | | 1.005 | 8 | - **限制 bucket 数(DoS 防护)**:库 **SHOULD** 提供 bucket 限制策略。Go 库策略由三参数定义:`NativeHistogramMaxBucketNumber`(uint,0=不限,其余参数被忽略;典型值 160)、`NativeHistogramMinResetDuration`(duration)、`NativeHistogramMaxZeroThreshold`(float)。超限时依次采用: 1. 距上次 reset ≥ MinResetDuration 则整个 histogram reset(视为正常 counter reset,桶/和/计数/零桶清零); 2. 否则(或 MinResetDuration=0)不 reset,而是增大 zero threshold 把近零 bucket 并入零桶,上限为 MaxZeroThreshold; 3. 仍超限则降到下一更低 schema(相邻 bucket 合并、宽度加倍),重复至满足或到达 schema -4。 步骤 2/3 生效后,待 MinResetDuration 过去会执行一次 reset 以恢复初始 zero threshold 与分辨率(含更新 start timestamp)。 - 全用高分辨率 + 合理 MaxBucketNumber 的策略(OTel 默认做法)在 Prometheus 场景**不推荐**:会引起埋点程序与 TSDB 的 churn,且典型查询需合并大量 histogram,最终取最低公共分辨率。 - **按标签分区**:native histogram 比 classic 更宽松;分区后各 histogram 通常填充更少 bucket,总填充 bucket 数的增长倍数明显小于分区倍数。 - **NHCB**:截至 2024-11-03 埋点库无法直接配置 NHCB;直接需要的场景现做法是用 classic histogram 埋点并让 scraper 摄入时转 NHCB。 #### 9.5.7 抓取配置与兼容性 - 开启抓取:`scrape_native_histograms: true`(单 config 或全局);开启会改变内容协商,优先 classic protobuf exposition 而非 OpenMetrics 1.x 文本。 - `scrape_protocols` 可全局/单 config 微调协商优先级;若开启 native 且未由 flag/用户设置,其有效值变为 `[PrometheusProto, OpenMetricsText1.0.0, OpenMetricsText0.0.1, PrometheusText0.0.4]`。只要 classic Prometheus protobuf 是唯一支持 native 的格式,则需同时开启 `scrape_native_histograms` 且协商 protobuf 方能摄入 native。 - 注意:文本格式抓取一般比 protobuf 抓取更省资源(tracking issue #14668);v2 下 `quantile`/`le` 标签格式化在某些情况下不一致(v3 一致),与 native 无直接关系但因需 protobuf 而可能同时出现。 - **摄入时限制 bucket 数与分辨率**(因用户可能无法改埋点、或故意高分辨率供不同 scraper 选择): - `native_histogram_bucket_limit`(单个 histogram bucket 数上限,标准 schema 超限则反复降 schema 至满足;NHCB 超限或 schema -4 仍超限则 scrape 失败); - `native_histogram_min_bucket_factor`(相邻增长因子下限,仅标准 schema,NHCB 无效;到 schema -4 仍满足不了也成功)。 两者接受 0 表示"不限"。Prometheus 目前最多存 schema 8,但接受 >8 至保留上限 52 并在摄入时降到 8(或按上述设置降更低)。 - 注意埋点的 bucket factor 是**上限**、抓取配置的是**下限**,故导出 schema 略有差异: | 埋点 factor → 摄入后 schema | 值 | | --- | --- | | 1.4 | 1 | | 1.1 | 2 | | 1.09 | 3 | | 1.04 | 4 | | 1.02 | 5 | | 1.01 | 6 | | 1.005 | 7 | | 1.002 | 8 | - `native_histogram_bucket_limit` 设**硬成本上限**,`native_histogram_min_bucket_factor` 适合避免无谓资源开销。 - **同抓 classic 与 native**:`scrape_native_histograms=false`(v3 默认)时完全忽略 native 部分;`true`(v4+ 默认)时优先 native,classic 仅在无 native 数据时抓取。迁移场景可设 `always_scrape_classic_histograms: true`(默认 false)同时摄入两版(需 classic 至少一个 bucket 且 native 至少一个 bucket span,可为 no-op)。不冲突:classic 摄入为带后缀的多个 series,native 为单个原名 series(如 `rpc_latency_seconds` vs `..._sum`/`..._count`/`..._bucket{le=...}`)。 - **classic 转 NHCB**:布尔配置 `convert_classic_histograms_to_nhcb` 可让 Prometheus 把 classic histogram 以 NHCB 摄入。NHCB 能自动协调不同 bucket 布局,但仅保留精确匹配的边界,布局差异大时可能只剩溢出桶一个 bucket。NHCB 存储成本低、追加 bucket 的边际成本低。 #### 9.5.8 TSDB 存储与编码实现 - integer 与 float histogram 分开存储;TSDB **MAY** 把 float histogram 按 integer histogram 存(若各 bucket 计数与总 count 都在 int64 范围内可精确表示)。Prometheus TSDB 尚未启用。 - **两种新 chunk encoding**(`chunkenc.Encoding`): | 常量 | 字符串 | 数值 | 用途 | | --- | --- | --- | --- | | `chunkenc.EncHistogram` | `histogram` | 2 | integer histogram | | `chunkenc.EncFloatHistogram` | `floathistogram` | 3 | float histogram | - **两种 WAL/内存快照 record type**(`record.Type`): | 常量 | 字符串 | 数值 | | --- | --- | --- | | `record.HistogramSamples` | `histogram_samples` | 9 | | `record.FloatHistogramSamples` | `float_histogram_samples` | 10 | 另有兼容旧 WAL 的 legacy 类型 `...Legacy`(7、8),为引入 NHCB custom values 之前所用。 - series 身份只由标签决定,sample type/flavor 不参与;同一 series **MAY** 混有不同类型/风味。TSDB 实现 **MUST** 处理类型变化,但 **MAY** 用较低效方式:遇到无法写入当前 chunk 的 sample 类型即关闭该 chunk、按合适编码开新 chunk。 - **三种自定义数值编码**(细节见 `tsdb/docs/format/chunks.md`): - `varbit-int`:有符号整数的可变位宽编码,1 bit–9 bytes,越接近 0 用位越少(类似 float chunk 时间戳编码,但位长分桶针对 histogram 取值分布优化); - `varbit-uint`:无符号整数的同族编码; - `varbit-xor`:float 序列的可变位宽编码,基于当前与前一个 float 的 XOR,1 bit–77 bits/float(与 float sample 现用编码完全相同)。 - **histogram chunk 布局**:chunk 样本数(uint16)→ 1 字节描述 gauge/counter(并含 counter reset 信息)→ **chunk layout(全 chunk 内所有 histogram 共享)**:零桶 threshold(自定义编码:零或某些 2 的幂 1 字节、任意值 9 字节)、schema(varbit-int)、正 spans(span 数 varbit-uint,随后每个 span 的 length varbit-uint + offset varbit-int,重复)、负 spans 同理,仅 schema `-53` 额外有 custom values(数量 varbit-uint + 逐个自定义编码)。 - **integer histogram 每样本数据**: - timestamp:varbit-int,首样本绝对、第 2 样本相对第 1 的 delta、其后"delta of deltas"(同 float chunk 时间戳双 delta,仅 varbit-int 位分桶不同); - count:首样本 varbit-uint,其后 varbit-int,同双 delta; - zero bucket population:同上(首 varbit-uint,其后 varbit-int,双 delta); - sum:首样本 float64,其后 varbit-xor(当前与前一样本 XOR); - 正 bucket population:每桶相对前一桶的 delta(首桶绝对),varbit-int,再套双 delta(因值本身已是 delta,故称"triple delta")。负 bucket 同理。 - **float histogram 差异**:count 与 zero bucket population 为 float,编码同 sum(首样本 float64、其后 varbit-xor);bucket population 为**绝对值**(非 delta),首样本全为 float64,其后 varbit-xor(对应 bucket 的当前与前一样本 XOR)。 - **切割新 chunk 的触发条件**:integer↔float 类型变化;gauge↔counter 变化;counter histogram 的 counter reset;schema 变化;zero threshold 变化;custom values 变化;staleness marker 后接常规 sample;超 chunk 尺寸上限。 - span 差异也会改变 chunk layout,但通过追加显式未填充 bucket 使 chunk 内所有 histogram 共享 span 结构来协调。bucket 消失对 gauge histogram 可行(缺失桶补为未填充);但已填充 bucket 消失对 counter 是 counter reset。更常见的是新追加 histogram 出现此前不存在的 bucket,此时需给此前所有 histogram 补显式未填充 bucket,导致整 chunk 重编码(有优化空间但实现复杂,目前性能影响不突出)。 - **Staleness markers**:float series 用特殊的 stale `NaN` 位模式。为免仅因标记 staleness 就切新 chunk(float 值不能接在 histogram 值后),另有 histogram 版 stale marker:将 sum 字段设为 special stale `NaN`,其余字段忽略(可设为利于存储的值)。**float/integer/float-histogram 三种 stale marker MUST 被 TSDB 视为等价。** - **Chunk 尺寸限制**:float chunk 限 1024 字节,histogram 一般沿用;但单个 histogram 可能很大,故需先达到**最少 10 个 histogram/chunk** 才启用 1024 字节限制,因此 histogram chunk 可远大于 1024 字节。 - **Counter reset 检测**(较复杂,宜在摄入时做一次并持久化结果): - gauge 与 counter histogram 显式区分(不同于 float sample 摄入后不再区分 gauge/counter);gauge histogram 不适用 counter reset。gauge 后接 counter 视为发生 counter reset(等价于 gauge 删除、counter 从 0 新建)。 - counter 接 counter:若都用标准 schema 但 schema 或 zero bucket 宽度不同,可能是"兼容的分辨率降低",须满足:schema 数字减小、且旧 histogram 的每个已填充常规 bucket 要么完全落入新 zero bucket、要么完全不落入(无部分重叠)。不满足则视为 counter reset 并结束;满足则先按前述方式转换第一个 histogram(合并相邻 bucket 降 schema、把常规 bucket 并入 zero bucket 增宽)。 - 两个 NHCB 的 custom values 差异按"histogram 间兼容性"规则协调。 - 此时两者 schema 与 zero bucket 宽度相同(NHCB 不用 zero bucket,其宽度与 population 视为相等)。构成 counter reset 的情形:**count 下降**(但 sum 下降**不**算);任一 bucket(含 zero bucket)population 下降——含已填充 bucket 消失(未表示 bucket 等价于 population 0)。 - counter reset 后切新 chunk 可提升压缩率(reset 后 bucket 清零,chunk 需表示所有 histogram bucket 的并集)。故 chunk 中首样本后不会再有 counter reset,只需持久化首样本的 counter reset 信息。 - **histogram flags**:chunk 样本数后一个字节,前两 bit 存 counter reset 信息,对应 `chunkenc` 包 `CounterResetHeader` 常量: | 常量 | 位模式 | 含义 | | --- | --- | --- | | `GaugeType` | `11` | gauge histogram | | `CounterReset` | `10` | 发生 counter reset | | `NotCounterReset` | `01` | 未发生 counter reset | | `UnknownCounterReset` | `00` | 未知(总是安全,只要求重新检测) | - 查询时以 `CounterResetHint`(Go 类型 `Histogram`/`FloatHistogram` 字段,常量同名)传给调用方。gauge histogram 的 hint 恒为 `GaugeType`;其他值即 counter histogram。chunk 内第 2 个及以后离序返回的 counter histogram hint 为 `NotCounterReset`。返回 counter chunk 首样本时 hint **MUST** 设为 `UnknownCounterReset`,**除非**实现能确保上一个返回的 histogram 正是摄入时用于检测 reset 的相邻 histogram(唯此可 MAY 直接用 chunk 信息)——因 tombstone 删除/回填插入会破坏原检测。目前 Prometheus 对所有 counter chunk 首样本都返回 `UnknownCounterReset`(issue #15346)。hint 为 `UnknownCounterReset` 时 querier **MUST** 重新执行检测。 - 重叠 block/乱序样本需特别小心,会**欠检测**或**过检测**。凡 TSDB 无法确定摄入时已做检测,则须再检测一次或对第二个样本返回 `UnknownCounterReset`。 - 存在检测不到的 reset:若 reset 后计数增长足够快,使得 reset 后首个样本无任何计数下降(float counter 更常见)。可用 start timestamp 更安全地标记。 #### 9.5.9 Start timestamp / Exemplars - OpenMetrics 的 created timestamp 后改名 **"start timestamp"**,表示 metric 最近创建或重置时间。counter histogram 也插入 **synthetic zero sample**(无已填充 bucket、sum/count/zero bucket 全 0);其 schema、zero 宽度、custom values 与 float/integer flavor **SHOULD** 与紧随其后的样本一致(避免误判 spurious counter reset);synthetic zero sample 的 counter reset 信息恒为 `CounterReset`。 - Exemplar 附着在**整个 histogram sample**(非单个 bucket),故一个样本常带多 exemplar。scraper **SHOULD** 检测未变 exemplar 以省存储。检测流程(假定新 exemplar 时间戳更近): 1. 新摄入 histogram 的 exemplar 按 timestamp→value→labels 排序; 2. 按序追加到 exemplar 存储; 3. 若某 exemplar 排序在最后一个成功追加者之前或相等则追加失败(可能来自上轮抓取); 4. 排序在之后则成功。 仅当**全部** exemplar 都排在最后成功追加者之前才计为乱序(混合新 exemplar 的乱序检测不到,可接受)。 #### 9.5.10 PromQL 支持 - 只处理 float histogram;integer histogram 取出时自动转 float。 - **Annotations**:warn 级表示很可能需用户处理的问题,info 级表示可能故意但仍不寻常。 - **兼容性(算子/函数作用于多个 histogram 时需同 schema、同 zero 宽度、同 custom values)**:NHCB 之间才兼容,不同 custom values 需协调(取所有原始 NHCB 共享的 custom values 为新集合,各 NHCB 合并入统一 bucket 集;可能无共享值→只剩溢出桶;此类查询加 info 级 annotation)。标准 schema 总可降到最低公共 schema(合并相邻 bucket)。不同 zero 宽度靠扩大较小的 zero bucket 并把落入的常规 bucket 并入;若最大公共宽度落在某已填充 bucket 中间则扩到与该 bucket 边界重合。不兼容阻止操作则加 warn 级 annotation。 - **Counter reset**:hint **MAY** 被采用以避免显式检测、并正确处理常规检测不到的 reset(best effort)。与 classic 不同:**sum 下降不构成 counter reset**。子查询返回的 counter histogram 的 hint **MUST NOT** 被采用(除非引擎能确定子查询连续 histogram 在 TSDB 中也连续)。 - **Gauge vs. counter**:通过 hint 可知 gauge/counter。作用于 counter 的函数仍会处理 gauge(反之亦然),但返回 warn 级 annotation,且此时须把 gauge 当 counter 显式检测 reset。 - **桶内插值**:classic 用线性插值(假设桶内均匀);实际可能偏差大,最大误差为一个桶宽。**NHCB** 用与 classic 相同的线性插值(主用途是 drop-in 替换 classic),特例:custom values 全正 → 最低桶下界视为 0(建议含 0 值以免此启发式);至少一个 custom value ≤ 0 → 最低桶下界视为 `-Inf`,但插值时最低桶所有观测视为等于该桶上界;溢出桶观测视为等于最后一个 custom value;无 custom values 的 NHCB 所有观测视为 0。**标准指数 schema** 用**指数外推**(假定桶二分后两半 population 平均相近),更契合密度随值增大而降低的分布;zero bucket 内仍用线性插值,且若所有已填充常规桶为正则假设 zero bucket 内观测也为正(在 0 与 zero bucket 上界间插值),全负则镜像。 - **Mixed series**:counter/gauge 混用不阻止操作但加 warn;float 与 histogram 混用较多问题,许多 range vector 函数会移除混合元素并加 warn。 - **一元负号**:对 native histogram 取反(bucket、count、sum 全部反号),counter reset hint 恒设 `GaugeType`;负 histogram 仅作中间结果、恒视为 gauge、不能作 recording rule 结果(否则报错)、不能用于任何交换格式。 - **二元算子**:多数不支持(histogram↔histogram、histogram↔float、histogram↔scalar 的不可能组合会移除元素并加 info 级 annotation——类似标签匹配,sample type 作用类比标签)。 | 算子 | 行为 | | --- | --- | | `+` / `-` | 两个兼容 histogram,逐桶及 count/sum 相加;缺失桶视为空;结果 flavor/hint 规则:同 hint 则保留、否则 `UnknownCounterReset`;减法结果恒标 gauge;hint 直接矛盾 `CounterReset` 与 `NotCounterReset` 触发 warn | | `*` | float/scalar × histogram,乘所有桶及 count/sum;乘负值结果恒 gauge | | `/` | histogram ÷ float/scalar,等价乘倒数;除以 0 得到无常规桶、zero population 与 count/sum 依输入正/负/零而设为 `+Inf`/`-Inf`/`NaN` 的 histogram | | `==` / `!=` | 比较 schema、custom values、zero threshold、所有 bucket、sum/count;flavor 无关 | | 逻辑/集合算子(`and`/`or`/`unless`) | 正常 | | **trim 算子** `/`(trim lower) | 专为 native 引入,左侧须 histogram、右侧 float/scalar,移除大于/小于阈值的观测 | - **trim 算子细节**:移除仅在阈值与桶边界重合时精确,否则用插值(NHCB 有特例:最低桶下界 `-Inf`→桶内视为 `-Inf`;最高桶上界 `+Inf`→视为 `+Inf`;单桶 `[-Inf,+Inf]` 时仍视为 0)。移除后 sum 由剩余桶估计:NHCB 最低桶上界若为负或 0 取该上界、溢出桶取下界、标准 schema 负溢出桶 `-Inf`、正溢出桶 `+Inf`、其余用算术平均(NHCB 与标准 schema zero bucket,注意前述特例),标准指数桶其余用几何平均;被部分移除的桶用算子阈值替换相应边界。此估计不精确(即使阈值与边界重合),仅宜用于分位数估计/过滤(sum 无关);trim 算子是 `histogram_fraction` 的可行替代(如需计数而非分数),如 `histogram_count(h >/ 0.2 _over_time()` 会从 range vector 移除 histogram sample(range 内混用则加 info annotation)。 - **Recording rules** **MAY** 产出 native histogram 值,按正常摄入写回(含 gauge/counter 与 hint;否则新发起检测);TSDB **MAY** 把 recording rule 产生的 float histogram 转为 integer(若可精确表示)。**Alerting rules** 正常但 **RECOMMENDED** 避免把 native histogram 作为告警输出值(模板中渲染为 `FloatHistogram.String` 简易文本,难读)。 - **Testing framework**:promtool 单测与规则单测支持 native histogram;`load_with_nhcb` 把 classic 转 NHCB 并同时加载 classic float 与 NHCB series;`expect` 关键字可断言 info/warn annotation。 - **Optimizations**:解码与深拷贝 histogram 昂贵;Prometheus 现尽量免复制(TODO:仍需正规 CoW)并跳过仅需 sum/count 场景的 bucket 解码。 #### 9.5.11 Query API / UI / 模板 / 远程读写 / OTLP - **Instant/Range query**:`vector` 结果新增与 `value` 同级的 `histogram` key(互斥;值为二元数组,第二元素为 histogram 对象)。`matrix` 新增与 `values` 同级的 `histograms` key(**不**互斥;同一时间戳只能有一个样本—float 或 histogram)。命名历史原因(本应叫 `float`/`histogram`、`floats`/`histograms`)。histogram 对象:`{"count":"...","sum":"...","buckets":[[,"","",""],...]}`;每个 bucket(含 zero bucket)显式表示边界与计数,**不含 spans 与 schema**,结构不依赖 schema。``:0=open left(左开右闭)、1=open right(左闭右开)、2=open both、3=closed both。标准 schema:正 bucket open left、负 bucket open right、zero bucket(左负右正)closed both;NHCB 全为 open left(镜像 classic)。 - **Metadata**:`series` endpoint 同 float 处理(不告知 sample type;native 摄入为原名 `request_duration_seconds`,classic 为 `..._sum`/`..._count`/`..._bucket`;两者都摄入则全部返回)。`targets/metadata` 与 `metadata` 作用于原始名字,classic 与 native 都表示为 `request_duration_seconds`(type 均为 `histogram`),**无法**仅凭 metadata 区分 native/classic,需再查 `series`。 - **UI**:Table view 用柱状图 + 文本(每柱一个 bucket,x 轴由上下界定,柱面积 ∝ population)。可切换指数/线性 x 轴(默认指数,适配标准 schema);指数轴上常规桶等宽(y 轴可显示真实 population,zero bucket 例外——技术宽度无限但按常规桶等宽渲染)。线性轴下 y 显示 population/宽度。Graph view 计划用 heatmap(TODO:尚未实现,现只画观测之和;issue #11268)。 - **Template expansion**:native histogram 可渲染为受开/闭区间数学记法启发的文本(Go `FloatHistogram.String`);桶多、边界小数位多时难读,慎用。示例:`{count:3493.3, sum:2.349209324e+06, [-22.62741699796952,-16):1000, ...}`。 - **Remote-write/read**:protobuf 扩展,不支持 native 的接收方会忽略新字段;发送需 `send_native_histograms=true`。remote-write v2 中 native histogram 是 stable。**SHOULD** 在抓取阶段把 classic 转 NHCB,避免 remote-write 传输 classic 的一致性问题;OTel 显式 histogram 也 **SHOULD** 在 OTLP 摄入时转 NHCB。TODO:多个 exemplar 分属不同 remote-write 请求的处理。 - **Federation**:native 联邦需 protobuf;OpenMetrics 文本待支持后用文本亦可但 protobuf 更高效。NHCB 经联邦端暴露时渲染为 classic float histogram,scraper 可选转回 NHCB 或作为 classic 摄入(后者可能命名冲突)。注意 OpenMetrics v1 不支持 classic float histogram,但 Prometheus 联邦不用 OM v1。 - **OTLP**:内置 receiver 用前述兼容性把 OTel exponential histogram 转 native(scale>8 降到 8;< -4 则摄入失败)。显式 OTel histogram 等价 classic,默认转 classic,可选直接转 NHCB。 - **Pushgateway**:逐步支持,v1.9 完整支持;基于 classic protobuf 内部模型,改动主要是 UI。混合 histogram(classic + native bucket)可推送并原样暴露;查询 API 只能返回一种 bucket,优先 native。 - **promtool**:`query ...` 支持(含新增 `query analyze` 分析 classic/native 使用模式);`test rules` 支持;`tsdb analyze`/`tsdb list` 正常(前者 `--extended` 有 histogram chunk 专节);`tsdb dump` 用 `FloatHistogram.String` 文本;`tsdb create-blocks-from rules` 支持;`promql ...` 全支持;`tsdb bench write` 暂不计划支持;依赖 OpenMetrics 文本格式的命令不支持 native:`check metrics`、`push metrics`、`tsdb dump-openmetrics`、`tsdb create-blocks-from openmetrics`。 - **prom2json**:v1.4 起支持;若 exposition 中 histogram 至少含一个 bucket span,则用 native bucket 替换 JSON 中常规 classic bucket(格式借鉴 query API)。 #### 9.5.12 迁移注意事项(Migration considerations) 三大问题来源: 1. **query 方式不同**(多数改动小,但有棘手边界,难以可靠自动转换); 2. **classic 与 native 不能相互聚合**(过渡点导致 dashboard 难做、跨过渡点的 range vector 必然不完整); 3. **classic 可把桶边界精确放在关注点,native 标准 schema 虽分辨率高但不能任意设边界**(此场景用户体验可能更差)。 - 应对 (3):可不迁移该 histogram;或保持埋点不变、摄入时把 classic 转 NHCB(利用存储优势,但仍需等同处理 (1)(2))。 - **保守做法**:允许较长过渡期(并行采集存储 classic 与 native)。步骤: 1. 先让埋点并行暴露 classic 与 native(若计划仅靠 NHCB 转换可跳过); 2. 配置同时抓取(必要时启用 classic→NHCB 转换); 3. 现有 classic 查询继续可用,逐步迁移 dashboard/alert/recording rule(注意长 range vector 如 `histogram_quantile(0.9, rate(rpc_duration_seconds[1d]))`,需已采集 ≥1d 才用;并行期足够长可免写复杂切换); 4. 确认迁移完毕后再改为只抓 native(也可用 relabel 增量移除 classic); 5. 最后从埋点移除 classic。 - Grafana Mimir 文档有同理念的详细迁移指南。 > 引自:[Native Histograms](https://prometheus.io/docs/specs/native_histograms/) ### 9.6 OpenMetrics 2.0 迁移指南(Client Libraries) **定位**:讲从 OpenMetrics(OM)1.0 到 2.0 中,影响 **client library(即 exposer)实现**的变更;章节按**可增量实施**排序(从版本协商开始,再到指标类型、语法、元数据)。 **免责**:本文可能有错;**任何与规范冲突处,以规范文档为准**。NOTE:OM 2.0 目前**实验态(RC)**,细节可能变;进度见 OpenMetrics issue #276。 **判定口径**:**Breaking** = 1.0 中合法的一行在 2.0 中变为非法;**Non-breaking** = 新增语法或放宽限制,不使既有合法行失效。 #### 9.6.1 Quick Reference 变更表(1.0 → 2.0 → 是否 Breaking) | 类别 | 1.0 → 2.0 | Breaking | | --- | --- | --- | | Negotiation | Content-Type 版本头 `version=1.0.0` → `version=2.0.0` | No | | Negotiation | 协商默认仍为最旧版本(默认 1.0) | No | | Naming | MetricFamily 名必须与 Metric 名匹配(1.0 隐式去后缀 → 2.0 精确匹配) | **Yes** | | Naming | Counter `_total` 后缀 `MUST` → `SHOULD` | No | | Naming | 保留后缀(1.0 未规定 → 2.0 `_count`/`_sum`/`_bucket` 等 SHOULD NOT) | No | | UTF-8 Names | metric/label 名 `[a-zA-Z0-9_:]` only → 允许 UTF-8、必要时加引号 | No | | Start Timestamps | 独立 `_created` 样本 → 样本行内联 `st@` | **Yes** | | CompositeValues | Summary/Histogram/GaugeHistogram 的展开 `_bucket`/`_count`/`_sum` 多行 → 单个 `{key:value}` CompositeValue | **Yes** | | CompositeValues | Sum 与 Count 由可选变为 CompositeValue 中**必需** | **Yes** | | Native Histograms | 1.0 N/A → 2.0 通过 `schema`/spans/buckets 等指数分桶 | No | | Exemplars | W3C trace context 键(`trace_id`、`span_id`)推荐 | No | | Exemplars | 时间戳由可选/最多 1 个 → **强制时间戳 + 允许多个** | Mixed | | Exemplars | 大小限制从 128 字符硬限、bucket 级放置 → 软限制、W3C 键、sample 级放置 | Mixed | | Unknown Type | Number only → 可用 Number 或 CompositeValue | No | #### 9.6.2 版本协商与 Content-Type - **Non-breaking**:OM 1.0 的 Content-Type 仍合法。 - OM 1.0:`application/openmetrics-text; version=1.0.0; charset=utf-8`;OM 2.0:把 `version=2.0.0`。 - **协商默认**:无指定版本时**必须默认最旧版本(1.0.0)**,用标准 HTTP content-type 协商;若 scraper 未请求 2.0.0 就回 1.0.0。即 exposer **默认继续提供 1.0**,仅在 consumer 要求时切 2.0。 - **Protobuf format removed**:OM 2.0 不再规定官方 protobuf 格式;1.0 的 protobuf 可继续支持,但 2.0 不含新的/更新的 protobuf 格式。Prometheus protobuf wire format 仍重要且维护(见 exposition formats 文档)。 #### 9.6.3 命名变更(Naming Changes) - OM 2.0 收紧 MetricFamily 名与 Metric 名的关系,并改 counter/info 的后缀规则。1.0 的 parser 会**隐式剥离**已知后缀以把样本名映射回 MetricFamily;2.0 取消该隐式剥离,MetricFamily 名必须与每个 Metric 的 Name **精确一致**。总体上强制的 unit 与 `_total` 后缀现为**推荐但非必需**。 - **术语变更**(Non-breaking,不影响暴露格式):数据模型层级由 `MetricSet → MetricFamily → Metric → MetricPoint → Sample`(1.0)改为 `MetricSet → MetricFamily → Metric → Sample`(2.0,Sample 携带 Number 或 CompositeValue):`MetricPoint` 被移除、`Sample` 升为一等对象;复杂类型(除 StateSet)改用 CompositeValue 而非多个 MetricPoint。StateSet 下:原 Metric → 现 MetricGroup;原 MetricPoint → 现 Metric。 - **MetricFamily 名必须匹配 Metric 名**(**Breaking**):TYPE/HELP/UNIT 注释里的 MetricFamily 名必须与 Metric 行上的名一致。 ```text # OM 1.0:注释用基名、样本带 _total # TYPE http_requests counter # HELP http_requests Total HTTP requests. http_requests_total 1027 # OM 2.0:注释名也带 _total # TYPE http_requests_total counter # HELP http_requests_total Total HTTP requests. http_requests_total 1027 ``` 这是**最常见需要改的点**:把 TYPE/HELP 元数据名补上原先只在样本上的后缀。 - **Counter `_total`**(Non-breaking):1.0 counter 名**必须**以 `_total` 结尾;2.0 变为**应该**(不再强制)。Tip:改为 should 主要为兼容 OpenTelemetry(其对 counter 名格式无要求)。 ```text # 1.0 # TYPE http_requests counter http_requests_total 1027 # 合法 2.0 # TYPE http_requests counter http_requests 1027 ``` - **保留后缀**(Non-breaking):MetricFamily 名 **should not** 以 `_count`、`_sum`、`_bucket`、`_gcount`、`_gsum` 结尾。原因:较老的 ingester 会把 histogram/summary 展开为带这些后缀的经典样本,若你另有 `foo_bucket` 的 gauge 会与 `foo` 直方图展开的 `foo_bucket` 冲突。因为是 should not(非 must not),既有指标仍能解析,但改名可避免碰撞 bug。 - **实践中命名**(完整 OM 2.0 暴露示例): ```text # TYPE http_requests_total counter # HELP http_requests_total Total HTTP requests received. http_requests_total{method="GET",code="200"} 1027 http_requests_total{method="POST",code="201"} 53 # TYPE http_request_duration_seconds histogram # HELP http_request_duration_seconds Duration of HTTP requests in seconds. http_request_duration_seconds{method="GET"} {count:1027,sum:172.5,bucket:[0.1:800,0.5:950,+Inf:1027]} # TYPE build_info info # HELP build_info Build metadata. build_info{version="1.4.2",branch="main",goversion="go1.22"} 1 # EOF ``` - `http_requests_total`(counter):TYPE 名与样本名精确一致,保留 `_total` 提升可读性与向后兼容; - `http_request_duration_seconds`(histogram):MetricFamily 名避开保留后缀;用 CompositeValue 单行表达全部桶; - `build_info`(info):TYPE 名与样本名一致,`_info` 后缀按要求出现在 MetricFamily 名上。 #### 9.6.4 UTF-8 名称 - **Non-breaking**:1.0 传统名语法仍合法。2.0 允许 metric/label 名含超出传统 `[a-zA-Z0-9_:]` 的 UTF-8 字符,主要为 OpenTelemetry bridge 场景(如 `process.cpu.seconds` 点分命名),与放宽的 `_total` 规则配合更好。**注意:并非所有 Prometheus 生态工具都支持 UTF-8 指标名**。 - **Metric name 加引号**:不匹配 `^[a-zA-Z_:][a-zA-Z0-9_:]*$` 的 metric 名**必须**用双引号包裹(任何名都可加引号,但仅超出传统字符集时必需)。引号内用 `\\` 表示反斜杠、`\"` 表示双引号、`\n` 表示换行。 ```text # TYPE "process.cpu.seconds" counter # HELP "process.cpu.seconds" Total user and system CPU time spent in seconds. {"process.cpu.seconds"} 4.20072246e+06 ``` - **Label name 加引号**:不匹配 `^[a-zA-Z_][a-zA-Z0-9_]*$` 的 label 名必须用双引号;规则同上。 ```text {"process.cpu.seconds","node.name"="my_node"} 4.20072246e+06 ``` - **替代花括号语法**:需要加引号的 metric 名要**移到花括号内作为第一个元素**(即 metric 名必须在花括号内,不能在外);ABNF 中为 `name-and-labels-in-braces` 产生式。完整示例: ```text # TYPE "process.cpu.seconds" counter # UNIT "process.cpu.seconds" seconds # HELP "process.cpu.seconds" Total user and system CPU time spent in seconds. {"process.cpu.seconds","node.name"="my_node"} 4.20072246e+06 ``` #### 9.6.5 起始时间戳(Start Timestamps) - **Breaking**:`_created` 样本不再用于提供 Start Time。 - OM 2.0 用样本行上的**内联 Start Timestamp(`st@`)**取代独立的 `_created` 样本,减小暴露体积并避免 `_created` 样本与值样本之间的竞态。Counter、histogram、summary 具有创建语义并支持 start timestamp;**gauge 不支持**。 - OM 2.0 样本行字段顺序: ```text metric_name value [timestamp] [st@start_timestamp] [# exemplar...] ``` - Counter 对比:1.0 用 `http_requests_total 1027` + `http_requests_created 1000000000` 两行;2.0 用 `# TYPE http_requests_total counter` + `http_requests_total 1027 st@1000000000.000`(`_created` 样本变为内联 `st@`)。 - Histogram 对比:1.0 是 `_bucket{le=...}` 多行 + `_sum`/`_count`/`_created`;2.0 为 `http_request_duration_seconds {count:1027,sum:172.5,bucket:[0.1:800,0.5:950,+Inf:1027]} st@1000000000.000`(`_created` 样本消失,`st@` 出现在该 CompositeValue 行的值之后)。 - 实践示例(含标签、时间戳、start timestamp、exemplar 的完整字段顺序): ```text # TYPE http_requests_total counter http_requests_total{method="GET",code="200"} 1027 1710000000.123 st@1000000000.000 # {trace_id="abc123",span_id="def456"} 1.0 1709999999.456 http_requests_total{method="POST",code="201"} 53 1710000000.123 st@1000000000.000 # TYPE http_request_duration_seconds histogram http_request_duration_seconds{method="GET"} {count:1027,sum:172.5,bucket:[0.1:800,0.5:950,+Inf:1027]} 1710000000.123 st@1000000000.000 # EOF ``` #### 9.6.6 CompositeValues - **Breaking**:传统多行复杂类型不再允许(StateSet 除外)。 - OM 1.0 中复杂类型(histogram、summary、gaugehistogram)表示为多条展开样本行(每桶一行、count 一行、sum 一行);OM 2.0 合并为**单行**,其值为 CompositeValue —— 一个结构化 `{key:value,...}` 块。**三种类型都必须用 CompositeValue 语法**。 - 语法概要:花括号包裹、逗号分隔 `key:value`、**内部无空白**;key 为固定字面名,value 依字段为 number/integer/方括号列表。 完整 key 表: | key | 格式 | 适用类型 | 含义 | | --- | --- | --- | --- | | `count` | number | histogram、summary | 观察计数 | | `sum` | number | histogram、summary | 观察值之和 | | `gcount` | number | gaugehistogram | gauge 观察计数 | | `gsum` | number | gaugehistogram | gauge 观察值之和 | | `quantile` | `[q:v,...]` | summary | 按分位数排序的 quantile/value 对 | | `bucket` | `[le:v,...,+Inf:v]` | histogram、gaugehistogram 经典 | 经典桶上界/计数对,**`+Inf` 必需** | | `schema` | integer | native hist | 原生直方图分辨 schema | | `zero_threshold` | realnumber | native | 零桶宽度 | | `zero_count` | number | native | 零桶内观察数 | | `negative_spans` | `[off:len,...]` | native | 负桶 span 定义 | | `negative_buckets` | `[v,...]` | native | 负桶计数 | | `positive_spans` | `[off:len,...]` | native | 正桶 span 定义 | | `positive_buckets` | `[v,...]` | native | 正桶计数 | 各类型必需 key: | 类型 | 必需 key | | --- | --- | | Summary | `count,sum,quantile` | | 经典 histogram | `count,sum,bucket` | | 经典 gaugehistogram | `gcount,gsum,bucket` | | native histogram | `count,sum,schema,zero_threshold,zero_count` 加可选正/负 spans+buckets | | native gaugehistogram | `gcount,gsum,schema,zero_threshold,zero_count` 加可选 | | combined(经典+native) | `count`/`gcount`、`sum`/`gsum`、native 字段、最后 `bucket` | 含全部 key 的抽象示例(combined): ```text {count:,sum:,schema:,zero_threshold:,zero_count:,negative_spans:[:,...],negative_buckets:[,...],positive_spans:[:,...],positive_buckets:[,...],bucket:[:,...,+Inf:]} ``` 其中 `` number、`` integer、`` real、`` 整数偏移、`` 正整数长度、`` 实数桶边界;**字段必须按所示顺序出现,花括号内不得有任何空白**。 - **Summary**:2.0 中 Summary Sample 必须含 Count、Sum 与一组 quantile(1.0 中三者均为 may);此前省略 count/sum 的 exposer 必须补上。1.0 五行 → 2.0 一行:`http_request_duration_seconds_summary {count:1027,sum:172.5,quantile:[0.5:0.013,0.9:0.025,0.99:0.10]}`;quantile 映射移入块内;无观察时用 `quantile:[]`。 - **Histogram**:所有 `_bucket`/`_sum`/`_count` 行并为单个 CompositeValue:`http_request_duration_seconds {count:1027,sum:172.5,bucket:[0.1:800,0.5:950,+Inf:1027]}`;bucket 列表用 `threshold:count` 对并含 `+Inf`;字段顺序必须为 `count,sum,bucket`;`_sum`/`_count` 后缀彻底消失。 - **GaugeHistogram**:度量当前分布(非 reset 型),如队列深度、在途请求大小;值具 gauge 语义,可升可降无需 reset。1.0 展开格式用 `_gcount`/`_gsum` 后缀;2.0 用 CompositeValue 的 `gcount`/`gsum` 字段(保留 gauge 语义前缀)。示例:`queue_depth_bytes {gcount:23,gsum:1048576,bucket:[1024:5,65536:18,+Inf:23]}`;字段顺序必须为 `gcount,gsum,bucket`;**GaugeHistogram 没有 Start Timestamp(无创建语义)**。 - **实践示例**(全类型同现): ```text # TYPE http_requests_total counter http_requests_total{method="GET",code="200"} 1027 1710000000 st@1000000000 # TYPE http_request_duration_seconds_summary summary http_request_duration_seconds_summary{path="/api/v1"} {count:1027,sum:172.5,quantile:[0.5:0.013,0.9:0.025,0.99:0.10]} 1710000000 st@1000000000 # TYPE http_request_duration_seconds histogram http_request_duration_seconds{path="/api/v1"} {count:1027,sum:172.5,bucket:[0.1:800,0.5:950,+Inf:1027]} 1710000000 st@1000000000 # TYPE queue_depth_bytes gaugehistogram queue_depth_bytes{queue="work"} {gcount:23,gsum:1048576,bucket:[1024:5,65536:18,+Inf:23]} 1710000000 # EOF ``` - counter 行用普通 Number(非 CompositeValue);summary 与 histogram 用 CompositeValue;gaugehistogram 无 Start Timestamp。 - **Sum 与 Count 必需**(**Breaking**):2.0 中 histogram 与 gaugehistogram 的 Sum、Count 不再允许省略(1.0 可选)。histogram 例:1.0 只有 bucket 行 + `_created` → 2.0 `http_request_duration_seconds {count:1027,sum:172.5,bucket:[...]} st@1000000000`;gaugehistogram 例:1.0 只有 bucket 行 → 2.0 `queue_depth_bytes {gcount:23,gsum:1048576,bucket:[1024:5,65536:18,+Inf:23]}`。 #### 9.6.7 Native Histograms(迁移视角) - **Non-breaking**:OM 1.0 完全不支持 native histogram。2.0 中取代"插桩时选定的固定桶边界",用**指数分桶 schema**在全部取值域自动提供分辨率,无需配置桶;`schema` 字段控制桶宽粒度,值越大桶越窄(越细)。字段沿用 CompositeValue 的 `{key:value,...}` 格式。 - **Native-only histogram**:CompositeValue 按顺序含 `count`(观察总数)、`sum`(观察值之和)、`schema`(整数 -4 到 8,控制指数桶宽,越大越细)、`zero_threshold`(非负浮点,定义零桶边界 `[-threshold, +threshold]`)、`zero_count`(零桶观察数)、`positive_spans`、`positive_buckets`。 - **Spans** 是 `offset:length` 对,描述哪些指数桶被填充:首个 span 的 offset 是起始桶下标(可为负),length 是从该下标起连续填充的桶数;其后每个 span 的 offset 是跳过的**空桶数**;所有 span length 之和等于 bucket 列表中的值总数。负向观察用 `negative_spans`/`negative_buckets`,语法相同。若无任何桶被填充,spans 与 buckets 字段可整体省略。 ```text # TYPE http_request_duration_seconds histogram http_request_duration_seconds {count:59,sum:120.0,schema:3,zero_threshold:1e-4,zero_count:2,positive_spans:[0:3,2:2],positive_buckets:[10,15,12,8,12]} 1710000000 st@1000000000 ``` 两个 span:首个从桶下标 0 起 3 个连续桶,跳过 2 个空桶,再 2 个连续桶;桶总数 3+2=5,与 `positive_buckets` 的 5 个值吻合。 - **Classic 与 Native 合并**:部分 consumer 尚不支持 native 桶;combined CompositeValue 在单行同时给出两种表示以向后兼容。字段顺序为 `count`、`sum`、全部 native 字段(`schema` 到 `positive_buckets`)、最后经典 `bucket` —— 这样偏好 native 的 parser 可在经典桶列表前停止读取。 ```text # TYPE http_request_duration_seconds histogram http_request_duration_seconds {count:59,sum:120.0,schema:3,zero_threshold:1e-4,zero_count:2,positive_spans:[0:3,2:2],positive_buckets:[10,15,12,8,12],bucket:[0.01:5,0.1:25,1.0:47,10.0:57,+Inf:59]} st@1000000000 ``` - **GaugeHistogram with Native Buckets**:语法同 histogram,但用 `gcount`/`gsum` 取代 `count`/`sum`;TYPE 行也作区分。 - **实践示例**(native-only 与 combined 同现): ```text # TYPE http_request_duration_seconds histogram http_request_duration_seconds{method="GET"} {count:59,sum:120.0,schema:3,zero_threshold:1e-4,zero_count:2,positive_spans:[0:3,2:2],positive_buckets:[10,15,12,8,12]} 1710000000 st@1000000000 http_request_duration_seconds{method="POST"} {count:34,sum:68.5,schema:3,zero_threshold:1e-4,zero_count:1,positive_spans:[0:2,3:1],positive_buckets:[8,12,13],bucket:[0.01:3,0.1:14,1.0:28,10.0:33,+Inf:34]} 1710000000 st@1000000000 # EOF ``` - GET 行用 native-only 字段;POST 行同时含 native 与经典桶字段,兼顾尚未支持 native 的 consumer。 #### 9.6.8 Exemplars(2.0 变更) - **强制时间戳**(**Breaking**):1.0 exemplar 时间戳可选(may);2.0 **每个** exemplar 必须含时间戳——这对不预期值后有时间戳的 1.0 parser 即为破坏性变更。 ```text http_requests_total 1027 1710000000 # {trace_id="abc123",span_id="def456"} 1.0 1709999999 ``` (`#` 之后为标签、值 `1.0`、强制时间戳 `1709999999`。) - **多个 Exemplars**(Non-breaking,单个仍允许):1.0 每 sample 最多 1 个 exemplar;2.0 允许单 sample 多个,每个以 `#` 起,各自带标签集、值与时间戳。 ```text http_requests_total 1027 1710000000 # {trace_id="abc123",span_id="def456"} 1.0 1709999999 # {trace_id="xyz789",span_id="uvw012"} 1.0 1709999800 ``` - **W3C Trace Context Keys**:2.0 推荐用 `trace_id` 与 `span_id` 作为 exemplar 标签键(遵循 W3C Trace Context);键名一致可让 consumer 无需逐 exporter 配置即可把指标与分布式 trace 关联。 - **Size Limit Relaxation**:1.0 中 exemplar 标签集有 **128 字符硬上限**;2.0 移除硬上限,改为软性指导(exposer 不应发出过大的 exemplar 标签集),把限制负担转移给 scraper。 - **Histogram Exemplar Placement**:1.0 中 histogram exemplar 出现在各 `_bucket` 行上;2.0 用 CompositeValue 后,exemplar 挂在 CompositeValue 之后的单一样本行上。无标签的 exemplar 必须用空 LabelSet `{}`;在同一 Sample 上挂多个 exemplar 时,各 exemplar 应使用一致的标签名。 #### 9.6.9 Unknown Type **Non-breaking**:1.0 中 Unknown 类型 Metric 的 Sample 只能是 Number 值;2.0 中也可为 CompositeValue。因 Unknown 一般不该用(为类型不确定的第三方指标而存在),这是次要变更,主要影响需处理任意 Unknown 数据的 ingester 与库。 #### 9.6.10 需修改的代码点(归纳) 1. **Content-Type 版本**:支持 `version=2.0.0`,且在未请求 2.0 时默认回 1.0.0(标准 content negotiation); 2. **TYPE/HELP/UNIT 元数据名**:补上原先只在样本上的后缀(如 `_total`),使 MetricFamily 名与 Metric 名精确一致(最常见改动); 3. **停止 `_created` 样本**:改为在样本行内联 `st@`;gauge/gaugehistogram 不加 start timestamp; 4. **复杂类型输出重写**:summary/histogram/gaugehistogram 从多行展开改为单行 CompositeValue,并**必须**含 count/sum(gaugehistogram 为 gcount/gsum),字段顺序严格;无空白; 5. **Exemplar 输出**:所有 exemplar **必须**带时间戳;支持单 sample 多个 exemplar;推荐 `trace_id`/`span_id`;histogram exemplar 移到 sample 行;无标签时用 `{}`; 6. **UTF-8 名支持**:不匹配传统正则的 metric/label 名必须用双引号,且加引号的 metric 名要放进花括号首位; 7. **命名清理**:避免(should not)以 `_count`/`_sum`/`_bucket`/`_gcount`/`_gsum` 结尾的 MetricFamily 名;counter 可去 `_total`(可选); 8. **Unknown 类型**:允许其 Sample 使用 CompositeValue。 > 引自:[OpenMetrics 2.0 Migration Guide for Client Libraries](https://prometheus.io/docs/guides/open_metrics_2_0_migration/) ## 第 10 章 参与社区 本章面向"想给 Prometheus 本身(而非自己的应用)写代码/写文档"的读者。制度来源为官方 Contributing 指南;Prometheus 社区其他仓库可能有各自指南与规则,仓库特定信息见各仓库的 `README.md` 与 `CONTRIBUTING.md`。 > 阅读路径建议:新人先看"如何挑选要做的任务";有经验的贡献者看"如何让 PR 被合并";已有较复杂具体计划的看"proposal process"。 ### 10.1 贡献流程 #### 10.1.1 PR 合并流程(面向有经验的贡献者) 1. 本地用 `make test` 测试改动并**尽量补充测试**; 2. 为避免 CI lint 失败,运行 `make common-format` 与 `make lint` 并修复问题; 3. 用 **`git commit -s`** 提交以**签署 DCO**; 4. 一般对默认分支提 PR(通常是 `main`,有时 `master`),评审者会协助处理例外; 5. 评审者应被自动分配,团队力求及时响应但可能因其他优先事项延迟; 6. 检查失败的 CI job,**全部应通过**; 7. 评审者可能要求改动,快速处理可缩短 PR 周期。 更多 GitHub 流程见 "GitHub guidelines"(见 10.4 节)。 #### 10.1.2 沟通渠道 面向用户的常规社区渠道用于讨论 Prometheus **使用**(含用 Prometheus 代码做非 Prometheus 开发,如用插桩库埋点);Prometheus 组件本身的**开发**走以下渠道: | 渠道 | 定位与注意事项 | | --- | --- | | **GitHub** | 贡献在 GitHub pull request 中评审;GitHub issue 常用于讨论具体 bug 与 feature request;非正式或全局性讨论更适合其他渠道 | | **CNCF Slack** | 大量非正式聊天发生在 CNCF Slack,主开发频道为 `#prometheus-dev`,还有大量专门频道(如 `#prometheus-...-dev`、`#prometheus-protobuf-dev`)。**注意 Slack 是信息孤岛**:内容不被外部搜索引擎索引、难以导出归档、只读也需登录,因此视为**临时且公众不可达**,内容不会永久保留。重要信息(如讨论结果)应通过其他渠道(GitHub 或邮件列表)发布;避免在别处直接链接 Slack 消息而不摘要其内容 | | **开发者邮件列表** | `prometheus-developers`(Google Groups,另有 mail-archive 镜像),适合公告与更正式的全局话题讨论;其归档被搜索引擎索引,便于查找过往讨论 | | **开发者峰会(Developer summits)** | 讨论更复杂开发话题的公开会议;日程、会议记录等见 "Developer summit details" | | **工作组(Work groups)** | 开发者为某主题定期开线上会则成立工作组,会议公开并经 Prometheus 日历发布 | #### 10.1.3 如何挑选任务 对你**重要**、你**已是用户或专家**、或你**想成为专家**的方向最佳。社区两种标识方式: - `good-first-issue` label:适合新贡献者的起点; - `triage/accepted` label:更复杂问题(已分诊、信息齐全、可开工); - `triage/needs-triage` 与 `triage/needs-information`:表示尚无人看或信息不足——**不要做这两个 label 的 issue**。 #### 10.1.4 提案流程(Proposal process) 较大的改动与想法需要在 **Proposal 仓库**(github.com/prometheus/proposals)做正式提案与评审,详见已接受的 "proposal proposal"(`proposals/0001-proposal-process.md`)。 #### 10.1.5 GitHub guidelines - **commit message 应描述本次提交的改动**;对提交粒度无强意见,按合理逻辑组织,好 commit 便于评审从而更快合并(例如一个 commit 加测试暴露 bug、另一个 commit 修复)。 - commit message **必须包含 `Signed-off-by: ` 行**,作者以此同意 https://developercertificate.org/ 的条款(针对该次贡献)。 - 有了想提的改动后,推到**个人 fork** 并对默认分支开 PR;默认分支通常是 `main`,个别仓库为 `master`。某些情况需对 **release 分支**提 PR(如 `release-3.5`),最常见是新版本 release candidate 的修复或 LTS 版本修复;不确定就问。 - 所需评审者会**自动添加**;其他应评审或想评审的人可在评论中用用户名提及,但请**勿随意 @ 社区成员**。每个 PR 都跑检查,必须**全部通过**才能合并,失败检查由 **PR 作者**排查解决。 - 评审中常追加 fixup commit;评审者可 squash 成连贯提交,但须确保最终 commit message **有意义、非自动生成**;也可要求作者合并前整理提交。一般允许 rebase 分支;**一旦他人基于尚未进入 `main` 的提交做了改动,作者就不应再改写这些提交**。PR 过大或混入不相关关注点(如重构与逻辑改动)时**应拆分为多个 PR**。 #### 10.1.6 AI 生成贡献 Prometheus 作者**不反对**用 AI 工具生成代码,但要求: - 每个 commit 有 **DCO**,作者据此证明贡献(整体或部分)由本人创作且有权提交;若基于既有工作,则须处于合适开源许可下且作者有权在修改后提交(详见 linuxfoundation.org/legal/generative-ai)。 - **人类作者必须完全理解所提交代码**,提交前应结合 DCO 仔细审查 AI 生成代码。 - **鼓励显式披露 AI 使用**,例如在相应 commit 加 `Assisted-by: `。 - 关于 issue 与 PR 的讨论,**强烈倾向人与人对话**。 > 引自:[Contributing](https://prometheus.io/docs/guides/contributing/) ### 10.2 DCO(Developer Certificate of Origin) - 提交方式:**`git commit -s`**,会在 commit message 末尾自动追加 `Signed-off-by: `。 - 语义:作者以此**同意** https://developercertificate.org/ 的条款(针对该次贡献)。 - 每个 commit **必须**有该行;**AI 生成代码同样适用**——DCO 是"我对这段代码负责"的签署,不理解代码就无法履行该责任。 - 建议配合 `Assisted-by:` 行显式披露 AI 工具使用(见 10.1.6)。 > 💡 补充:DCO 签署的常见踩坑是"忘了 `-s` 而 PR 被 CI 拦下"。补救方式是 `git commit --amend -s`(改最后一个 commit)或 `git rebase --signoff `(批量补签),然后 `git push --force-with-lease` 更新 PR——**注意不要在他人已基于你的提交工作后再改写历史**(见 10.1.5)。 ### 10.3 开发环境搭建 Prometheus 主体用 Go 编写,开发环境的核心是"能跑 make 目标"。 #### 10.3.1 必备命令 | 命令 | 用途 | | --- | --- | | `make test` | 运行测试,本地验证改动 | | `make common-format` | 通用格式化,避免 CI lint 失败 | | `make lint` | 运行 linter 并修复问题 | | `make format` | 格式化 | | `make style` | 风格检查/修正 | 项目有**大量 linter**,务必用上述目标正确格式化,不要手工调整风格。 #### 10.3.2 编码风格 - 使用该语言**惯用**代码;**遵循周边既有风格**,不把风格改进与代码改动混在一起; - 使用正确英语语法与标点,避免不必要缩写: - BAD:`// batchQueue full, try again later` - GOOD:`// The batchQueue is full, so we need to try again later.` - Markdown 中**除行尾为 URL 外,行宽限制 80 字符**,便于评审评论。 #### 10.3.3 Go 风格指南 Go 是 Prometheus 及生态的主要语言,参考 https://go.dev/wiki/CodeReviewComments。Prometheus 额外规则: - **命名导入常放单独 block**,并按 **stdlib / 其他仓库 / 本仓库** 分组; - 导出类型的 doc comment **不被 linter 强制**(误报太多)但团队在意,应在合理处使用; - **长函数签名按下述方式折行**——首行以左括号结尾,参数按需分多行,最后以单独一行右括号开头结束: ```go func (s *shards) sendSamples( ctx context.Context, samples []prompb.TimeSeries, sampleCount, exemplarCount, histogramCount int, pBuf *proto.Buffer, buf compression.EncodeBuffer, compr compression.Type, ) error { ``` - **反例(错误写法)**:参数首行不以左括号结尾、或在最后一参行末尾写右括号。 #### 10.3.4 开发者峰会细节 - 通常**每月最后一个周四**线上举行;另有在开发者集中某地(如 PromCon、Kubecon EU)时的**全天线下峰会**。 - 线上会面向所有人开放,线下会可能因后勤原因有限制,不确定就问;线下会尽力以 best-effort 方式让线上参与者接入。 - 议题由团队根据近期讨论拟定,可在会议记录顶部提出,或在峰会前**至少 24 小时**发到开发者邮件列表。 - **会议记录**:维护滚动记录文档(当前版本自 2024-09-13 起);历史记录含 2017、2018、2019(含第二次)、2020 虚拟峰会 1–5、2021 虚拟峰会 1、2021-2024 滚动记录等。 - **Facilitator**:为帮助团队有效运行峰会而设的**轮值角色**(每次会议轮换),职责分为会前、会中、会后: | 阶段 | 职责与建议 | | --- | --- | | **会前** | 帮助确定议程与议题,确保高票议题相关方能参会。建议:会前两三天在公开社区渠道提醒大家添加 Agenda Topics,并请 Prometheus 团队成员与 maintainer 对议题投票;会前一天联系得票最多的 "Topic owners" 确认能到场 | | **会中** | 确保会议顺利进行并在需要时达成共识。建议:准时开始,使用 `@prometheus.io` 账号获取 admin 会议权限;开始录制并说明适用 Code of Conduct;根据投票与在场人员选择讨论议题;记笔记或找志愿者在共享文档记笔记;讨论停滞或偏离议题时策略性介入;需要时发起共识征询 | | **会后** | 最后一项任务是为下届峰会找新 Facilitator,方式是在 Prometheus Team 邮件列表发邮件 | ### 10.4 代码审查与文档贡献 #### 10.4.1 代码审查 - 评审者由系统**自动分配**;如认为某人应评审,可在 PR 评论中提及,但请勿随意 @ 社区成员。 - CI 检查**必须全部通过**才能合并;失败由 PR 作者修复。 - 评审中常出现 fixup commit: - 评审者可 squash 成连贯提交,但最终 commit message 必须有意义(非自动生成); - 也可要求作者合并前自行整理提交; - 一般允许 rebase;但**他人已基于尚未进入 `main` 的提交工作后,不应再改写**。 - PR 过大或混入不相关关注点时应**拆分**。 #### 10.4.2 文档贡献 - 本手册大量引用的官网文档,其**源文件位于 `prometheus/docs` 仓库**(`docs/instrumenting/exporters.md` 等)。 - **把新导出器加入"可用导出器列表"的正确做法**:给邮件列表发邮件宣布,并**提交一个 PR** 编辑 https://github.com/prometheus/docs/blob/main/docs/instrumenting/exporters.md(见 6.1.13)。 - Markdown 行宽限制 80 字符(除行尾 URL),便于评审逐行评论。 - 文档与代码同源:`docs/` 下的规范文件(如 `docs/specs/om/*.md`、`docs/specs/prw/*.md`)就是本手册第 9 章的原始权威来源,任何与规范冲突的描述以规范文档为准。 #### 10.4.3 公告节奏小结(以导出器为例) | 步骤 | 动作 | | --- | --- | | 1 | 在开发过程中申请端口号(Default port allocations wiki,宣布前先占) | | 2 | 完成导出器并自测(遵循 6.3 节规范) | | 3 | 给 development mailing list 发邮件公告 | | 4 | 提 PR 把它加入 `/docs/instrumenting/exporters.md` | | 5 | 接受社区反馈并持续维护 | > 引自:[Contributing](https://prometheus.io/docs/guides/contributing/) --- ## 全篇覆盖清单(第 4 篇 · 完整 10 章) 下表列出 **第 4 篇**覆盖的全部官网页面 URL 及其归属章节。第 1–4 章(Alertmanager)由同篇上半部分撰写,此处一并列出以保证第 4 篇的**完整性可核对**;第 5–10 章为本下半部分撰写内容。 | # | 官网页面 URL | 归属章节 | 状态 | | --- | --- | --- | --- | | 1 | https://prometheus.io/docs/alerting/latest/overview/ | 第1章 AM 架构与概念 | ✅ | | 2 | https://prometheus.io/docs/alerting/latest/alertmanager/ | 第1章 AM 架构与概念 | ✅ | | 3 | https://prometheus.io/docs/alerting/ | 第1章(导航页) | ✅ | | 4 | https://prometheus.io/docs/alerting/latest/configuration/ | 第2章 AM 配置全解 | ✅ | | 5 | https://prometheus.io/docs/alerting/latest/notifications/ | 第3章 通知路由与模板 | ✅ | | 6 | https://prometheus.io/docs/alerting/latest/notification_examples/ | 第3章 通知路由与模板 | ✅ | | 7 | https://prometheus.io/docs/alerting/latest/integrations/ | 第3章 通知路由与模板 | ✅ | | 8 | https://prometheus.io/docs/alerting/latest/high_availability/ | 第4章 高可用与 API | ✅ | | 9 | https://prometheus.io/docs/alerting/latest/https/ | 第4章 高可用与 API | ✅ | | 10 | https://prometheus.io/docs/alerting/latest/alerts_api/ | 第4章 高可用与 API | ✅ | | 11 | https://prometheus.io/docs/alerting/latest/management_api/ | 第4章 高可用与 API | ✅ | | 12 | https://prometheus.io/docs/instrumenting/clientlibs/ | 第5章 插桩与客户端库 | ✅ | | 13 | https://prometheus.io/docs/instrumenting/exposition_formats/ | 第5章 插桩与客户端库 | ✅ | | 14 | https://prometheus.io/docs/instrumenting/content_negotiation/ | 第5章 插桩与客户端库 | ✅ | | 15 | https://prometheus.io/docs/instrumenting/escaping_schemes/ | 第5章 插桩与客户端库 | ✅ | | 16 | https://prometheus.io/docs/instrumenting/pushing/ | 第5章 插桩与客户端库 | ✅ | | 17 | https://prometheus.io/docs/instrumenting/ | 第5章(导航页) | ✅ | | 18 | https://prometheus.io/docs/practices/pushing/ | 第5章·Pushgateway 适用性 | ✅ | | 19 | https://prometheus.io/docs/instrumenting/exporters/ | 第6章 导出器与集成生态 | ✅ | | 20 | https://prometheus.io/docs/instrumenting/writing_exporters/ | 第6章 导出器与集成生态 | ✅ | | 21 | https://prometheus.io/docs/instrumenting/writing_clientlibs/ | 第6章 导出器与集成生态 | ✅ | | 22 | https://prometheus.io/docs/operating/integrations/ | 第6章 导出器与集成生态 | ✅ | | 23 | https://prometheus.io/docs/operating/ | 第6章(导航页) | ✅ | | 24 | https://prometheus.io/docs/guides/node-exporter/ | 第6章·node_exporter 部署实践 | ✅ | | 25 | https://prometheus.io/docs/guides/cadvisor/ | 第6章·cAdvisor 实践 | ✅ | | 26 | https://prometheus.io/docs/guides/multi-target-exporter/ | 第6章·multi-target exporter 模式 | ✅ | | 27 | https://prometheus.io/docs/guides/go-application/ | 第7章 应用集成实战 | ✅ | | 28 | https://prometheus.io/docs/tutorials/instrumenting_http_server_in_go/ | 第7章 应用集成实战 | ✅ | | 29 | https://prometheus.io/docs/guides/opentelemetry/ | 第7章 应用集成实战 | ✅ | | 30 | https://prometheus.io/docs/visualization/browser/ | 第8章 可视化 | ✅ | | 31 | https://prometheus.io/docs/visualization/grafana/ | 第8章 可视化 | ✅ | | 32 | https://prometheus.io/docs/visualization/perses/ | 第8章 可视化 | ✅ | | 33 | https://prometheus.io/docs/visualization/consoles/ | 第8章 可视化 | ✅ | | 34 | https://prometheus.io/docs/visualization/ | 第8章(导航页) | ✅ | | 35 | https://prometheus.io/docs/practices/consoles/ | 第8章·console 实践 | ✅ | | 36 | https://prometheus.io/docs/tutorials/visualizing_metrics_using_grafana/ | 第8章·可视化实战 | ✅ | | 37 | https://prometheus.io/docs/specs/native_histograms/ | 第9章 规范·Native Histograms | ✅ | | 38 | https://prometheus.io/docs/specs/om/open_metrics_spec/ | 第9章 规范·OpenMetrics 1.0 | ✅ | | 39 | https://prometheus.io/docs/specs/om/open_metrics_spec_2_0/ | 第9章 规范·OpenMetrics 2.0 | ✅ | | 40 | https://prometheus.io/docs/specs/prw/remote_write_spec/ | 第9章 规范·Remote Write 1.0 | ✅ | | 41 | https://prometheus.io/docs/specs/prw/remote_write_spec_2_0/ | 第9章 规范·Remote Write 2.0 | ✅ | | 42 | https://prometheus.io/docs/guides/open_metrics_2_0_migration/ | 第9章 规范·迁移指南 | ✅ | | 43 | https://prometheus.io/docs/specs/ | 第9章 规范(导航页) | ✅ | | 44 | https://prometheus.io/docs/specs/om/ | 第9章(导航页) | ✅ | | 45 | https://prometheus.io/docs/specs/prw/ | 第9章(导航页) | ✅ | | 46 | https://prometheus.io/docs/guides/contributing/ | 第10章 参与社区 | ✅ | | 47 | https://prometheus.io/docs/guides/ | 附录·站点结构(导航页) | ✅ | | 48 | https://prometheus.io/docs/prometheus/ | 附录·站点结构(导航页) | ✅ | **覆盖统计**:第 4 篇共覆盖 **48** 个官网页面,其中: - 第 1–4 章(Alertmanager):11 个; - 第 5 章(插桩与客户端库):7 个(含 1 个导航页,另含 `practices/pushing` 补充); - 第 6 章(导出器与集成生态):8 个(含 1 个导航页); - 第 7 章(应用集成实战):3 个; - 第 8 章(可视化):7 个(含 1 个导航页); - 第 9 章(规范):9 个(含 3 个导航页); - 第 10 章(参与社区):1 个; - 附录·站点结构:2 个导航页。 **导航页说明**:标注"(导航页)"的 URL 为官网纯目录/索引页,正文为空(仅含 frontmatter),已在对应章节中作**结构说明**收录,不计入实质知识条目。