6.9 OpenTelemetry 与统一可观测性

预计阅读时间:11 分钟

📖 目录

学习目标

  • 理解 Metrics / Logs / Traces 三大可观测性支柱及 OpenTelemetry 的定位
  • 掌握 OTel Collector 的 Pipeline 架构与核心配置
  • 学会使用 OTel SDK 对 Python / Node.js / Java 应用做自动插桩
  • 了解 Jaeger 与 Grafana Tempo 两种链路追踪后端的部署与用法
  • 能够在 Grafana 中配置 Metrics↔Logs↔Traces 的关联跳转

核心知识

概念说明
OpenTelemetryCNCF 毕业项目(2025 年 3 月从孵化器毕业),提供统一的 API、SDK、协议(OTLP)采集遥测数据
OTLPOpenTelemetry Protocol,gRPC(4317)与 HTTP(4318)双通道传输
Collector代理/网关模式,通过 Receivers → Processors → Exporters 三段式 Pipeline 处理数据
Auto-instrumentation通过字节码注入或 monkey-patch 零代码改动即可采集 Traces 和 Metrics
Sampling按比例或按策略降采样以控制成本,推荐 tail-based sampling 保留错误/慢请求
TraceQLTempo 的查询语言,支持基于 Span 属性和结构的高阶聚合检索

知识关联

原理讲解

传统运维依赖单一日志,"出了事再看"。现代微服务和分布式系统跨越成百上千个进程,一个用户请求可能穿越 10+ 服务,任何一个环节的异常都可能导致整体体验下降。可观测性(Observability) 要求系统能通过外部输出来回答"内部发生了什么"。

三大支柱职责清晰:

支柱数据类型回答的问题
Metrics数值时间序列系统健康吗?P99 延迟多少?错误率?
Logs结构化/非结构化文本具体发生了什么?错误栈、SQL 慢查询?
Traces带上下文的 Span DAG哪个环节耗时最长?请求走了哪条路径?

OpenTelemetry 的作用:提供一套不绑定厂商的 API/SDK 和协议,让应用一次插桩,数据就能发送到任意后端(Jaeger、Tempo、Prometheus、Loki 等)。Collector 位于应用和后端之间,负责协议转换、缓冲、过滤和采样。

┌─────────┐   OTLP    ┌──────────────────────┐   OTLP/Prom/HTTP   ┌──────────┐
│ 应用 A  │──────────→│  OTel Collector      │──────────────────→│  Jaeger  │
│ (SDK)   │           │  Receivers            │                   │ (Trace)  │
└─────────┘           │  ↓                    │                   └──────────┘
┌─────────┐   OTLP    │  Processors (batch,   │   Prometheus HTTP ┌──────────┐
│ 应用 B  │──────────→│   memory_limiter,     │──────────────────→│Prometheus│
│ (SDK)   │           │   filter, sampler)    │                   │ (Metric) │
└─────────┘           │  ↓                    │                   └──────────┘
┌─────────┐   OTLP    │  Exporters            │   Loki HTTP       ┌──────────┐
│  Nginx  │──────────→│  (otlp/prometheus/    │──────────────────→│  Loki    │
└─────────┘           │   loki/otlp)          │                   │ (Log)    │
                       └──────────────────────┘                   └──────────┘

Metrics——健康仪表盘:指标是数值型时间序列,回答"系统现在健康吗"。指标体积小、可长期保存,适合告警与容量规划。核心注意点是基数(Cardinality):把用户 ID、请求 URL 等无限取值放进取样标签会造成存储爆炸,标签取值组合数必须可控。

Logs——事件真相:日志回答"具体发生了什么"。日志要集中收集(Loki/ELK)并尽可能结构化(JSON),且必须包含 trace_id / service.name 等关联字段,否则只是"能搜的文本",无法与链路对接。

Traces——调用链路地图:一个 Trace 由若干 Span 组成有向无环图,每个 Span 记录操作名称、起止时间、父引用与属性。跨进程传播依赖 W3C traceparent 请求头(格式 00-{trace_id}-{span_id}-{flags}),SDK 自动注入 HTTP/gRPC 请求,服务间才能拼出完整链路。

三大支柱如何联动:典型排障路径是——Metrics 告警(P99 延迟飙升)→ 跳转 Trace(发现支付服务耗时占 80%)→ 跳转该 Span 的日志(定位到 SQL 慢查询)。Grafana 的 Data Link 与 Loki derived fields 正是完成这三跳的胶水。

OpenTelemetry 架构四件套:整套体系由四个组件构成,职责边界清晰:

