☰
Prometheus监控系统实战:从单机部署到多主机告警
2026/10/29 6:06:01 网站建设 项目流程

凌晨两点十七分,手机在枕头边震了三下。值班的同学发来一句话:某台业务机的磁盘写满了,日志落不了盘,服务直接卡死。事后复盘发现,那台机器的磁盘其实从 78% 爬到 100% 用了整整六个小时,中间没有任何人收到提醒,因为当时的监控只有一个简单的外部探测——探的是端口通不通,端口一直通,所以一直是绿的。这件事之后,我把整套监控换成了 Prometheus 监控系统,从一台机器扩到几十台主机,踩了不少坑,也积累了一套可以照着抄的落地方案。

这篇内容讲的是一套完整的 Prometheus 实战路径:单机怎么装、怎么配、多主机怎么纳管、指标怎么查、告警怎么发、出问题怎么排。核心关键词包括 Prometheus、监控系统、多主机监控、node_exporter、PromQL、告警规则、服务发现,这些都会在实际操作里反复出现。适合三类人看:一是从来没碰过 Prometheus、想从零搭一套能用的;二是已经装上了但只用来看几个默认图表、没真正发挥作用的;三是手里机器数量上来了、静态配置写到崩溃、想搞明白服务发现和 relabel 的。哪怕你只会敲几条 Linux 命令,跟着走也能把整套东西跑起来。

1. 为什么最后选了 Prometheus 这套组合拳

1.1 从一次漏报告诉你:监控到底该盯什么

很多人对监控的理解停留在“服务挂了给我发条消息”,这个理解不算错,但太窄。真正干活的时候,监控要回答的是四类问题,我习惯按紧急程度排:第一类是“现在是不是坏了”,比如进程没了、端口不通、接口 5xx 暴涨,这类要秒级发现;第二类是“快要坏了没有”,比如磁盘还剩 8%、内存可用量在持续下滑、连接池占用到 90%,这类要能提前几十分钟甚至几小时预警;第三类是“够不够用”,比如 CPU 峰值利用率、带宽水位、单机承载的连接数上限,这类是容量规划的依据;第四类是“趋势往哪走”,比如业务量涨了 30%,资源消耗涨了多少,是不是有浪费。

只做第一类的监控,就是开头那个事故的成因:端口通着,但服务其实已经写不进数据了。Prometheus 的价值在于它天生就是围绕时序指标设计的,它不只是记录“活/死”,而是把 CPU、内存、磁盘、网络、连接数、请求量、延迟分布这些连续变化的数值都存下来,你可以拿这些数值做聚合、做同比、做线性预测。这也是我最终选它的第一个理由:它解决的是“看得见变化”,而不是“看得见状态”。

第二个理由是生态。绝大多数中间件、数据库、Web 服务器、消息队列都有现成的 exporter,把指标暴露成一个 HTTP 端点,Prometheus 去抓就行。你要监控的东西,大概率不用自己写采集代码。

第三个理由是查询语言 PromQL。这东西初看有点别扭,但用过一段时间会发现它的表达力很强,rate()、increase()、histogram_quantile()、predict_linear()这几个函数直接把“速率”“增量”“分位数”“趋势预测”变成了一行表达式。磁盘写满预测就是靠predict_linear做的,比设一个 90% 的固定阈值聪明得多。

1.2 拉模式不是缺点,是设计取舍

刚接触的人最容易问的一个问题是:为什么 Prometheus 要主动去拉数据,而不是让被监控端推过来?推送不是更灵活吗?

我当初也纠结过,后来想明白了:拉模式的本质是把“目标清单”这个真相源握在监控系统手里。被监控端不需要知道监控系统在哪、有几个、地址变了怎么办,它只要老老实实把自己的一亩三分地暴露在一个端点上。监控系统这边增减目标、改抓取间隔、加标签,全都是单边操作,不用去动被监控机。这一点在多主机场景下太重要了——你要是推模式,几十台机器上各配一份上报地址,哪天真要迁移监控服务,就得挨个改配置,想想就头皮发麻。

