云原生可观测性实战:构建 Metrics、Logs 和 Traces 的统一视图

· 阅读约需21分钟

云原生可观测性实战:构建 Metrics、Logs 和 Traces 的统一视图

摘要:在微服务和分布式系统日益复杂的今天,可观测性(Observability)已从“锦上添花”变为“生存刚需”。本文将带你从零开始,理解可观测性的三大支柱(Metrics、Logs、Traces),并基于 OpenTelemetry、Prometheus、Grafana 和 Jaeger 等开源工具,构建一套生产级的统一可观测性平台。全文包含大量实战代码和配置,助你快速落地。


1. 为什么可观测性如此重要?

传统的监控(Monitoring)只能回答“系统是否挂了”,而可观测性(Observability)能回答“系统为什么挂了”以及“接下来会发生什么”。在 Kubernetes 和微服务环境下,一个请求可能流经数十个服务,任何一环的延迟或错误都可能导致用户体验下降。可观测性通过三大信号——指标(Metrics)日志(Logs)链路追踪(Traces)——让我们能够深入系统的内部状态,实现故障快速定位、性能瓶颈分析和容量规划。

2. 可观测性三大支柱概览

信号作用典型工具
Metrics聚合的数值数据,用于告警和趋势Prometheus, Grafana
Logs离散的事件记录,用于调试和审计Elasticsearch, Loki, Fluentd
Traces请求在分布式系统中的完整路径Jaeger, Tempo, Zipkin

三者相辅相成:Metrics 告诉你“出问题了”,Logs 提供“现场细节”,Traces 帮你“还原路径”。现代可观测性实践倾向于使用 OpenTelemetry 作为统一的数据采集和标准化层,从而让这三者能够关联查询。

3. 实战环境准备

我们将基于 Docker Compose 启动一套完整的可观测性栈,包括:

  • Prometheus(时序数据库 + 指标采集)
  • Grafana(可视化面板)
  • Loki(日志聚合,与 Grafana 深度集成)
  • Tempo(分布式追踪后端,兼容 Jaeger 协议)
  • OpenTelemetry Collector(接收、处理并导出遥测数据)

同时,我们会编写一个简单的微服务示例(Go + Gin),演示如何埋点并发送数据。

3.1 Docker Compose 文件(docker-compose.yml

version: '3'
services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin

  loki:
    image: grafana/loki:latest
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml

  tempo:
    image: grafana/tempo:latest
    ports:
      - "3200:3200"
      - "4318:4318"   # OTLP HTTP receiver
    command: -config.file=/etc/tempo.yaml
    volumes:
      - ./tempo.yaml:/etc/tempo.yaml

  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    ports:
      - "4317:4317"   # OTLP gRPC
      - "4318:4318"   # OTLP HTTP
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    command: --config /etc/otel-collector-config.yaml

3.2 配置文件要点

1. Prometheus 配置(prometheus.yml

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'otel-collector'
    static_configs:
      - targets: ['otel-collector:8889']
  - job_name: 'user-service'
    static_configs:
      - targets: ['user-service:8080']

2. Tempo 配置(tempo.yaml

server:
  http_listen_port: 3200

distributor:
  receivers:
    otlp:
      protocols:
        grpc:
        http:

ingester:
  trace_idle_period: 10s
  max_block_bytes: 1_000_000
  max_block_duration: 5m

storage:
  trace:
    backend: local
    local:
      path: /var/tempo/traces

3. OpenTelemetry 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_percentage: 80

exporters:
  prometheus:
    endpoint: "0.0.0.0:8889"
    namespace: "otel"
  loki:
    endpoint: "http://loki:3100/loki/api/v1/push"
    tls:
      insecure: true
  tempo:
    endpoint: "tempo:4317"
    tls:
      insecure: true

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

4. 编写可观测性微服务(Go 示例)

我们的服务是一个简单的 HTTP API,模拟用户注册流程,包含数据库查询和外部调用。我们将使用 OpenTelemetry Go SDK 自动注入 instrumentation。

4.1 初始化 Tracer 和 Meter

package main

import (
    "context"
    "log"
    "net/http"
    "time"

    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)

func initTracer() func() {
    ctx := context.Background()
    exporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint("otel-collector:4317"))
    if err != nil {
        log.Fatal(err)
    }
    res, _ := resource.New(ctx,
        resource.WithAttributes(
            semconv.ServiceNameKey.String("user-service"),
            semconv.ServiceVersionKey.String("1.0.0"),
        ),
    )
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(res),
    )
    otel.SetTracerProvider(tp)
    return func() { _ = tp.Shutdown(ctx) }
}

4.2 业务处理函数(带埋点)

func registerHandler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    tracer := otel.Tracer("user-service")
    ctx, span := tracer.Start(ctx, "registerUser")
    defer span.End()

    step1(ctx, "validate input")
    step2(ctx, "query database")
    step3(ctx, "send welcome email")

    w.WriteHeader(http.StatusOK)
    w.Write([]byte("registered"))
}

