在 Kubernetes 中构建统一的 OpenTelemetry 流水线

译文 2025-11-17 11:43:27

去年发生一起生产环境故障时,我打开了所有标准工具来调试支付失败问题。Grafana 显示 CPU 峰值,CloudWatch 中的日志分散在三个服务中,Jaeger 展示了 50 条外观相似的追踪数据。二十分钟过去了,我依然无法回答一个基本问题:“哪条追踪数据对应实际失败的请求?” 告警提示我们支付功能已中断,日志显示错误信息,追踪数据也存在,但这些数据之间毫无关联。最后,我只能在日志组中逐个搜索请求 ID,才找到问题根源。

问题不在于工具或数据——我们两者都不缺,而在于“关联”的缺失。在之前关于 kube-prometheus-stack 的文章中,我解释过为何监控仪表盘不等于可观测性;本文将展示如何借助 OpenTelemetry 真正实现可观测性:你将搭建一条统一的流水线,让指标、日志和追踪数据在共享上下文的环境中流转,只需几秒(而非几小时)就能从告警定位到具体失败的追踪数据。

OpenTelemetry 解决了厂商锁定(及其他一系列问题)

OpenTelemetry 是 CNCF(云原生计算基金会)的毕业项目,提供与厂商无关的 instrumentation 库和 Collector(收集器),能够大规模接收、处理和导出遥测数据。你无需将应用代码与特定厂商绑定,只需通过 OTel SDK 完成一次埋点,即可将遥测数据路由到任意所需的后端。

在一个自由职业项目中,我曾将系统从 kube-prometheus-stack 迁移到 OTel。当时我们需要自定义指标、日志和追踪数据,而“厂商锁定”是核心顾虑:kube-prometheus-stack 虽能处理基础的 Prometheus 指标,但要添加分布式追踪,就必须额外集成独立系统;且厂商服务的成本会迅速攀升。

借助 OTel,我只需对应用进行一次埋点,就能灵活评估不同后端——无需修改代码。我们最初使用自托管的 Grafana,两周后测试某商业厂商服务时,仅修改了 Collector 的导出器配置,未对应用做任何调整。这种灵活性正是 OTel 的核心优势。

但“厂商灵活性”并非最主要的价值,OTel 真正的价值在于集中式增强与关联:所有经过 Collector 的信号(指标、日志、追踪数据)都会附加相同的 Kubernetes 元数据(Pod、命名空间、团队注解)、遵循相同的采样规则、共享相同的追踪上下文。这意味着日志中的 service.nametrace_id 与追踪数据一致,而追踪数据的属性又与指标属性匹配。

当所有数据共享上下文时,故障排查过程中就能在不同信号间自由切换,无需手动关联时间戳或靠猜测定位问题。

Collector 的三种部署方式

大多数团队在部署 Collector 时存在误区:要么给每个 Pod 都部署 Sidecar(边车),导致 YAML 配置爆炸;要么用 DaemonSet 部署到所有节点,却疑惑为何节点会内存耗尽。

OTel Collector 可通过 Sidecar、DaemonSet 或集中式网关(Gateway)三种模式部署,每种模式各有取舍:

部署模式 描述 优势 劣势 适用场景
Sidecar(边车) 每个 Pod 部署一个 Collector 容器 隔离性强、支持按服务控制、延迟最低 每个工作负载需额外 YAML 配置、配置缩放难度高 高安全性工作负载或对延迟敏感的应用
DaemonSet(代理) 每个节点部署一个 Collector 运维简单、可收集主机+Pod 遥测数据、配置清单少 处理能力受节点 CPU/内存限制、不适合重处理 集群级广泛覆盖、轻量数据转换场景
Gateway(部署) 集中式 Collector 服务 配置集中、支持重处理、易于扇出(fan-out) 多一次网络跳转、存在潜在瓶颈 集中式策略管控、采样、多后端路由

我采用的方案是:在每个节点用 DaemonSet 部署代理(Agent)负责数据收集,同时部署一个 Gateway 负责数据处理。Agent 将原始信号转发到 Gateway,由 Gateway 执行数据增强、采样和路由。这种架构能避免节点资源过载(许多团队因在 DaemonSet 代理中执行重处理导致节点内存耗尽,务必避免此做法),同时将复杂配置集中在一处管理。

在 Kubernetes 中安装 OpenTelemetry Operator

