从零搭建一个单节点 K8S 可观测实验室(六):安装 Tempo + OpenTelemetry,构建链路追踪链路

在前面的文章中,我们已经完成了 Kubernetes 可观测体系中的两个部分:

复制代码
Metrics    指标    Prometheus
Logs       日志    Loki

Prometheus 可以帮助我们发现:

  • Node CPU 是否过高

  • Memory 是否不足

  • Pod 是否频繁重启

  • 服务错误率是否突然升高

Loki 则可以帮助我们进一步查看:

  • 数据库连接是否失败

  • 配置文件是否缺失

  • 应用是否出现异常

  • 某个 Pod 在故障前输出了什么日志

但是,当系统由多个服务组成时,仅仅查看 Metrics 和 Logs 仍然不够。

假设一次用户请求需要经过下面几个服务:

复制代码
用户请求
   │
   ▼
Frontend
   │
   ▼
Backend
   │
   ▼
Database

现在请求耗时突然从:

复制代码
200 ms

上升到:

复制代码
2 s

Prometheus 可以发现:

复制代码
接口延迟上升

Loki 也可能找到一些相关日志。

但是我们仍然需要回答:

这 2 秒究竟消耗在哪个服务上?

是 Frontend 处理缓慢?

还是 Backend 调用了一个很慢的接口?

或者 Database 查询耗费了大量时间?

这正是分布式链路追踪需要解决的问题。

这一篇,我们继续搭建可观测体系的第三部分:

复制代码
Tracing    链路追踪    Tempo

本篇将在单节点 Kubernetes 实验环境中部署:

  • Tempo:存储和查询 Trace

  • OpenTelemetry Collector:接收、处理和转发 Trace

  • Python Demo:生成最简单的测试 Trace

  • Grafana:查询和展示完整调用链

最终形成下面这条链路:

复制代码
Demo Application
        │
        │ OTLP/gRPC 4317
        ▼
OpenTelemetry Collector
        │
        │ OTLP/gRPC 4317
        ▼
Tempo
        │
        │ HTTP 3200
        ▼
Grafana Explore

一、什么是 Trace?

一次请求进入系统后,可能会经过多个处理步骤。

例如:

复制代码
HTTP Request
     │
     ├── Validate Request
     │
     ├── Query Database
     │
     └── Build Response

在链路追踪系统中,一次完整请求通常称为:

复制代码
Trace

Trace 内部的每个处理步骤称为:

复制代码
Span

例如:

复制代码
Trace: 用户请求
│
├── Span: HTTP Request          320 ms
│
├── Span: Validate Request       10 ms
│
├── Span: Query Database        250 ms
│
└── Span: Build Response         60 ms

通过这些 Span,我们可以直观看出:

复制代码
Query Database

占用了大部分时间。

因此,Tracing 主要回答:

一次请求经过了哪些步骤?

以及:

时间究竟消耗在哪里?


二、OpenTelemetry 和 Tempo 分别负责什么?

在本实验中,OpenTelemetry 和 Tempo 承担不同的角色。

OpenTelemetry

OpenTelemetry 是一套开放的可观测标准和工具体系。

它可以处理:

复制代码
Metrics
Logs
Traces

本篇主要使用其中两个部分:

复制代码
OpenTelemetry SDK
OpenTelemetry Collector

应用通过 OpenTelemetry SDK 创建 Trace 和 Span,然后将数据发送给 OpenTelemetry Collector。

Collector 主要负责:

  • 接收应用发送的遥测数据

  • 批量处理 Trace

  • 添加或修改属性

  • 将 Trace 转发给后端存储系统

  • 将应用和具体存储后端解耦

Tempo

Tempo 是 Grafana 生态中的分布式链路追踪后端。

它主要负责:

  • 接收 Trace

  • 存储 Trace

  • 根据 Trace ID 查询

  • 根据服务名、Span 名称和属性搜索 Trace

  • 将查询结果提供给 Grafana

Tempo 支持单体和微服务两种部署模式。