组件部署位置职责
API应用内(库)定义 Trace/Meter/Logger 接口,零开销探针
SDK应用内(库)实现采样、上下文传播、批处理与导出
Instrumentation应用内(插件)自动插桩:HTTP 框架、数据库驱动、消息队列客户端
Collector独立进程接收 → 处理 → 导出三段式 Pipeline,协议转换与缓冲

Collector 有两种部署形态:Agent 模式(每台主机一个,随应用部署、就近接收)与 Gateway 模式(集中式集群,做采样、脱敏、多后端分发)。生产标准做法是 Agent 收集 → Gateway 汇流 → 后端存储,SDK 永远不直写存储后端。

示例代码

1. Collector 配置(otel-collector-config.yaml)

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  memory_limiter:
    check_interval: 1s
    limit_mib: 512

exporters:
  otlp/tempo:
    endpoint: tempo.monitoring:4317
    tls:
      insecure: true
  prometheus:
    endpoint: 0.0.0.0:8889
  loki:
    endpoint: http://loki.monitoring:3100/loki/api/v1/push

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp/tempo]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [loki]

2. Python 自动插桩

pip install opentelemetry-distro opentelemetry-exporter-otlp
# 输出: Successfully installed opentelemetry-api-1.28.0 opentelemetry-sdk-1.28.0 ...
opentelemetry-bootstrap -a install
# 输出: Instrumenting flask requests urllib ...

OTEL_SERVICE_NAME=my-python-app \
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 \
OTEL_TRACES_SAMPLER=parentbased_traceidratio \
OTEL_TRACES_SAMPLER_ARG=0.1 \
opentelemetry-instrument python app.py

3. Node.js 手动初始化

npm install @opentelemetry/sdk-node \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/exporter-trace-otlp-proto
# 输出: added 342 packages in 5s

// tracing.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto');

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: 'http://otel-collector:4318/v1/traces',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();

4. Java Agent 注入

java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=my-java-app \
  -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
  -Dotel.traces.sampler=traceidratio \
  -Dotel.traces.sampler.arg=0.1 \
  -jar app.jar

5. Grafana 联动配置

# Loki derived fields — 从日志行提取 trace_id 跳转到 Tempo
# 正则: trace_id=(\w+)
# URL: ${__value.raw} → http://grafana:3000/explore?orgId=1&left=["now-1h","now","tempo",{"query":"${value}"}]

# Metrics Panel Data Link — 从高延迟跳转到 Loki 日志
# 字段: ${__value.raw} → 打开 Loki 查询

# TraceQL 示例(Tempo 中查询所有 5xx POST 请求)
# { .http.method = "POST" && .http.status_code >= 500 } | count() > 10

6. Python 手动埋点(自定义 Span 与属性)

pip install opentelemetry-sdk opentelemetry-api

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
    BatchSpanProcessor(OTLPSpanExporter(endpoint="http://otel-collector:4317"))
)

tracer = trace.get_tracer("order-service")
with tracer.start_as_current_span("checkout.process") as outer:
    outer.set_attribute("order.id", "A-20260731-001")
    with tracer.start_as_current_span("payment.deduct") as inner:
        inner.set_attribute("payment.method", "alipay")
        # 业务代码...
        inner.record_exception(RuntimeError("balance insufficient"))
        inner.set_status(trace.Status(trace.StatusCode.ERROR))

7. Go 自动埋点

# 依赖
go get go.opentelemetry.io/otel \
       go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc \
       go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp

# main.go:初始化 TracerProvider(OTLP 导出 + 服务名资源)
exporter, _ := otlptracegrpc.New(ctx,
    otlptracegrpc.WithEndpoint("otel-collector:4317"),
    otlptracegrpc.WithInsecure())
tp := sdktrace.NewTracerProvider(
    sdktrace.WithBatcher(exporter),
    sdktrace.WithResource(resource.NewWithAttributes(
        semconv.SchemaURL, semconv.ServiceName("go-order-api"))))
otel.SetTracerProvider(tp)

# HTTP 服务一行接入:用 otelhttp 包装 handler
mux.Handle("/api/order",
    otelhttp.NewHandler(orderHandler, "order.api"))

Trace 采样策略

全量采集成本极高(单个 Span 约 1-2 KB,高流量服务每秒产生数万 Span),生产必须采样。两种主流策略对比:

维度Head-based(头部采样)Tail-based(尾部采样)
决策时机请求入口(Span 创建时)完整 Trace 聚合后(Collector 端)
保留对象按比例随机保留精确保留错误 / 慢请求 / 指定路径
数据一致性依赖 parentbased_* 前缀保证父子一致天然完整(先全量缓冲再筛选)
资源开销低(入口一次判断)高(需缓冲全部 Trace 等待决策)
典型配置traceidratio + 1%-10%Collector tail_sampling processor