你可以通过原始配置清单部署 Collector,但 OpenTelemetry Operator 提供了 OpenTelemetryCollector CRD(自定义资源定义),能自动处理服务发现和 RBAC(基于角色的访问控制)。该 Operator 需要 cert-manager 来支持准入 Webhook,部署步骤如下:

  1. 先安装 cert-manager

    kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.0/cert-manager.yaml
    

    等待约一分钟,确保 cert-manager 就绪。

  2. 安装 OpenTelemetry Operator

    helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
    helm repo update
    helm install opentelemetry-operator open-telemetry/opentelemetry-operator \
      --namespace opentelemetry-operator \
      --create-namespace \
      --set manager.replicas=2
    
  3. 验证安装结果

    kubectl -n opentelemetry-operator get pods
    

    其中 manager.replicas=2 是为了确保高可用。安装完成后,你只需定义 Collector 自定义资源,Operator 会自动创建所需的其他资源。

备选方案:通过 opentelemetry-kube-stack Helm 图表安装

若你倾向于用单个 Helm 图表集成常用组件,可选择 opentelemetry-kube-stack 图表。它能快速搭建集群级遥测基础,适合作为初始方案;后续若需更精细的控制,再将配置拆分到由 Operator 管理的独立 Collector 即可。

helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update
helm install otel-kube-stack open-telemetry/opentelemetry-kube-stack \
  --namespace observability \
  --create-namespace

查看图表的配置选项和结构:opentelemetry-kube-stack

OpenTelemetry Gateway Collector 配置

Gateway 接收基于 OTLP(OpenTelemetry 协议)的信号——OTLP 是标准的传输协议,支持通过 gRPC(4317 端口)或 HTTP(4318 端口)承载指标、日志和追踪数据。Gateway 的核心功能是为信号附加 Kubernetes 元数据、执行智能采样,并将数据导出到后端。其中关键组件是 k8sattributes 处理器,配置如下:

processors:
  k8sattributes:
    auth_type: serviceAccount
    extract:
      metadata: [k8s.namespace.name, k8s.pod.name, k8s.deployment.name]
      annotations:
        - tag_name: team
          key: team
          from: pod
        - tag_name: runbook.url
          key: runbook-url
          from: pod

该配置会自动为所有信号附加 Kubernetes 元数据,并从 Pod 注解中提取自定义字段(如 teamrunbook-url),使每条追踪数据、指标和日志都包含“服务归属”信息和故障修复文档链接。在故障排查时,这能帮你省去在 Wiki 或 Slack 中查找“服务负责人”的时间。

采样配置:基于尾部的智能采样

我采用尾部采样(tail-based sampling) 策略,保留 100% 的错误请求和慢请求数据。若你的应用每天处理 1000 万次请求,若存储所有追踪数据,会迅速耗尽存储资源并影响查询性能;采样则能在保留关键数据的同时减少无效存储。

基础的概率采样(如随机保留 10% 数据)存在缺陷:它对所有追踪数据一视同仁,可能会遗漏关键的错误追踪。而尾部采样更智能:它会等待追踪数据完整生成后,再根据规则决定是否保留,配置如下:

tail_sampling:
  decision_wait: 10s  # 等待 10 秒确保追踪数据完整
  policies:
    - name: errors-first
      type: status_code
      status_code: {status_codes: [ERROR]}  # 保留 100% 错误请求
    - name: slow-requests
      type: latency
      latency: {threshold_ms: 2000}  # 保留 100% 耗时超 2 秒的请求
    - name: probabilistic-sample
      type: probabilistic
      probabilistic: {sampling_percentage: 10}  # 其他请求保留 10%

这种配置能确保关键问题(错误、慢请求)的可见性,同时减少 80%-90% 的存储消耗。建议初始采样率设为 10%,后续根据查询模式为高价值流程适当提高采样率。

内存限制配置

memory_limiter 处理器能在内存使用率接近阈值时,通过“背压”(back-pressure)机制限制接收器(receiver)的数据接收,避免 Collector 因内存溢出(OOM)被杀死:

memory_limiter:
  check_interval: 1s  # 每秒检查一次内存使用
  limit_mib: 3072     # 内存上限 3GB
  spike_limit_mib: 800  # 内存峰值上限 800MB

部署 Gateway

完整的 Gateway 配置(包含所有接收器、导出器和资源限制)可在 gateway.yaml 中查看,部署命令如下:

kubectl create namespace observability  # 创建命名空间
kubectl apply -f gateway.yaml          # 部署 Gateway
kubectl -n observability get pods -l app.kubernetes.io/name=otel-gateway  # 验证部署