拉模式还自带一个隐性好处:up这个指标。Prometheus 每次抓取都会记录这次抓取成不成功,up == 0就代表这个目标抓不到了。你不用额外做什么,就免费得到了一个最基础的可用性探测。当然它也有代价,比如短生命周期的批处理任务可能抓不到,那就得靠 Pushgateway 中转,这是另一个话题了。

注意:拉模式的隐含前提是监控系统能主动访问到被监控机。跨网段、跨安全域的场景要提前把链路和策略理清楚,别等装完了才发现抓不到。

1.3 组件拆解:谁负责哪一段,别装重了

整套东西不是一个程序,是几个各司其职的组件,我先把分工摆清楚,后面部署的时候你就知道每一步在干嘛。

组件职责默认端口是否必需
Prometheus Server抓取、存储、查询、触发告警规则9090必需
Exporter(如 node_exporter)把本机指标暴露成 HTTP 端点9100必需(每台被监控机都要)
Alertmanager告警去重、分组、抑制、路由、发送9093需要发告警就必需
Grafana可视化面板3000可选但强烈建议
Pushgateway接收短任务主动推送的指标9091按需

我见过有人把 Grafana 当成监控系统本身,以为装了 Grafana 就有监控了。不是的,Grafana 只是个画图的,数据得从 Prometheus 来。也见过有人在每台机器上都装一个 Prometheus Server,各自为战,最后查一个跨机器的问题要开五个页面。正确姿势是:一个(或一组)Prometheus Server 集中抓,被监控机只装 exporter。

2. 环境与规划:上线前必须想清楚的几件事

2.1 机器规格与磁盘容量怎么估

Prometheus 本身不吃 CPU,吃内存和磁盘 IO。内存主要跟活跃序列数(active series)挂钩,磁盘跟序列数、抓取频率、保留天数挂钩。粗糙但够用的经验值是:每 100 万个活跃序列大约需要 2 到 4 GB 内存,磁盘按下面这个算法估。

先算每秒样本数:

每秒样本数 = 活跃序列总数 ÷ 抓取间隔(秒)

再算磁盘占用,压缩后的时序数据每个样本大约 1 到 2 字节,取 1.5 字节做保守估算:

每日磁盘 = 每秒样本数 × 1.5 字节 × 86400 秒

举个具体例子。50 台主机,每台跑 node_exporter,默认采集器下大约 800 到 1200 个活跃序列,取 1000 算:

  • 活跃序列总数 = 50 × 1000 = 50000
  • 抓取间隔设 15 秒,每秒样本数 = 50000 ÷ 15 ≈ 3333
  • 每日磁盘 = 3333 × 1.5 × 86400 ≈ 432 MB
  • 保留 15 天 ≈ 6.3 GB

实际部署时我会在这个数上乘 2 到 3 倍留余量,因为还要算索引、WAL(预写日志)、以及后续新增的业务指标。50 台机器给 20 GB 是比较舒服的。如果后面要接业务侧的直方图指标(histogram 的序列数增长很快,一个带 10 个 bucket 的直方图等于 12 个序列),这个数字还得往上翻。

主机规格上,50 台以内的规模,2 核 4 GB、SSD 盘就够跑了。别用机械盘,时序数据的写入模式对随机 IO 不友好,机械盘上很容易出现抓取超时。

2.2 目录与端口规划:一开始就定死,后面省事

我在第一套环境里吃过亏:数据目录随手放在了/opt/prometheus/data,后来磁盘不够要挂新盘,迁移的时候路径写死在 systemd 单元里、写死在启动参数里,改得手忙脚乱。后来我固定了一套目录规范,新环境直接套用。

路径用途
/opt/prometheus/程序二进制与配置文件
/opt/prometheus/prometheus.yml主配置
/opt/prometheus/rules/告警规则、记录规则目录
/data/prometheus/数据目录,单独挂盘
/etc/systemd/system/prometheus.service服务托管

端口方面,9090 是 Prometheus 自己的,9100 是 node_exporter 的,9093 是 Alertmanager 的,3000 是 Grafana 的。这几个端口在规划阶段就要确认没有被占用,尤其是 3000,很多开发环境会拿它跑本地服务。

# 上线前先扫一遍端口占用 ss -lntp | grep -E '9090|9091|9093|9100|3000'