关键原则:head-based 采样必须用 parentbased_traceidratio 而非 traceidratio,否则同一 Trace 的父子 Span 可能分别被保留/丢弃,链路断裂。开发环境 100% 采样,生产 1%-10% 起步,成本允许再上调。

Tail-based 配置示例(Collector):

processors:
  tail_sampling:
    decision_wait: 10s          # 等待完整 Trace 的最大时间
    num_traces: 50000           # 内存中最多缓冲的 Trace 数
    policies:
      - name: keep-errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: keep-slow
        type: latency
        latency: { threshold_ms: 1000 }
      - name: sample-ratio
        type: probabilistic
        probabilistic: { sampling_percentage: 10 }

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, tail_sampling, batch]
      exporters: [otlp/tempo]

实战案例:微服务链路追踪定位慢请求

背景:订单系统由 gateway → order → inventory → payment 四个服务组成(Python + Go 混合),用户反馈下单耗时从 200ms 涨到 3 秒。

第一步看 Metrics:Grafana 中 gateway 的 P99 延迟从 250ms 飙升至 3.2s,错误率平稳——排除宕机与 500 风暴,锁定为单请求变慢。

第二步看 Trace:Tempo 中过滤慢请求({ resource.service.name = "gateway" } | duration > 2s),对比正常与异常两个 Trace 的 Span 瀑布图:

Trace A(正常,200ms)              Trace B(异常,3s)
gateway: 201ms                      gateway: 3012ms
 ├─ order.place: 180ms               ├─ order.place: 2950ms
 │   ├─ inventory.check: 120ms       │   ├─ inventory.check: 120ms
 │   └─ payment.deduct: 40ms         │   └─ payment.deduct: 2750ms
 └─ db.query: 15ms                   └─ db.query: 18ms

第三步看 Logs:从慢 Trace 的 payment.deduct Span 取出 trace_id,在 Loki 中过滤对应日志,发现大量 retry: connection timeout to payment-gw (3rd attempt)

定位与修复:payment 服务依赖的外部支付网关连接池配置了 3 秒超时且重试 3 次,网关侧抖动导致每次请求空等。将连接超时降到 800ms 并加熔断后,P99 恢复 240ms。

复盘沉淀:为 payment 服务补充 payment.gateway.latency 指标与告警;为网关超时日志统一注入 span_id;此后同类问题可从告警一步跳到 Trace 再跳 Logs,全程不切换工具。

常见错误

错误原因解决
Collector 启动后日志无数据进入端口被占用或防火墙未放行 4317/4318ss -tlnp | grep 4317 检查监听
SDK 报错 "Failed to export span"OTLP endpoint 配置错误或 Collector 未就绪验证 OTEL_EXPORTER_OTLP_ENDPOINT 可达
Trace 不完整,缺少中间服务采样策略不一致,导致父子 Span 被分别丢弃使用 parentbased_traceidratio 确保一致性
日志中有 trace_id 但无法跳转Loki derived fields 正则不匹配日志格式grep -oP 'trace_id=(\w+)' 确认实际输出
Grafana 中看不到任何 TraceTempo 数据源未配置或 Collector exporter 指向错误curl tempo:4317 测试 gRPC 连通性
Metrics 或 Traces 存储膨胀过快未开启采样或 batch processor 配置不当设置 sampler=traceidratio 降至 1-10%

最佳实践

  • 始终在生产环境部署 OTel Collector,而不是让 SDK 直写后端
  • Collector 配置三段:receivers: [otlp]processors: [memory_limiter, batch]exporters: [...]
  • Trace 采样用 parentbased_traceidratio+10%;生产用 tail-based sampling(保留错误/慢请求)
  • 日志中包含 trace_idservice.name 字段,方便 Grafana 关联跳转
  • Grafana 每个 Panel 的 Data Link 至少配置 Log 和 Trace 两个跳转目标
  • 使用官方 docker-otel-lgtm 一键启动 Tempo + Loki + Prometheus + Collector 全套
  • 定期监控 Collector 自身的 CPU/内存使用,设置 memory_limiter 防止 OOM
  • OTEL_RESOURCE_ATTRIBUTES 统一注入环境信息(region、version、环境名),Span 属性是后续过滤与聚合的基础
  • 给 Collector 配置 health_check / pprof 扩展并导出自身指标到 Prometheus,避免"观测系统自身失明"

