前面篇章都是基于单机 Prometheus ,已能满足中小企业的基本需求。但针对一些重要业务系统,或把监控数据作为重要数字资产的企业,单机 Prometheus 将存在重大风险。
本篇目标:讲清单机 Prometheus 的可靠性边界在哪,如何用两套 Prometheus + Alertmanager 集群把采集与告警做成高可用,以及如何用 Thanos / VictoriaMetrics 把数据推进对象存储,支撑一年以上的回查。
一、单机 Prometheus 的边界
1、三个真实故障场景
单机部署的问题不是「会不会出事」,而是出事时恰好失去了一切观测能力:
- 场景一:磁盘写满。 TSDB 在磁盘达到阈值后停止写入,
/api/v1/query还能查旧数据,但新样本全部丢弃,且没有显式告警,最直接的信号反而是 Grafana 上所有曲线停止更新。而「Grafana 曲线停更」也依赖 Grafana 可用,故障是自指的。 - 场景二:进程重启。 TSDB 每 2 小时把内存里的 head block 落成一个持久块,之间的数据靠 WAL 兜底。正常重启 WAL 能恢复,但机器断电、OOM Kill 这类非优雅退出会丢掉最近几分钟的样本,Grafana 上表现为一段缺口。
- 场景三:单点宕机。 采集停、告警哑、面板白屏,三件事同时发生。如果 Prometheus 和被监控的应用在同一批机器上,机器级故障时告警链路和故障一起消失。
2、分清两个问题:可用性 与 保留期
「高可用」和「长期存储」经常被混在一句话里说,但它们是两个独立的问题,解法也不同:
| 问题 | 关心什么 | 失败的样子 | 解决方向 |
|---|---|---|---|
| 高可用 | 数据不中断、告警不哑、面板不断 | 宕机期间采集与告警全断 | 双副本采集 + 告警去重 + 查询聚合 |
| 长期存储 | 存得久、查得动、成本可控 | 保留期一到数据被淘汰;回查大范围直接 OOM | 对象存储 + 降采样 + 全局查询 |
⚠️ 注意:高可用不等于备份。 两台一模一样的 Prometheus,配置错误同时写坏数据、或误删同一批块,结果和单点没有区别。可用性解决的是「服务在」,备份解决的是「数据在」。
3、本地 TSDB 的三个天花板
1)保留期
默认只保留 15 天,老版本通过启动参数调整:
bash
# 两个参数同时给时,先到者生效,即哪个条件先触发,就先删旧数据
--storage.tsdb.retention.time=30d
--storage.tsdb.retention.size=45GB
# 注意:size 的最小值是 512MB,单位按样本块计算
# 这是**防磁盘打爆的兜底保护**:当指标基数暴涨(series cardinality 突增),哪怕数据还没到 30 天,只要 TSDB 块总量超过 45GB,就会清理老数据
但这两个命令行参数在新版 Prometheus 已经标记废弃 ,推荐迁移到 prometheus.yml 配置内 storage.tsdb 字段配置。
配置示例:
yaml
# prometheus.yml
storage:
tsdb:
retention:
time: 30d
size: 45GB
通过 prometheus --help 命令查看是否已废弃,如下所示,表示已废弃。
bash
$ prometheus --help
usage: prometheus.exe [<flags>]
The Prometheus monitoring server
Flags:
-h, --[no-]help Show context-sensitive help (also try
--help-long and --help-man).
--storage.tsdb.path="data/"
Base path for metrics storage. Use with server
mode only.
--storage.tsdb.retention.time=STORAGE.TSDB.RETENTION.TIME
[DEPRECATED] How long to retain samples
in storage. If neither this flag nor
"storage.tsdb.retention.size" is set,
the retention time defaults to 15d.
Units Supported: y, w, d, h, m, s, ms.
This flag has been deprecated, use the
storage.tsdb.retention.time field in the config
file instead. Use with server mode only.
--storage.tsdb.retention.size=STORAGE.TSDB.RETENTION.SIZE
[DEPRECATED] Maximum number of bytes that can
be stored for blocks. A unit is required,
supported units: B, KB, MB, GB, TB, PB, EB. Ex:
"512MB". Based on powers-of-2, so 1KB is 1024B.
This flag has been deprecated, use the
storage.tsdb.retention.size field in the config
file instead. Use with server mode only.
2)查询成本
range query 的成本随时间跨度线性增长,回查 30 天的 rate() 在序列数过万后会直接打爆内存。本地 TSDB 没有「降采样」概念,查一年的曲线和查一小时的曲线,扫描的是同一种精度的块。
3)磁盘膨胀
压缩后每个样本约 1.5~2 字节,账很好算:
磁盘/天 ≈ 序列数 × (86400 / 抓取间隔秒) × 2 字节
按本系列验证环境的规模算一笔账:
| 来源 | 序列数(约) | 抓取间隔 | 样本/秒 | 磁盘/天 |
|---|---|---|---|---|
| 5 台主机(node_exporter) | 4,000 | 15s | 267 | 46 MB |
| 3 个 Spring Boot 应用 | 4,500 | 15s | 300 | 52 MB |
| 直方图开启后(×20 基数,第4篇踩坑) | 90,000 | 15s | 6,000 | 1 GB |
没开直方图时 15 天也就 1.5G,本地盘完全够;直方图一开,账立刻变成另一个量级。这说明很多「需要长期存储」的场景,先做减法比先上架构更有效。
二、高可用:两套 Prometheus 是官方答案
1、为什么不是集群
Prometheus 的设计哲学是单机自治:每个实例独立抓取、独立存储、独立评估规则,Server 本身没有内置集群协议。官方对高可用的答案是朴素的两句话:跑两个功能完全相同的 Prometheus,抓同一批 target。
两个实例不做任何主从协调,没有 leader 选举,谁也不备份谁。代价是每个 target 被抓两次、每份数据存两份;换来的是任何一个实例宕机,采集、规则评估、告警链路都不中断。
两个实例共享同一份 targets 文件,配置天然一致:
yaml
# 两套 Prometheus 共用同一个 file_sd 目录(挂载同一个文件)
file_sd_configs:
- files:
- /etc/prometheus/targets/*.yml
refresh_interval: 30s
2、副本标签:不加会出乱序
两个实例抓同一个 target,产生的序列 label 完全相同。如果直接把两份数据写进同一个下游(remote_write 或对象存储),时间戳相同、值可能有微差(抓取时刻不同),下游会把后到的判定为乱序样本直接拒绝。
解法是给两套实例打上不同的 external_labels:
yaml
# prometheus-a.yml ------ 与 prometheus-b.yml 唯一的区别就是 replica 值
global:
scrape_interval: 15s
external_labels:
replica: A # B 实例写 replica: B
⚠️ 注意:
replica标签必须在 external_labels 里打,不能在 relabel_configs 里打。 external_labels 会附加到该实例产出的所有样本与告警上;而 relabel 打的标签只影响抓取目标,WAL、告警、remote_write 的元数据不会带上它,去重时会漏。external_labels的作用范围被严格限定在与外部系统通信时 。其官方定义明确指出,这些标签仅添加在发往联合(federation)、远程存储(remote storage)、Alertmanager 的数据上,因此,在 prometheus ui 上查不到这些标签,验证也只能通过远程存储或 alertmanager 接收到的告警。
3、Alertmanager 集群:告警去重
两套 Prometheus 同时评估同一批规则,同一条告警必然触发两次。如果 Alertmanager 也是单点,告警链路依然脆弱;正确做法是部署 Alertmanager 集群(gossip 协议),并让两套 Prometheus 把告警发给所有节点:
yaml
# 两套 Prometheus 的 alerting 配置完全相同
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager-1:9093
- alertmanager-2:9093
- alertmanager-3:9093
bash
# Alertmanager 节点间互相发现
docker run -d --name=alertmanager-1 \
--network my-bridge \
-v /opt/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml \
prom/alertmanager:v0.27.0 \
--config.file=/etc/alertmanager/alertmanager.yml \
--cluster.listen-address=0.0.0.0:9093 \
--cluster.peer=alertmanager-2:9093 \
--cluster.peer=alertmanager-3:9093
集群内的去重不需要任何额外配置:Alertmanager 按 group_by识别同一条告警,两个 Prometheus 发来的重复通知只会被投递一次。注意重复告警在收到第一个通知后 5 分钟内再次到达,默认不会再发,正好覆盖了两套 Prometheus 抓取错峰带来的时间差。
验证集群状态:
bash
$ amtool cluster show --alertmanager.url=http://localhost:9093
$ docker exec -it alertmanager amtool cluster show --alertmanager.url=http://localhost:9093
Cluster Status: ready
Node Name: 01M39TMA1Y0XFV9NTSACRZZPK6
4、Grafana 怎么接两个数据源
双副本之后,Grafana 面板面对的是两个 Prometheus。三个方案:
| 方案 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 手动切数据源 | 两种方式:1、配两个 datasource,面板里切换2、只配置一个 datasource,不可用时修改 datasource 配置,切换到另一个 prometheus 上 | 零成本 | 排查时要人肉确认看的哪个副本,容易看漏 |
| Grafana 聚合 | 混合数据源查询 | 无需中间件 | 每个面板都要改查询,聚合语义易出错 |
| 统一查询层 | Thanos Query 作为唯一 datasource | 面板零改动,自动去重 | 引入一套新组件 |
三、长期存储:先把口子留好
1、remote_write:成本最低的保留手段
如果需求只是「数据多留一年,偶尔查一下」,不必急着上 Thanos。Prometheus 原生的 remote_write 可以把样本实时转发给任意外部存储,本地 TSDB 只保留近期数据:
yaml
remote_write:
- url: http://victoria-metrics:8428/api/v1/write
queue_config:
max_shards: 30
capacity: 10000
max_samples_per_send: 2000
batch_send_deadline: 5s
metadata_config:
send: true
send_interval: 1m
三个 queue 参数的含义:
capacity是内存队列长度max_samples_per_send控制单批大小batch_send_deadline保证低流量时样本也不会无限攒着
远端不可达时队列会在内存里堆积,max_shards 限制了最多并行发多少。如果远端宕机超过几小时,旧的未发送样本会被丢弃并在日志里报错。
⚠️ 注意:remote_write 不是备份。 它只转发新样本,Prometheus 本地已有的历史不会回补;而且目标存储挂掉期间的数据同样有丢失窗口。只能把它当「延长保留期」的手段,不能当灾备方案。
2、先做减法:降基数与分层抓取
前文的算账表已经说明,存储压力的大头往往是基数而不是天数。因此,在引入任何存储组件之前,可先通过以下两种方式减轻存储压力:
1)分层抓取间隔
前面文章里统一用了 15s,但那是为告警灵敏度服务的。如果部分场景只做趋势分析,可以拆出独立的 job 用 60s 抓取,样本量直接降 75%:
yaml
# 同一个 exporter,两个 job 两档间隔
- job_name: "node-detail" # 告警用,15s
scrape_interval: 15s
file_sd_configs: [{files: ["/etc/prometheus/targets/node.yml"]}]
- job_name: "node-archive" # 长期趋势用,60s
scrape_interval: 60s
file_sd_configs: [{files: ["/etc/prometheus/targets/node.yml"]}]
2)丢弃确定不看的序列
用 metric_relabel_configs 在采集末端裁剪,它发生在 relabel_configs 之后、样本入库之前:
yaml
metric_relabel_configs:
# node_exporter 的 Go runtime 指标对业务无用,直接丢
- source_labels: [__name__]
regex: "go_(gc|memstats|threads|info)_.*"
action: drop
# 只保留 2xx/5xx 状态码维度的桶,1xx/3xx 不看
- source_labels: [status]
regex: "1..|3.."
action: drop
⚠️ 注意:drop 是不可逆的,宁可保守。 删掉一条序列,历史数据一起消失。规则上线前先在验证环境跑一周,确认没有面板和告警引用这些序列再全量铺开。
四、方案对比:Thanos、VictoriaMetrics、Mimir
本章只作扩展介绍,没有经过实际验证。
当「remote_write + 本地减法」不再够用,如需要全局查询、需要跨多套 Prometheus 聚合、需要自动降采样,就需在以下三套主流方案里选择:
| 对比维度 | Thanos | VictoriaMetrics | Mimir |
|---|---|---|---|
| 出身 | Prometheus 官方生态(Improbable 发起,CNCF) | 独立项目(Percona 系团队) | Grafana Labs(Cortex 演进) |
| 接入方式 | Sidecar 挂在 Prometheus 旁边,不改抓取 | 通常用 vmagent 替换抓取,或 Prometheus remote_write | Prometheus remote_write |
| 存储 | 对象存储(S3/MinIO/OSS) | 自有存储(本地盘或共享盘) | 对象存储 |
| 查询语言 | PromQL(原样) | MetricsQL(PromQL 超集,兼容为主) | PromQL |
| 降采样 | Compactor 自动 5m/1h 两级 | 内置(查询时自动降精度) | 内置(blocks 分层) |
| 组件数量 | 5 个(sidecar/query/store/compactor/ruler) | 单机版 1 个,集群版 3 类 | 3 类组件 |
| 运维成本 | 中(对象存储 + 多组件) | 低 | 中高(K8s 友好,裸机部署较繁) |
| 适合谁 | 已有多套 Prometheus,想统一查询与归档 | 中小规模,想要省心省资源 | 大规模、多租户、Kubernetes 环境 |
用三个问题定方案:
- 能不能改 Prometheus 的抓取链路? 不能 → Thanos sidecar,它对 Prometheus 零侵入,只要求 2 小时块机制;能 → VictoriaMetrics + vmagent 是更省资源的路线。
- 有没有对象存储可用? 有(云 OSS 或自建 MinIO) → Thanos / Mimir 顺理成章;只有本地盘 → VictoriaMetrics 单机版是务实之选。
- 查询方是不是只认 PromQL? 第11篇的所有报告 PromQL 都基于原版语义,语法方言的细微差别会让报告模板失效。严格兼容 → Thanos / Mimir。
「remote_write + 本地减法」已能满足绝大部分场景的应用,因此,本文不再扩展介绍以上三种方式。后续若有实际应用场景,再专门写博文分析。
五、备份与兜底
remote_write 方式能够解决长期存储问题,但还可以再上一道保险:本地快照。
Prometheus 自带快照 API,可以对本地 TSDB 做一致性快照:
# 需要启动参数 --web.enable-admin-api
curl -XPOST http://prometheus-a:9090/api/v1/admin/tsdb/snapshot
# 返回快照目录,rsync 到备份机即可
# {"status":"success","data":{"name":"20260924T191200Z-xxxx"}}
七、小结
本篇核心要点:
- 单机 Prometheus 的边界是结构性的:TSDB 默认 15 天、range query 成本随跨度线性增长、进程即单点,加参数只能缓解,不能根治;
- 高可用的官方答案就是双副本:两个实例用
external_labels区分(放 relabel 里会漏)、共享同一份 file_sd targets、规则各自评估,靠 Alertmanager 集群的 gossip 与group_by完成告警去重; - 长期存储先做减法再做加法:分层抓取间隔与 metric_relabel 裁剪能救回大半块磁盘,remote_write 是成本最低的保留期延长手段,但都不是备份;