简介:Prometheus监控系统学习指南,面向具备一定Linux基础的系统管理员、DevOps工程师及开发人员,系统讲解从部署到实战的完整路径。内容覆盖时间序列数据模型、PromQL查询语法、数据采集方式等核心概念,并对比其与关系型数据库、NoSQL在时序处理上的差异。教程以实例演示和操作步骤贯穿,详细展示Prometheus服务安装配置、通过node_exporter监控远程Linux主机、通过mysqld_exporter监控MySQL、与Grafana集成构建可视化仪表盘,以及基于Alertmanager实现报警通知,帮助读者掌握从指标定义到故障排查的全流程技能。资源为单份PDF文档包,整体大小约2.95MB,便于离线阅读与随时查阅。已有633人学习使用,适合希望快速上手Prometheus并落地到真实运维场景的技术人员。
1. Prometheus 从入门到精通:从一个凌晨的告警漏报说起
Prometheus 从入门到精通这条路,我是从一次漏报开始意识到必须走通的。凌晨线上接口时延飙高,业务一路追到监控台,主面板曲线却是平的。查了一个小时才发现采集周期是 45 秒,CPU 尖峰只持续 10 秒,两层轮询刚好把故障漏掉了。
Prometheus 是一套开源的时间序列监控与告警系统:exporter 暴露指标,Prometheus 按周期拉取并写入本地 TSDB,你用 PromQL 把指标查出来,再由规则引擎评估告警。它能解决的是“指标抓不回来、问题没人喊、查询越跑越慢”这三类问题。
适合的人很明确:运维、SRE、后端开发,以及所有想真正看清系统现状的人。下面按从入门到精通的路径推进:先打通抓取链路,再玩透 PromQL,然后跑通告警闭环,最后排掉那些容易反复踩的坑。
2. 先打通抓取链路:从 exporter 到本地存储
2.1 拉模型为什么是 Prometheus 的立身之本
Prometheus 与很多监控系统的第一个区别是采集方式:它默认用 pull,而不是 push。每个被监控目标在/metrics路径上以文本格式暴露指标,Prometheus 按scrape_interval去抓取。这个模型一开始让人不习惯,但好处很明显。
故障判定权在监控端,不在被监控端。目标挂了,Prometheus 拉取失败,up指标变成 0,这只取决于“能不能拉到”,不依赖 agent 是否主动上报。排错时也简单,手动 curl 一下目标地址,就能确认是 exporter 没起来还是网络不通,不用去猜采集链路里某段丢了数据。
拉模型也有代价:应用要么自己暴露/metrics,要么在旁边跑一个 exporter。常见做法是每台机器装node_exporter,数据库、中间件、黑盒探测都各有对应 exporter。我一般先保证所有核心节点都有 node_exporter,然后再按业务加专用 exporter。
exporter 和 Prometheus 不要求同机部署,只要网络能访问端口,就能跨主机抓取。这也是多机房监控的第一个基础:把 Prometheus 放在能访问所有目标的网络位置,目标列表写进配置就会自动纳管。
在拉取入口上,我们可以统一做超时、限流、标签改写,这个入口就是scrape_configs。下面是最小可用配置。
2.2 最简可复现的抓取配置:prometheus.yml 这样写
准备一个目录,放入 Prometheus 和 node_exporter 的二进制,然后写一份最小配置:
global: scrape_interval: 15s scrape_timeout: 10s evaluation_interval: 15s scrape_configs: - job_name: "node" static_configs: - targets: ["localhost:9100"]这段配置的意思是:每 15 秒拉取一次job="node"目标,地址是localhost:9100,单次拉取最多等 10 秒。启动后打开 Prometheus 的/targets页面,能看到这个目标,up为 true 说明链路已经通了。
scrape_interval决定数据密度。15 秒能反映大多数瞬时抖动;业务敏感时我会调到 5 秒,但这会增加 exporter 和网络的压力,不建议全局低于 5 秒。scrape_timeout是一个容易被忽略的参数,它必须小于scrape_interval,否则上一次抓取还没结束,下一次抓取就到了,日志里会出现大量context deadline exceeded。
接入目标前先手动验证一下:
curl -s http://localhost:9100/metrics | head -20如果看到node_cpu_seconds_total这类指标,说明 exporter 正常,再接入 Prometheus。否则不要急着查 Prometheus,问题大概率在目标侧。
static_configs适合一台实验机,生产环境几十台机器时,我会用目标文件和服务发现来维护节点列表。后面第 6 章会给file_sd的具体写法,这里先把静态链路跑通。
2.3 指标格式读懂:counter、gauge、histogram 的选择与读法
在/metrics返回的文本里,最核心的三行长这样:
# HELP node_cpu_seconds_total Seconds the cpus spent in each mode. # TYPE node_cpu_seconds_total counter node_cpu_seconds_total{cpu="0",mode="idle"} 123456.7HELP和TYPE是指标元信息,真正被存储的是带 label 的样本行。四个基础类型里,counter只增不减,适合累计总量,比如连接数总和、发包总字节;gauge可增可减,适合当前内存、负载、队列深度;histogram用一组_bucket累积计数,用于计算分位数,配套还有_sum和_count;summary则是在客户端算好分位数,直接暴露_quantile指标。
我的选型习惯是:速率类用 counter,状态类用 gauge,耗时分布用 histogram。别用 gauge 模拟 counter,比如统计请求总量时不考虑重启归零,后面用rate会算出奇怪的波形。类型错了,PromQL 写起来很别扭。
relabel可以删标签、改标签,但不会改指标类型。类型定义在 exporter 或应用代码里,Prometheus 只负责存。某个指标类型不对,必须从源头改,监控侧没有后悔药。
另外,每次抓取成功,Prometheus 都会自动生成一个up指标,值等于本次抓取结果。所以判断采集目标健康,先看up{job="..."}。它是整个采集链路最直观的晴雨表。
2.4 TSDB 本地存储:数据写去哪,保留期怎么给
Prometheus 把抓到的样本写进自己的本地时序库 TSDB。默认数据目录是data/,由--storage.tsdb.path指定。TSDB 按 2 小时一个 block 写入,随后后台 compaction 合并旧 block。不需要把细节背下来,但要理解几个现象。
新写入的数据在 head block 中,查询很新的事件时会读取这部分;历史 block 经过压缩,长期趋势查询会更快。prometheus_tsdb_head_series这个指标能看当前活跃序列数,prometheus_tsdb_head_samples_appended_total能看写入速率,排查性能问题时先看这两个。
保留期参数,我通常这样给:
./prometheus \ --config.file=prometheus.yml \ --storage.tsdb.path=/data/prometheus \ --storage.tsdb.retention.time=15d \ --storage.tsdb.retention.size=50GBretention.time按时间保留,retention.size按块维度控制上限。两个都设时,先命中的生效。注意size是块级别的删除,不是逐字节精确回收,所以磁盘预留要留余量。如果业务产生了大量高基数 label,50GB 可能装不了几天,这个问题后面避坑章专门展开。
本地 TSDB 不适合无限扩容。数据长期留存超过一两个月,常见做法是 remote write 转发给外部长期存储。但 remote write 会引入额外故障点,对大多数 15 天以内的监控,本地 TSDB 加合理保留期完全够用。
3. PromQL 实战:从查指标到算分位数
3.1 先把两个向量分清:瞬时向量与区间向量
刚上手 PromQL 最容易翻车的就是把方括号漏掉。node_load1返回的是“当前”瞬时值,而node_load1[5m]返回的是最近 5 分钟的所有样本。
规则很简单:
- 在面板里画一条曲线,用的是瞬时向量,比如
node_load1。 - 做速率、增量计算时,必须给区间向量,比如
rate(node_cpu_seconds_total[5m])。 - 一旦误写
rate(node_cpu_seconds_total),PromQL 会直接报expected range vector,这其实是善意的报错。
一个反直觉点是:avg_over_time(node_load1[5m])返回的仍然是“瞬时向量”,只是每个时间点上取的是前 5 分钟平均值。它看起来像区间,但在类型上不是。这个区分如果没建立,后面写告警规则时容易把for语义搞混。
提示:方括号只属于选择器,不代表最终返回类型。函数决定返回类型,rate 返回瞬时向量,原始值返回的也是瞬时向量。
3.2 rate 与 irate:算速率的两种姿势
rate(v[5m])计算区间内的每秒平均增长率,它正确处理了 counter 重启归零的问题。适合平滑毛刺,也适合长时间段大盘。
irate(v[5m])只取区间最后两个样本的差值并外推到每秒,反应快,能看到瞬时尖峰。但样本间隔不稳定时,曲线会跳得比较厉害。
算 CPU 使用率,我通常这样写:
100 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100先取 idle 秒数,算 5 分钟每秒平均空闲值,按 instance 聚合平均,再用 100 减去,得到每个实例的 CPU 使用率。by (instance)是保每个实例一行,没加的话会得到全局平均,单台故障会被淹没。
rate对 counter 重启的处理,前提是区间内有足够样本。scrape_interval是 15 秒,5 分钟才有 20 个样本;区间太短,只有两三个样本时,首次抓取产生的计数器跳变会影响结果。经验是:区间至少容纳 2 个抓取周期。
网络流量方向,用 counter 类型的node_network_receive_bytes_total:
increase(node_network_receive_bytes_total{device="eth0"}[1h])它表示过去 1 小时该网卡收到多少字节,比直接看瞬时速率更符合“一个小时内收了多大流量”的提问方式。
3.3 聚合与 topk:把一堆机器合并成一条线
多实例合并是日常高频操作。sum by (job)(rate(...))按 job 合并;sum without (cpu, mode)去掉 cpu 和 mode 维度,保留其他标签。两者效果接近,without更适合“不想指定到底保留哪些标签,只想删掉一两个”的场景。
磁盘剩余比例的例子:
topk(5, max by (fs) (node_filesystem_avail_bytes / node_filesystem_size_bytes))这会返回可用剩余比例最小的 5 个文件系统。但要注意,topk结果不适合直接写进告警规则。原因是规则评估每轮独立执行,topk 选中的对象可能变化,告警归属不稳定,今天报 A 盘明天报 B 盘,排障体验很差。
要监控“某个盘快满了”,更可靠的是对每个序列直接算阈值:
node_filesystem_avail_bytes / node_filesystem_size_bytes < 0.1这样每台机器都有自己的告警判断,不会被 topk 的轮换逻辑影响。
3.4 histogram_quantile:从 bucket 算出 P95/P99
耗时类指标大多用 histogram 暴露,PromQL 侧用histogram_quantile还原分位数:
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le) )histogram 的每个 bucket 是累计计数。要算最近 5 分钟的分位数,必须先用rate把 bucket 转成每秒速率,再sum by (le)把多实例合并。le是分位数计算的横坐标,必须保留。
我这里踩过一个大坑:直接对_bucket原始值做histogram_quantile,得到的是进程启动以来的累计分布,不是最近 5 分钟的分位。老慢请求一直在累积,告警会持续误报。所以凡是写“最近一段时间”的分位数,就必须包一层 rate。
分位数只在 bucket 边界内插值,是一种近似。如果 bucket 设计得太粗,P99 可能和 P95 落在同一个档位,这时需要回源头调整应用上报的 bucket 定义。
平均时延不用动 bucket,直接用_sum和_count:
rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m])3.5 写查询时顺手防住超时
PromQL 写复杂后,一个常见现象是查询大时间范围时转圈很久,甚至超时报错。原因多半是查询一次性扫描了大量 series,特别是没有加任何 label 过滤的查询。
我的习惯是先加范围限制,再写聚合。例如:
sum(rate(node_cpu_seconds_total{instance=~"10\\..*"}[5m])) by (instance)用正则先圈定实例网段,缩小数据量,然后再算。这个习惯在实例上千后非常重要,也是从“会写 PromQL”到“能在生产环境用”的分水岭。Prometheus 的查询参数--query.max-concurrency和--query.timeout可以限制资源占用,但查询语句本身尽量自带过滤才是根本。
4. 把告警闭环跑起来:规则、Alertmanager 与抑制
4.1 告警规则的语法与 for 的真实含义
Prometheus 本体的告警规则写在独立规则文件里,再在 prometheus.yml 中挂载:
rule_files: - "rule.yml"rule.yml 内容:
groups: - name: node_rules rules: - alert: NodeHighCPU expr: 100 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100 > 80 for: 5m labels: severity: critical annotations: summary: "{{ $labels.instance }} CPU 使用率过高" description: "当前值 {{ $value }}%"expr返回结果为真的序列会触发这条告警。for: 5m表示该条件必须持续满足 5 分钟,Prometheus 会先把告警标记为 pending,连续满足后才变成 firing。这个字段是防告警风暴的第一道防线。
没有for,一次瞬时毛刺就会触发通知;for设太长,持续故障也要等几分钟才被人看到。我一般把瞬时类指标设为 1~5 分钟,趋势类指标设为 10~15 分钟。labels用于告警分组和路由,annotations是给人看的,支持{{ $labels.xxx }}和{{ $value }}模板。
写完规则,先做静态校验:
promtool check rules rule.yml它会报 YAML 或 PromQL 语法错误。CI 里加一步promtool check config检查主配置,是我比较推荐的习惯。
4.2 Alertmanager 路由:分组、等待和重复
Prometheus 只负责产生告警,真正发送、去重、分组的活由 Alertmanager 做。Prometheus 通过配置里的alertmanagers地址把告警推过去。Alertmanager 自己的配置:
route: group_by: ['alertname', 'instance'] group_wait: 30s group_interval: 5m repeat_interval: 4h receiver: "ops-webhook" receivers: - name: "ops-webhook" webhook_configs: - send_resolved: true url: "http://xxx.example.com/hook/prom"group_by决定哪些告警合并成一个通知,我常用alertname加instance,同一实例同类型告警归到一组。group_wait是组内第一条告警到首次发送的等待时间,用来吸收同时爆发的告警,避免一瞬间收到几十条。group_interval是组内后续告警的发送间隔。repeat_interval是整个组重复提醒的最小间隔,生产里我会调到 3~4 小时,不然同一个告警每 5 分钟刷屏一次,再好的消息通道也会被轰炸。
如果需要按级别分流,可以加 routes:
routes: - match: severity: critical receiver: ops-phone把 critical 告警走独立通道,warning 走 webhook。注意 route 是逐级向下匹配的,父级 route 的 receiver 不生效,必须落到叶子节点。
4.3 抑制规则:避免一个故障变成一批告警
没有抑制时,一台机器宕机,你会同时收到 NodeDown、NodeHighCPU、磁盘不可达等一堆告警,其实只源于同一个根因。抑制规则用一条高级别告警压住低级别告警:
inhibit_rules: - source_matchers: ["severity=\"critical\""] target_matchers: ["severity=\"warning\""] equal: ["instance"]含义是:如果同一 instance 上已经有 critical 告警,那么该 instance 上所有 warning 告警都会被抑制,直到 critical 告警恢复。source_matchers是 Alertmanager 新版推荐的写法,旧版本用source_labels。迁移到新写法时,注意 label 值要用双引号包裹,否则字符串可能会被解析错。
equal里的instance是连接点,必须两边都有这个 label 才生效。如果告警来源不同导致标签值不一致,抑制规则会静默失效。所以设计告警 label 时尽量统一,避免一套系统用instance另一套用host的情况。
Alertmanager 的静默功能也很实用。发布窗口常用:
amtool silence add 'alertname=NodeHighCPU' --duration=1h它只按 label 匹配,不支持匹配 annotation。用完记得到了时间要清理,长期挂着的静默会把告警悄悄吞掉。
4.4 告警链路排查的顺序
告警“不响”最常见的三处断点:
第一,Prometheus 规则状态。打开/alerts页面,如果一直 pending 不进 firing,先看for是否太长,以及expr是否真的为真。
第二,Prometheus 到 Alertmanager 的连接。Prometheus 启动后,可以查prometheus_notifications_alertmanagers_discovered指标,确认发现到的 alertmanager 数量是否正确。数量为 0,说明配置里的地址或服务发现没生效。
第三,Alertmanager 到接收端。打开 Alertmanager UI 的/alerts,如果告警存在但没有发送,看路由匹配对不对、receiver 是否为空。如果 receiver 是 webhook,可以先临时改成测试地址,触发一次真实告警,再去看接收端是否收到 POST。
5. 玩转 Prometheus 的避坑清单:现象、原因与修复
5.1 采集目标偶尔失败,曲线上出现规则空洞
现象:图上看某个实例每隔几小时缺一个点,时间序列不是连续曲线,而是一段段虚线。日志里能找到context deadline exceeded或dial tcp timeout。
原因:最常见的是scrape_timeout和scrape_interval配置不合理。比如 interval 10 秒、timeout 10 秒,任何一次网络抖动都可能导致超时,后面的抓取又会因为上一次还没结束而被跳过。
解决:把 timeout 调到 interval 的 60%~70%。15 秒周期配 10 秒超时,5 秒周期配 3 秒超时。如果个别 exporter 计算量大,可以为该 job 单独设置更长的 timeout,但必须同时调整 interval。还有一个隐蔽原因:目标机器负载过高,exporter 响应慢,这是源头问题,要优化 exporter 端而不是一味调大超时。
5.2 告警一直 Pending 不 Firing
现象:/alerts页面里告警长时间停在 pending,明明值已经超过阈值。
原因:for时间没到是一种情况;另一种情况是evaluation_interval偏大,规则评估次数太少,导致for计数不连续。Prometheus 对 pending 的计数不是按真实时间均匀推进,而是按每次 evaluation 的间隔估算,如果 evaluation 是 30 秒一次,for: 1m需要连续两次评估都命中,实际可能要超过 1 分钟。
解决:把evaluation_interval调低到 15 秒,并同步缩小for,或者反过来,让for至少是 evaluation_interval 的整数倍。还有一个小坑:expr里用了topk,每轮评估选中的对象不同,告警在 pending/firing 之间反复横跳,建议避免用 topk 写告警。
5.3 重启后长时间没数据,别急着删目录
现象:Prometheus 进程重启后,面板上几分钟内没有新数据,查询历史数据也是空的。
原因:TSDB 启动时要回放 WAL,把上一次崩溃或重启前还没进入 block 的样本重新加载。这个阶段指标查询会出现暂时空洞,尤其是数据量大时可能持续几分钟。
解决:先看日志里的WAL segment loaded记录,等回放完成。千万不要在此时删data/目录“重置”,一旦删除,所有历史数据就真没了。如果每次重启回放都很慢,说明 WAL 积压太多,考虑缩短scrape_interval或检查 TSDB 写入性能。
5.4 label 基数失控,内存和磁盘撑不住
现象:Prometheus 内存持续上涨,查询越来越慢,prometheus_tsdb_head_series达到几十万甚至百万。一张表看过去全是高基数 label。
原因:有些 label 的取值接近无限。典型是把用户 ID、订单号、设备指纹写进指标 label。一个指标加一个 ID label,每秒产生成千上万个新序列,TSDB 很快爆炸。
解决:先在接入端控制 label。设计指标时,label 只留枚举值可控的维度,比如method、code、instance、job。如果某个 label 是业务主动上报的,用 Prometheus 的 relabel 把它删掉:
metric_relabel_configs: - source_labels: [user_id] regex: ".+" action: drop也可以用 labeldrop 直接删除:
metric_relabel_configs: - action: labeldrop regex: "user_id|request_id"必须承认,当 label 基数已经写进了历史数据时,删 label 并不能回收旧 block,只能停掉增长,等待保留期自然淘汰。所以这个坑最重要的是接入端把控。
5.5 查询越来越慢,但资源看起来还有余量
现象:同样的查询以前秒出,现在转圈,或者时报 503,日志里出现query timeout。
原因:查询没有 label 过滤,全量扫描所有序列;或告警规则里写了超复杂的正则,每次评估都要扫大量样本。
解决:把查询里不需要的维度去掉,加 instance 或 job 过滤。Prometheus 的--query.max-concurrency控制并发上限,--query.timeout控制单查询超时,但调参只是治标。
真正治本的是把高频聚合用记录规则落下来,把复杂的 PromQL 提前算好,查询时直接读结果。记录规则的具体做法在第 6 章给出。
5.6 Alertmanager 没收到告警,Prometheus 却已经触发
现象:Prometheus 的/alerts已经 firing,但 Alertmanager UI 里没有任何告警。
原因:最常见是 Prometheus 配置里的alertmanagers服务发现没生效,或者地址端口写错。也可能是 Alertmanager 收到了但被路由丢弃,比如 receiver 为空或 match 条件不满足。
解决:先查prometheus_notifications_alertmanagers_discovered看 discovery 数量,再查prometheus_notifications_sent_total看发送计数。如果发送计数涨了但 Alertmanager 没有,多半是网络或地址问题;如果发送计数没涨,去看 Prometheus 到 Alertmanager 的握手配置。Alertmanager 侧再看/#/alerts和 receiver 日志,确认路由匹配。
6. 进阶:服务发现、分片抓取与记录规则
6.1 用 file_sd 动态维护目标列表
节点数量超过几十台后,static_configs 不好维护。我用的最省事方案是file_sd,让 Prometheus 监听一批 YAML 文件的变化:
scrape_configs: - job_name: "nodes" file_sd_configs: - files: - "/etc/prometheus/targets/*.yml" refresh_interval: 1m目标文件内容:
- targets: - "10.0.0.1:9100" - "10.0.0.2:9100" labels: env: prod region: cn-east新增机器时,只需要往里追加一行,Prometheus 会在 1 分钟内感知。目标文件可以用配置管理工具批量分发,也可以配合 CMDB 生成,比手改主配置安全得多。
6.2 分片抓取:一台 Prometheus 扛不住时怎么办
当实例数量很大,一台 Prometheus 抓取压力过高,常见做法是把目标按 hash 分片到多台 Prometheus,各自抓取一部分。用 hashmod 加 keep 实现:
scrape_configs: - job_name: "nodes" static_configs: - targets: ["10.0.0.1:9100", "10.0.0.2:9100"] relabel_configs: - source_labels: [__address__] target_label: __tmp_hash modulus: 6 action: hashmod - source_labels: [__tmp_hash] regex: "^0$" action: keep这台只保留哈希结果为 0 的目标,另一台把 regex 改成^1$,以此类推,6 台 Prometheus 各抓一份。这样每台的抓取目标和存储量都降下来。
分片之后,查询侧需要一个统一入口。常见做法是再加一层联邦 Prometheus,配置如下:
scrape_configs: - job_name: "federate" metrics_path: /federate params: match[]: - '{__name__=~"node_.*"}' static_configs: - targets: ["prometheus-a:9090", "prometheus-b:9090"]联邦层只聚合需要的指标子集,不建议把全部指标都拉上来,否则分片省下的压力又会在联邦层回到原样。
6.3 记录规则:让复杂查询秒级返回
最后我保留的进阶习惯不是换存储,而是用记录规则把高频查询先算好。比如 CPU 使用率这个查询在多个面板和告警里反复出现,我会写成:
groups: - name: recording_rules rules: - record: cpu:node_avg_usage expr: 100 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100面板和告警再查时直接用cpu:node_avg_usage即可,不再触发大范围扫描。写记录规则时要考虑维度,保留instance、job这类常用维度,去掉 cpu、mode 这些不常用的。落到记录规则后,每次评估只写一次结果,比每个查询各自扫原始 series 便宜得多。
每次改完配置,我强制自己跑一遍promtool check config prometheus.yml和promtool check rules rule.yml,再通过 HTTP 接口重载配置。这套习惯帮我避免了很多次凌晨的配置故障。Prometheus 玩到深处,坑大多是采集链路、维度设计和查询写法这三件事引起的,把这些根子打牢,剩下的就是根据业务不断补充新的指标维度了。希望帮到你。
本文还有配套的精品资源,点击获取