技术博客

Prometheus 大规模优化:Recording Rules、Federation 与 Thanos 长期存储

系统讲解 Prometheus 大规模生产优化方案:Recording Rules 预计算高频 PromQL 提升查询性能、Federation 联邦架构跨集群聚合、Thanos 组件(Sidecar/Store/Query/Compactor)实现无限长期存储与全局查询、Prometheus 内存和存储调优参数配置。

PrometheusThanosRecording Rules可观测性监控性能优化运维

单机 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 是生产标配。