文章

OpenTelemetry Collector 深度运维

OpenTelemetry Collector 深度运维

概述

OTel Collector 是 OTel 体系中最被低估的组件。大多数人把它当成一个「透明的管道」——数据从 Receivers 进,从 Exporters 出。但实际上 Collector 可以做采样、数据清洗、协议转换、实时告警、多租户路由等大量工作,只是这些能力的入口——OTTL(OpenTelemetry Transformation Language)——文档分散,很少有人系统地掌握。

一句话:如果你只会配 Receiver → Processor → Exporter,你只用到了 Collector 20% 的能力。学会 OTTL,Collector 就从一个管道变成了一个可编程的数据处理引擎。

Collector 内部组件生命周期

设计哲学:有向无环图(DAG)

Collector 不是简单的线性 Pipeline,而是一个由组件节点和连接边构成的有向无环图(DAG)。每个 Pipeline 内的 Receiver → Processor → Exporter 形成一条有序路径,多条 Pipeline 可以共享同一个 Receiver 或 Exporter

Component 生命周期:
  Start()   → 被添加到 Pipeline
  Shutdown() → 被移除(配置更新或进程退出)

Pipeline 内执行顺序:
  Receiver → Processor[0] → Processor[1] → ... → Processor[N] → Exporter

关键规则:
  - Processor 之间数据传递是**同步的**(下一个 Processor 必须等上一个返回)
  - Exporter 接收数据后是**异步发送的**(batch processor 之后的数据流)
  - 同一 Pipeline 内的 Processor 之间**有界队列**(queue_size 控制背压)

背压(Backpressure)机制

当 Exporter 发送速度跟不上 Receiver 接收速度时,Collector 通过背压层层向上游传递信号:

Exporter 发送慢(后端慢/网络慢)
  → Processor 的 output queue 满
  → Processor 拒绝接收新数据
  → Receiver 的接收队列满
  → Receiver 丢弃数据或返回 429/503
  → 上游 SDK 收到错误 → 降低发送频率或丢弃本地缓冲的 span

这就是 memory_limiterbatch processor 存在的意义:它们定义了背压链条中的「水位线」。

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 1024               # 总内存上限
    spike_limit_mib: 256          # 单次 spike 容忍内存
    # 当内存使用达到 limit_mib → 强制拒绝所有新数据
    # 当 spike 超过 spike_limit_mib → 丢弃本次 spike

  batch:
    send_batch_size: 8192         # 攒到 8192 条 → 发送
    timeout: 5s                   # 最多等 5 秒就发送
    send_batch_max_size: 0        # 0=不限制,设值可限制单批次最大条数

OTTL —— Collector 的可编程数据层

OTTL 是 OTel Collector 内置的领域特定语言(DSL),用于在 Processor 中转换、过滤和修改遥测数据。它让你可以实现「把某个属性的值 hash 之后做匿名化」这种正则做不到的操作。

基础语法

# 条件表达式(必须返回 bool)
attributes["http.status_code"] >= 500

# 转换表达式(修改数据)
set(attributes["custom.tag"], "value")

# 路径导航
span.name                                     # Span 名称
attributes["db.system"]                       # 属性值
resource.attributes["service.name"]           # Resource 属性
instrumentation_scope.name                    # 探针名称

30 个实用的 OTTL 语句

1-5:属性操作

transform:
  trace_statements:
    # 1. 设置属性
    - set(attributes["env"], "production")

    # 2. 删除属性
    - delete_key(attributes, "user.password")

    # 3. 重命名属性
    - set(attributes["deployment.environment"], attributes["env"])
    - delete_key(attributes, "env")

    # 4. 条件设置
    - set(attributes["tier"], "premium") where resource.attributes["namespace"] == "bigdata"

    # 5. 从 URL 提取路径(正则替换)
    - replace_pattern(attributes["http.route"], "/users/[0-9]+", "/users/:id")

6-10:数值转换

transform:
  metric_statements:
    # 6. 单位转换(微秒 → 毫秒)
    - set(unit, "ms") where unit == "us"
    - set(value_double, value_double / 1000.0) where unit == "us"

    # 7. Metric 名称标准化
    - set(metric.name, Concat(["custom.", metric.name], "")) where not IsMatch(metric.name, "^custom\\.")

    # 8. 限幅(clamp)
    - set(value_double, 100.0) where value_double > 100.0

    # 9. IsMatch 条件过滤
    - set(attributes["priority"], "high") where IsMatch(attributes["endpoint"], "^(/api/checkout|/api/payment)")

11-15:字符串操作

transform:
  log_statements:
    # 11. 拼接
    - set(attributes["full_name"], Concat([attributes["first_name"], " ", attributes["last_name"]], ""))

    # 12. 子串提取
    - set(attributes["region"], Substring(attributes["az"], 0, 9))  # "us-east-1a" → "us-east-1"

    # 13. 大小写
    - set(attributes["pod"], ConvertCase(attributes["pod"], "lower"))   # 全小写

    # 14. 字符串替换
    - replace_pattern(attributes["message"], "secret_key=[A-Za-z0-9]+", "secret_key=[REDACTED]")

    # 15. 长度截断(防止属性值过长)
    - set(attributes["body"], Substring(attributes["body"], 0, 1024)) where Len(attributes["body"]) > 1024

