高可用与长期存储
上一章结尾我们留下了三个联邦解决不了的问题:高可用、长期存储、全局明细查询。这一章就来正面回答它们。
需要先说清楚一件事:Prometheus 的设计哲学是「每个实例都是独立、自包含、无依赖的」。它故意不做集群、不做数据复制、不做分布式共识——因为监控系统最重要的属性是在别的东西都挂了的时候它还活着,而分布式系统会引入一堆新的失败模式。所以 Prometheus 的高可用不是「让一个集群变得可靠」,而是「跑两个互不相干的实例,用外围组件处理重复」。这个思路一开始会让人觉得粗糙,但用久了你会发现它简单得可爱。
至于长期存储,Prometheus 的做法是把这件事外包出去:本地 TSDB 只负责最近几天到几周的数据,更久的数据通过 remote_write 推给专门的系统。围绕这个接口,社区长出了 Thanos、Cortex、Mimir、VictoriaMetrics 等一整个生态。
读完本章你会掌握:
- 本地 TSDB 的存储结构、
--storage.tsdb.retention.time与--storage.tsdb.retention.size的行为 - 双副本高可用的完整做法,以及为什么必须用
alert_relabel_configs处理replica标签 - Alertmanager 集群的 gossip 去重机制,以及为什么不能把 Alertmanager 放在负载均衡后面
remote_write的分片队列模型、queue_config调优与write_relabel_configs过滤- Thanos 各组件(Sidecar / Query / Store / Compact / Receive)的职责与部署要点
- Cortex 与 Mimir 的关系、架构差异,以及三套方案按规模的选型建议
一、先搞清楚本地存储的边界
在谈「长期存储」之前,得先知道「短期存储」的能力到底在哪里。
TSDB 的物理结构
Prometheus 的数据目录(默认 data/,用 --storage.tsdb.path 修改)大致长这样:
data/
├── 01HXYZ.../ # 一个持久化块(block)
│ ├── chunks/
│ │ └── 000001 # 实际的样本数据,单文件最大 512MB
│ ├── index # 倒排索引,标签到序列的映射
│ ├── meta.json # 块的元信息:时间范围、序列数、压缩层级
│ └── tombstones # 删除标记
├── 01HXZA.../
├── chunks_head/ # 内存中 head 块已落盘的部分
├── wal/ # 预写日志
│ ├── 00000001
│ ├── 00000002
│ └── checkpoint.000001/
└── lock工作方式是这样的:新到达的样本先写进内存中的 head 块,同时追加到 WAL(预写日志)保证崩溃后能恢复。每隔 2 小时,head 块被持久化成一个磁盘上的 block 并清空 WAL。后台的 compaction 会把相邻的小 block 逐步合并成更大的 block(最大不超过保留期的 10% 或 31 天,取较小者),合并过程中会删掉过期数据。
理解这个结构很重要,因为它解释了很多现象:为什么 Prometheus 重启后要花一段时间「replaying WAL」;为什么内存占用和「活跃序列数」强相关;为什么删除数据不是立刻生效的(要等下一次 compaction)。
保留期怎么配
./prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--storage.tsdb.retention.time=30d \
--storage.tsdb.retention.size=400GB规则如下:
--storage.tsdb.retention.time控制按时间保留,支持s/m/h/d/w/y单位。--storage.tsdb.retention.size控制按磁盘大小保留,支持B/KB/MB/GB/TB/PB单位(也接受KiB这类二进制单位)。- 两个都设置时,任意一个条件被触发就会删除最老的块,相当于取更严格的那个。
- 两个都不设置时,默认按 15 天保留。
- 只设置 size 不设置 time 时,时间维度的保留就被关闭了,只按容量淘汰。这一点经常被误解。
早期版本里有一个不带后缀的 --storage.tsdb.retention 参数,它在 2.x 中被标记为废弃,在 Prometheus 3.0 中已被彻底移除。如果你的启动脚本或 systemd unit 文件里还写着它,升级到 3.x 后 Prometheus 会直接启动失败。
升级前记得全局搜一遍这个字符串,改成 --storage.tsdb.retention.time。
单机能撑多大
给一个粗略的量级感受(具体数值和硬件、指标特征关系很大,仅供估算):
| 活跃序列数 | 大致内存需求 | 备注 |
|---|---|---|
| 10 万 | 2 到 4 GB | 小团队,随便一台机器 |
| 100 万 | 8 到 16 GB | 中等规模,需要认真规划 |
| 500 万 | 32 到 64 GB | 接近舒适区上限 |
| 1000 万以上 | 需要分片 | 单机可以跑但风险很高 |
磁盘方面,一个样本压缩后平均占用 1 到 2 字节。用这个数可以估算:100 万活跃序列、15 秒采集间隔、保留 30 天,大约需要 1e6 × (30×24×3600/15) × 1.5B,约等于 260 GB。
当单机撑不住时,你的选项是:先做垂直优化(降低采集频率、用 metric_relabel_configs 丢掉高基数指标、治理标签),再做水平分片(上一章讲过的 hashmod,或者按业务拆多个 Prometheus),最后才上远程存储。远程存储解决的是「存多久」和「怎么全局查」,它并不能减轻单个 Prometheus 的抓取压力。
二、高可用:双副本 + 去重
基本做法
Prometheus 的高可用方案简单到只有一句话:跑两台配置完全相同的实例,让它们各自独立抓取同一批目标。
副本 A 的配置:
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
cluster: 'prod-bj'
replica: 'A'
alerting:
alertmanagers:
- static_configs:
- targets:
- 'alertmanager-1:9093'
- 'alertmanager-2:9093'
- 'alertmanager-3:9093'副本 B 的配置只有一处不同:
global:
external_labels:
cluster: 'prod-bj'
replica: 'B'其余的 scrape_configs、rule_files 全部一模一样。两台机器完全对等,没有主从、没有选举、没有数据同步,谁挂了另一台照常工作。
这是必须接受的事实。两台实例的抓取时刻是独立调度的,A 可能在 10:00:03 抓,B 在 10:00:11 抓。所以:
- 同一时刻两边的样本值会有细微差异(尤其是 Gauge)。
- 如果查询请求在两个副本之间随机切换,Grafana 图上会出现数值跳变。
- 某个副本重启期间会有数据空洞,另一个副本没有——两边的空洞位置不同。
这不是 bug,是设计权衡的结果:用少量的数据不一致换取了架构上的极度简单。绝大多数监控场景里,几秒钟的采集偏移完全无所谓。
查询侧的处理
查询侧有三种选择,复杂度递增:
方案一:负载均衡 + 会话保持。 在两个副本前面放一个 nginx 或 HAProxy,开启 sticky session(比如按源 IP 哈希)。这样同一个用户的查询总是落到同一个副本,避免图上跳变。缺点是副本切换时(比如 A 重启)用户会看到数据不连续。
upstream prometheus {
ip_hash;
server prom-a:9090;
server prom-b:9090;
}方案二:主动指定。 Grafana 里配两个数据源,日常用 A,A 挂了手动切 B。简单粗暴但需要人工介入。
方案三:Thanos Query 去重。 Thanos Query 同时连接两个副本,用 --query.replica-label=replica 告诉它「replica 这个标签是副本标识」。查询时它会把 replica="A" 和 replica="B" 的同名序列合并,优先取数据更完整的那一条,从而输出一条无空洞、无跳变的序列。这是最优雅的方案,代价是要引入 Thanos。
告警侧的处理:这里有个大坑
两个副本都在独立评估告警规则,都会往 Alertmanager 发告警。如果不做处理,你会收到两份一模一样的告警通知。
很多人以为「Alertmanager 会自动去重啊」——它确实会,但去重的判断依据是告警的标签集完全相同。而我们刚刚给两个副本打了 replica: A 和 replica: B 的 external_labels,这些标签会附加到告警上,导致两条告警的标签集不相同,Alertmanager 认为它们是两个不同的告警,于是各发一遍。
解决办法是用 alert_relabel_configs,在发送给 Alertmanager 之前把 replica 标签删掉:
alerting:
alert_relabel_configs:
# 发给 Alertmanager 前去掉副本标识,让两个副本的告警标签集一致
- regex: 'replica'
action: labeldrop
alertmanagers:
- static_configs:
- targets:
- 'alertmanager-1:9093'
- 'alertmanager-2:9093'
- 'alertmanager-3:9093'这样副本 A 和副本 B 发出的告警标签集完全相同,Alertmanager 的去重机制才能生效,最终只发出一条通知。
alert_relabel_configs 处理的是「即将发往 Alertmanager 的告警」,它不会改变本地存储里的时间序列。所以你的 TSDB 里、Thanos 里,replica 标签依然存在,Thanos Query 的去重也依然能正常工作。两者互不干扰。
某团队部署了双副本 Prometheus,配置如下(两台只有 replica 值不同):
global:
external_labels:
cluster: 'prod'
replica: 'A'
prometheus_host: 'prom-a.internal'
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager-lb:9093']上线后出现两个现象:(1)每次故障都收到 2 条一模一样的钉钉通知;(2)某次 Alertmanager 实例 2 重启后,有几条告警发了 3 遍。
请分析两个现象的原因并给出修正配置。
三、Alertmanager 的高可用细节
既然上面已经提到了,就把 Alertmanager 集群讲完整。
Alertmanager 用 gossip 协议(基于 HashiCorp memberlist)组成集群,没有 leader、没有共识算法。集群内同步两类状态:
- Silences(静默规则):你在任意一个实例上创建的静默,会广播到所有实例。
- Notification Log(nflog):「哪条告警在什么时候已经通知过了」的记录,这是去重的核心。
去重的实际流程是:告警到达所有实例后各自开始计时(group_wait),但每个实例会根据自己在集群成员列表里的位置序号额外等待一段时间(大约 位置序号 × 15 秒)。排在第一位的实例最先发出通知并广播 nflog;排在后面的实例在轮到自己时先查 nflog,发现「已经发过了」就跳过。
这个机制有几个实践含义:
- 集群规模建议 3 个实例。 2 个也能工作但没有冗余余量,超过 3 个只会增加末位实例的通知延迟。
- 通知会有额外延迟。 如果第一个实例卡住了,第二个实例要等 15 秒左右才会接手。这是可靠性的代价。
- 静默的生效有传播延迟。 刚创建的静默需要几秒才能同步到所有实例,极端情况下可能漏掉一条已经在发送途中的通知。
- 网络分区会导致重复通知。 如果集群被网络分区切成两半,两边都会认为自己该发通知。这是 gossip 系统的固有取舍:宁可重复通知,不可漏发通知。
Alertmanager 挂了是最危险的故障之一——所有告警都发不出去,而且没有任何东西会告诉你。务必加上这几条规则(放在 Prometheus 侧):
# Alertmanager 实例宕机
up{job="alertmanager"} == 0
# 集群成员数不足
alertmanager_cluster_members < 3
# 通知发送失败
rate(alertmanager_notifications_failed_total[5m]) > 0再进一步,很多团队会配一条永远处于 firing 状态的「心跳告警」(Watchdog),路由到一个外部的 dead man's switch 服务。只要那个服务超过 5 分钟没收到心跳,就说明整条告警链路断了。
你要为一个生产集群搭建高可用监控。要求:
- Prometheus 双副本,任意一台宕机不影响告警和查询
- Alertmanager 三副本,不重复通知
- 有办法发现「整条告警链路失效」
- Grafana 查询时不出现数据跳变
请写出关键配置,并说明每一处的作用。
四、remote_write 与 remote_read
remote_write 的工作原理
remote_write 是 Prometheus 对接外部存储的标准接口。它的数据流是:
样本进入 head 块 → 同时写入 WAL → remote_write 组件读取 WAL → 按序列哈希分配到若干个分片队列 → 每个分片攒够一批就用 protobuf + snappy 压缩后 POST 给远端。
这里有几个设计要点值得注意:
- 基于 WAL 而不是内存。 这意味着远端短暂不可用时,数据不会丢——Prometheus 会在 WAL 里保留读取位置,恢复后继续从断点发送。但 WAL 本身有保留期,远端挂太久(通常超过 2 小时)还是会丢数据。
- 分片是动态的。 Prometheus 会根据「样本积压量」和「远端响应速度」自动调整分片数量,在
min_shards和max_shards之间伸缩。 - 它是异步的。 抓取和远程写入是解耦的,远端慢不会阻塞抓取。
配置示例:
remote_write:
- url: 'https://thanos-receive.internal/api/v1/receive'
name: 'thanos'
# 认证
basic_auth:
username: 'prometheus'
password_file: '/etc/prometheus/remote_write_password'
# 只把需要长期保存的指标写出去
write_relabel_configs:
# 丢弃 Go runtime 这类噪音指标
- source_labels: [__name__]
regex: 'go_.*|process_.*'
action: drop
# 丢弃已知的高基数指标
- source_labels: [__name__]
regex: 'apiserver_request_duration_seconds_bucket'
action: drop
queue_config:
capacity: 10000 # 每个分片的内存队列容量
max_shards: 50 # 分片数上限
min_shards: 1 # 分片数下限
max_samples_per_send: 2000 # 每次请求带多少样本
batch_send_deadline: 5s # 攒不满一批时最长等待
min_backoff: 30ms # 失败重试的最小退避
max_backoff: 5s # 失败重试的最大退避如果你用的是按数据量计费的托管服务(Grafana Cloud、各家云厂商的托管 Prometheus),write_relabel_configs 直接决定了你的账单。
实践中通常能砍掉一半以上的数据量:go_* 和 process_* 这类运行时指标几乎没人看;*_bucket 这类 Histogram 序列数量巨大但只有少数几个真的会被查;很多 exporter 会暴露上百个默认指标而你只用其中几个。
比 drop 更激进的做法是反过来用 keep——只把明确需要的指标写出去:
write_relabel_configs:
- source_labels: [__name__]
regex: '(job|service|cluster):.*|up|http_requests_total|node_.*'
action: keep白名单的维护成本更高,但对成本的控制力也强得多。
队列积压怎么排查
remote_write 出问题时最典型的症状是「远端数据落后于本地」。看这几个指标:
# 积压程度:最新采集的样本时间 与 已成功发送的样本时间 之差(秒)
prometheus_remote_storage_highest_timestamp_in_seconds
- ignoring(remote_name, url)
prometheus_remote_storage_queue_highest_sent_timestamp_seconds
# 分片数是否已经打满上限
prometheus_remote_storage_shards
/ prometheus_remote_storage_shards_max
# 发送失败速率
rate(prometheus_remote_storage_samples_failed_total[5m])
# 因队列满而被丢弃的样本
rate(prometheus_remote_storage_samples_dropped_total[5m])第一个查询给出的「落后秒数」是最直观的健康指标。持续超过 60 秒就该介入了:先看是不是远端太慢(看响应延迟),再考虑调大 max_shards 和 max_samples_per_send,或者用 write_relabel_configs 减少数据量。
Remote Write 2.0
Prometheus 3.0 引入了 Remote Write 协议的 2.0 版本,主要改进是:用字符串表(string interning)显著降低了重复标签的传输体积;原生支持传输元数据(HELP / TYPE / UNIT)、exemplars 和创建时间戳;对原生直方图(native histograms)有一等支持。
配置上通过 protobuf_message 指定版本:
remote_write:
- url: 'https://mimir.internal/api/v1/push'
protobuf_message: 'io.prometheus.write.v2.Request'客户端和服务端会通过 HTTP 头协商版本,远端不支持 2.0 时可以回落到 1.0。除非你的后端明确支持,否则暂时保持默认(1.0)更稳妥。
remote_read 及其局限
remote_read 是反方向的接口:让 Prometheus 在执行查询时,把请求也发一份给远端存储,然后把结果和本地数据合并。
remote_read:
- url: 'https://remote-storage.internal/api/v1/read'
# 只有匹配这些标签的查询才走远端,避免每次查询都打扰远端
required_matchers:
cluster: 'prod-bj'
# false 表示「本地已有的时间范围就不查远端了」
read_recent: false说实话,remote_read 在生产里用得很少,原因有三:
- 性能差。 它需要把远端的原始样本全部传回本地,再由本地的 PromQL 引擎计算。查询一个月的数据可能要传输几百 MB,还没有任何下推优化。
- 没有全局视图。 它只能让「这一个 Prometheus」看到更长的历史,别的实例的数据依然看不到。
- 有更好的替代。 Thanos Query、Mimir、VictoriaMetrics 都提供了自己的 PromQL 查询端点,Grafana 直接连它们就行,计算在存储侧完成,效率高得多。
用 remote_write 把数据送出去,然后直接查远端存储的查询端点。 不要试图让 Prometheus 通过 remote_read 充当一个「统一查询入口」,那不是它擅长的角色。
线上告警显示远程写入持续落后 20 分钟。你查到以下数据:
prometheus_remote_storage_shards = 200
prometheus_remote_storage_shards_max = 200
prometheus_remote_storage_samples_pending = 1800000
远端接口 P99 响应时间 = 3.2s
Prometheus 每秒产生样本数 = 150000
写出的指标里,go_* 和 *_bucket 占了 62%请分析瓶颈并给出至少三个改进方向,写出具体配置。
五、长期存储方案对比
现在进入选型环节。三个主流方案的定位差异其实很清晰。
Thanos
Thanos 的核心思路是「给现有的 Prometheus 加装扩展件,数据放对象存储」。它不要求你改变现有部署,只是在每个 Prometheus 旁边贴一个 sidecar。
| 组件 | 一句话职责 |
|---|---|
| Sidecar | 和 Prometheus 同 Pod 部署,把 2 小时 block 上传到对象存储,并通过 StoreAPI 暴露 Prometheus 本地的近期数据 |
| Query | 无状态查询层,实现 PromQL,向所有 StoreAPI 扇出查询并合并结果,负责副本去重 |
| Store Gateway | 把对象存储里的历史 block 通过 StoreAPI 暴露出来,本地只缓存索引头 |
| Compact | 对对象存储里的 block 做压缩、降采样(5 分钟和 1 小时两级)、按分辨率应用保留策略 |
| Ruler | 在全局数据上评估 recording rules 和告警规则 |
| Receive | 接收 remote_write 推送,为「不方便部署 sidecar」的场景提供推模式入口 |
| Query Frontend | 查询拆分与结果缓存,加速大跨度查询 |
一个最小可用的 Thanos 部署长这样:
Prometheus A ──┐ ┌── Store Gateway ──┐
+ Sidecar │ │ │
├── Thanos Query ────────┤ ├── 对象存储
Prometheus B ──┤ (去重) │ │ (S3/GCS/COS)
+ Sidecar │ └── Compact ────────┘
│ (降采样)
└── Grafana 连这里部署 Thanos Sidecar 时,Prometheus 的启动参数必须改:
./prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--storage.tsdb.retention.time=2d \
--storage.tsdb.min-block-duration=2h \
--storage.tsdb.max-block-duration=2h \
--web.enable-lifecycle这是 Thanos Sidecar 的硬性要求,原因是:Prometheus 默认会做本地 compaction,把小 block 合并成大 block。如果 sidecar 正在上传一个 block,而 Prometheus 同时把它合并掉了,就会产生数据损坏或上传失败。
把两个参数都设成 2h 等于禁用本地 compaction,让 Thanos Compact 在对象存储侧统一负责压缩和降采样。
同时,本地 retention.time 要留够至少 6 小时(推荐 2 天),给 sidecar 足够的时间完成上传,也给对象存储或网络出故障时留出缓冲。设成 2h 或更短是危险的——block 还没传完就被删了,数据就永久丢失了。
Thanos 的降采样是它相对于「简单堆磁盘」的最大优势。Compact 会为每个 block 额外生成 5 分钟和 1 小时分辨率的版本,查询时 Thanos Query 根据请求的时间跨度自动选择合适的分辨率。查一年的趋势图时用 1 小时分辨率,数据量是原始数据的几百分之一,查询秒开。
同一个对象存储 bucket 只能有一个 Compact 实例在运行。两个实例同时对同一批 block 做压缩会互相覆盖元数据,造成数据损坏,而且这种损坏很难修复。
如果用 Kubernetes 部署,务必用 replicas: 1 的 StatefulSet 或 Deployment,并且确认滚动更新策略是 Recreate 而不是 RollingUpdate——后者会短暂出现两个实例并存。
Cortex
Cortex 走的是完全不同的路线:它是一个多租户的、水平扩展的、推模式的时序数据库,Prometheus 通过 remote_write 把数据推给它。
架构是典型的微服务拆分:
- Distributor:接收
remote_write请求,校验后按序列哈希分发给多个 Ingester(默认写 3 副本)。 - Ingester:在内存里构建 TSDB head 块,定期刷成 block 上传到对象存储。
- Querier:执行 PromQL,同时向 Ingester(近期数据)和 Store Gateway(历史数据)取数。
- Store Gateway / Compactor / Ruler / Alertmanager / Query Frontend:职责与 Thanos 中的同名组件类似。
多租户是 Cortex 的一等特性:每个请求带 X-Scope-OrgID 头,数据在存储和查询层面完全隔离,每个租户还能有独立的限流配额。这让它很适合做「内部监控 PaaS」。
Cortex 早期用的是自研的 chunks 存储(依赖 Cassandra / DynamoDB 等),运维复杂且性能不佳。现在已经全面转向基于 Prometheus TSDB 的 blocks 存储,只依赖对象存储,架构清爽了很多。Cortex 目前是 CNCF 孵化项目。
Mimir
Grafana Mimir 是 Grafana Labs 在 2022 年从 Cortex fork 出来的项目,可以理解为 Cortex 的演进版本。 它保留了 Cortex 的整体架构(distributor / ingester / querier / store-gateway / compactor 那一套),在此基础上做了大量工程优化:
- 查询分片(query sharding):把一个大查询自动拆成多个可并行执行的子查询,大幅提升重查询的速度。
- 更快的 compactor:支持按时间和序列并行压缩,能处理更大的数据量。
- 单体部署模式:用
-target=all可以把所有组件跑在一个二进制里,本地测试和小规模部署时不需要拉起十几个微服务。这大幅降低了上手门槛。 - 乱序样本写入:支持接收时间戳早于当前 head 的样本,对边缘采集、批量回填场景很有用。
- 官方声称可扩展到 10 亿级活跃序列。
需要注意 Mimir 的许可证是 AGPLv3,而 Cortex 和 Thanos 都是 Apache 2.0。如果你要把它嵌入商业产品对外提供服务,这个差异需要法务评估。Cortex 项目本身仍在 CNCF 下继续维护,但社区活跃度和新特性投入明显向 Mimir 倾斜。
还有 VictoriaMetrics
值得一提的第四个选项。VictoriaMetrics 不是 Prometheus 生态的「原生」项目,而是一套独立实现的时序数据库,兼容 Prometheus 的 remote_write 和查询接口(用它自己的 MetricsQL,是 PromQL 的超集)。
它的卖点是极致的资源效率和运维简单性:单二进制模式下一个进程就能跑,压缩率通常比 Prometheus TSDB 高数倍,内存占用也低不少,不依赖对象存储(用本地磁盘,也有集群版)。对于「不想引入对象存储、不想管十个微服务、就想让数据存久一点」的团队来说,它往往是投入产出比最高的选择。
代价是它偏离了 Prometheus 官方生态,MetricsQL 和 PromQL 有细微行为差异,遇到问题时能参考的社区资料相对少一些。
选型速查
| 维度 | Thanos | Cortex | Mimir | VictoriaMetrics |
|---|---|---|---|---|
| 数据流向 | 拉(Sidecar 上传)为主,也支持推 | 推(remote_write) | 推(remote_write) | 推(remote_write) |
| 是否改动现有 Prometheus | 只加 sidecar,配置微调 | 加 remote_write | 加 remote_write | 加 remote_write |
| 多租户 | 弱(靠标签隔离) | 一等公民 | 一等公民 | 集群版支持 |
| 降采样 | 有(5m / 1h) | 无 | 无 | 无(靠高压缩率) |
| 组件数量 | 中(5 到 7 个) | 多(10 个以上) | 多,但可单体部署 | 少 |
| 许可证 | Apache 2.0 | Apache 2.0 | AGPLv3 | Apache 2.0 |
| 上手难度 | 中 | 高 | 中到高 | 低 |
按规模给个粗略建议:
- 活跃序列 100 万以内、单团队、只需要 30 到 90 天数据:老老实实用单机 Prometheus(或双副本),把
retention.time调大、磁盘给够就行。不要过早引入长期存储,它带来的运维复杂度往往超过收益。 - 多个集群、需要全局视图、需要存 1 年以上、已经有对象存储:Thanos。它对现有部署侵入最小,降采样让长跨度查询变得可用,是这个场景的最优解。
- 需要做内部监控平台、多个团队多租户、超大规模、有专门的团队维护:Mimir(新项目直接选 Mimir,不建议再从 Cortex 起步)。
- 想要最低运维成本、单一二进制、对生态兼容性要求不极端:VictoriaMetrics。
一个常见的误解是「用了 Thanos 就不能用 remote_write」。实际上 Thanos Receive 组件本身就是一个 remote_write 接收端。
两种模式的取舍是:Sidecar 模式下数据每 2 小时才上传一次,全局查询近期数据要经过 sidecar 代理,但架构简单、Prometheus 保持完全自治;Receive 模式下数据实时推送,全局查询延迟低,适合 Prometheus 部署在你无法控制的环境(比如客户侧、边缘节点),代价是 Receive 集群本身需要做高可用和哈希环管理。
大多数团队从 Sidecar 开始,只在确实需要实时全局查询时才引入 Receive。
为下面三个团队做长期存储选型,说明理由并给出关键配置片段。
团队 A:创业公司,一套 K8s 集群,约 40 万活跃序列,2 个后端工程师兼职做运维,需求是「数据能存 90 天,能查历史做容量规划」。
团队 B:中型公司,6 个 K8s 集群分布在 3 个地域,共约 800 万活跃序列,有 3 人 SRE 团队,已经在用对象存储,需求是「统一大盘、数据存 1 年、排障时能查任意集群的明细」。
团队 C:云厂商内部,为 200 多个业务团队提供监控服务,需要按团队隔离和限流,总量约 3 亿活跃序列,有专职的 10 人监控平台团队。
小结
- 本地存储:2 小时一个 block,WAL 保证崩溃恢复,
--storage.tsdb.retention.time和.size共同决定保留策略(都不设默认 15 天,只设 size 则关闭时间维度)。老的--storage.tsdb.retention在 3.0 已移除。 - 高可用:跑两个配置相同的独立副本,用
external_labels里的replica区分。必须用alert_relabel_configs剥离所有副本间取值不同的标签,否则 Alertmanager 无法去重。 - Alertmanager:三副本 gossip 集群,Prometheus 要把告警发给每一个实例而不是 LB。加上 Watchdog 心跳告警,让外部服务来监控告警链路本身。
- remote_write:基于 WAL 的异步分片队列。
write_relabel_configs是控制成本和缓解积压的第一手段;高延迟远端优先调大max_samples_per_send而不是max_shards。remote_read在生产里基本不用。 - 长期存储选型:Thanos 适合「多集群 + 对象存储 + 需要降采样 + 侵入性小」;Mimir 适合「多租户 + 超大规模 + 有专职团队」;VictoriaMetrics 适合「要运维简单」;小规模团队最好的方案是什么都不上,把本地保留期调大。
到这里,Prometheus 的采集、查询、告警、扩展性话题都覆盖了。但我们始终是在「从内部看系统」——所有指标都来自被监控对象自己暴露的接口。下一章我们换个视角,看看怎么从外部像用户一样探测服务,这就是黑盒监控。