2.3 时间同步与主机命名:两个不起眼但要命的细节

时间不同步是时序数据库的隐形杀手。Prometheus 存的是带时间戳的样本,如果被监控机的时间比监控机快了几分钟,你会看到数据点在时间轴上“跳”,rate()算出来的值会莫名其妙地出现尖刺或者负值。所以所有节点必须先做时间同步,误差控制在秒级以内。

# 检查时间偏移 timedatectl status # 确认 NTP 同步服务处于 active 且已同步状态

主机命名同样重要。Prometheus 的instance标签默认是IP:端口,纯 IP 的话,过半年你根本不知道10.x.x.37是哪台机器、跑的是什么。我的做法是统一在采集层就带上语义标签:env(环境)、role(角色)、hostname(主机名)。这样后面写告警的时候可以按角色分组,写查询的时候可以按环境过滤,不用再回来补标签。

3. 单机跑通:先把 Prometheus 拉起来

3.1 二进制部署与 systemd 托管

我不用容器跑 Prometheus Server,原因是数据目录挂载和配置热更新的处理在裸机上更直观,出问题看日志也方便。二进制部署就三步:下载解压、放配置、写 systemd。

# 建目录 mkdir -p /opt/prometheus/rules /data/prometheus # 解压后把二进制和默认配置放进去(版本号按你实际下载的替换) tar -xzf prometheus-*.tar.gz cp prometheus-*/prometheus /opt/prometheus/ cp prometheus-*/promtool /opt/prometheus/ cp prometheus-*/prometheus.yml /opt/prometheus/ # 建一个专用低权限用户,别用 root 跑 useradd --no-create-home --shell /usr/sbin/nologin prometheus chown -R prometheus:prometheus /opt/prometheus /data/prometheus

systemd 单元我用的版本如下。几个参数值得说一下:--storage.tsdb.path指到独立磁盘;--storage.tsdb.retention.time=15d控制保留天数;--web.enable-lifecycle打开之后可以用 HTTP 接口热加载配置,改完prometheus.yml不用重启,curl -X POST http://localhost:9090/-/reload就生效,这个在生产上非常实用。

[Unit] Description=Prometheus Server After=network-online.target Wants=network-online.target [Service] User=prometheus Group=prometheus Type=simple ExecStart=/opt/prometheus/prometheus \ --config.file=/opt/prometheus/prometheus.yml \ --storage.tsdb.path=/data/prometheus \ --storage.tsdb.retention.time=15d \ --web.listen-address=:9090 \ --web.enable-lifecycle \ --web.enable-admin-api Restart=on-failure RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target
systemctl daemon-reload systemctl enable --now prometheus systemctl status prometheus

启动后访问http://<监控机IP>:9090,能看到自带的那张查询页面就说明服务起来了。这时候 Targets 页面里应该只有一个它自己。

提示:--web.enable-admin-api会打开删除数据等管理接口,如果监控系统对外网可访问,务必加上反向代理的认证,或者干脆不要开这个参数。

3.2 配置文件逐行拆解:别照抄,要理解

默认配置里有一大堆注释,实际用不到那么多。我把它精简成下面这个骨架,每一段的作用我都标出来了。

global: scrape_interval: 15s # 默认抓取间隔 evaluation_interval: 15s # 告警规则评估间隔 scrape_timeout: 10s # 单次抓取超时,必须小于 scrape_interval alerting: alertmanagers: - static_configs: - targets: ['127.0.0.1:9093'] rule_files: - /opt/prometheus/rules/*.yml scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['127.0.0.1:9090'] - job_name: 'node' static_configs: - targets: ['127.0.0.1:9100'] labels: env: 'prod' role: 'monitor'

几个容易被忽略的点。job_name会成为job标签的值,这是 PromQL 里最常用的过滤维度,起名要有意义,别用job1、job2。scrape_timeout必须严格小于scrape_interval,否则会出现抓取重叠,我在一台慢机器上见过抓取还没结束下一轮又开始了,最后序列数据出现异常。evaluation_interval是告警规则的评估频率,它跟抓取间隔是两回事,很多人会把它们搞混。