对于入门、开发和小规模环境,Grafana 官方建议使用单体模式;微服务模式更加复杂,适合需要独立扩展各组件的场景。

本实验室的目标仍然是:

复制代码
单节点
+
低资源占用
+
便于学习

因此采用:

复制代码
Tempo Monolithic

也就是单体部署模式。


三、整体架构

本篇完整架构如下:

复制代码
Kubernetes Cluster

Demo Trace Application
        │
        │ OpenTelemetry SDK
        │ OTLP/gRPC :4317
        ▼
OpenTelemetry Collector
        │
        │ Batch Processor
        │ OTLP/gRPC :4317
        ▼
Tempo
        │
        │ Query API :3200
        ▼
Grafana

这里为什么不让应用直接连接 Tempo?

理论上可以:

复制代码
Application
     │
     ▼
Tempo

但是在真实系统中,更常见的结构是:

复制代码
Application
     │
     ▼
OpenTelemetry Collector
     │
     ▼
Tracing Backend

Collector 相当于应用和后端之间的统一中转站。

以后即使将 Tempo 替换成其他 Trace 后端,应用端也不一定需要修改。

Collector 还可以统一完成:

  • 数据批处理

  • 数据过滤

  • 属性补充

  • 采样

  • 多后端转发

  • 重试和队列缓冲

因此,本实验采用更加标准的结构:

复制代码
Application
     ↓
Collector
     ↓
Tempo
     ↓
Grafana

四、安装 Tempo

前面的 Prometheus 和 Grafana 已经安装在:

复制代码
monitoring

Namespace 中。

为了方便 Grafana 访问,这一篇也将 Tempo 安装在:

复制代码
monitoring

Namespace 中。

1. 添加 Grafana Helm Repository

如果前面安装 Loki 时已经添加过,可以直接执行更新:

复制代码
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

查看 Tempo Chart:

复制代码
helm search repo grafana/tempo

Grafana 提供了单体和分布式 Tempo Helm 部署方式;对于本实验,使用单体 grafana/tempo Chart 即可。

2. 准备 tempo-values.yaml

创建配置文件:

复制代码
vim tempo-values.yaml

内容如下:

复制代码
tempo:
  # 关闭匿名使用情况上报
  reportingEnabled: false

  # 开启 OTLP 接收端口
  receivers:
    otlp:
      protocols:
        grpc:
          endpoint: 0.0.0.0:4317
        http:
          endpoint: 0.0.0.0:4318

# 单节点实验环境不使用持久化存储
persistence:
  enabled: false

service:
  type: ClusterIP

这里开启了两个 OTLP 接收协议:

复制代码
4317    OTLP/gRPC
4318    OTLP/HTTP

本篇主要使用:

复制代码
4317    OTLP/gRPC

Tempo 的 OTLP Receiver 默认可能只监听本地地址,因此在 Kubernetes 中需要明确配置为:

复制代码
0.0.0.0:4317
0.0.0.0:4318

这样其他 Pod 才能通过 Kubernetes Service 访问。Tempo 官方配置文档也说明,Receiver 默认可能监听 localhost,若需要接收外部容器发送的数据,应配置监听地址。

3. 关于临时存储

这里设置了:

复制代码
persistence:
  enabled: false

这意味着 Tempo 数据只用于实验。

如果 Tempo Pod 被删除或重新创建,已经存储的 Trace 可能丢失。

因此,这套配置适用于:

  • 学习 Tempo

  • 验证 OTLP 链路

  • 测试 Grafana Trace 查询

  • 单节点可观测实验室

不适合生产环境。

生产环境通常应该使用:

  • PVC

  • Amazon S3

  • Google Cloud Storage

  • Azure Blob Storage

  • 其他对象存储

Tempo 官方也建议生产环境优先使用对象存储;本地文件系统更加适合单体测试和开发场景。

4. 安装 Tempo

执行:

复制代码
helm install tempo grafana/tempo \
  -n monitoring \
  -f tempo-values.yaml

查看 Helm Release:

复制代码
helm list -n monitoring

查看 Pod:

复制代码
kubectl get pods -n monitoring

正常情况下应该看到类似结果:

复制代码
tempo-0    1/1    Running

不同版本的 Helm Chart,Pod 名称和容器数量可能略有差异。

5. 查看 Tempo Service

执行:

复制代码
kubectl get svc -n monitoring

应该可以看到:

复制代码
tempo

进一步查看端口:

复制代码
kubectl get svc tempo -n monitoring

结果中通常可以看到:

复制代码
3200/TCP
4317/TCP
4318/TCP

它们分别对应:

复制代码
3200    Tempo HTTP 查询接口
4317    OTLP/gRPC
4318    OTLP/HTTP

Tempo 的完整 Kubernetes Service DNS 为:

复制代码
tempo.monitoring.svc.cluster.local

后续 OpenTelemetry Collector 将数据发送到:

复制代码
tempo.monitoring.svc.cluster.local:4317

6. 查看 Tempo 日志

执行:

复制代码
kubectl logs -n monitoring statefulset/tempo --tail=100

如果当前 Chart 创建的不是 StatefulSet,也可以先查看资源:

复制代码
kubectl get deployment,statefulset -n monitoring

然后根据实际资源名称查看日志。

如果 Tempo 正常启动,日志中不应该持续出现:

复制代码
address already in use

或者:

复制代码
connection refused

等错误。


五、安装 OpenTelemetry Collector

Tempo 已经能够接收 Trace,接下来部署 OpenTelemetry Collector。

Collector 官方 Helm Chart 要求显式设置运行模式,可选值包括:

复制代码
daemonset
deployment
statefulset

本篇只需要一个集中接收 Trace 的 Collector,因此使用:

复制代码
deployment

官方当前安装示例推荐使用:

复制代码
otel/opentelemetry-collector-k8s

镜像。

1. 添加 OpenTelemetry Helm Repository

执行:

复制代码
helm repo add open-telemetry \
  https://open-telemetry.github.io/opentelemetry-helm-charts

helm repo update

查看 Chart:

复制代码
helm search repo open-telemetry/opentelemetry-collector

2. 创建 observability Namespace

创建专门用于 Collector 和测试应用的 Namespace:

复制代码
kubectl create namespace observability

如果 Namespace 已经存在,会提示:

复制代码
AlreadyExists

也可以使用更加幂等的写法:

复制代码
kubectl create namespace observability \
  --dry-run=client -o yaml | kubectl apply -f -

检查:

复制代码
kubectl get namespace

应该可以看到:

复制代码
observability

六、准备 Collector 配置

创建配置文件:

复制代码
vim otel-collector-values.yaml

内容如下:

复制代码
mode: deployment

image:
  repository: otel/opentelemetry-collector-k8s

alternateConfig:
  extensions:
    health_check:
      endpoint: ${env:MY_POD_IP}:13133

  receivers:
    otlp:
      protocols:
        grpc:
          endpoint: ${env:MY_POD_IP}:4317
        http:
          endpoint: ${env:MY_POD_IP}:4318

  processors:
    memory_limiter:
      check_interval: 5s
      limit_percentage: 80
      spike_limit_percentage: 25

    batch: {}

  exporters:
    otlp/tempo:
      endpoint: tempo.monitoring.svc.cluster.local:4317

      tls:
        insecure: true

  service:
    extensions:
      - health_check

    pipelines:
      traces:
        receivers:
          - otlp

        processors:
          - memory_limiter
          - batch

        exporters:
          - otlp/tempo

ports:
  otlp:
    enabled: true

  otlp-http:
    enabled: true

  jaeger-compact:
    enabled: false

  jaeger-thrift:
    enabled: false

  jaeger-grpc:
    enabled: false

  zipkin:
    enabled: false

service:
  type: ClusterIP

resources:
  limits:
    memory: 256Mi

  requests:
    cpu: 50m
    memory: 128Mi

这里使用了:

复制代码
alternateConfig:

而不是直接使用:

复制代码
config:

原因是 OpenTelemetry Collector Chart 自带默认配置,其中包括:

  • Logs Pipeline

  • Metrics Pipeline

  • Debug Exporter

  • Jaeger Receiver

  • Zipkin Receiver

如果直接覆盖部分 config,Helm 会将自定义内容与默认内容合并。

对于本篇只处理 Trace 的最小环境来说,可能会引入不必要的配置。

alternateConfig 不与默认配置合并,可以提供一份完全独立的 Collector 配置。但使用这种方式时,必须保留健康检查扩展,否则 Chart 的 Readiness 和 Liveness Probe 可能失败。

1. Receiver

下面的配置表示 Collector 接收 OTLP 数据:

复制代码
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: ${env:MY_POD_IP}:4317
      http:
        endpoint: ${env:MY_POD_IP}:4318

支持两种协议:

复制代码
OTLP/gRPC    4317
OTLP/HTTP    4318

2. Processor

这里配置了两个 Processor:

复制代码
memory_limiter:
batch:

memory_limiter 用于限制 Collector 内存占用。

batch 会将多个 Span 组成批次后再发送,从而减少网络请求次数。

因此,处理过程大致是:

复制代码
接收 Span
    │
    ▼
检查内存限制
    │
    ▼
批量处理
    │
    ▼
发送给 Tempo

3. Exporter

下面的配置表示将 Trace 发送到 Tempo:

复制代码
exporters:
  otlp/tempo:
    endpoint: tempo.monitoring.svc.cluster.local:4317

    tls:
      insecure: true

由于 Collector 和 Tempo 都运行在同一个 Kubernetes Cluster 内部,因此可以使用 Kubernetes Service DNS:

复制代码
tempo.monitoring.svc.cluster.local

本实验没有配置 TLS,所以设置:

复制代码
insecure: true

4. Pipeline

Trace Pipeline 如下:

复制代码
pipelines:
  traces:
    receivers:
      - otlp

    processors:
      - memory_limiter
      - batch

    exporters:
      - otlp/tempo

完整的数据流为:

复制代码
OTLP Receiver
      │
      ▼
Memory Limiter
      │
      ▼
Batch Processor
      │
      ▼
OTLP Tempo Exporter

七、安装 OpenTelemetry Collector

执行:

复制代码
helm install otel-collector \
  open-telemetry/opentelemetry-collector \
  -n observability \
  -f otel-collector-values.yaml

查看 Helm Release:

复制代码
helm list -n observability

1. 查看 Pod

执行:

复制代码
kubectl get pods -n observability

正常情况下可以看到类似结果:

复制代码
otel-collector-opentelemetry-collector-xxxxxxxxxx-xxxxx   1/1   Running

2. 查看 Deployment

执行:

复制代码
kubectl get deployment -n observability

应该可以看到:

复制代码
otel-collector-opentelemetry-collector

3. 查看 Service

执行:

复制代码
kubectl get svc -n observability

应该可以看到:

复制代码
otel-collector-opentelemetry-collector

查看具体端口:

复制代码
kubectl get svc \
  otel-collector-opentelemetry-collector \
  -n observability

应该包含:

复制代码
4317/TCP
4318/TCP

完整 Service DNS 为:

复制代码
otel-collector-opentelemetry-collector.observability.svc.cluster.local

4. 查看 Collector 日志

执行:

复制代码
kubectl logs \
  -n observability \
  deployment/otel-collector-opentelemetry-collector \
  --tail=100

不同版本的 Collector 日志格式可能略有差异。

正常情况下应该能够看到 OTLP Receiver 和健康检查扩展启动,并且不应该持续出现:

复制代码
connection refused

或者:

复制代码
failed to export

等错误。

此时链路的基础设施部分已经完成:

复制代码
OpenTelemetry Collector
          │
          ▼
        Tempo
          │
          ▼
        Grafana

但是目前还没有应用产生 Trace。

接下来部署一个最小 Python 应用。


八、创建最小 Trace Demo

为了避免一次部署复杂的 OpenTelemetry Demo 系统,本篇只创建一个最简单的 HTTP 应用。