练习题

  1. 部署 OTel Collector 并配置接收 OTLP gRPC,将 Traces 转发至本地启动的 Jaeger all-in-one。
  2. 用 Python 编写一个简单的 Flask 应用,通过自动插桩在访问后能看到 Jaeger 中的 Trace。
  3. 在 Collector 配置中添加 Prometheus exporter,验证 curl localhost:8889/metrics 输出指标。
  4. 使用 Grafana docker-otel-lgtm 套件启动全部组件,配置一个 Loki derived field 从日志提取 trace_id 跳转到 Tempo。
  5. TraceQL 查询:找出过去 1 小时内所有 HTTP 响应码 >= 400 的 Trace 并统计占比。
点击查看答案
  1. Collector 配置 receivers: otlp: protocols: grpc: + exporters: jaeger: endpoint: localhost:14250。Jaeger 用 docker run -d --name jaeger -p 16686:16686 -p 14250:14250 jaegertracing/all-in-one
  2. 安装 opentelemetry-distro opentelemetry-exporter-otlpOTEL_SERVICE_NAME=flask-app OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 opentelemetry-instrument python app.py。访问后 Jaeger 显示 trace。
  3. Collector config 加 exporters: prometheus: endpoint: 0.0.0.0:8889curl localhost:8889/metrics 应输出 otel_* 指标。
  4. LGTM 套件启动后用 Loki 查询日志,配置 derived fields 正则提取 trace_id。从日志行直接链接到 Tempo 的 trace 视图。
  5. { .http.status_code >= 400 } | stats count() by (.http.status_code)。Tempo 返回匹配 Trace 列表和统计。

学习检查点

学完本章后,请检验自己是否掌握以下内容:

检查项自测问题验证方法
概念理解能用自己的话解释 OpenTelemetry 的三大信号和 SDK 架构尝试向他人讲解
命令操作能不查文档完成 OTel Collector 部署和应用埋点在终端实际执行
原理掌握能说出 OpenTelemetry 的 Trace、Metrics、Logs 关联原理画出流程图
故障排查能独立排查遥测数据丢失或采样率不正确的问题模拟故障并修复
最佳实践能说明为什么需要统一可观测性标准而非使用多种私有方案对比不同方案

本章总结

速查表

组件用途
OTel SDK应用内自动/手动插桩生成遥测数据
OTel Collector接收→处理→导出三段式 Pipeline
OTLP 协议统一遥测传输格式(gRPC/HTTP)
Jaeger/TempoTrace 存储与查询后端
PrometheusMetrics 存储与告警
LokiLogs 聚合与查询

OpenTelemetry 是 CNCF 主推的统一遥测数据标准,核心生态包含:

  • OTLP 协议——gRPC/HTTP 双通道,SDK→Collector→后端的标准通讯格式
  • Collector——三段式 Pipeline(Receiver→Processor→Exporter),缓冲/采样/转发
  • SDK 自动插桩——Python/Node.js/Java 等语言零代码或一行启动
  • Jaeger / Tempo——链路追踪后端,配合 TraceQL 做聚合分析
  • Grafana——统一可视化层,配置 Data Link 实现 Metrics↔Logs↔Traces 互跳

推荐实施路线:部署 LGTM 栈(Loki + Grafana + Tempo + Metrics/Prometheus + Collector)→ 关键服务开启自动插桩 → 设置 tail-based sampling → 配置 Grafana 关联跳转。

延伸阅读

常见问题

OpenTelemetry 的三类信号是什么?
Traces(链路追踪):记录一个请求在多个服务间的完整路径,包含每个服务的耗时和状态。Metrics(指标):聚合的数值数据,如请求数、错误率、P99 延迟。Logs(日志):带结构化的时间戳事件。OTel 的口号是"一个 SDK 采集所有信号",避免为每种信号使用不同的 agent。
OTel Collector 和生产环境怎么部署?
推荐部署模式:两台 Collector — ① Agent Collector(和应用程序同节点,采集日志/指标/trace,轻量处理);② Gateway Collector(中央集群,做数据过滤、丰富、路由到后端)。Gateway Collector 建议至少 2 副本做高可用。内存分配根据数据量:通常每 10K spans/sec 需要约 1GB 内存。
链路追踪(Distributed Tracing)解决什么问题?
微服务架构中一个请求经过 10+ 服务,传统日志无法串联。Tracing 通过 Trace ID(跨服务传递)将同一请求的所有 span 关联起来。用 Jaeger/Tempo 可以:① 看到请求经过哪些服务、每个服务耗时;② 找到性能瓶颈(哪个服务最慢);③ 分析错误发生在哪个环节。核心优化目标:降低 P99 延迟。
↑ 回到顶部