改完配置别急着重启,先用 promtool 校验一遍语法,这个习惯能省掉很多“重启失败导致监控中断”的事故:

/opt/prometheus/promtool check config /opt/prometheus/prometheus.yml /opt/prometheus/promtool check rules /opt/prometheus/rules/*.yml

3.3 node_exporter:每台被监控机的标配

node_exporter 干的事很纯粹:把 Linux 的/proc、/sys里的信息翻译成 Prometheus 能读的指标,暴露在 9100 端口。装法就是下二进制、写 systemd、启动。

tar -xzf node_exporter-*.tar.gz cp node_exporter-*/node_exporter /usr/local/bin/ useradd --no-create-home --shell /usr/sbin/nologin node_exporter
[Unit] Description=Node Exporter After=network-online.target [Service] User=node_exporter Group=node_exporter Type=simple ExecStart=/usr/local/bin/node_exporter \ --collector.systemd \ --collector.processes \ --web.listen-address=:9100 Restart=on-failure [Install] WantedBy=multi-user.target

启动后直接 curl 一下验证,看能不能吐出指标文本:

curl -s http://127.0.0.1:9100/metrics | head -n 20 curl -s http://127.0.0.1:9100/metrics | wc -l

第二个命令我每次部署都会跑,看指标行数。正常一台 Linux 机器在 800 到 1500 行之间(取决于启用了哪些采集器)。如果只有几十行,说明采集器大面积失败,通常是权限问题;如果超过 5000 行,检查一下是不是把容器相关的采集器都打开了,序列数会暴涨。

--collector.systemd和--collector.processes这两个采集器我通常会开,前者能监控关键 systemd 服务的状态(服务挂了能直接告警),后者能看进程数。代价是序列数会多几百个,容量估算的时候记得算进去。

注意:node_exporter 是默认禁用部分采集器的。想启用某个采集器,除了加--collector.xxx参数,某些版本还需要在启动参数里显式关闭--no-collector.xxx的反向开关,装完先在/metrics输出里搜一下指标名确认真实存在,别想当然。

3.4 数据落盘与保留策略

Prometheus 的存储是按两小时一个块(block)组织的,落盘目录里你会看到一堆01HXXXXX这样的块目录,加一个wal目录和一个chunks_head目录。不要手动去动这些文件,保留策略交给--storage.tsdb.retention.time就行。

保留天数怎么定?我的经验是:排障需要 7 天,趋势分析需要 30 天,容量规划需要 90 天以上。但 90 天的原始数据磁盘成本很高。折中方案是原始数据保留 15 天,同时用记录规则把关键指标的聚合结果(比如每 5 分钟的主机级 CPU 均值)单独算成低基数序列,这部分数据保留期可以设更长。如果确实需要长期存储,那是另一个话题了(远程写入到对象存储或专门的时序库),单机方案不用一开始就上。

还有一个实操细节:/data/prometheus这个目录要盯着磁盘水位。Prometheus 自己写满磁盘之后行为很不友好,会直接进入只读状态甚至数据损坏。我给这个目录单独配了一条磁盘告警,阈值设在 85%,比业务盘的 90% 更早触发。

4. 多主机监控:服务发现的三种落地方式

4.1 静态配置:20 台以内够用,再往上就是自找麻烦

最直接的方式就是在static_configs里一台台列出来:

- job_name: 'node' static_configs: - targets: - '10.0.1.11:9100' - '10.0.1.12:9100' - '10.0.1.13:9100' labels: env: 'prod' role: 'app'

这种方式的好处是零依赖、一眼看全。坏处也很明显:机器一多,配置文件变成几百行,谁加的、哪台下线了该删,全靠人工维护。我经历过一次扩容,一口气加了 30 台,改完之后手抖把一台的 IP 写错了一位,结果那台机器整整三天没被监控到,直到业务出问题才发现。20 台以内可以用静态配置,超过 20 台就该换方式了。

4.2 文件服务发现:一个折中的甜点方案

file_sd_configs是我最推荐的中小规模方案。Prometheus 监视一个或多个 JSON/YAML 文件,文件变了它自动重新加载目标列表,不用重启、不用热加载接口。文件可以由配置管理工具生成,也可以由脚本从资产系统导出。

