Prometheus 大规模优化:Recording Rules、Federation 与 Thanos 长期存储
系统讲解 Prometheus 大规模生产优化方案:Recording Rules 预计算高频 PromQL 提升查询性能、Federation 联邦架构跨集群聚合、Thanos 组件(Sidecar/Store/Query/Compactor)实现无限长期存储与全局查询、Prometheus 内存和存储调优参数配置。
单机 Prometheus 在规模增大后会遇到三个瓶颈:查询慢(PromQL 太复杂)、存储满(默认只保留 15 天)、多集群数据分散(无法全局查询)。本文讲解解决这三个问题的方法:Recording Rules(预计算)、Federation(联邦聚合)、Thanos(长期存储 + 全局查询)。
问题分析
Prometheus 单机瓶颈:
1. 查询性能:
- 复杂 PromQL(多层 rate/sum/histogram_quantile)每次执行都全量计算
- Dashboard 同时打开 50 个 Panel,每个 Panel 刷新一次 = 50 次全量计算
- 告警规则 1min 评估一次,复杂规则让 CPU 持续 100%
2. 存储规模:
- 默认 15 天数据,合规要求 1 年甚至更长
- 本地磁盘不够用(10 万指标 × 15 天 ≈ 几十到几百 GB)
3. 多集群:
- 生产有 10 个 K8s 集群,每个都有独立 Prometheus
- 需要跨集群查询(全公司所有服务的错误率)
- 每次查询要手动切换集群
一、Recording Rules(预计算)
Recording Rules 把复杂 PromQL 预先计算并存为新指标,查询 Dashboard 时直接读新指标(秒级响应),而不是每次执行复杂计算。
# prometheus-rules.yml - Recording Rules 配置
groups:
- name: http_metrics_recording
interval: 1m # 每 1 分钟计算一次,存储结果
rules:
# 预计算:服务请求速率(按 namespace 和 service 聚合)
# 原始 PromQL 每次计算耗时 500ms,预计算后查询 < 10ms
- record: namespace_service:http_requests:rate5m
expr: |
sum by (namespace, service) (
rate(http_requests_total[5m])
)
# 预计算:错误率
- record: namespace_service:http_errors:rate5m
expr: |
sum by (namespace, service) (
rate(http_requests_total{status=~"5.."}[5m])
)
# 预计算:P99 延迟(histogram 计算很慢)
- record: namespace_service:http_duration_p99:rate5m
expr: |
histogram_quantile(0.99,
sum by (namespace, service, le) (
rate(http_request_duration_seconds_bucket[5m])
)
)
# 预计算:节点 CPU 使用率
- record: node:cpu_usage:rate5m
expr: |
1 - avg by (node, cluster) (
rate(node_cpu_seconds_total{mode="idle"}[5m])
)
# 预计算:K8s Pod 内存使用率
- record: namespace_pod:memory_usage_ratio:latest
expr: |
container_memory_working_set_bytes{container!=""}
/
container_spec_memory_limit_bytes{container!=""} > 0
- name: slo_recording
interval: 1m
rules:
# SLO 指标预计算(告警规则直接引用,不再重复计算)
- record: slo:http_success_rate:ratio_rate5m
expr: |
sum(rate(http_requests_total{status!~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
# Grafana Dashboard 中引用 Recording Rule 结果(极快)
# 不使用(慢,每次计算):
# sum by (namespace, service) (rate(http_requests_total{status=~"5.."}[5m]))
# 使用 Recording Rule(快,直接读预计算结果):
# namespace_service:http_errors:rate5m
# 命名规范:level:metric:operation
# level = 聚合层级(namespace_service, node, cluster 等)
# metric = 原指标名(http_requests, cpu_usage 等)
# operation = 计算类型(rate5m, p99, ratio 等)
二、Federation 联邦架构
Federation 让一个“全局” Prometheus 从多个“本地” Prometheus 拉取已聚合的数据,实现跨集群查询。
# 全局 Prometheus 配置(global-prometheus.yml)
# 从各集群的本地 Prometheus 拉取 Recording Rule 结果
scrape_configs:
# 从集群1的 Prometheus 拉取
- job_name: 'federate-cluster-sh01'
scrape_interval: 1m
honor_labels: true # 保留原始 Prometheus 的标签(不被全局 Prometheus 覆盖)
metrics_path: '/federate'
params:
# 只拉取 Recording Rules 的结果(不拉原始指标,节省带宽)
match[]:
- '{__name__=~"namespace_service:.*"}'
- '{__name__=~"node:.*"}'
- '{__name__=~"slo:.*"}'
static_configs:
- targets: ['prometheus-sh01.monitoring:9090']
labels:
cluster: 'sh01' # 打上集群标签
- job_name: 'federate-cluster-sh02'
scrape_interval: 1m
honor_labels: true
metrics_path: '/federate'
params:
match[]:
- '{__name__=~"namespace_service:.*"}'
- '{__name__=~"node:.*"}'
- '{__name__=~"slo:.*"}'
static_configs:
- targets: ['prometheus-sh02.monitoring:9090']
labels:
cluster: 'sh02'
# ... 更多集群
# 全局 Prometheus 上查询:所有集群的错误率
sum by (cluster, namespace, service) (
namespace_service:http_errors:rate5m
)
# 找出所有集群中错误率最高的 5 个服务
topk(5,
sum by (cluster, service) (namespace_service:http_errors:rate5m)
/
sum by (cluster, service) (namespace_service:http_requests:rate5m)
)
Federation 的限制: 只适合拉取聚合后的 Recording Rule 指标,不适合拉取大量原始指标(带宽和存储压力大)。长期存储和全局原始查询需要 Thanos。
三、Thanos 架构
Thanos 给 Prometheus 加上无限长期存储和全局查询能力,且不需要改动现有 Prometheus 配置。
Thanos 组件:
Sidecar(跟每个 Prometheus 一起部署):
- 代理 Prometheus 的查询 API
- 定期把 Prometheus 历史块上传到对象存储(S3/OSS)
Store Gateway:
- 从对象存储读取历史数据
- 对外提供 gRPC 查询接口(和 Sidecar 接口相同)
Query(全局查询层):
- 聚合来自多个 Sidecar 和 Store Gateway 的数据
- 提供统一的 Prometheus-兼容查询 API
- 自动去重(多副本 Prometheus 的重复数据)
Compactor:
- 对对象存储中的数据做压缩和降采样
- 2h 块 → 合并成 24h 块 → 合并成 2h 分辨率的 7d 块 → ...
Ruler:
- 基于全局数据评估告警规则和 Recording Rules(可选)
Thanos Sidecar 部署
# 以 Kubernetes StatefulSet 为例,给每个 Prometheus 加 Sidecar
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: prometheus
namespace: monitoring
spec:
replicas: 2 # 2 副本高可用(Thanos 会自动去重)
selector:
matchLabels:
app: prometheus
template:
spec:
containers:
# Prometheus 容器(正常配置)
- name: prometheus
image: prom/prometheus:v3.1.0
args:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=3d # 本地只保留 3 天(历史靠 Thanos)
- --storage.tsdb.min-block-duration=2h
- --storage.tsdb.max-block-duration=2h # 块大小固定 2h(Sidecar 需要)
- --web.enable-lifecycle
volumeMounts:
- name: prometheus-data
mountPath: /prometheus
# Thanos Sidecar(核心:上传 + 代理)
- name: thanos-sidecar
image: quay.io/thanos/thanos:v0.37.2
args:
- sidecar
- --prometheus.url=http://localhost:9090
- --tsdb.path=/prometheus # 访问 Prometheus 数据目录
- --grpc-address=0.0.0.0:10901 # gRPC 接口(供 Query 访问)
- --http-address=0.0.0.0:10902
# 上传到阿里云 OSS
- --objstore.config-file=/etc/thanos/objstore.yml
volumeMounts:
- name: prometheus-data
mountPath: /prometheus
- name: objstore-config
mountPath: /etc/thanos
volumes:
- name: prometheus-data
persistentVolumeClaim:
claimName: prometheus-data
- name: objstore-config
secret:
secretName: thanos-objstore-secret
# objstore.yml - 对象存储配置(阿里云 OSS)
type: S3
config:
bucket: "company-thanos-metrics"
endpoint: "oss-cn-shanghai-internal.aliyuncs.com"
access_key: "${OSS_ACCESS_KEY}"
secret_key: "${OSS_SECRET_KEY}"
insecure: false
Thanos Query(全局查询)
# thanos-query-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: thanos-query
namespace: monitoring
spec:
replicas: 2
template:
spec:
containers:
- name: thanos-query
image: quay.io/thanos/thanos:v0.37.2
args:
- query
- --http-address=0.0.0.0:9090 # 暴露 Prometheus-兼容 API
- --grpc-address=0.0.0.0:10901
# 连接各集群的 Sidecar(gRPC)
- --endpoint=dnssrv+_grpc._tcp.thanos-sidecar-sh01.monitoring.svc.cluster.local
- --endpoint=dnssrv+_grpc._tcp.thanos-sidecar-sh02.monitoring.svc.cluster.local
# 连接 Store Gateway(读取历史数据)
- --endpoint=thanos-store.monitoring.svc.cluster.local:10901
# 去重配置(两副本 Prometheus 的数据去重)
- --query.replica-label=prometheus_replica
- --query.auto-downsampling # 查询长时间范围时自动使用降采样数据
---
# thanos-store-gateway.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: thanos-store
namespace: monitoring
spec:
replicas: 3
template:
spec:
containers:
- name: thanos-store
image: quay.io/thanos/thanos:v0.37.2
args:
- store
- --data-dir=/var/thanos/store # 本地缓存目录
- --objstore.config-file=/etc/thanos/objstore.yml
- --grpc-address=0.0.0.0:10901
# 分片(多个 Store Gateway 分担对象存储读取)
- --selector.relabel-config=|
- source_labels: [__block_id]
modulus: 3
target_label: __tmp_shard
action: hashmod
- source_labels: [__tmp_shard]
regex: 0 # 这个实例处理 shard 0
action: keep
Thanos Compactor(压缩降采样)
# thanos-compactor(单实例,不能多副本)
apiVersion: apps/v1
kind: Deployment
metadata:
name: thanos-compactor
spec:
replicas: 1 # 必须是 1,多副本会冲突
template:
spec:
containers:
- name: thanos-compactor
image: quay.io/thanos/thanos:v0.37.2
args:
- compact
- --data-dir=/var/thanos/compact
- --objstore.config-file=/etc/thanos/objstore.yml
- --wait # 持续运行(不是一次性任务)
# 降采样配置
- --downsampling.disable=false
# 数据保留策略
- --retention.resolution-raw=30d # 原始数据保留 30 天
- --retention.resolution-5m=180d # 5min 降采样保留 180 天
- --retention.resolution-1h=365d # 1h 降采样保留 1 年
四、Prometheus 性能调优参数
# prometheus.yml 性能相关配置
global:
scrape_interval: 30s # 默认 15s,大规模时改 30s(减少采集压力)
evaluation_interval: 30s # 告警规则评估间隔,同步改 30s
# 启动参数优化
# --storage.tsdb.retention.time=15d # 本地存储保留时间(Thanos 后可缩短至 3d)
# --storage.tsdb.retention.size=100GB # 磁盘使用上限(超出自动删旧数据)
# --storage.tsdb.wal-compression # 启用 WAL 压缩(减少 ~45% 磁盘使用)
# --query.max-concurrency=20 # 最大并发查询数(防止查询风暴)
# --query.timeout=2m # 查询超时(防止慢查询拖垮)
# --rules.alert.for-grace-period=10m # 告警 for 宽限期
# 资源配置建议
# 内存:每个 Series 约 3KB,100 万 Series = 3GB 内存
# CPU:主要消耗在 PromQL 查询,建议至少 4 core
# 磁盘:每个 Series 每天约 100KB,100 万 × 15 天 = 1.5TB(需要 SSD)
五、Grafana 对接 Thanos
# Grafana 数据源配置(指向 Thanos Query 而不是 Prometheus)
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-datasources
data:
datasources.yml: |
apiVersion: 1
datasources:
- name: Thanos
type: prometheus
url: http://thanos-query.monitoring:9090 # ← 指向 Thanos Query
isDefault: true
jsonData:
httpMethod: POST
queryTimeout: "120s"
timeInterval: "30s" # 和 Prometheus 采集间隔一致
# Thanos Query 支持额外的 Deduplication 参数
# 在 Grafana Panel 中可以看到 "Enable deduplication" 选项
# 开启后,双副本 Prometheus 的数据自动去重(不会出现重复数据)
# 查询历史数据(超过 Prometheus 本地保留期的)
# Thanos Store Gateway 自动从 OSS 读取,对用户透明
# 可以查询 1 年前的数据,和查询今天的数据一样操作
小结
三个方案解决三个不同问题。Recording Rules 解决查询慢:把高频复杂 PromQL 预计算存为新指标,Dashboard 和告警都引用结果(查询从秒级降到毫秒级)。Federation 解决多集群聚合:本地 Prometheus 保留原始数据,全局 Prometheus 只拉 Recording Rule 结果(轻量)。Thanos 解决长期存储和真正的全局查询:Sidecar 上传到 OSS,Compactor 压缩降采样,Store Gateway 读取历史,Query 对外提供统一入口(1 年数据和实时数据用同一个 Grafana 数据源查询)。三者可以组合使用:Recording Rules + Thanos 是生产标配。