16-20:条件过滤

transform:
  trace_statements:
    # 16. 丢弃健康检查的 span(降低噪音)
    - drop() where attributes["http.route"] == "/health"

    # 17. 只保留错误 span 的关键属性
    - delete_key(attributes, "http.request.body") where attributes["http.status_code"] < 500

    # 18. 按服务名改写 span 名称
    - set(span.name, Concat(["api.", span.name], "")) where resource.attributes["service.name"] == "api-tpa"

    # 19. 多条件 AND
    - set(attributes["sla"], "breached") where attributes["http.status_code"] >= 500 and duration > 5000000000

    # 20. NOT 条件
    - drop() where instrumentation_scope.name != "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"

21-25:类型与资源操作

transform:
  # 21. 类型转换(string → int)
  - set(attributes["gpu.count"], Int(attributes["gpu.count"]))

  # 22. 从 resource 复制到 span attributes
  - set(attributes["k8s.namespace.name"], resource.attributes["k8s.namespace.name"])

  # 23. 统一多集群命名
  - set(resource.attributes["cluster.name"], "prod-shanghai")
  - truncate_all(attributes, 256)     # 强制所有属性值 ≤ 256 字符

  # 24. 哈希匿名化(用于合规)
  - set(attributes["user.id"], SHA256(attributes["user.id"]))

  # 25. 根据 parent span 状态标记子 span
  - set(attributes["parent.error"], "true") where parent_span.attributes["error"] == "true"

26-30:Metric 专属

transform:
  metric_statements:
    # 26. 聚合 type → 名称前缀
    - set(metric.name, Concat([metric.type, ".", metric.name], "")) where metric.type == "Sum"

    # 27. Delta → Cumulative 标记
    - set(attributes["temporality"], "delta") where IsMatch(metric.name, "^container\\.")

    # 28. 结合 resource 属性
    - set(metric.description, Concat(["from ", resource.attributes["service.name"]], ""))
      where metric.description == ""

    # 29. 限制数据点数量
    - limit(attributes, 10, ["keep.these.keys.*"])            # 只保留 10 个+通配符属性

    # 30. 按条件保留/丢弃整个 Telemetry
    - keep_keys(attributes, ["http.method", "http.status_code", "http.route"])

OTTL vs Filter Processor

场景用什么
简单丢弃某类数据filter processor(YAML 更简洁)
修改属性值、类型转换、正则替换transform processor + OTTL
跨 signal 联动(根据 trace 状态修改 metric)OTTL 做不到(signal 间隔离),用 Connector

Connector —— 跨 Pipeline 桥接

Connector 是 OTel v0.83+ 引入的新组件类型,位于 Receiver 和 Exporter 之间,可以从一个信号生成另一种信号

spanmetrics —— 从 Trace 自动生成 RED 指标

这是最实用的 Connector:从 Span 数据中提取 request count、error count 和 duration,生成 Metric。这解决了 「做了 OTel tracing 但没有 request count 指标」的问题。

connectors:
  spanmetrics:
    # 按下列维度聚合
    dimensions:
      - name: http.method
        default: GET
      - name: http.status_code
      - name: service.name       # resource attribute
      - name: http.route

    # 直方图分桶(毫秒)
    histogram:
      explicit:
        buckets: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 5000, 10000]

    # 生成哪些指标
    metrics_flush_interval: 15s

    # 排除不需要聚合的 Span(如健康检查)
    exclude_patterns:
      - name: "GET /health"

然后把这些由 span 生成的 metric 发送到 Prometheus:

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [spanmetrics]      # 注意:spanmetrics 作为 exporter

    metrics/span:                    # 新 Pipeline:从 spanmetrics 接收 metric
      receivers: [spanmetrics]      # spanmetrics 在这里是 receiver
      processors: [batch]
      exporters: [prometheusremotewrite]

servicegraph —— 自动生成服务依赖拓扑

connectors:
  servicegraph:
    latency_histogram_buckets: [2, 4, 6, 8, 10, 50, 100, 200, 400, 800, 1000, 1400, 2000, 5000, 10000, 15000]
    dimensions: [cluster, namespace]
    store:
      ttl: 2s
      max_items: 1000

生成的指标:traces_service_graph_request_totaltraces_service_graph_request_server_seconds,可直接用于 Grafana Node Graph 面板展示服务依赖拓扑。

Collector 扩展运维

Scaling 与 HPA

Collector 的瓶颈通常不在 CPU,而在内存(tail sampling buffer + batch buffer)。HPA 应该基于内存:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: otel-gateway
spec:
  scaleTargetRef:
    apiVersion: opentelemetry.io/v1alpha1
    kind: OpenTelemetryCollector
    name: otel-gateway
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 70

