WEBKT

Prometheus生态向OpenTelemetry演进:构建Pull/Push混合模式的可观测性架构实践

8 0 0 0

现状困境:为什么需要"混合架构"

在现有的云原生监控体系中,Prometheus 凭借 Pull 模式和 PromQL 已成为事实标准。但随着微服务规模扩大,我们面临三个结构性矛盾:

  1. 协议碎片化:Metrics 走 Prometheus,Traces 用 Jaeger/Zipkin,Logs 又是 ELK/Loki,三套存储导致关联查询变成"数据考古"
  2. 网络边界限制:跨 VPC、边缘计算场景下,防火墙规则使得 Prometheus 的主动拉取(Pull)难以穿透
  3. 多租户隔离成本:单一 Prometheus 实例面对高基数(High Cardinality)指标时,内存膨胀问题难以根治

OpenTelemetry(OTel)提供了统一的语义约定(Semantic Conventions)和 OTLP 协议,但完全替换现有 Prometheus 生态既不现实也不经济。我们需要一种"混合架构":既保留 Prometheus 的采集能力,又通过 OTel Collector 实现数据路由、加工和关联。

架构核心:OTel Collector 作为转换枢纽

混合架构的关键在于将 OTel Collector 部署为"边车"(Sidecar)或"独立网关",而非直接替换 Prometheus Server。其核心数据流如下:

Prometheus Agent (Pull) 
    ↓
OTel Collector (Prometheus Receiver)
    ↓ (Processor: 属性注入、采样、裁剪)
    ↓
[Metrics → OTLP → Backend]
[Logs    → OTLP → Backend]  
[Traces  → OTLP → Backend]

关键组件配置

Prometheus Receiver 的兼容模式
Collector 需要同时支持两种采集语义:

  • Scrape 模式:主动拉取遗留的 /metrics 端点,兼容现有 exporter
  • Remote Write 接收:直接接收 Prometheus Server 推送的数据,适合短期过渡
# otel-collector-config.yaml
receivers:
  prometheus:
    config:
      scrape_configs:
        - job_name: 'legacy-services'
          scrape_interval: 15s
          kubernetes_sd_configs:
            - role: pod
          relabel_configs:
            # 保留原有标签,同时注入 OTel 资源属性
            - source_labels: [__meta_kubernetes_pod_name]
              target_label: k8s.pod.name
            - source_labels: [__meta_kubernetes_namespace]
              target_label: k8s.namespace.name

processors:
  resource:
    attributes:
      # 统一服务标识,实现 Metrics/Traces/Logs 关联
      - key: service.name
        from_attribute: k8s.pod.name
        action: insert
      - key: deployment.environment
        value: "production"
        action: upsert
  
  metricstransform:
    transforms:
      # 解决标签冲突:Prometheus 的 __name__ 与 OTel 的 metric.name
      - include: http_requests_total
        match_type: regexp
        action: update
        operations:
          - action: aggregate_labels
            label_set: [method, status_code]
            aggregation_type: sum

exporters:
  otlp:
    endpoint: "otel-backend:4317"
    tls:
      insecure: true
  prometheusremotewrite:
    # 双写策略:过渡期同时写入旧 Prometheus 和新后端
    endpoint: "http://prometheus:9090/api/v1/write"

数据关联:从孤岛到统一视图

仅仅将数据导入同一存储远远不够,真正的价值在于建立跨信号(Cross-Signal)的关联

1. Exemplar 链路打通

在 Prometheus 指标中嵌入 Trace ID,实现从宏观指标到微观链路的跳转:

# 应用层需引入 OTel SDK 并注入 Exemplar
processors:
  # 在 Metric 中自动附加当前 Span 的 Trace ID
  metricstransform/exemplar:
    transforms:
      - include: .*
        match_type: regexp
        action: update
        operations:
          - action: add_label
            new_label: trace_id
            new_value: "${TRACE_ID}"  # 通过上下文变量注入

生产注意:Exemplar 会显著增加存储成本,建议仅对关键黄金指标(Error Rate、Latency P99)开启,并设置采样率(如 1%)。

2. 资源属性(Resource Attributes)标准化

Prometheus 的 instancejob 标签与 OTel 的资源模型存在语义鸿沟。建立映射表:

Prometheus 标签 OTel Resource Attribute 说明
job service.name 服务标识
instance service.instance.id 实例唯一 ID
kubernetes_pod_name k8s.pod.name K8s 原生属性
cluster k8s.cluster.name 集群层级

通过 resource processor 统一转换后,无论数据来源于旧 Exporter 还是新 OTel SDK,在 Jaeger/Grafana Tempo 中都能通过同一组标签过滤。

渐进式迁移路线图

阶段一: Collector 作为代理(0-2个月)

目标:零侵入接入,验证数据质量

  • 部署 DaemonSet 模式的 Collector,与 Prometheus Server 并行运行
  • 使用 prometheusreceiver 拉取现有指标,通过 prometheusremotewrite 回写给原 Prometheus,形成"旁路验证"
  • 验收标准:OTel pipeline 处理的指标与原生抓取误差率 < 0.1%

阶段二:双写与灰度(2-4个月)

目标:建立新数据链路,保留旧系统作为 fallback

  • 关键服务逐步接入 OTel SDK(Java/Go/Node.js),开启 OTLP 直接上报
  • Collector 配置 routing processor,实现按服务名分流:
    • 遗留服务 → Prometheus Receiver → 旧存储
    • 新服务 → OTLP Receiver → 新可观测平台(如 Grafana Cloud/自建 VictoriaMetrics)
  • 关键动作:建立对照仪表盘,比对旧 PromQL 与新 MetricsQL 的查询结果一致性

阶段三:原生 OTLP 与 Collector 中心化(4-6个月)

目标:完全摆脱 Prometheus Server 的存储依赖

  • 停用 Prometheus Server 的存储功能,仅保留抓取能力(或完全停用)
  • Collector 升级为集群模式(StatefulSet + Target Allocator),自动分配抓取任务
  • 启用 Tail-based Sampling、Attribute 裁剪等高级 Processor,解决之前无法处理的高基数问题

生产环境的三大陷阱

1. 基数爆炸(Cardinality Explosion)的转移

Prometheus 的 rate()increase() 在本地计算,而 OTel Collector 的 deltatocumulative processor 会将 Delta 转为 Cumulative,若上游标签未规范化,内存占用可能飙升 3-5 倍。

解决方案:在 Collector 中启用 memory_limitercardinality filter,对未预期的标签(如动态 user_id、request_path)进行裁剪或脱敏。

2. 时间戳对齐问题

Prometheus 的抓取时间戳由 Server 决定,而 OTel SDK 上报的 Metrics 时间戳由客户端生成。在混合架构中,若两者差异超过 1 分钟,时序数据库(如 Mimir/Cortex)会拒绝写入。

解决方案:在 Collector 的 prometheusreceiver 中启用 use_start_time_metric: true,强制使用客户端时间戳,并确保 NTP 同步。

3. Histogram 分桶差异

Prometheus 的默认分桶(0.005, 0.01, 0.025...)与 OTel 的 Explicit Bucket Boundaries 不同,直接转换会导致查询时百分位数计算偏差。

解决方案:在 SDK 初始化时显式定义与旧系统一致的 Boundaries:

// Go SDK 示例
view.Register(view.AggregationExplicitBucketHistogram(
    []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10},
))

验证与回滚策略

建立监控的监控(Meta-Monitoring):

  1. Collector 健康度:监控 otelcol_process_uptimeotelcol_exporter_queue_size,队列积压超过 1000 条即触发告警
  2. 数据完整性:对比 scrape_samples_scraped(旧)与 otelcol_receiver_accepted_metric_points(新),差异率 > 5% 自动切回旧链路
  3. 查询兼容性:编写自动化测试,对比旧 PromQL 与新 DSL 在关键指标(QPS、Error Rate、Latency)上的返回结果

结语

从 Prometheus 向 OpenTelemetry 的过渡不是协议替换,而是观测范式的升级——从分散的指标收集转向关联驱动的可观测性。混合架构的价值在于承认技术债的客观存在,通过 OTel Collector 的灵活处理能力,在保护现有投资的同时,逐步建立 Metrics/Logs/Traces 的语义关联。

建议从非关键服务开始试点,重点关注资源属性映射和基数控制,6-12 个月内完成核心链路的迁移。记住:可观测性系统的首要任务是可靠地反映系统状态,技术新鲜感永远排在稳定性之后

运维札记 可观测性架构

评论点评