func step1(ctx context.Context, name string) {
    _, span := otel.Tracer("user-service").Start(ctx, name)
    defer span.End()
    time.Sleep(10 * time.Millisecond)
    span.SetAttributes(attribute.String("step", name))
}

4.3 启动 HTTP 服务并添加 Metrics

func main() {
    cleanup := initTracer()
    defer cleanup()

    handler := otelhttp.NewHandler(http.HandlerFunc(registerHandler), "register")
    http.Handle("/register", handler)
    http.Handle("/metrics", otelhttp.NewHandler(http.DefaultServeMux, "metrics"))

    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

5. 三支柱数据关联查询实战

5.1 场景:排查“用户注册超时”

  1. 从 Metrics 发现异常:Grafana 面板显示 /register 接口的 P99 延迟突然从 200ms 上升到 2s,触发告警。
  2. 跳转到 Logs:在 Grafana Explore 中,根据时间范围和 service_name="user-service" 过滤 Loki 日志,发现大量 "database query timeout" 错误。
  3. 关联到 Traces:在日志中提取 trace_id,点击链接跳转到 Tempo,查看该 trace 的完整瀑布图,发现瓶颈在 step2 的数据库查询。

5.2 实现日志与 Trace 关联

在 Go 中,我们可以在日志中加入 trace_idspan_id

import "go.opentelemetry.io/otel/trace"

spanCtx := trace.SpanContextFromContext(ctx)
if spanCtx.IsValid() {
    log.Printf("trace_id=%s span_id=%s msg=something", spanCtx.TraceID(), spanCtx.SpanID())
}

6. 统一仪表盘与告警配置

在 Grafana 中创建:

  • 服务概览面板:显示各服务的请求量、错误率、延迟(RED 方法)。
  • 依赖关系图:基于 Trace 数据生成服务拓扑。
  • 日志流面板:实时展示 Loki 中的日志,支持关键词搜索。
  • 告警规则:如“错误率 > 5% 持续 1 分钟”触发 AlertManager。

7. 生产环境最佳实践

实践项说明
采样策略使用概率采样(如 10%)或基于错误/慢请求的尾部采样,控制 trace 数据量
Metrics 高基数问题避免使用无限维度(如 user_id),合理聚合,使用 recording rules
日志结构化统一 JSON 格式,包含 service, version, trace_id 等字段
存储容量规划Prometheus 使用 TSDB 压缩,Loki 使用块存储,Tempo 可配置 S3 廉价存储
安全与认证启用 OAuth2 或 API Token,加密传输(TLS)
持续优化定期 review 仪表盘,裁剪无用指标,优化采集频率

8. 总结与展望

通过本文的实战,你已经掌握了如何基于开源生态构建一套完整的可观测性体系。从三大支柱的采集、存储到可视化关联,再到实际排障场景,这套方案足以支撑中小规模的生产环境。

随着 eBPFOpenTelemetry 的快速发展,未来的可观测性将更加“零侵入”和“智能化”。但无论技术如何演进,理解数据背后的业务逻辑和系统架构,始终是我们构建可靠系统的基础。

希望这篇博文能为你落地可观测性提供一份清晰的路线图。如果你有任何问题或实践经验,欢迎在评论区交流讨论!


参考资源