每次访问:

复制代码
/

应用会生成一个 Trace,其中包含三个 Span:

复制代码
http-request
│
├── step-1
└── step-2

两个子步骤分别模拟:

复制代码
step-1    100 ms
step-2    200 ms

通过这个简单例子,可以直观看到:

  • Trace

  • Root Span

  • Child Span

  • Span Duration

  • Span Attribute

  • Service Name

1. 创建 demo-config.yaml

创建文件:

复制代码
vim demo-config.yaml

内容如下:

复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: demo-trace-app
  namespace: observability

data:
  app.py: |
    import os
    import time

    from flask import Flask

    from opentelemetry import trace
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
    from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
        OTLPSpanExporter,
    )

    app = Flask(__name__)

    service_name = os.getenv(
        "OTEL_SERVICE_NAME",
        "demo-trace"
    )

    otlp_endpoint = os.getenv(
        "OTEL_EXPORTER_OTLP_ENDPOINT",
        "otel-collector-opentelemetry-collector."
        "observability.svc.cluster.local:4317"
    )

    resource = Resource.create({
        "service.name": service_name,
        "deployment.environment": "k8s-lab",
        "service.version": "1.0.0"
    })

    provider = TracerProvider(
        resource=resource
    )

    exporter = OTLPSpanExporter(
        endpoint=otlp_endpoint,
        insecure=True
    )

    processor = BatchSpanProcessor(
        exporter
    )

    provider.add_span_processor(
        processor
    )

    trace.set_tracer_provider(
        provider
    )

    tracer = trace.get_tracer(
        "demo-trace"
    )

    @app.route("/")
    def hello():
        with tracer.start_as_current_span(
            "http-request"
        ) as span:

            span.set_attribute(
                "demo.tag",
                "v1"
            )

            span.set_attribute(
                "http.route",
                "/"
            )

            with tracer.start_as_current_span(
                "step-1"
            ):
                time.sleep(0.1)

            with tracer.start_as_current_span(
                "step-2"
            ):
                time.sleep(0.2)

        return "hello otel trace\n"

    @app.route("/slow")
    def slow():
        with tracer.start_as_current_span(
            "slow-request"
        ) as span:

            span.set_attribute(
                "demo.slow",
                True
            )

            with tracer.start_as_current_span(
                "slow-operation"
            ):
                time.sleep(1)

        return "slow trace generated\n"

    if __name__ == "__main__":
        app.run(
            host="0.0.0.0",
            port=8080
        )

这个程序显式设置了几个 Resource Attribute:

复制代码
service.name
deployment.environment
service.version

其中最重要的是:

复制代码
service.name = demo-trace

后续 Grafana 将根据这个属性查询服务。

应用 ConfigMap:

复制代码
kubectl apply -f demo-config.yaml

检查:

复制代码
kubectl get configmap -n observability

应该可以看到:

复制代码
demo-trace-app

九、部署 Demo Application

创建:

复制代码
vim demo-app.yaml

内容如下:

复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-trace
  namespace: observability

spec:
  replicas: 1

  selector:
    matchLabels:
      app: demo-trace

  template:
    metadata:
      labels:
        app: demo-trace

    spec:
      containers:
        - name: demo-trace
          image: python:3.11-slim

          command:
            - sh
            - -c

          args:
            - |
              pip install --no-cache-dir \
                flask \
                opentelemetry-api \
                opentelemetry-sdk \
                opentelemetry-exporter-otlp &&

              echo "Starting demo trace application" &&

              python /app/app.py

          env:
            - name: OTEL_SERVICE_NAME
              value: demo-trace

            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: >-
                otel-collector-opentelemetry-collector.observability.svc.cluster.local:4317

          ports:
            - name: http
              containerPort: 8080

          readinessProbe:
            httpGet:
              path: /
              port: 8080

            initialDelaySeconds: 3
            periodSeconds: 5

          resources:
            requests:
              cpu: 50m
              memory: 64Mi

            limits:
              memory: 256Mi

          volumeMounts:
            - name: app
              mountPath: /app

      volumes:
        - name: app
          configMap:
            name: demo-trace-app