- job_name: 'node' file_sd_configs: - files: - /opt/prometheus/targets/node_*.json refresh_interval: 30s relabel_configs: - source_labels: [__address__] regex: '(.+):9100' target_label: hostname replacement: '$1'

对应的 JSON 文件长这样:

[ { "targets": ["10.0.1.11:9100", "10.0.1.12:9100"], "labels": { "env": "prod", "role": "app", "idc": "primary" } }, { "targets": ["10.0.2.21:9100"], "labels": { "env": "staging", "role": "app", "idc": "backup" } } ]

文件名用node_prod.json、node_staging.json这样分环境拆开,好处是改一个环境不影响另一个,也方便做权限划分。refresh_interval默认是 5 分钟,对于频繁变动的环境太慢,我一般设 30 秒。

这个方案的关键价值在于解耦:目标清单的真相源从prometheus.yml里独立出来了,谁来生成、怎么生成都可以变,Prometheus 只负责读。你可以手工维护,也可以写个十几行的脚本从 CMDB 拉数据生成,都比直接改主配置干净。

4.3 基于注册中心的服务发现:动态环境的正解

容器化或者弹性伸缩的环境里,IP 是天天变的,文件服务发现也不够用了,得上服务发现。Prometheus 原生支持一批服务发现机制,原理大同小异:从某个注册中心或者云平台的接口拉取实例列表,转换成目标。

这部分配置格式随环境不同差别很大,我不在这里贴具体配置(容易抄错),但要说清楚它解决了什么问题:目标列表由系统自动产生,不再需要人工维护。实例上线自动被纳入监控,实例下线自动从监控里消失。

代价是依赖变多了。注册中心挂了,Prometheus 拿不到新列表,但它会继续用最后一次成功获取的列表抓取,只是不再更新——这个降级行为是可以接受的,不会导致全量监控中断。但注册中心的地址变更、认证凭据过期这类问题会变成新的运维负担,所以要评估一下规模,几台机器真没必要上。

4.4 relabel_configs:这套系统里最值得花时间搞懂的东西

不管用哪种服务发现,最后都会经过 relabel 这一步。它是 Prometheus 里最抽象、也最强大的部分。我用一句话概括:relabel 是在“目标被正式纳入抓取”之前,对目标的标签做增删改的一道流水线。

这里有个关键区别要分清:relabel_configs处理的是目标(target),发生在抓取之前;metric_relabel_configs处理的是抓下来的指标(sample),发生在抓取之后。前者用来筛选和标记机器,后者用来丢弃不想存的指标。

先看一个最常用的场景——只保留特定标签的目标:

relabel_configs: # 只抓取带有 monitor=true 标签的目标 - source_labels: [__meta_filepath, env] regex: '.*prod.*' action: keep

再看一个更实用的——把注册中心返回的元数据标签,转成我们想要的业务标签:

relabel_configs: - source_labels: [__meta_discovery_role] target_label: role - source_labels: [__meta_discovery_hostname] target_label: hostname - source_labels: [__meta_discovery_zone] target_label: idc

还有一类高频用法是改写instance标签。默认instance是IP:端口,端口对查询来说是噪音,我一般会把端口去掉:

relabel_configs: - source_labels: [__address__] regex: '([^:]+):\d+' target_label: instance replacement: '$1'

metric_relabel_configs的典型用法是丢弃高基数或者没用的指标,这个对控制存储成本非常有效。比如 node_exporter 里有一批关于 CPU 各模式的细粒度指标,如果你只关心使用率,可以把某些模式的序列直接 drop 掉:

metric_relabel_configs: # 丢弃不需要的挂载点,避免容器挂载点把序列数撑爆 - source_labels: [__name__, mountpoint] regex: 'node_filesystem_.*;/var/lib/containers/.*' action: drop

提示:relabel 的规则是自上而下依次执行的,顺序会影响结果。写完一定要去 Targets 页面看实际生效的标签,别只在脑子里推演。页面上每个目标展开都能看到Discovered labels和Target labels的对照,这是排查 relabel 问题最快的入口。

5. PromQL 实战:从能看变成会看

5.1 四个黄金信号在主机监控里怎么落地