若需通过 prometheusremotewrite 导出器将指标导出到 Prometheus,需确保 Prometheus 启动时添加参数 --web.enable-remote-write-receiver
备选方案:直接对接原生支持远程写入的后端(如 Grafana Mimir、Cortex、Thanos),或使用 Collector 的 prometheus 导出器,再配置 Prometheus 抓取 Collector 的数据。

DaemonSet 代理(Agent)配置

Agent 的配置需保持精简,仅负责“接收-批量处理-转发”流程,避免占用过多节点资源:

spec:
  mode: daemonset  # 以 DaemonSet 模式部署
  config: |
    receivers:
      # 接收 OTLP 信号(HTTP 和 gRPC 协议)
      otlp:
        protocols: {http: {endpoint: 0.0.0.0:4318}, grpc: {endpoint: 0.0.0.0:4317}}
      # 收集主机指标(CPU、内存、磁盘、网络)
      hostmetrics:
        scrapers: [cpu, memory, disk, network]
    
    processors:
      # 批量处理数据,减少网络请求次数
      batch: {timeout: 5s}
      # 内存限制,避免节点资源过载
      memory_limiter:
        check_interval: 1s
        limit_mib: 400
        spike_limit_mib: 100
    
    exporters:
      # 将数据转发到 Gateway
      otlp:
        endpoint: otel-gateway.observability.svc.cluster.local:4317
    
    service:
      pipelines:
        # 追踪数据流水线:接收 → 内存限制 → 批量处理 → 转发到 Gateway
        traces: {receivers: [otlp], processors: [memory_limiter, batch], exporters: [otlp]}
        # 指标流水线:接收(OTLP + 主机指标)→ 内存限制 → 批量处理 → 转发
        metrics: {receivers: [otlp, hostmetrics], processors: [memory_limiter, batch], exporters: [otlp]}
        # 日志流水线:接收 → 内存限制 → 批量处理 → 转发
        logs: {receivers: [otlp], processors: [memory_limiter, batch], exporters: [otlp]}    

Agent 会收集主机指标和 Pod 发送的 OTLP 信号,批量处理后转发到 Gateway。完整配置可参考 agent.yaml,部署命令如下:

kubectl apply -f agent.yaml  # 部署 Agent
# 验证部署(-o wide 可查看 Agent 所在节点)
kubectl -n observability get pods -l app.kubernetes.io/name=otel-agent -o wide

应用埋点(Instrumentation)

要让 Collector 正常工作,应用需主动输出遥测信号。以下是一个基于 Python Flask 框架的应用示例,已集成 OpenTelemetry 追踪功能:

from flask import Flask, request
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.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor

# 定义资源属性(所有遥测信号共享的上下文)
resource = Resource.create({
    "service.name": "checkout-service",  # 服务名(关键关联字段)
    "service.version": "v1.0.0",         # 服务版本
    "deployment.environment": "production",  # 部署环境
    "team": "payments",                  # 负责团队
})

# 初始化追踪提供者
trace_provider = TracerProvider(resource=resource)
# 配置 OTLP 导出器,将追踪数据发送到 Agent
otlp_exporter = OTLPSpanExporter(
    endpoint="http://otel-agent.observability.svc.cluster.local:4318/v1/traces"
)
# 添加批量 span 处理器(减少网络开销)
trace_provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
# 设置全局追踪提供者
trace.set_tracer_provider(trace_provider)

# 初始化 Flask 应用并自动埋点
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)  # 自动捕获 Flask 请求的追踪数据

#  checkout 接口示例
@app.route("/checkout", methods=["POST"])
def checkout():
    # 创建自定义 span(追踪 checkout 业务逻辑)
    with trace.get_tracer(__name__).start_as_current_span("checkout") as span:
        # 添加业务属性(如用户 ID),便于故障排查
        span.set_attribute("user.id", request.json.get("user_id"))
        # 业务逻辑(此处省略)
        return {"status": "success"}, 200

部署应用时添加注解

部署应用的 Pod 时,需添加注解以便 Collector 自动发现和关联元数据:

metadata:
  annotations:
    team: "payments"  # 与应用埋点中的 team 属性一致
    runbook-url: "https://runbooks.internal/payments/checkout"  # 故障修复文档链接

此时,每条追踪数据都会包含 service.nameteamrunbook.url。故障排查时,你可在 Grafana 中按“团队”筛选数据,并直接访问修复文档。

关联是核心:打通指标、日志与追踪数据