应用:

复制代码
kubectl apply -f demo-app.yaml

查看 Deployment:

复制代码
kubectl get deployment -n observability

查看 Pod:

复制代码
kubectl get pods -n observability

刚开始可能会看到:

复制代码
ContainerCreating

随后进入:

复制代码
Running

由于容器启动时需要执行:

复制代码
pip install

第一次启动可能需要等待一段时间。

查看应用日志:

复制代码
kubectl logs \
  -n observability \
  deployment/demo-trace \
  -f

正常情况下应该可以看到:

复制代码
Starting demo trace application
Running on http://0.0.0.0:8080

十、访问 Demo 并生成 Trace

为了从 Kubernetes Node 访问 Demo,先安装socat,执行:

复制代码
sudo apt update
sudo apt install -y socat

验证:

复制代码
command -v socat

应该输出:

复制代码
/usr/bin/socat

然后使用:

复制代码
kubectl port-forward \
  -n observability \
  deployment/demo-trace \
  8080:8080

这里直接使用:

复制代码
deployment/demo-trace

而不是填写具体 Pod 名称。

因为 Pod 名称包含随机字符串,例如:

复制代码
demo-trace-5ffd95c654-hs448

Pod 重新创建后名称会发生变化,而 Deployment 名称保持稳定。

端口转发成功后,会看到类似输出:

复制代码
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

保持这个终端不关闭。

重新打开另一个终端,多访问几次:

复制代码
curl http://localhost:8080
curl http://localhost:8080
curl http://localhost:8080

输出:

复制代码
hello otel trace

再访问慢请求:

复制代码
curl http://localhost:8080/slow

输出:

复制代码
slow trace generated

每执行一次请求,应用都会生成一条新的 Trace。

此时完整链路为:

复制代码
curl
  │
  ▼
Demo Application
  │
  │ OpenTelemetry SDK
  ▼
OpenTelemetry Collector
  │
  │ OTLP/gRPC
  ▼
Tempo

正常使用 kubectl port-forward 时,一般不需要额外安装 socat

只有在特定旧环境或特殊转发工具明确报告缺少 socat 时,才需要单独处理。


十一、在 Grafana 中配置 Tempo

进入 Grafana。

依次打开:

复制代码
Connections
    ↓
Add new connection
    ↓
Tempo
    ↓
Add new data source

填写 URL:

复制代码
http://tempo.monitoring.svc.cluster.local:3200

这里使用的是:

复制代码
Tempo HTTP Query API

而不是 OTLP 数据接收端口。

两者的作用不同:

复制代码
4317    Collector 向 Tempo 写入 Trace
3200    Grafana 从 Tempo 查询 Trace

本实验中的 Grafana 和 Tempo 都运行在 Kubernetes Cluster 内部,因此 Grafana 可以通过 Kubernetes Service DNS 访问 Tempo。

点击:

复制代码
Save & Test

如果看到类似提示:

复制代码
Data source is working

说明 Grafana 已经能够查询 Tempo。


十二、在 Grafana 中查询 Trace

进入:

复制代码
Explore

选择数据源:

复制代码
Tempo

可以在查询界面中选择:

复制代码
Service Name

然后选择:

复制代码
demo-trace

点击:

复制代码
Run query

应该可以看到刚才生成的 Trace。

注意:这里的Limit默认是20,查询的Trace可能较少,可以自行改大。

2. 使用 TraceQL 查询

也可以切换到 TraceQL 模式,输入:

复制代码
{ resource.service.name = "demo-trace" }

这里需要注意,TraceQL 的完整查询需要使用花括号。

不是:

复制代码
service.name = "demo-trace"

而是:

复制代码
{ resource.service.name = "demo-trace" }

3. 查询普通请求

查询 Root Span:

复制代码
{ name = "http-request" }

4. 查询慢请求

查询:

复制代码
{ name = "slow-request" }

或者根据自定义属性查询:

复制代码
{ span.demo.slow = true }

5. 根据环境查询

查询:

复制代码
{
  resource.deployment.environment = "k8s-lab"
}

6. 根据版本查询

查询:

复制代码
{
  resource.service.version = "1.0.0"
}

十三、查看完整 Trace

点击其中一条 Trace,可以看到类似结构:

复制代码
demo-trace: http-request
│
├── http-request       300 ms
│
├── step-1             100 ms
│
└── step-2             200 ms

其中:

复制代码
http-request

是 Root Span。

下面两个是 Child Span:

复制代码
step-1
step-2

可以看到:

复制代码
step-1    大约 100 ms
step-2    大约 200 ms

对于慢请求,则可以看到:

复制代码
slow-request
└── slow-operation    大约 1 s

这就是链路追踪最核心的能力:

将一次请求拆分成多个步骤,并显示每个步骤的耗时。

如果真实系统中出现延迟,就可以判断时间主要消耗在:

  • HTTP 请求处理

  • 数据库查询

  • 外部接口调用

  • 消息队列

  • 缓存访问

  • 业务计算


十四、Trace、Span 和上下文传播

在本篇 Demo 中,我们创建了一个父 Span:

复制代码
with tracer.start_as_current_span(
    "http-request"
):

然后在父 Span 内创建两个子 Span:

复制代码
with tracer.start_as_current_span(
    "step-1"
):

以及:

复制代码
with tracer.start_as_current_span(
    "step-2"
):

OpenTelemetry 会自动识别当前上下文,因此形成:

复制代码
http-request
│
├── step-1
└── step-2

这就是 Span 上下文传播。

如果没有正确传播上下文,可能会变成三条互不相关的 Trace:

复制代码
Trace A    http-request

Trace B    step-1

Trace C    step-2

而不是一条完整调用链。

在真正的分布式系统中,上下文还需要通过 HTTP Header 在服务之间传播。

常见 Header 为:

复制代码
traceparent

例如:

复制代码
Frontend
    │
    │ traceparent
    ▼
Backend
    │
    │ traceparent
    ▼
Database Service

这样不同服务产生的 Span 才能被组合成同一条 Trace。

本篇 Demo 只在一个进程内部创建父子 Span,因此 OpenTelemetry SDK 可以自动处理上下文。


十五、为什么使用 Collector,而不是直接写入 Tempo?

现在的链路是:

复制代码
Application
     │
     ▼
Collector
     │
     ▼
Tempo

看起来比直接连接 Tempo 多了一层。

但是 Collector 带来了几个重要好处。

1. 应用与存储后端解耦

应用只需要知道:

复制代码
OTLP Endpoint

不需要了解 Tempo 的具体实现。

以后后端变更时,可以只修改 Collector。

2. 批量发送

Collector 使用:

复制代码
batch processor

将多个 Span 合并后发送,减少网络请求次数。

3. 数据处理

Collector 可以在发送前:

  • 删除敏感字段

  • 添加 Kubernetes Metadata

  • 修改属性

  • 丢弃无用数据

  • 对 Trace 进行采样

4. 多后端输出

同一份 Trace 可以同时发送到多个系统:

复制代码
Application
      │
      ▼
Collector
   │      │
   ▼      ▼
Tempo   Another Backend

5. 统一入口

多个应用可以统一发送到 Collector:

复制代码
Frontend ──┐
Backend  ──┼──► Collector ──► Tempo
Worker   ──┘

应用不需要分别维护后端连接。


十六、清理测试资源

本篇创建的:

复制代码
demo-trace ConfigMap
demo-trace Deployment

主要用于生成测试 Trace。

确认已经能够在 Grafana 中查询 Trace 后,可以删除:

复制代码
kubectl delete -f demo-app.yaml
kubectl delete -f demo-config.yaml

检查:

复制代码
kubectl get pods -n observability

此时应该只保留 OpenTelemetry Collector。