业界常说的四个黄金信号是延迟、流量、错误、饱和度。放到主机监控这个层面,我的映射是这样的:饱和度看 CPU 和内存水位,流量看网络和磁盘 IO,错误看抓取失败和日志错误计数,延迟看抓取耗时和服务响应时间。先不追求一步到位,把下面这几条基础查询跑通,你就能覆盖 80% 的日常排障。

CPU 使用率,注意node_cpu_seconds_total是累计值,必须用rate()转成速率:

100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

内存使用率,用MemAvailable而不是MemFree,前者才是真正“还能拿来用”的量:

(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100

磁盘使用率,注意过滤掉 tmpfs 和 overlay 这类虚拟文件系统,否则容器环境里会冒出一堆无意义的挂载点:

(1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs"}) * 100

5.2 一份可以直接抄的查询模板表

我把日常最常用的查询整理成了一张表,出问题的时候按表查,比现场想表达式快得多。

场景PromQL 表达式说明
主机失联up == 0抓取失败的实例
CPU 使用率100 - avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))*1005 分钟窗口
内存使用率(1 - node_memory_MemAvailable_bytes/node_memory_MemTotal_bytes)*100排除缓存影响
磁盘使用率见上方表达式按 mountpoint 区分
磁盘写满预测`predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfsoverlay"}[6h], 4*3600) < 0`
网络入流量rate(node_network_receive_bytes_total{device!="lo"}[5m])字节/秒
磁盘 IO 等待rate(node_cpu_seconds_total{mode="iowait"}[5m])*100单核百分比
负载 1 分钟node_load1与核数对比
系统启动时间time() - node_boot_time_seconds秒,用来发现意外重启
抓取耗时scrape_duration_seconds > 5抓取太慢的目标
序列数count({__name__=~".+"}) by (job)各 job 的序列规模

其中“磁盘写满预测”这一条是我最推荐加进告警的。固定阈值告警的问题是:阈值低了天天误报,阈值高了来不及处理。predict_linear的思路是——用过去 6 小时的可用空间变化趋势,线性外推 4 小时后还剩多少。小于 0 说明按当前写入速度,4 小时内会写满。这个表达式在磁盘平稳的时候不会响,在快速写入的时候会提前几小时预警,准确率高得多。

5.3 告警规则怎么写才不吵人

告警规则文件放在rules/目录下,格式是 YAML。我拿磁盘告警做例子,把几个关键字段的作用说清楚。

groups: - name: host-basic interval: 30s rules: - alert: HostDown expr: up{job="node"} == 0 for: 2m labels: severity: critical annotations: summary: "主机 {{ $labels.instance }} 抓取失败" description: "已持续 2 分钟无法抓取 {{ $labels.instance }},检查 node_exporter 是否存活。" - alert: HostDiskWillFillIn4h expr: | predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay",mountpoint="/"}[6h], 4*3600) < 0 and node_filesystem_avail_bytes{fstype!~"tmpfs|overlay",mountpoint="/"} > 0 for: 10m labels: severity: warning annotations: summary: "{{ $labels.instance }} 根分区预计 4 小时内写满" description: "当前可用 {{ $value | humanize }}B,请检查日志与大文件。" - alert: HostHighCpu expr: | 100 - avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))*100 > 85 for: 10m labels: severity: warning annotations: summary: "{{ $labels.instance }} CPU 使用率持续高于 85%"

for这个字段是降噪的核心。它表示表达式连续为真的时长,只有持续满足才真正触发。HostDown设 2 分钟,是因为偶发的单次抓取失败不值得半夜打电话;HostHighCpu设 10 分钟,因为编译、备份这类短时任务会把 CPU 打到 90% 以上,但不代表有问题。我刚上手的时候所有告警都不写for,结果每天早上打开手机几十条告警,全是瞬时抖动,人直接被训练成不看告警了——这是比没有监控更危险的状态。

另一条经验是severity标签要严格分级,我一般只分两级:critical表示影响可用性、需要立刻处理;warning表示需要关注但不紧急、工作时间处理。分级超过三级之后,值班的人就开始纠结“这条到底算 P1 还是 P2”,反而耽误时间。