统一流水线的价值,仅在故障排查时能“跨信号导航”才得以体现:当你看到“错误率过高”的告警,需要查看对应服务的日志,再定位到具体失败的追踪数据。若缺乏关联,你只能在三个工具中手动匹配时间戳,靠猜测寻找正确请求;而有了完善的关联,只需点击几次就能从告警跳转到日志再到追踪数据。

要实现这一点,需满足三个条件:

  1. 所有信号(指标、日志、追踪)共享一致的资源属性(如 service.nameteam);
  2. 每条日志都注入追踪上下文(trace_idspan_id);
  3. 在 Grafana 中配置数据源关联,支持跨信号跳转。

为日志注入追踪上下文

通过 OTel SDK 可将 trace_idspan_id 注入日志,示例代码如下(基于上述 Flask 应用扩展):

import logging
from flask import Flask, request
from opentelemetry import trace
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter

# 复用之前定义的资源(确保与追踪数据的上下文一致)
# resource = Resource.create({"service.name": "checkout-service", ...})

# 初始化日志导出器(发送日志到 Agent)
log_exporter = OTLPLogExporter(
    endpoint="http://otel-agent.observability.svc.cluster.local:4318/v1/logs"
)
# 初始化日志提供者
logger_provider = LoggerProvider(resource=resource)
logger_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter))

# 配置 Python 标准日志模块,注入 OTel 上下文
handler = LoggingHandler(logger_provider=logger_provider)
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)

# 复用之前的追踪器
tracer = trace.get_tracer(__name__)

@app.route("/checkout", methods=["POST"])
def checkout():
    with tracer.start_as_current_span("checkout") as span:
        # 获取当前 span 的上下文(包含 trace_id 和 span_id)
        span_context = trace.get_current_span().get_span_context()
        # 记录日志时注入 trace_id 和 span_id
        logging.info(
            "Processing checkout",  # 日志消息
            extra={
                "trace_id": format(span_context.trace_id, "032x"),  # 格式化 trace_id(32 位十六进制)
                "span_id": format(span_context.span_id, "016x"),    # 格式化 span_id(16 位十六进制)
                "user_id": request.json.get("user_id")              # 业务属性(用户 ID)
            }
        )
        # 业务逻辑(此处省略)
        return {"status": "success"}, 200

配置完成后,你可在 Grafana 中搭建这样的仪表盘:展示 Prometheus 错误率告警、按 service.nametrace_id 筛选的 Loki 日志,以及展示完整请求流程的 Tempo 追踪数据。点击告警 → 查看日志 → 跳转追踪数据,故障排查时间可从几小时缩短到几分钟。

生产环境前的验证

部署到生产环境前,需通过冒烟测试验证流水线是否正常工作:

  1. 端口转发 Gateway 的 4318 端口(用于发送测试追踪数据):

    kubectl -n observability port-forward svc/otel-gateway 4318:4318
    
  2. 发送测试追踪数据(模拟应用输出的 OTLP 信号):

    curl -X POST http://localhost:4318/v1/traces \
      -H "Content-Type: application/json" \
      -d '{
        "resourceSpans": [{
          "resource": {"attributes": [{"key": "service.name", "value": {"stringValue": "test-service"}}]},
          "scopeSpans": [{
            "spans": [{
              "traceId": "5b8aa5a2d2c872e8321cf37308d69df2",
              "spanId": "051581bf3cb55c13",
              "name": "test-span",
              "kind": 1,
              "startTimeUnixNano": "1609459200000000000",
              "endTimeUnixNano": "1609459200500000000"
            }]
          }]
        }]
      }'
    
  3. 验证追踪数据是否到达后端(以 Tempo 为例,端口转发后查询):

    kubectl -n monitoring port-forward svc/tempo 3100:3100
    curl http://localhost:3100/api/search?q=test-service  # 搜索 test-service 的追踪数据
    

若指标、日志和追踪数据均能到达对应后端,说明流水线正常。包含完整指标、日志和追踪数据验证的脚本可参考 test-pipeline.sh

运维与扩展

需将 OTel Collector 配置视为应用代码进行管理:将配置清单存储在 Git 中,配置变更需通过 PR 审批,按“开发环境→预发环境→生产环境”的顺序部署,并对 Collector 的健康状态(队列大小、数据丢弃率、CPU/内存使用率)设置告警。

为 Gateway 配置水平自动扩缩容(HPA)

基于 CPU 使用率为 Gateway 配置 HPA,确保高负载时能自动扩容:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: otel-gateway
  namespace: observability