Collector 和 Tempo 可以继续保留,因为后续还可以用于:

  • 接入真实应用

  • 演示跨服务 Trace

  • 将 Trace 与 Logs 关联

  • 将 Trace 与 Metrics 关联

  • 进行故障注入实验

如果希望删除整个 Collector 和 Demo 环境,可以执行:

复制代码
helm uninstall otel-collector \
  -n observability

kubectl delete namespace observability

如果还需要删除 Tempo:

复制代码
helm uninstall tempo \
  -n monitoring

不过本系列后续还会继续使用 Tempo,因此暂时建议保留:

复制代码
Tempo
OpenTelemetry Collector
Grafana

本系列采用的资源管理原则仍然是:

复制代码
基础设施组件:继续保留
临时测试业务:验证完成后删除

这样既能保持实验连续性,也可以避免无用 Pod 不断堆积。


十七、小结:Metrics、Logs 和 Tracing

到这里,单节点 Kubernetes 可观测实验室已经完成了三项核心能力。

Metrics

复制代码
Kubernetes Metrics
        │
        ▼
Prometheus
        │
        ▼
Grafana

Metrics 主要回答:

复制代码
系统发生了什么?

例如:

  • CPU 是否过高

  • Memory 是否不足

  • Pod 是否频繁重启

  • 请求延迟是否上升

Logs

复制代码
Pod stdout / stderr
        │
        ▼
Fluent Bit
        │
        ▼
Loki
        │
        ▼
Grafana

Logs 主要回答:

复制代码
为什么会出现问题?

例如:

  • 数据库连接失败

  • 配置文件缺失

  • 权限错误

  • 应用异常退出

Tracing

复制代码
Application
        │
        ▼
OpenTelemetry Collector
        │
        ▼
Tempo
        │
        ▼
Grafana

Tracing 主要回答:

复制代码
问题发生在哪个调用步骤?

例如:

  • 哪个服务最慢

  • 哪次数据库查询耗时过长

  • 请求经过了哪些服务

  • 某个步骤花费了多长时间

将三者放在一起:

复制代码
Metrics
   +
Logs
   +
Tracing

就形成了一个相对完整的可观测体系。

可以简单理解为:

复制代码
Metrics    发现问题
Logs       解释问题
Tracing    定位问题

现在,我们的单节点 Kubernetes 实验室已经具备:

复制代码
Prometheus
Grafana
Loki
Fluent Bit
Tempo
OpenTelemetry Collector

完整链路为:

复制代码
Metrics ──► Prometheus ──┐
                         │
Logs ─────► Loki ────────┼──► Grafana
                         │
Traces ───► Tempo ───────┘

当然,目前三种数据仍然相对独立。

真正更有价值的可观测体验,是将它们关联起来:

复制代码
从 Metrics 发现延迟升高
        │
        ▼
进入对应 Trace
        │
        ▼
定位最慢 Span
        │
        ▼
查看相关 Pod 日志

这也是后续可以继续完善的方向。

相关推荐
wsad05321 小时前
Docker 网络故障排查记:当 bridge 模式失效时,host 模式如何救场
运维·docker·容器
张毅2004-10-101 小时前
docker详解:安装docker,docker架构,docker镜像、容器、网络、存储,容器监控,容器日志,综合实验
linux·运维·docker·云原生·云计算
小狼嚎月2 小时前
K8s PV / PVC / StorageClass 有状态应用持久化
云原生·容器·kubernetes
AAA@峥3 小时前
K8s 资源混乱怎么办?Namespace 隔离 + 上下文切换实战
容器·贪心算法·kubernetes
重庆小透明3 小时前
深入探寻微服务【第一篇微服务的坏】
微服务·云原生·架构
Zhu7588 小时前
在k8s集群部署MySQL单实例,支持多个主流发行版
android·mysql·kubernetes
张文君11 小时前
docker使用代理拉取镜像
运维·docker·容器
2401_8346369912 小时前
保姆级 Kubernetes 部署教程|从原理到三节点集群落地
云原生·容器·kubernetes
陌路2012 小时前
Docker教程从入门到精通(0基础)
运维·docker·容器