5.4 记录规则:把重查询变成预计算

有些查询很重,比如按实例聚合几百个序列、或者跨多个 job 做复杂运算。如果在 Grafana 面板上每次都现算,页面加载会明显变慢,在目标数多的时候还会拖累 Prometheus 本身。记录规则(recording rule)解决的就是这个问题——定期把查询结果算好,存成一个新的指标,查询的时候直接读。

groups: - name: host-recording interval: 30s rules: - record: instance:node_cpu_utilization:ratio expr: 1 - avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) - record: instance:node_memory_utilization:ratio expr: 1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes

命名我用level:metric:operation这套社区惯例,比如instance:node_cpu_utilization:ratio一眼就能看出来是按实例聚合的、指标是 CPU 利用率、运算是比值。这套命名不是为了好看,是为了三个月后你自己还能看懂。

记录规则会额外占用存储(每条记录规则产出的序列都要存下来),所以只给真正高频、真正重的那几条查询加,不要看到什么都加一条。

6. 告警链路与可视化

6.1 Alertmanager 的三个核心机制:分组、抑制、静默

Prometheus 只负责“判定”,真正的发送逻辑在 Alertmanager。它有三个机制,用好了告警质量会有质变。

分组(group_by):同一批机器的同一种告警合并成一条消息。比如一次机架断电,20 台机器同时HostDown,不分组的后果是 20 条独立消息,分组之后是一条“20 个实例失联”,点开看详情。配置里group_by: ['alertname', 'env']是最常用的组合。

抑制(inhibit_rules):高等级告警触发时,压掉由它引起的低等级告警。典型场景是主机失联的时候,上面所有的 CPU、内存、磁盘告警都会跟着响,但这些全是噪音,根因只有一个。抑制规则可以写成“当某实例的HostDown处于 firing 状态,该实例上所有severity=warning的告警全部静默”。

静默(silence):计划内维护时手动压制。这个功能在 Alertmanager 的 Web 界面(9093 端口)上直接点就行,支持按标签匹配、设定时间范围,比去改配置文件方便。我每次做变更前都会先加一个静默,变更完成确认无误再删掉。

6.2 一份可用的告警路由配置

route: receiver: 'default' group_by: ['alertname', 'env'] group_wait: 30s group_interval: 5m repeat_interval: 4h routes: - matchers: ['severity="critical"'] receiver: 'oncall' group_wait: 10s repeat_interval: 1h - matchers: ['severity="warning"'] receiver: 'team' inhibit_rules: - source_matchers: ['alertname="HostDown"'] target_matchers: ['severity="warning"'] equal: ['instance']

group_wait是首次通知前的等待时间,给同批次告警一个汇聚窗口,critical 我设 10 秒,尽量快;group_interval是同一分组内新增告警的发送间隔;repeat_interval是同一条告警的重复提醒周期,critical 设 1 小时,warning 设 4 小时——提醒太频繁会让人麻木,太稀疏又会忘。

inhibit_rules里的equal: ['instance']很关键,它限定了抑制只在同一个实例范围内生效,不会误伤其他机器的告警。

6.3 Grafana 面板:别做艺术品,做能用的东西

Grafana 接 Prometheus 就是加一个数据源,填上http://127.0.0.1:9090,保存测试通过就行。面板设计上我有一条原则:首页只放能一眼看出“现在有没有事”的东西,细节往下钻。

我的主机监控面板通常是这样组织的:最上面一行是四个数字面板(主机总数、失联数、告警数、平均 CPU),红黄绿配色,异常一眼能看到;第二行是全局的 CPU、内存、磁盘水位趋势图,用avg by (env)聚合;第三行开始是明细表格,列出所有实例的当前 CPU、内存、磁盘,按磁盘使用率倒序排列,谁快满了排最前面。

几个实操细节:图的时间范围默认设 6 小时,不要设 5 分钟(看不出趋势);rate()的窗口要和面板的min step匹配,否则会出现阶梯状的假象;变量(Variables)里定义一个$instance的下拉框,用label_values(up, instance)取值,这样一块面板能看所有机器,不用复制 N 份。

7. 踩坑记录与排查速查表

7.1 常见问题速查表