gRPC 负载均衡:Collector Gateway 多副本时,需要无状态 gRPC 负载均衡。一个常见方案是前面加一个 headless Service + 客户端侧 round_robin load balancing:

# DaemonSet Collector 连接 Gateway 的配置
exporters:
  otlp:
    endpoint: otel-gateway-headless.monitoring:4317
    tls:
      insecure: true
    # gRPC 客户端侧负载均衡
    balancer_name: round_robin

磁盘缓冲(防止后端不可用时丢数据)

当后端(Tempo / Prometheus)不可用时,内存中有界队列很快会满。启用磁盘缓冲可以让 Collector 把积压的数据暂存磁盘:

exporters:
  otlp/tempo:
    endpoint: tempo.monitoring:4317
    sending_queue:
      enabled: true
      num_consumers: 10                # 并发发送 goroutine
      queue_size: 5000                 # 内存队列容量
      storage: file_storage            # 溢出到磁盘
# 磁盘存储配置
extensions:
  file_storage:
    directory: /var/lib/otelcol/filestorage
    timeout: 1s
    compaction:
      directory: /var/lib/otelcol/filestorage/compaction
      on_start: true
      on_rebound: true                 # 积压消解后主动压缩

Collector 自身 metrics

Collector 自身暴露 Prometheus metrics 在 :8888/metrics,这是排障的第一入口:

# Collector 是否在拒绝数据
otelcol_processor_refused_spans > 0
otelcol_exporter_send_failed_spans > 0

# Collector 数据吞吐
rate(otelcol_receiver_accepted_spans[1m])

# Collector 内存
otelcol_process_memory_rss

# 队列积压
otelcol_exporter_queue_size
otelcol_exporter_queue_capacity

生产安全

数据传输加密

# Collector 间 TLS(DaemonSet → Gateway)
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
        tls:
          cert_file: /certs/server.crt
          key_file: /certs/server.key
          client_ca_file: /certs/ca.crt    # mTLS

exporters:
  otlp:
    endpoint: otel-gateway:4317
    tls:
      ca_file: /certs/ca.crt
      cert_file: /certs/client.crt
      key_file: /certs/client.key

敏感数据脱敏

processors:
  transform:
    trace_statements:
      # 脱敏 HTTP Authorization header
      - replace_pattern(attributes["http.request.header.authorization"], "Bearer .+", "Bearer [REDACTED]")
      # 脱敏 URL 中的 token
      - replace_pattern(attributes["url.full"], "token=[^&]+", "token=[REDACTED]")
      # 脱敏 email
      - replace_pattern(attributes["enduser.id"], ".+@.+", "[REDACTED]@example.com")
      # 删除电话号码
      - delete_key(attributes, "phone.number")
      # 哈希 IP 地址(保留用于地理分析,但无法反追到个人)
      - set(attributes["client.address"], SHA256(attributes["client.address"])) where attributes["client.address"] != nil

多租户路由

通过 attribute 将不同租户的数据路由到不同后端:

exporters:
  # 租户 A 的数据去集群内 Prometheus
  prometheusremotewrite/tenant-a:
    endpoint: "http://prometheus-tenant-a:9090/api/v1/write"

  # 租户 B 的数据去 SaaS
  datadog/tenant-b:
    api:
      key: ${env:DD_API_KEY}
    hostname: tenant-b

processors:
  transform:
    metric_statements:
      - drop() where resource.attributes["tenant"] == "a" and exporter != "prometheusremotewrite/tenant-a"

生产排障手册

症状 → 定位 → 修复

症状定位修复
Collector OOMKilledotelcol_process_memory_rss 持续增长增大 memory_limiter.limit_mib 或减少 tail_sampling.num_traces
Span 延迟 5 分钟后才到达后端batch.timeout 太大或 sending_queue 积压减小 batch.timeout、增加 num_consumers
Tail Sampling 不生效(所有 span 都被保留)decision_wait 太短,span 还没聚合完成就超时增大 decision_wait(需要更大的内存 buffer)
部分 span 丢失sending_queue.queue_size 已满且没有 file_storage增加 queue_size 并启用 file_storage
gRPC 连接错误网络策略阻断 4317 端口或 TLS 证书不一致grpc_health_probe 验证、检查 Cilium/Calico 策略
otelcol_receiver_refused_spans > 0memory_limiter 触发拒绝,或 Pipeline 队列满增大 limit_mib 或 HPA 扩容 Collector 副本

健康检查与调试

# Collector 健康检查(gRPC)
grpc_health_probe -addr otel-gateway:4317

# 查看 Collector 自身 metrics
curl http://otel-gateway:8888/metrics | grep otelcol_receiver_accepted

# 调试:把 Collector pipeline 的输出导出到 stdout
exporters:
  debug:
    verbosity: detailed      # 打印每条数据
  # 临时加到 pipeline:  exporters: [..., debug]

关联知识

参考资源

学习时间

阶段时间备注
Collector 深入2026-07-02背压机制、OTTL 30 例、Connector 桥接、Scaling/HPA/磁盘缓冲、安全、排障

状态: 🌱 学习中 下次复习日期: 2026-07-09