1. 先把架构想清楚:单机演练和多主机生产的差距在哪
监控这件事,最怕的不是不会装软件,而是装完了不知道自己要什么。我见过太多人第一次接触 Prometheus,照着教程在本地机器上跑起一个prometheus二进制,打开 9090 端口看到那个 Graph 页面能画出曲线,就以为已经"会 Prometheus 了"。等到真要把公司里几十台机器纳入监控,问题就全冒出来了:目标怎么批量加、配置文件改一次要不要重启、告警为什么半夜炸了三次、磁盘怎么一个月就写满了。
Prometheus 监控系统的本质是一套"指标采集 + 时序存储 + 规则计算 + 告警分发"的流水线,它的核心价值在于用一套统一的拉取协议,把散落在各台主机上的运行状态(CPU、内存、磁盘、网络、进程)变成可以查询、可以聚合、可以触发告警的时间序列数据。而"从零搭建到多主机监控"这个跨度,真正难的不是单点安装,是主机规模上来以后配置怎么管、标签怎么设计、容量怎么估算、故障怎么定位。
这篇内容面向三类人:完全没接触过 Prometheus、想搞清楚整套东西怎么落地的新手;已经跑起来单机版、准备接入第二批主机的运维同学;以及被"target down""告警风暴""磁盘写满"折腾过、想找一套系统排查思路的人。下面所有步骤和参数都是我实际搭过、调过、踩过坑之后整理出来的,能直接抄的地方我会把配置贴全,需要你按自己环境改的地方我会标出来。
1.1 为什么是拉取模型,而不是"主机主动上报"
这是理解 Prometheus 的第一道坎。市面上不少监控方案走的是推送模型:被监控端装个 agent,定期把数据推到中心服务器。Prometheus 反过来,中心节点主动去每个目标的/metrics接口"抓"数据,默认路径固定、默认端口固定、返回格式是纯文本。
这个设计带来的好处,用过一段时间你就会上瘾。第一,目标是否存活天然可知。Prometheus 每次抓取都会生成一个up指标,抓成功是 1,抓失败是 0,你不需要额外写心跳逻辑,up == 0就是最可靠的主机失联告警。第二,调试极其方便。怀疑某台机器没数据,直接curl http://10.0.1.11:9100/metrics | head -50,能不能出内容一目了然,不用去翻 agent 的日志文件猜它到底推没推。第三,配置集中在一处。新增一台主机,只需要在 Prometheus 这一侧加一条 target,被监控端只要跑着 exporter 就行,不依赖它知道中心节点地址。
代价也很明确:目标必须网络可达。如果某些机器在防火墙后面、或者只允许出方向流量,就得借助中间层或者让 exporter 通过反向代理暴露出来。这个取舍在多主机场景下要提前想明白,否则后面会遇到"配置写对了但一直 down"的困惑。
1.2 四个组件各管什么,别混着用
一套完整的 Prometheus 监控栈通常是四个角色,我在早期犯过的错误是把它们的职责混在一起,结果越用越乱。
| 组件 | 默认端口 | 核心职责 | 容易踩的坑 |
|---|---|---|---|
| Prometheus Server | 9090 | 抓取目标、写入 TSDB、计算规则 | 当数据库用,什么都往里塞 |
| Exporter | 9100 等 | 把本机状态翻译成指标文本 | 采集项开太多,指标爆炸 |
| Alertmanager | 9093 | 告警去重、分组、抑制、静默、路由 | 以为 Server 能直接发通知 |
| Grafana | 3000 | 查询与可视化 | 面板查询写得太重,拖慢 Server |
关于告警,这里必须强调一句:Prometheus Server 只负责判断"条件成不成立",不负责"发给谁、发几次"。规则触发后它只是把告警推给 Alertmanager,真正决定要不要合并成一条、多久重复一次、什么情况下压制的,都是 Alertmanager 的活。很多人一开始图省事,想直接在 Server 里配个 webhook,最后必然遇到同一个问题在一分钟内发几十条的情况。
1.3 多主机的演进路径:从手写配置到文件发现
主机数量是决定配置方式的关键变量。我的建议是分三个阶段走,不要一上来就追求自动发现。
主机在 10 台以内,直接static_configs手写 target 列表最省事,改完systemctl reload prometheus或者发个POST /-/reload就生效。
到了 30 台以上,手写就开始出错了——漏一台、写错 IP、改完忘了 reload,都是常事。这时候换成file_sd_configs:Prometheus 监控一个目录下的 JSON 文件,文件内容变化后它自己按refresh_interval重新读取,完全不需要重启或 reload。这个机制非常关键,它让"配置生成"和"服务运行"解耦了,你可以用任何脚本、任何配置管理工具去生成那个 JSON。
再往上到上百台、或者主机频繁伸缩,才需要考虑服务发现。但坦白讲,多数业务集群的规模并没有大到必须上 Consul 或 DNS 发现,file_sd_configs配上一个定时生成 JSON 的脚本,能扛很久。
1.4 磁盘容量必须先算一遍,别等写满
这是最容易被忽略的一步。Prometheus 的本地 TSDB 压缩率不错,但也不是无限的,动手前先估个容量心里有底。
粗略的估算方式是:每秒新增样本数 × 保留时长(秒) × 每样本约 1.5 到 2 字节。其中每秒新增样本数 =所有目标的活跃时间序列总数 ÷ 抓取间隔。一台标准 Linux 主机跑node_exporter,按默认采集项,活跃序列大约在 600 到 1200 条之间,取 800 做估算比较稳。
| 主机数 | 估算活跃序列 | 抓取间隔 | 每秒样本 | 每天新增样本 | 压缩后日增 | 15 天总量 | 建议磁盘 |
|---|---|---|---|---|---|---|---|
| 20 | 16000 | 15s | 1066 | 约 9200 万 | 约 160 MB | 约 2.4 GB | 20 GB |
| 50 | 40000 | 15s | 2666 | 约 2.3 亿 | 约 390 MB | 约 5.9 GB | 30 GB |
| 100 | 80000 | 15s | 5333 | 约 4.6 亿 | 约 780 MB | 约 11.8 GB | 50 GB |
| 200 | 160000 | 15s | 10666 | 约 9.2 亿 | 约 1.6 GB | 约 23.5 GB | 80 GB |
表里只算了压缩后的数据块,实际还要加上倒排索引、WAL 日志、以及规则计算产生的记录规则序列,通常再留 50% 到 100% 的余量才安全。上面"建议磁盘"那一列已经是放过余量的结果。另外--storage.tsdb.retention.size一定要配上,作为时间保留策略的兜底,防止某天基数突然涨了直接写爆分区。
2. 核心概念和配置细节:那些教程里一句话带过的东西
2.1 时间序列到底是什么,为什么标签设计要谨慎
Prometheus 里所有数据都是时间序列,一条时间序列由指标名 + 一组标签唯一确定。比如:
node_cpu_seconds_total{cpu="0", instance="10.0.1.11:9100", job="node", mode="idle"}指标名node_cpu_seconds_total说明它是 CPU 各模式累计时间,标签cpu="0"区分核心、mode="idle"区分模式、instance区分机器。这一整套标签组合,就是一条独立的时间序列,占用独立的内存和存储。
理解这一点之后,所谓"基数爆炸"就好懂了:标签值的组合数量决定序列数量,序列数量决定内存和磁盘。如果你的标签里放了用户 ID、请求路径、会话 ID 这种东西,几秒钟就能造出几十万条序列,Prometheus 内存会以肉眼可见的速度往上涨,然后 OOM。
注意:任何"取值可能非常多"的字段,都不要放进标签。真需要区分,放在日志里查,别放在指标里存。
2.2 四种指标类型,选错了后面全别扭
| 类型 | 语义 | 典型例子 | 查询方式 |
|---|---|---|---|
| Counter | 只增不减的累计值 | 请求总数、CPU 累计秒数 | rate()求速率 |
| Gauge | 可增可减的瞬时值 | 内存使用量、温度、连接数 | 直接取值或用delta() |
| Histogram | 分桶统计的分布 | 请求延迟、响应体大小 | histogram_quantile() |
| Summary | 客户端预先算好分位 | 较少使用 | 直接读{quantile="0.99"} |
这里有个实际经验:延迟类指标优先用 Histogram,不要用 Summary。原因是 Summary 的分位数是在被监控端算好写出来的,多个实例的数据没法合并——你无法从两台机器的 p99 推出整体的 p99。Histogram 存的是各桶的累计计数,可以跨实例求和之后再算分位,这才是多主机场景需要的。
代价是 Histogram 的桶边界要提前设计,设得不合理分位数就不准。默认桶从0.005到10秒,覆盖 HTTP 服务够用,如果监控的是数据库慢查询,可能得自定义到几十秒。
2.3 五个必须背下来的 PromQL 写法
装完就能用的查询其实不多,但下面这几个是日常最高频的,直接拿去用。
CPU 使用率(按实例聚合,排除空闲):
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)内存使用率(用 MemAvailable 比 MemFree 更准,因为 Free 不含可回收缓存):
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100磁盘使用率(排除 tmpfs、overlay 等虚拟文件系统,否则会看到一堆 100%):
(1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs"}) * 100主机是否失联:
up{job="node"} == 01 分钟负载除以核数,判断是否过载:
node_load1 / count by (instance) (node_cpu_seconds_total{mode="idle"})提示:
rate()后面的区间至少要是抓取间隔的 4 倍。抓取间隔 15s,就用[1m]起步,用[5m]更平滑。区间太短会导致数据点不够,曲线变成锯齿甚至断点。
2.4 记录规则不只是"省事",是为了控住查询压力
Grafana 面板上如果有几个查全量数据的重查询,每次刷新都要扫几十万条序列,服务器 CPU 会被拖得很高。解决办法不是把面板刷新间隔调长,而是用记录规则(recording rule)把常用聚合结果预先算好,存成新的指标。
比如把每台主机的 CPU、内存、磁盘使用率算好,存成instance:node_cpu_usage:avg_rate5m这样的指标,面板直接查这个新指标,成本就降下来了。命名上我习惯用level:metric:operations的格式,冒号表示这是规则生成出来的指标,不会和原始采集指标混淆。
记录规则的interval一般和evaluation_interval一致,15s 或 30s。规则文件放在独立目录,用rule_files通配符加载,改完发个 reload 就生效,不用重启。
2.5 告警规则里for这个字段,值不值得设
几乎所有新手都会在这个字段上纠结。for的意思是"条件持续满足多久才真正触发",它是抑制误报最有效的手段。
我的标准是这样:主机失联这类硬故障,for: 2m就够,再长就没意义了;磁盘使用率超过 85%,for: 15m,因为磁盘不会瞬间满,短时抖动很正常;CPU 使用率超过 90%,for: 10m,否则一次编译就能把告警打爆;业务层面的错误率,for: 5m比较合适。
不设for的告警,基本等于给自己找麻烦。半夜被一条"CPU 瞬时 95%"叫起来,结果机器两分钟就自己恢复了,这种事发生两次你就会开始无视告警,然后真正的故障来临时你也无视了。
3. 从零开始的完整实操:三台机器到一批主机
这一节按真实搭建顺序走,每一步都给出可直接落地的配置和命令。假设环境是这样的:一台监控服务器10.0.0.10,装 Prometheus + Alertmanager + Grafana;若干台被监控主机,先在10.0.1.11和10.0.1.12上验证,跑通之后批量铺开。系统统一用常见 Linux 发行版,内网互通,防火墙策略由你自己按实际情况配。
3.1 目录规划和专用账号,这一步别省
所有组件都用一个非登录的系统账号跑,不要用 root。二进制统一放/usr/local/bin,配置放/etc/prometheus,数据放/var/lib/prometheus。这样后面升级、备份、迁移都清爽。
# 在监控服务器上执行 sudo useradd --no-create-home --shell /usr/sbin/nologin prometheus sudo mkdir -p /etc/prometheus/rules /etc/prometheus/targets sudo mkdir -p /var/lib/prometheus sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus目录规划里特意建了targets,就是给file_sd_configs用的。后面所有主机的抓取目标都放这个目录,配置文件和目标列表彻底分开管理。
3.2 安装 Prometheus Server 并托管成系统服务
下载对应架构的压缩包解压,把prometheus和promtool两个二进制拷到/usr/local/bin。promtool一定要一起装,它能在启动前把配置文件语法和规则表达式都校验一遍,省掉大量"改完起不来"的时间。
核心配置文件prometheus.yml先写一个能跑通所有环节的版本:
global: scrape_interval: 15s scrape_timeout: 10s evaluation_interval: 15s external_labels: cluster: prod-a rule_files: - /etc/prometheus/rules/*.yml alerting: alertmanagers: - static_configs: - targets: - 10.0.0.10:9093 scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] - job_name: 'node' file_sd_configs: - files: - /etc/prometheus/targets/hosts_*.json refresh_interval: 30s relabel_configs: - source_labels: [__address__] regex: '([^:]+):.*' target_label: instance replacement: '${1}'最后那个relabel_configs的作用是把instance标签从10.0.1.11:9100改写成10.0.1.11,去掉端口。看着是小事,但在 Grafana 上做图例、在告警标题里显示主机名的时候,端口会一直烦你。
配置文件写完先校验:
promtool check config /etc/prometheus/prometheus.yml看到SUCCESS再继续。然后是 systemd 单元:
[Unit] Description=Prometheus Server After=network-online.target Wants=network-online.target [Service] User=prometheus Group=prometheus Type=simple ExecStart=/usr/local/bin/prometheus \ --config.file=/etc/prometheus/prometheus.yml \ --storage.tsdb.path=/var/lib/prometheus \ --storage.tsdb.retention.time=15d \ --storage.tsdb.retention.size=30GB \ --web.listen-address=0.0.0.0:9090 \ --web.enable-lifecycle \ --web.max-connections=512 Restart=on-failure RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target几个参数展开说一下。--web.enable-lifecycle是必须开的,开了之后curl -X POST http://10.0.0.10:9090/-/reload就能热加载配置,不用重启进程,也不会有几十秒的数据空窗。--storage.tsdb.retention.time和.size同时设,哪个先到就按哪个删,这是双保险。--web.max-connections在抓取目标多的时候有用,默认值偏小。
--web.enable-admin-api这个开关我建议生产环境不开。它提供删除时间序列、创建快照的能力,权限太大了,一旦误操作可能直接把数据删掉。真需要删数据,走快照加离线处理的流程更稳妥。
3.3 被监控端部署 node_exporter
每台被监控主机都要装。同样用独立账号运行,监听端口 9100。这里有个安全细节要强调:默认监听是:9100,等于对所有网卡开放,如果机器有公网地址,那指标就裸奔了。加--web.listen-address=10.0.1.11:9100只绑内网地址,再配防火墙只允许监控服务器访问,才算基本安全。
sudo useradd --no-create-home --shell /usr/sbin/nologin prometheus sudo mkdir -p /var/lib/node_exporter/textfile sudo chown -R prometheus:prometheus /var/lib/node_exportersystemd 单元:
[Unit] Description=Node Exporter After=network-online.target Wants=network-online.target [Service] User=prometheus Group=prometheus Type=simple ExecStart=/usr/local/bin/node_exporter \ --web.listen-address=:9100 \ --collector.textfile.directory=/var/lib/node_exporter/textfile \ --collector.filesystem.mount-points-exclude="^/(sys|proc|dev|run|var/lib/docker/.+)($|/)" Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target--collector.filesystem.mount-points-exclude这个参数强烈建议加。不加的话,容器化环境里会冒出大量 overlay 挂载点,每个都占一堆序列,磁盘使用率图上还会出现一堆看起来"100% 已满"的假报警。
textfile收集器是个很实用的口子。任何脚本只要把符合格式的纯文本写到那个目录下,node_exporter 就会把它一起暴露出去。比如你写了个检查证书剩余天数的脚本,输出一行tls_cert_days_remaining{domain="example.internal"} 43写到xxx.prom,Prometheus 就能直接抓到,不用自己开发 exporter。
启动并验证:
sudo systemctl daemon-reload sudo systemctl enable --now node_exporter curl -s http://127.0.0.1:9100/metrics | grep -c '^node_'最后那行输出的数字就是这个 exporter 暴露的指标条数,正常在几百到一千多之间。如果只有十几个,说明采集器没正常加载,去看journalctl -u node_exporter。
3.4 多主机的抓取目标:用 JSON 文件管起来
现在到关键一步。在监控服务器上创建/etc/prometheus/targets/hosts_web.json:
[ { "targets": [ "10.0.1.11:9100", "10.0.1.12:9100" ], "labels": { "env": "prod", "role": "web", "idc": "a-room" } } ]再建一个hosts_db.json,标签改成"role": "db"。file_sd_configs里用的是hosts_*.json通配符,所以这两个文件都会被加载,而且新增文件不需要改主配置。这个设计的意义在于:业务线自己维护自己的目标文件,加机器不用碰主配置,不会有并发修改冲突。
注意文件权限,Prometheus 是以prometheus账号读这个目录的:
sudo chown -R prometheus:prometheus /etc/prometheus/targets sudo chmod 640 /etc/prometheus/targets/*.json然后去 Web 界面http://10.0.0.10:9090/targets看,两个新目标应该出现在node这个 job 下,状态是 UP。如果显示 DOWN,先别改配置,按后面的排查顺序走一遍。
批量铺开的做法很简单,写个脚本从资产清单里读主机列表,按角色分组生成 JSON,用jq或者模板都行,生成完chmod一下就行,Prometheus 会在 30 秒内自动发现。这里的关键是资产清单必须是唯一数据源,不要手工维护一份 JSON 又维护一份 CMDB,两边迟早对不上。
3.5 把告警链路打通:Alertmanager 的分组参数怎么调
Alertmanager 的配置文件不算复杂,但几个时间参数调不好就会出现"该合并的没合并、该发的没发"。
global: resolve_timeout: 5m route: receiver: 'default-receiver' group_by: ['alertname', 'cluster', 'role'] group_wait: 30s group_interval: 5m repeat_interval: 4h routes: - matchers: - severity="critical" receiver: 'urgent-receiver' group_wait: 10s repeat_interval: 1h receivers: - name: 'default-receiver' webhook_configs: - url: 'http://10.0.0.20:8080/alert/default' send_resolved: true - name: 'urgent-receiver' webhook_configs: - url: 'http://10.0.0.20:8080/alert/urgent' send_resolved: true inhibit_rules: - source_matchers: - alertname="InstanceDown" target_matchers: - severity=~"warning|critical" equal: ['instance']四个时间参数的实际含义,我用一个具体场景说明:某台主机断网,同时触发了"抓取失败""CPU 无数据""磁盘无数据"三条告警。
group_wait: 30s:第一条告警进来后先等 30 秒,攒一攒,把这几条合并成一个通知发出去,而不是发三次。group_interval: 5m:同一个分组里如果有新告警加入,或者有告警恢复,最快 5 分钟才更新一次通知,避免刷屏。repeat_interval: 4h:告警一直不恢复,每 4 小时提醒一次,别让它在收件箱里沉底。resolve_timeout: 5m:如果 Alertmanager 5 分钟没收到 Prometheus 推来的任何数据,就认为告警源失联了,把相关告警标记为已解决。这个值不要设太小。
inhibit_rules那段是最有价值的。它的作用是:当InstanceDown已经触发时,同实例上的 CPU、磁盘、内存告警全部压制掉。因为主机都连不上了,那些告警毫无意义,纯粹是噪音。equal: ['instance']保证只压制同一台机器上的。
3.6 Grafana 接进来,别自己从零画面板
Grafana 装好之后配数据源,类型选 Prometheus,URL 填http://10.0.0.10:9090,抓取间隔设成和 Prometheus 一致。有个细节:数据源里有个 "Scrape interval" 字段,建议设成15s,这样面板查询时$__rate_interval变量才能自动算出合适的区间,不然新手很容易写出rate(x[1m])这种点太少导致曲线断裂的查询。
至于面板,社区里有现成的主机监控模板可以直接导入,导入时指定数据源和 job 名就能用,省掉大量画图时间。但我建议导入之后一定要做两件事:把用不到的图表行删掉(模板通常为了通用性塞了太多东西),把变量默认值改成你实际的标签值。否则每次打开面板都要手动选一遍,用两天就烦了。
3.7 抓取侧的加固:认证、超时、限流
内网环境可能觉得没必要,但只要有一台机器跨了网段、走了专线,就该考虑这几项。
- job_name: 'node-secure' scrape_timeout: 8s sample_limit: 5000 label_limit: 100 basic_auth: username: monitor password_file: /etc/prometheus/.scrape_pass tls_config: insecure_skip_verify: false ca_file: /etc/prometheus/ca.crt file_sd_configs: - files: - /etc/prometheus/targets/secure_*.jsonsample_limit和label_limit是防呆用的。如果某个 exporter 因为 bug 突然吐出几十万条序列,这两个限制会让这次抓取直接失败并记一条scrape_samples_limit相关日志,而不是把内存吃光。我吃过一次亏,某个自研 exporter 的标签里带上了进程启动时间戳,序列数每分钟涨一批,二十分钟后服务器 OOM。加上sample_limit之后,这类问题最多是某个 target 变 DOWN,主进程不受影响。
密码不要直接写在配置里,用password_file单独存,文件权限设成 600,只管给prometheus账号读。
4. 故障实录:那些让你怀疑人生的报错
4.1 target 变 DOWN 的标准排查顺序
这是最高频的问题,我整理了一套固定顺序,按这个走能覆盖九成情况。
| 顺序 | 检查项 | 命令 | 判据 |
|---|---|---|---|
| 1 | 目标端口是否监听 | ss -lntp | grep 9100 | 无输出说明 exporter 没起 |
| 2 | 本机能否取到指标 | curl -s http://127.0.0.1:9100/metrics | head | 有内容说明服务正常 |
| 3 | 跨机是否可达 | curl -sv http://10.0.1.11:9100/metrics | 看是否超时或被拒 |
| 4 | 防火墙规则 | 查本机与目标机的入站策略 | 9100 是否放行 |
| 5 | 监听地址是否正确 | 看 exporter 启动参数 | 是否绑了 127.0.0.1 |
| 6 | 时间是否同步 | timedatectl或date对比 | 两边差值应小于 2 秒 |
| 7 | 抓取超时是否过短 | 看 Web 界面 error 文案 | 是否显示context deadline exceeded |
第 5 项是新手最容易掉的坑。为了"安全",把--web.listen-address设成了127.0.0.1:9100,本机 curl 一切正常,但别的机器当然连不上。第 7 项也常见:目标机器负载很高,默认 10 秒超时不够,指标还没吐完就断了,这种把scrape_timeout调到 20 秒就能解决,但如果长期超时,说明该排查目标机本身的问题,不要一味加超时。
Web 界面上的错误信息很值得读。connection refused是端口没开;context deadline exceeded是超时;server returned HTTP status 403 Forbidden是认证或中间层拦了;no such host是 DNS 解析失败。每种对应完全不同的处理方向。
4.2 时间不同步,最隐蔽的一类问题
Prometheus 在写入样本时会带上抓取时间戳,并且在查询时拒绝返回超出时间窗口的数据。如果目标机和监控服务器时间差了几分钟,你可能会看到这样的报错:
Error executing query: out of bounds或者日志里的Error on ingesting samples with timestamps older than the max time。数据抓到了,但被判为"太旧"丢弃,图表上表现为某台机器数据时有时无、断断续续。
我遇到过一台虚拟机因为宿主机的时钟漂移,比标准时间慢了将近 4 分钟,那台机器的所有图表都出现空洞,排查花了半天才发现是时间问题。判断方法很简单,在目标机和监控服务器上同时执行date -u +%s,对比两个数字,差值超过 5 秒就该处理了。
处理方式就是配时间同步服务,但要确认同步服务本身是在跑的,而不只是装了软件。timedatectl status里能看到System clock synchronized: yes才算真同步。容器环境还要注意宿主机时间是否正确,容器通常继承宿主机的时钟。
4.3 内存曲线一路向上:高基数排查
服务器内存突然涨到 80% 以上并且不回落,八成是高基数问题。下面两条查询基本能定位。
看哪些指标名的序列数最多:
topk(10, count by (__name__) ({__name__=~".+"}))看某个指标下是哪个目标贡献的序列最多:
topk(10, count by (instance, __name__) ({__name__=~"node_netstat.*"}))找到元凶之后,用metric_relabel_configs丢掉不需要的指标,或者加sample_limit直接限制。丢弃配置长这样:
metric_relabel_configs: - source_labels: [__name__] regex: 'node_(netstat|sockstat|softnet).*' action: drop - source_labels: [mountpoint] regex: '/var/lib/docker/.+' action: drop这里要提醒一句:丢弃是永久的,数据不会重新采集。所以先确认这些指标真的没人用,再动手。建议的操作顺序是先加sample_limit拦一下,观察一星期没人反馈缺数据,再上drop。
还有一个隐蔽的高基数来源是自研 exporter。有些人喜欢把"当前处理的请求路径"做标签,路径带随机 ID,序列数就是无限的。这种问题只能在应用侧改,drop只能治标。
4.4 告警风暴:从"被打爆"到"只看有用的"
告警风暴通常有三种成因,处理方式完全不同。
第一种是分组参数没配好。50 台机器同时断网,group_by里没写alertname,就会分成 50 条通知。改配置让同一次故障合并成一条,第一句话写"以下 50 台主机失联",后面的列表自己看。
第二种是阈值设得太敏感。CPU 85% 就告警,稍微跑个批处理就触发。这种告警除了消耗信任没有任何价值。判断标准是:每一条告警都应该对应一个明确的、需要人立刻动手的动作,如果没有动作可做,就不该告警,放进面板看就行。
第三种是缺少抑制规则。上游依赖挂了,下游所有服务全部报错,几百条告警一起响。这种要给上下游建抑制关系:核心网关的InstanceDown触发时,压制所有依赖它的业务告警。虽然要花点时间梳理依赖关系,但做一次能省下无数次半夜的无效响应。
4.5 速查表:几个常见报错和对应处理
| 现象或报错 | 大概率原因 | 处理方式 |
|---|---|---|
context deadline exceeded | 抓取超时 | 调大scrape_timeout,同时查目标机负载 |
connection refused | 端口未监听或进程挂了 | 查进程状态和监听地址 |
out of bounds | 时间不同步 | 校准 NTP,核对两边时钟 |
permission denied打不开配置文件 | 文件属主不对 | chown prometheus:prometheus |
启动报unknown field | 配置项拼写错或版本不支持 | promtool check config定位行号 |
| 图表有断点 | 抓取间隔与 rate 区间不匹配 | 区间至少放大到间隔的 4 倍 |
| 磁盘占用远超预期 | 基数增长或保留期过长 | 查基数,调retention.size |
| 告警已恢复还在发 | resolve_timeout太长或心跳断了 | 检查 Server 到 Alertmanager 的连通性 |
| reload 后配置没生效 | 没开--web.enable-lifecycle | 加参数并重启一次 |
4.6 几条只有踩过才知道的经验
升级前先导快照。Prometheus 跨大版本升级时 TSDB 格式可能变化,回退很麻烦。用POST /api/v1/admin/tsdb/snapshot打快照(需要开 admin api),把数据目录整体备份一份,磁盘占用翻倍也值得。升级时先把二进制换掉、配置用promtool检查一遍、再启动,别一次性全改。
记录规则要跟着业务变。早期设的记录规则,业务改版之后可能没人维护,变成纯消耗 CPU 的垃圾。每隔一两个月看一眼规则文件里哪些指标 Grafana 已经不用了,直接删掉。
不要给所有指标都加告警。我现在的原则是:告警条数控制在 20 条以内,每一条都能说清楚"收到之后第一步做什么"。超过这个数,说明在用告警代替思考,效果一定不好。
面板查询别写rate(x[1m])配 15s 抓取间隔。1 分钟里只有 4 个点,算出来的速率波动极大,看起来像心脏跳动。统一用$__rate_interval或者手写[5m],曲线会平滑很多,也更能反映真实趋势。
5. 规模再往上走,该关注什么
主机从几十台到几百台的跨越,带来的变化不在安装环节,而在抓取能力和存储压力上。一台 Prometheus 实例的抓取上限,主要受 CPU 和磁盘 IO 约束,经验数据是单实例在 15s 间隔下大约能稳定抓取 50 万到 100 万条活跃序列,再往上就会出现抓取延迟、scrape_duration_seconds持续走高。
这时候的思路不是"换更强的机器",而是按业务或机房分片:每台 Prometheus 只负责一部分目标,然后用一个全局查询层做聚合。分片键选job或idc比较自然,因为同机房内部的网络延迟低,抓取超时风险小。分片之后要注意external_labels必须配,否则不同实例的数据混在一起无法区分来源,这个标签会在跨实例聚合时自动加上。
长期存储是另一个话题。本地 TSDB 保留 15 天够看短期趋势,但要对比上季度的容量变化就不够了。常见的做法是把数据远程写到对象存储,保留期做长、查询走降采样。这块配置量不小,建议等真有"看一年前数据"的需求时再上,不要一开始就堆上去。
至于抓取目标超过几千台之后的自动发现,虽然值得研究,但多数团队在到达那个规模之前,file_sd_configs配一套可靠的资产同步脚本已经完全够用。我见过太多项目在只有二十台机器的时候就开始折腾服务发现和联邦集群,最后监控没做好,脚本倒写了一堆。
最后分享一个我觉得最值的小习惯:每周花十分钟看一次/targets页面和告警历史。看看有没有长期 DOWN 但没人管的目标——那通常意味着某台机器已经悄悄下线,或者某个 exporter 挂了很久没人发现。监控系统本身的质量,往往比监控覆盖了多少台机器更重要。