spec:
  scaleTargetRef:  # 目标扩容对象(Gateway Deployment)
    apiVersion: apps/v1
    kind: Deployment
    name: otel-gateway
  minReplicas: 2    # 最小副本数(确保高可用)
  maxReplicas: 10   # 最大副本数
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70  # CPU 使用率阈值 70%

监控 Collector 关键指标

Collector 会在 :8888/metrics 端口暴露自身指标,需重点监控以下指标并设置告警:

  • otelcol_receiver_accepted_spans:接收器已接收的 span 数量
  • otelcol_receiver_refused_spans:接收器拒绝的 span 数量(需告警)
  • otelcol_exporter_sent_spans:导出器已发送的 span 数量
  • otelcol_exporter_send_failed_spans:导出器发送失败的 span 数量(需告警)
  • otelcol_processor_batch_batch_send_size:批量处理器的发送大小(监控批量效率)

若“拒绝数”或“发送失败数”突增,需及时排查 Collector 资源限制、后端连接或配置问题。

Grafana 跨信号导航配置

要实现“指标→日志→追踪数据”的点击跳转,需在 Grafana 中配置数据源关联。以下是关键配置步骤:

1. Tempo(追踪)→ Loki(日志)关联

在 Tempo 数据源中添加“追踪到日志”的链接,点击追踪数据时可直接查看对应日志:

{
  "datasourceUid": "loki-uid",  // Loki 数据源的 UID
  "tags": [{"key": "service.name", "value": "service_name"}],  // 共享 service.name 属性
  "query": "{service_name=\"${__field.labels.service_name}\"} |~ \"${__span.traceId}\""  // Loki 查询语句(按服务名和 trace_id 筛选)
}

2. Loki(日志)→ Tempo(追踪)关联

在 Loki 数据源中添加“日志到追踪”的链接,点击日志中的 trace_id 可直接跳转追踪数据:

{
  "datasourceUid": "tempo-uid",  // Tempo 数据源的 UID
  "field": "trace_id",           // 日志中存储 trace_id 的字段
  "url": "/explore?left={\"datasource\":\"tempo-uid\",\"queries\":[{\"query\":\"${__value.raw}\"}]}"  // 跳转 URL
}

配置完成后,在 Tempo 中查看追踪数据时,可点击“Logs for this trace”查看所有相关日志;在 Loki 中查看日志时,点击 trace_id 字段可直接打开对应的 Tempo 追踪数据。

迁移后的实际收益

去年我主导了一个生产环境迁移项目,将三个独立的代理(Prometheus 导出器、Fluentd、Jaeger 代理)整合为单一的 OTel 流水线。三个月后,我们观察到以下显著收益:

  • 故障排查时间:中位数从 90 分钟缩短至 25 分钟,工程师无需在四个工具间切换,一个带跨链接的 Grafana 仪表盘即可满足需求。
  • 存储成本:追踪采样使存储消耗减少 85%,且未损失调试能力。
  • 运维效率:每条信号都包含 teamrunbook.url 属性,彻底消除了“谁负责这个服务?”的疑问。

最大的收益并非技术层面,而是运维流程的优化:值班工程师无需猜测,只需遵循固定路径排查问题——告警 → 仪表盘 → 追踪数据 → 修复文档。当你能从 Prometheus 告警一键跳转到 Loki 日志,再定位到 Tempo 中具体失败的追踪数据时,可观测性就从“理论概念”变成了“实用工具”。

下一步建议

  1. 先部署 Gateway 和 Agent,完成基础流水线搭建;
  2. 选择一个核心服务进行埋点,验证遥测数据的收集与关联;
  3. 在 Grafana 中配置跨信号导航,测试“告警→日志→追踪”的跳转流程;
  4. 逐步将所有服务迁移到 OTel 流水线,同时优化采样策略和配置。

理解 Kubernetes 部署模式(如 DaemonSet、Deployment)有助于确保监控基础设施的可靠性,尤其是在集群升级时需保证 Collector 持续运行。

扩展:将安全信号纳入统一流水线

当你已搭建好指标、日志、追踪数据的统一流水线后,可进一步将安全信号整合进来。我的另一篇文章《Security Observability in Kubernetes Goes Beyond Logs》(Kubernetes 安全可观测性:不止于日志)详细介绍了如何将 Kubernetes 审计日志、Falco 运行时检测和网络流量可见性接入 OpenTelemetry 流水线,实现“安全事件→请求追踪”的关联,帮助你定位触发安全事件的具体请求。

原文链接:https://fatihkoc.net/posts/opentelemetry-kubernetes-pipeline/

快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云
OpenSource
开源版
Flashcat
Flashcat
Flashduty
Flashduty