现象可能原因排查动作
Targets 里目标显示 DOWNexporter 没起、端口不通、防火墙拦截在监控机上curl目标端点
抓取超时目标负载过高、scrape_timeout太小看scrape_duration_seconds,适当调大超时
图表出现断点抓取中断、Prometheus 重启、时间不同步查up指标历史,比对时间
rate()出现负值或尖刺目标重启导致计数器归零、时间偏移确认node_boot_time_seconds,检查时间同步
磁盘增长远超预期高基数标签(如 URL、用户 ID 进了标签)用count by(__name__)找序列大户
告警不触发for太长、规则未加载、表达式为空在 Prometheus 页面直接跑表达式看是否有结果
告警重复轰炸没配group_by、repeat_interval太短检查 Alertmanager 配置与静默状态
内存持续上涨活跃序列过多、查询范围过大看prometheus_tsdb_head_series,优化查询

7.2 几个不那么容易发现的坑

坑一:高基数标签把存储吃光。这是最常见的性能事故。有人在业务指标里带上了request_id、user_id、完整 URL 这类高基数标签,一条指标瞬间变成几十万序列,Prometheus 内存直接打满。判断方法就是查序列总数:prometheus_tsdb_head_series。如果这个数在短时间内暴涨,立刻去查是哪个 job 引入的:

topk(10, count by (__name__, job) ({__name__=~".+"}))

找到了就用metric_relabel_configs把那部分指标 drop 掉,别犹豫。

坑二:instance标签在 relabel 里被覆盖后,up指标对不上。我改过一次instance标签,把端口去掉了,结果之前基于instance写的告警规则全部失效,因为标签值变了。改标签这类操作一定要全局搜一遍现有的告警规则和面板,确认没有依赖再动手。

坑三:规则文件加载了但不生效。rule_files的路径是 glob 匹配,写rules/*.yml但文件后缀是.yaml就匹配不到。而且这个错误在启动日志里只是一行 WARN,很容易忽略。养成习惯:改完规则,去 Prometheus 的 Rules 页面确认规则数量和状态,别只看文件在不在。

坑四:容器里的 node_exporter 看到的是容器自己的文件系统。这个坑很隐蔽。node_exporter 跑在容器里、没有挂载宿主机的/proc和/,采集到的内存和磁盘全是容器的,数值看起来“正常”但毫无意义。要监控宿主机,必须把宿主机的/proc、/sys、/以只读方式挂进去,并且路径映射要对。

7.3 长期运行我总结的几条经验

第一,监控系统本身也要被监控。我给 Prometheus 单独配了一条外部探测,检查/‑/ready接口和 TSDB 的状态。有一回 Prometheus 因为磁盘满进入只读,自己不发告警(它连告警都算不出来了),是外部探测发现的。别让监控成为单点盲区。

第二,每次告警都要复盘,不是关掉就完事。我的习惯是每周看一次告警列表,凡是“响了但不用处理”的,要么调阈值、要么加for、要么直接删掉。告警的价值不在数量,在于每一条响起来都值得看一眼。一条长期误报的告警会污染整个告警通道的可信度。

第三,配置全部进版本管理。prometheus.yml、rules/*.yml、alertmanager.yml、Grafana 面板的 JSON,全部放代码仓库。这样改错了能回滚,换了人能看懂改了什么。我吃过一次亏:手工在服务器上改了一个规则,改错了,两周后才发现,中间那段时间的告警全是错的。

第四,定期做一次“演练”。手动停掉一台机器的 node_exporter,确认告警按预期在 2 分钟内发出、分组正确、通知到了对的人。监控这种东西,不出事的时候你永远不知道它到底能不能用,等到真出事才发现告警通道配错了,那就太晚了。


我个人在实际操作中的体会是:Prometheus 上手门槛不高,装起来半小时就能看到图,但真正把它用出价值,难点全在“设计”上——标签怎么规划、告警怎么分级、指标怎么控制基数。这些没有标准答案,都得结合自己环境的规模和历史包袱去调。最后分享一个小技巧:新接手一套环境,先别急着看面板,去 Targets 页面把所有目标的标签展开看一遍,你对这套监控的质量心里就有数了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询