Prometheus 长期存储与多集群查询实战:Thanos 架构落地与踩坑复盘
一、为什么我最终选了 Thanos
我们公司监控最早是"每个集群一套 Prometheus",一共五套,散落在华北、华东两个机房。起初还够用,直到老板要一张全公司总览大屏——把五个集群的 cpu_usage 拼成一张图。我第一反应是联邦(federation),搭完后发现两个问题:一是联邦是拉模式,聚合节点挂了整个顶层监控就断;二是历史数据只能留 15 天,Prometheus 本地 TSDB 撑不住长期留存,老板要的"去年同期的对比"根本查不到。
试过单独把 Prometheus 的 retention 调到 90 天,结果磁盘爆了,WAL 打满整块盘,还拖垮了抓取。后来我在网上翻到 Thanos,发现它把历史数据搬到对象存储,Prometheus 只管热数据,这个思路正是我要的。于是花了一个周末把 PoC 搭起来,从此再没回头。
二、Thanos 组件架构:一张图看懂各角色
Thanos 不是一个单体,是一组可独立部署的组件,各司其职:
| 组件 | 职责 |
|---|---|
| Sidecar | 挂在每个 Prometheus 旁,把本地 TSDB 块上传到对象存储 |
| Query | 无状态查询入口,聚合各数据源(Prometheus/Store/Receive)的结果 |
| Store | 从对象存储读取历史块,对 Query 暴露 gRPC StoreAPI |
| Compact | 对对象存储里的块做压缩、合并,生成 downsampling 数据 |
| Receive | 接收远程写(remote write),实现 Prometheus 无状态化(可选) |
我用一句话记住它:Sidecar 负责"推上去",Store 负责"读下来",Query 负责"算出来",Compact 负责"压小了"。Receive 是给那些不想保留 Prometheus 本地的场景用的,我们后面细说。
三、对象存储选型:MinIO 还是 S3
对象存储是 Thanos 的地基,选型直接决定稳定性。我们一开始图省事想用云厂商的 OSS,但测试发现回填时并发上传把 OSS 的限流打爆了,于是转向自建 MinIO。
# 用 docker 起一个 MinIO(生产建议集群模式 + 纠删码)
docker run -d --name minio \
-p 9000:9000 -p 9001:9001 \
-e MINIO_ROOT_USER=thanos \
-e MINIO_ROOT_PASSWORD=supersecret \
-v /data/minio:/data \
minio/minio server /data --console-address ":9001"
关键配置点:bucket 务必开启版本控制,这样即使 compact 误删块也能回滚;再配一条生命周期规则,把 30 天前的对象转低频存储(S3 的 STANDARD_IA 或 MinIO 的 ILM),能省一半成本。
用 mc 创建 bucket 并开版本控制:
mc alias set thanos http://minio:9000 thanos supersecret
mc mb --ignore-existing thanos/thanos-data
mc version enable thanos/thanos-data
# 30 天前对象转低频,180 天可彻底清理
mc ilm rule add thanos/thanos-data \
--expire-transition "30d" --transition "STANDARD_IA" \
--expire-delete "180d"
生产上别用单机 MinIO,数据在磁盘上只有一份,任何一块盘坏了历史就丢。要上分布式纠删码(至少 4 个盘):
# minio 分布式部署,4 节点纠删码(每节点 2 数据盘,可容忍部分坏盘)
services:
minio-1: {image: minio/minio, command: server --console-address ":9001" http://minio{1...4}/data{1...2}, volumes: [./minio1/data1:/data1, ./minio1/data2:/data2]}
minio-2: {image: minio/minio, command: server --console-address ":9001" http://minio{1...4}/data{1...2}, volumes: [./minio2/data1:/data1, ./minio2/data2:/data2]}
minio-3: {image: minio/minio, command: server --console-address ":9001" http://minio{1...4}/data{1...2}, volumes: [./minio3/data1:/data1, ./minio3/data2:/data2]}
minio-4: {image: minio/minio, command: server --console-address ":9001" http://minio{1...4}/data{1...2}, volumes: [./minio4/data1:/data1, ./minio4/data2:/data2]}
Thanos 通过 --objstore.config 传 YAML 连接对象存储,各组件都要带:
type: S3
config:
bucket: thanos-data
endpoint: minio:9000
access_key: thanos
secret_key: supersecret
insecure: true
signature_version2: false
如果企业环境用的是云厂商对象存储,--objstore.config 直接换对应配置即可,接口都是 S3 兼容的:
# 阿里云 OSS / 腾讯云 COS / AWS S3(选一,均兼容 S3 API)
type: S3
config:
bucket: my-thanos-bucket
endpoint: oss-cn-hangzhou.aliyuncs.com # 或 cos.ap-guangzhou.myqcloud.com
access_key: AKIAXXXX
secret_key: XXXXXX
insecure: false
http_config:
idle_conn_timeout: 90s
# 云上建议开 SSE 加密,历史数据更安全
sse_config:
type: SSE-S3
四、部署 Sidecar:把历史数据推上对象存储
Sidecar 是整套架构里最先要落地的。它读取 Prometheus 的 --storage.tsdb.path 目录,把已经封口的 2 小时块(block)上传到对象存储。启动命令:
thanos sidecar \
--prometheus.url=http://prometheus-1:9090 \
--tsdb.path=/prometheus \
--objstore.config-file=/etc/thanos/objstore.yml \
--reloader.config-file=/etc/prometheus/prometheus.yml \
--grpc-address=0.0.0.0:10901 \
--http-address=0.0.0.0:10902
有两个参数我踩过坑,必须强调:
--shipper.upload-compacted:默认不传。如果你不传,Prometheus 本地已 compact 过的块(比如你手动触发过tsdb compact)Sidecar 不会上传,历史数据就永远上不去。回填场景必须带上。- Prometheus 本身要开
--web.enable-lifecycle和--web.enable-admin-api,否则 Sidecar 无法通过 reload 接口做配置热更新。
五、部署 Query:跨集群统一查询
Query 是无状态的,可以多副本水平扩展,前面挂个负载均衡即可。它接受 PromQL,把请求下发给所有注册的数据源(各 Prometheus 的 Sidecar、Store、Receive),然后做去重合并。
thanos query \
--store=sidecar-1:10901 --store=sidecar-2:10901 \
--store=store-gateway:10901 \
--query.replica-label=prometheus_replica \
--query.timeout=60s \
--store.timeout=90s \
--query.auto-downsampling \
--http-address=0.0.0.0:19190
--query.auto-downsampling 让 Query 对长跨度查询自动优先使用降采样数据,配合第七节效果更好。如果你想让别的系统(比如 Grafana 之外的内部平台)也能查到全量历史,可以给 Query 开一个 Prometheus 兼容的读取端点,让外部通过 remote read 拉取:
# prometheus.yml 里给每个集群配 remote_read,指向统一 Query
remote_read:
- url: http://thanos-query:19190/api/v1/read
read_recent: true
--query.replica-label 很关键:它让 Query 根据标签去重,多副本 Prometheus 抓同一目标时不会重复计。跨集群统一查询后,一张大屏就能同时看到华北和华东的指标:
跨集群大屏我常用的几组查询,全部走同一个 Query 端点:
# 某集群在线主机数(跨集群求和)
sum by (cluster) (up{job="node"})
# 全部集群的 7 天平均负载对比(自动走降采样)
avg by (cluster) (node_load1[7d])
# 全公司内存水位,按集群分组看异常尖峰
max by (cluster) (rate(node_memory_MemAvailable_bytes[5m]))
从原来的联邦方案迁移到 Thanos,我建议按这个顺序,先加后拆、随时可回滚:
# 1. 先起 Sidecar + Query + Store + Compact + MinIO,数据开始同步
# 2. 验证 Query 能查到各 Prometheus 的实时数据
curl -s "http://query:19190/api/v1/query?query=up" | jq '.data.result | length'
# 3. 等 Sidecar 上传历史块到对象存储(观察 thanos_shipper_uploads_total)
# 4. 确认 Store 能从对象存储读到历史块后,再逐步停掉联邦聚合节点
# 5. 把 Grafana 数据源从"联邦节点"切到 Query,验证大屏无缺失
# 6. 观察 1 周无异常后,彻底下线联邦节点
# 迁移中重点盯这几个指标,全部归零才算稳
curl -s http://query:19190/metrics | grep -E "thanos_query_partial_response|thanos_store_series"
六、Store 与 Compact:读取与压缩
Store 是读路径的核心,它把对象存储里的历史块映射成 gRPC StoreAPI 供 Query 拉取。它本身无状态,可多副本,每个副本都要能访问对象存储。
thanos store \
--objstore.config-file=/etc/thanos/objstore.yml \
--data-dir=/data/thanos/store-cache \
--http-address=0.0.0.0:19191
Compact 是写路径的压缩器。它定期扫描对象存储,把小的 2 小时块合并成更大块,并生成 downsampling 数据。它需要独占一个数据目录做本地缓存,且只能单实例运行(多实例会互相抢锁、产生竞态)。
thanos compact \
--objstore.config-file=/etc/thanos/objstore.yml \
--data-dir=/data/thanos/compact \
--consistency-delay=30m \
--compact-concurrency=4 \
--http-address=0.0.0.0:19192
--consistency-delay 我建议设 30 分钟以上,防止 compact 拿到还没完全上传完的块,这是后面事故三的伏笔。
Compact 的并发参数直接影响对象存储压力,块多、盘慢的集群要收敛一点,避免把自己和 MinIO 一起打爆:
# 块多且对象存储 IO 紧张时,降低并发、拉大块合并窗口
thanos compact \
--objstore.config-file=/etc/thanos/objstore.yml \
--compact-concurrency=2 \
--block-sync-concurrency=5 \
--consistency-delay=2h
Store 的本地缓存对读性能影响很大,尤其是高频查询同一批历史块时,缓存能省掉大量对象存储 IO:
# Store 本地块索引缓存 + 元数据缓存,显著降低跨集群长查询延迟
thanos store \
--objstore.config-file=/etc/thanos/objstore.yml \
--data-dir=/data/thanos/store-cache \
--block-sync-concurrency=20 \
--index-cache-size=500MB \
--store.grpc.series-sample-limit=500000
验证 Compact 是否真正在工作,用 thanos tools bucket ls 看压缩层级和块数量变化:
# 看对象存储里各层的块与大小
thanos tools bucket ls \
--objstore.config-file=/etc/thanos/objstore.yml \
--min-time=20260101T000000Z \
--max-time=20260401T000000Z
七、Downsampling:让长周期查询飞起来
这是 Thanos 最容易被忽略但收益最大的能力。Compact 会自动把原始块降采样成 5m(两周以上)和 1h(一个月以上)两个层级,查询超过这些时间跨度时自动用降采样数据,扫描的块数量减少一两个数量级。
验证是否生效,看对象存储里有没有 downsample 目录,或者用 thanos tools bucket inspect:
thanos tools bucket inspect \
--objstore.config-file=/etc/thanos/objstore.yml \
--selector='{__name__="up"}' --downsample
降采样层级的原始数据是否在 Query 端生效,可以直接对比查询耗时:
我查"去年同期的 CPU 对比",降采样前要扫 400 多个原始块,降采样后只扫 30 个 1h 块,查询从 9 秒降到 0.8 秒——这是我在生产上最直观的一次性能提升。
降采样数据的保留时长在 Compact 上单独控制,和原始数据分开,避免长期留存撑爆对象存储:
# raw 保留 30 天,5m 保留 180 天,1h 保留 365 天
thanos compact \
--objstore.config-file=/etc/thanos/objstore.yml \
--retention.resolution-raw=30d \
--retention.resolution-5m=180d \
--retention.resolution-1h=365d
跨集群的频繁查询还可以用 recording rule 预聚合,把"每集群每小时负载"这种重查询提前算好:
# 跨集群 recording rule,查询直接命中聚合结果
groups:
- name: cross_cluster_agg
rules:
- record: job:cluster:cpu:avg5m
expr: avg by (cluster) (rate(node_cpu_seconds_total{mode!="idle"}[5m]))
八、事故一:回填数据"凭空消失"
我遇到过的真实案例:给新接入的一个 K8s 集群做历史回填,我用 promtool tsdb create-blocks-from openmetrics 生成了两周的模拟数据,Sidecar 也显示上传成功。但第二天 Query 一查,两周数据全没了。
排查过程:thanos tools bucket ls 看对象存储,块还在;thanos tools bucket inspect 发现这些块 mtime 都是很久以前的时间戳。原因找到了——Sidecar 的 shipper 有个"块最小年龄"逻辑,它认为 mtime 过老的块是已经被删除的过期块,直接当垃圾跳过甚至清理了。
回填时我用的是这个命令生成块(promtool 是 Prometheus 自带的工具):
# 从 OpenMetrics 文本生成两周的 TSDB 块,注意 dir 目录名要带"最终时间"
promtool tsdb create-blocks-from openmetrics \
./backup.metrics --min-time=20260101T000000Z \
--max-time=20260115T000000Z --output-dir=/prometheus
修复:给 Sidecar 加 --shipper.upload-compacted(回填块通常已被 compact),并把回填块的目录时间戳对齐到当前,或者干脆用 thanos tools bucket rewrite 重写块元数据。教训:回填时块的时间戳必须"新",否则会被当成历史垃圾。
完整的回填与验证流程,我后来固化成脚本,每次回填前先备份再确认:
#!/bin/bash
set -euo pipefail
TSDB_PATH=/prometheus
# 1. 生成回填块
promtool tsdb create-blocks-from openmetrics ./backup.metrics \
--min-time=20260101T000000Z --max-time=20260115T000000Z \
--output-dir="$TSDB_PATH"
# 2. 把生成块的 mtime 对齐到当前,避免被 shipper 当垃圾
find "$TSDB_PATH" -maxdepth 1 -type d -name '01*' \
-exec touch -d "$(date -u +%Y-%m-%dT%H:%M:%SZ)" {} \;
# 3. 触发 Sidecar 重扫并确认上传
curl -s -X POST http://sidecar:10902/-/reload
thanos tools bucket ls --objstore.config-file=/etc/thanos/objstore.yml
九、事故二:跨集群查询超时
我遇到过的真实案例:把第五个集群接入后,大屏上 up 查询偶尔报 context deadline exceeded,且集中在凌晨。凌晨是 compact 任务最活跃的时候,对象存储 IO 被占满,Store 拉块变慢。
排查:用 Query 的 --query.tracing 开 Jaeger tracing,看到耗时全在Store 返回块数据这一段 gRPC 流上,单次拉取一个 2h 块要 12 秒。根因是降采样没跑完,Query 还在扫原始块,块体积大、传输慢。
修复三步:一是把 --query.timeout 从默认 2 分钟调大到 60 秒并配合 --store.timeout;二是确保 Compact 的 --consistency-delay 足够,让降采样数据稳定生成;三是给 Store 和 Query 之间加内网直连,别让 gRPC 大流量走跨机房专线。改完后再没超时过。
用 Query 自带的 tracing 把耗时链路打出来,是定位这类问题最快的方式:
# 开启 Jaeger 追踪,把请求链路发到 jaeger:4317
thanos query \
--store=sidecar-1:10901 \
--query.timeout=60s \
--tracing.config='{"type":"JAEGER","config":{"service_name":"thanos-query","sampler":{"type":"const","param":1},"agent":{"host":"jaeger","port":6831}}}'
十、事故三:Compact 一直失败
我遇到过的真实案例:某天收到告警 ThanosCompactHalted,Compact 组件 halt 了。看日志全是 partial block 和 out of order,对象存储里一堆 partial 目录。
根因:Compact 去合并某个块时,Sidecar 还在往同一时间范围上传新块,compact 拿到的是"半成品",校验失败,连续多次失败就自动 halt 自我保护了。
修复:把 --consistency-delay 调到 30 分钟以上(我后来直接设为 2h,和块大小一致),让 compact 只处理"足够老"的块;同时清理残留的 partial 目录。这解释了为什么第六节我要强调这个参数。
清理残留 partial 目录用 bucket 工具,别手动删对象存储里的数据:
# 列出并清理所有 partial 块
thanos tools bucket inspect --objstore.config-file=/etc/thanos/objstore.yml | grep -i partial
thanos tools bucket cleanup --objstore.config-file=/etc/thanos/objstore.yml --delete-delay=48h
十一、完整 docker-compose 生产模板
多集群场景下,不是一套 compose 管到底,而是每集群一套 prometheus+sidecar,全部指向同一个 MinIO 和 Query。这是主题的核心——多套 Prometheus 用 Thanos 统一。先看单集群侧:
# 每个集群一份,只改 cluster 标签和 targets
services:
prometheus:
image: prom/prometheus:v2.53.0
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=7d
- --web.enable-lifecycle
- --web.enable-admin-api
volumes: [./prometheus.yml:/etc/prometheus/prometheus.yml:ro, prom-data:/prometheus]
sidecar:
image: quay.io/thanos/thanos:v0.36.0
command:
- sidecar
- --prometheus.url=http://prometheus:9090
- --tsdb.path=/prometheus
- --objstore.config-file=/etc/thanos/objstore.yml
- --shipper.upload-compacted
volumes: [./objstore.yml:/etc/thanos/objstore.yml:ro, prom-data:/prometheus]
depends_on: [prometheus, minio]
然后把这些 sidecar 全部注册到统一 Query 的 --store 参数里,就完成了多集群联邦查询。
把上面的散件拼成一套可复制的模板(已去掉敏感信息,生产替换为真实值即可)。这套还额外加了 Receive,用于想彻底去掉本地 Prometheus 的场景——把 prometheus.yml 里的 remote_write 指向它:
version: "3.8"
services:
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
environment: [MINIO_ROOT_USER=thanos, MINIO_ROOT_PASSWORD=changeme]
volumes: [minio-data:/data]
ports: ["9000:9000", "9001:9001"]
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 10s
retries: 5
prometheus:
image: prom/prometheus:v2.53.0
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=7d
- --web.enable-lifecycle
- --web.enable-admin-api
volumes: [./prometheus.yml:/etc/prometheus/prometheus.yml:ro, prom-data:/prometheus]
depends_on: [minio]
restart: unless-stopped
sidecar:
image: quay.io/thanos/thanos:v0.36.0
command:
- sidecar
- --prometheus.url=http://prometheus:9090
- --tsdb.path=/prometheus
- --objstore.config-file=/etc/thanos/objstore.yml
- --shipper.upload-compacted
- --min-time=20260101T000000Z
volumes: [./objstore.yml:/etc/thanos/objstore.yml:ro, prom-data:/prometheus]
depends_on: [prometheus, minio]
restart: unless-stopped
query:
image: quay.io/thanos/thanos:v0.36.0
command:
- query
- --store=sidecar:10901
- --store=store-gw:10901
- --store=receive:10901
- --query.replica-label=prometheus_replica
- --query.auto-downsampling
- --query.timeout=60s
- --store.timeout=90s
ports: ["19190:19190"]
depends_on: [sidecar, store-gw, receive]
restart: unless-stopped
store-gw:
image: quay.io/thanos/thanos:v0.36.0
command: [store, --objstore.config-file=/etc/thanos/objstore.yml, --data-dir=/data/store]
volumes: [./objstore.yml:/etc/thanos/objstore.yml:ro, store-data:/data]
depends_on: [minio]
restart: unless-stopped
compact:
image: quay.io/thanos/thanos:v0.36.0
command:
- compact
- --objstore.config-file=/etc/thanos/objstore.yml
- --data-dir=/data/compact
- --consistency-delay=2h
- --compact-concurrency=4
- --retention.resolution-raw=30d
- --retention.resolution-5m=180d
volumes: [./objstore.yml:/etc/thanos/objstore.yml:ro, compact-data:/data]
depends_on: [minio]
restart: unless-stopped
receive:
image: quay.io/thanos/thanos:v0.36.0
command:
- receive
- --objstore.config-file=/etc/thanos/objstore.yml
- --tsdb.path=/data/receive
- --receive.hashrings-file=/etc/thanos/hashrings.json
- --receive.local-endpoint=receive:10901
- --grpc-address=0.0.0.0:10901
- --http-address=0.0.0.0:10902
volumes: [./objstore.yml:/etc/thanos/objstore.yml:ro, ./hashrings.json:/etc/thanos/hashrings.json:ro, receive-data:/data]
depends_on: [minio]
restart: unless-stopped
volumes: {minio-data: {}, prom-data: {}, store-data: {}, compact-data: {}, receive-data: {}}
配套的 objstore.yml(给各组件挂载)与 hashrings.json(Receive 分片环):
# objstore.yml —— 所有组件共用
type: S3
config:
bucket: thanos-data
endpoint: minio:9000
access_key: thanos
secret_key: changeme
insecure: true
signature_version2: false
Receive 多实例时用哈希环做分片,把远程写压力摊开,hashrings.json 这样写:
// 3 实例 Receive 哈希环,Prometheus remote_write 可任指一个
[
{"hashring":"ring-a","endpoints":["receive-1:10901","receive-2:10901","receive-3:10901"]}
]
生产注意:Receive 的 --tsdb.path 会保留一份本地数据做缓冲,retention 别设太小,否则 remote write 断流期间的数据会先被删掉。
Receive 完整启动参数(含本地缓冲保留与写超时):
thanos receive \
--objstore.config-file=/etc/thanos/objstore.yml \
--tsdb.path=/data/receive \
--tsdb.retention=2h \
--receive.hashrings-file=/etc/thanos/hashrings.json \
--receive.local-endpoint=receive:10901 \
--receive.hashring-algorithm=ketama \
--grpc-address=0.0.0.0:10901
配套的 prometheus.yml 才是这套架构的"入口",里面要同时把 remote_write 指向 Receive、remote_read 指向 Query:
# prometheus.yml —— 本地热数据 + 远程读写
global:
scrape_interval: 15s
external_labels:
cluster: hua-bei-1
remote_write:
- url: http://receive:10902/api/v1/receive
queue_config:
capacity: 10000
max_shards: 300
min_shards: 1
max_samples_per_send: 5000
batch_send_deadline: 5s
min_backoff: 30ms
max_backoff: 5s
metadata_config:
send: true
send_interval: 1m
remote_read:
- url: http://query:19190/api/v1/read
read_recent: true
scrape_configs:
- job_name: node
file_sd_configs:
- files: [/etc/prometheus/targets/*.yml]
refresh_interval: 30s
relabel_configs:
- source_labels: [__meta_file_path]
regex: .*/(.+).yml
target_label: cluster
replacement: ${1}
- job_name: thanos
static_configs:
- targets: ["sidecar:10902", "query:19190", "compact:19192"]
上面的 file_sd_configs 引用的 targets 目录,每个集群放一个文件,文件名即集群名:
# targets/hua-bei-1.yml —— 文件名会被 relabel 成 cluster 标签
- targets: ["10.0.1.10:9100", "10.0.1.11:9100"]
labels: {env: prod}
十二、给 Thanos 自己上监控
Thanos 的组件如果挂了,你连"监控坏了"都不知道。我的做法是给每个组件配一套告警规则,一旦 halt 或长时间不工作立刻通知。这些是生产必配的:
# thanos-alerts.yml —— 监控 Thanos 自身的告警规则
groups:
- name: thanos.rules
rules:
- alert: ThanosCompactHalted
expr: thanos_compact_halted == 1
labels: {severity: critical}
annotations: {summary: "Thanos Compact 已 halt,历史数据停止压缩"}
- alert: ThanosStoreIsDown
expr: up{job="thanos-store"} == 0
labels: {severity: critical}
annotations: {summary: "Thanos Store 宕机,历史查询将失败"}
- alert: ThanosSidecarUploadFailing
expr: rate(thanos_shipper_uploads_failed_total[5m]) > 0
labels: {severity: warning}
annotations: {summary: "Sidecar 上传块持续失败,检查对象存储连接"}
- alert: ThanosQuerySlow
expr: histogram_quantile(0.95, rate(thanos_query_query_duration_seconds_bucket[5m])) > 30
labels: {severity: warning}
annotations: {summary: "Query 95 分位耗时超 30s,检查降采样是否生效"}
Grafana 侧接数据源很简单,把 Query 当成一个 Prometheus 数据源加进去,选 Prometheus 类型、URL 填 http://thanos-query:19190 即可,查询语法完全一致。
十三、排障命令速查
把三类事故的排查命令固化成一个速查表,遇到问题先跑这几条再定位:
# 1. 对象存储块是否完整、有无 partial
thanos tools bucket inspect --objstore.config-file=/etc/thanos/objstore.yml
thanos tools bucket ls --objstore.config-file=/etc/thanos/objstore.yml
# 2. 降采样目录是否已生成(长查询慢多半是它没跑)
thanos tools bucket inspect --objstore.config-file=/etc/thanos/objstore.yml --downsample
# 3. 各组件健康与运行状态
curl -s http://query:19190/-/healthy
curl -s http://sidecar:10902/-/healthy
curl -s http://compact:19192/-/healthy
# 4. 组件自身指标里看上传/压缩速率
curl -s http://sidecar:10902/metrics | grep thanos_shipper_uploads
curl -s http://compact:19192/metrics | grep thanos_compact_blocks_processed
# 5. 对象存储写入/读取吞吐,判断是存储瓶颈还是查询重
curl -s http://minio:9000/minio/health/cluster
# 6. 确认对象存储连通性与 bucket 可访问
thanos tools bucket web --objstore.config-file=/etc/thanos/objstore.yml --http-address=0.0.0.0:19193
# 7. 检查对象存储里的块是否损坏,坏块用 rewrite 修复
thanos tools bucket verify --objstore.config-file=/etc/thanos/objstore.yml
# 8. 若 verify 报损坏,用 rewrite 重写该块元数据后再查
thanos tools bucket rewrite --objstore.config-file=/etc/thanos/objstore.yml
查询慢但不超时时,优先怀疑两件事:降采样没跑(扫原始块)或对象存储 IO 打满。前者查 downsample 目录,后者看 MinIO 的并发与带宽指标,对症下药。
版本兼容是我每次升级前必查的坑。Thanos 和 Prometheus 有对应关系,Sidecar 对 Prometheus 版本有最低要求,升级前先确认:
# 查看当前各组件版本
thanos --version
prometheus --version
# 兼容性速记:Thanos 0.3x 配 Prometheus 2.4x 及以上
# 升级顺序:先升 Prometheus,确认抓取正常,再升 Thanos 组件
# 升级 Thanos 时先升 Query/Store(无状态),最后升 Compact(有状态)
历史数据的备份与恢复同样走对象存储,灾备时直接同步 bucket 即可:
# 把对象存储整个同步到另一个 bucket/机房做异地备份
rclone sync s3:thanos-data s3:thanos-backup --s3-no-check-bucket
# 恢复时用 thanos tools 校验块完整性
thanos tools bucket verify \
--objstore.config-file=/etc/thanos/objstore.yml \
--repair=false
Grafana 用 provisioning 方式接入 Thanos,声明式、可进 Git:
# grafana/datasources/thanos.yml
apiVersion: 1
datasources:
- name: Thanos
type: prometheus
url: http://thanos-query:19190
isDefault: true
jsonData:
timeInterval: 1m
十四、FAQ 速查
Q:Thanos 和 Prometheus 联邦到底怎么选? A:规模小、集群少、能接受单点聚合风险,联邦够用;一旦要长期留存或多集群统一查询,直接上 Thanos,别犹豫。
Q:Receive 什么时候用? A:当你想把 Prometheus 完全无状态化(数据全落对象存储,本地只留 WAL)时用。代价是多了远程写这一跳,网络故障会丢数,我一般建议保留本地 Prometheus,只靠 Sidecar 上传。
Q:块太小 compact 频繁怎么办?
A:把 --consistency-delay 调大,让 block 攒得更厚再合并,同时适当调低 --compact-concurrency 避免对象存储被打爆。
Q:如何快速判断是对象存储还是查询的问题?
A:用 thanos tools bucket inspect 看对象存储里的块元数据是否完整、降采样目录是否存在;再用 Query 的 tracing 看耗时落在哪一段,就能定位是 Store 拉块慢还是 PromQL 本身重。