OpenTelemetry 生产实践:Skyscanner 24 集群管理经验与 Mastodon 小团队方案

1,459 阅读7分钟

1. 引言:为什么需要两套参考实现

OpenTelemetry(OTel)已经成为云原生可观测性的事实标准,但"标准"只解决了协议与 SDK 层面的问题,真正落地到生产环境时,最难的往往不是埋点,而是 Collector 怎么部署、怎么管理、怎么控制成本。

本文拆解两套公开的参考实现:

  • Skyscanner:跨 24 个生产集群、数百个 Collector 实例的大规模统一管理方案;
  • Mastodon:每个 Namespace 一个 Collector 的极简设计,由小团队维护。

两者没有优劣之分,只有规模与团队约束的匹配度差异。理解它们的取舍,比照搬任何一套配置都更有价值。

2. 两套参考实现的对比:大规模 vs 小团队的 OTel 策略

先给出一张整体对比表,后续章节再逐项展开。

维度SkyscannerMastodon
集群规模24 个生产集群少量集群,单团队维护
Collector 部署模式Gateway(集中式)+ 少量 DaemonSet每个 Namespace 一个 Deployment
配置管理GitOps + 集中式配置分发随应用清单一起版本化
多租户隔离按集群/团队划分管道按 Namespace 天然隔离
采样策略分层采样 + 尾部采样头部采样为主,低比例
团队投入专职可观测性平台团队兼职维护,追求"零运维"
核心目标统一治理、成本可控简单、可预测、够用

一句话总结:Skyscanner 用架构复杂度换治理能力,Mastodon 用功能克制换维护成本。

从整体策略上看,两套方案的取舍可以抽象成一条对比路径:

flowchart LR
  subgraph S["Skyscanner:大规模治理"]
    S1["24 个生产集群"] --> S2["分层 Collector"]
    S2 --> S3["GitOps 集中配置"]
    S3 --> S4["分层采样 + 尾部采样"]
  end
  subgraph M["Mastodon:小团队极简"]
    M1["少量集群 / 单团队"] --> M2["Namespace 级 Collector"]
    M2 --> M3["配置随应用版本化"]
    M3 --> M4["头部采样为主"]
  end
  S4 --> R["统一 OTLP 协议与后端"]
  M4 --> R

3. Skyscanner 方案:跨 24 个生产集群的 Collector 管理架构

3.1 架构分层

Skyscanner 的可观测性平台团队面对的核心问题是:24 个集群、大量业务团队、多种信号(traces/metrics/logs),如何避免每个团队各自为政。

他们的做法是分层 Collector:

flowchart TD
  A["应用 Pod(OTel SDK)"] --> B["节点级 Agent(DaemonSet)"]
  B --> C["集群级 Gateway(Deployment)"]
  C --> D["区域聚合层"]
  D --> E["后端存储(Tempo / Mimir / Loki)"]
  • 节点级 Agent:以 DaemonSet 形式运行,负责接收本节点上应用通过 OTLP 上报的数据,做最轻量的处理(如资源属性补全、批处理)。
  • 集群级 Gateway:每个集群一组 Gateway,承担采样、过滤、路由、限流等重活。
  • 区域聚合层:跨集群汇聚后再写入后端,减少后端连接数与写入压力。

3.2 配置管理:把 Collector 当代码管

24 个集群意味着 24 份(甚至更多)配置。Skyscanner 的关键经验是:

  • 配置集中版本化:所有 Collector 配置存放在统一仓库,通过 GitOps 下发,禁止手工改线上配置;
  • 配置分层复用:基础配置(batch、memory_limiter、health_check)作为公共片段,各集群只覆盖差异部分;
  • 变更灰度:配置变更先在单个集群验证,再逐步推广,避免一次错误配置打穿全部集群。

配置从提交到全量生效的流程可以概括为:

flowchart TD
  A["修改配置仓库"] --> B["CI 校验配置合法性"]
  B --> C["单集群灰度下发"]
  C --> D{"指标与错误率是否正常"}
  D -->|否| E["自动回滚并告警"]
  D -->|是| F["分批推广到其余集群"]
  F --> G["全量生效并记录变更"]

3.3 大规模下的稳定性要点

  • 每个 Collector 必须配置 memory_limiter,防止突发流量导致 OOM;
  • Gateway 前面加负载均衡,避免单实例热点;
  • 对下游后端做背压保护,队列满时优先丢弃低优先级数据而不是阻塞应用。

4. Mastodon 方案:每个 Namespace 一个 Collector 的极简设计

4.1 设计思路

Mastodon 的规模远小于 Skyscanner,团队也没有专职可观测性工程师。他们的选择非常直接:每个 Namespace 部署一个 Collector Deployment。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: otel-collector
  namespace: mastodon
spec:
  replicas: 1
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      containers:
        - name: otel-collector
          image: otel/opentelemetry-collector-contrib:0.100.0
          args: ["--config=/conf/config.yaml"]
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              memory: 256Mi
          volumeMounts:
            - name: config
              mountPath: /conf
      volumes:
        - name: config
          configMap:
            name: otel-collector-config

4.2 为什么这样够用

  • 隔离天然成立:Namespace 本身就是租户边界,一个 Collector 只处理本 Namespace 的数据,不需要复杂的路由规则;
  • 配置随应用走:Collector 的 ConfigMap 和业务清单放在同一个仓库,改配置和改应用是同一个 PR;
  • 故障域小:某个 Collector 挂了只影响一个 Namespace,不会引发全局雪崩;
  • 心智负担低:没有 Gateway 层、没有跨集群聚合,新人半天就能看懂。

数据流向非常直观,每个 Namespace 自成一条独立链路:

flowchart LR
  subgraph NS1["Namespace: mastodon"]
    A1["应用 Pod"] --> C1["otel-collector"]
  end
  subgraph NS2["Namespace: mastodon-streaming"]
    A2["应用 Pod"] --> C2["otel-collector"]
  end
  C1 --> B["OTLP 后端"]
  C2 --> B

4.3 代价与边界

这套方案的代价也很明确:

  • 实例数量随 Namespace 线性增长,资源利用率不高;
  • 无法做跨 Namespace 的全局采样与聚合;
  • 每个 Collector 都要单独配置后端导出,重复度高。

适用边界:Namespace 数量在几十个以内、团队没有专职平台工程师、对成本不极度敏感。

5. Collector 部署模式选择:DaemonSet、Sidecar 还是 Gateway

三种模式各有适用场景,实际生产中往往是组合使用。

模式优点缺点典型场景
DaemonSet每节点一个,资源可控;就近采集主机指标无法做跨节点聚合;升级影响整节点主机指标、日志采集
Sidecar与应用同生命周期,隔离性最好实例数爆炸,资源开销大多租户强隔离、特殊协议转换
Gateway集中处理,便于采样/路由/限流单点压力大,需要高可用设计统一出口、尾部采样

5.1 选择建议

  • 默认从 DaemonSet 起步:覆盖大部分节点级采集需求,运维成本最低;
  • 需要集中治理时加 Gateway:采样、脱敏、路由这类"重活"放在 Gateway,Agent 保持轻量;
  • Sidecar 谨慎使用:只在强隔离或协议转换场景下使用,否则实例数会失控。

Skyscanner 采用的是 DaemonSet + Gateway 的组合,Mastodon 则用 Namespace 级 Deployment 近似 Gateway 的角色,但范围缩小到单个租户。

三种部署模式在数据链路中的位置关系如下:

flowchart TD
  A["应用 Pod"] -->|"同 Pod 内"| S["Sidecar Collector"]
  A -->|"OTLP"| D["节点级 DaemonSet Agent"]
  S --> G["Gateway Collector"]
  D --> G
  G -->|"采样 / 脱敏 / 路由"| B["后端存储"]

6. 采样策略与数据管道优化:控制成本的同时保持可观测性

6.1 分层采样

采样是控制成本最有效的手段,但粗暴地全局降采样会丢掉关键链路。推荐分层:

  • 头部采样(Head Sampling):在 SDK 侧按比例采样,成本最低,但无法基于完整链路做决策;
  • 尾部采样(Tail Sampling):在 Collector 侧基于完整 trace 决策,可保留所有错误链路和慢链路;
  • 组合策略:SDK 侧先做 10%~20% 的头部采样,Gateway 侧再做尾部采样,保留 100% 错误与超阈值慢请求。

分层采样在链路中的决策位置如下:

flowchart LR
  A["应用 SDK"] -->|"头部采样 10%~20%"| B["Collector"]
  B --> C{"尾部采样决策"}
  C -->|"错误链路"| D["100% 保留"]
  C -->|"慢链路 > 500ms"| D
  C -->|"普通链路"| E["按 5% 概率保留"]
  D --> F["后端存储"]
  E --> F

6.2 尾部采样配置示例

processors:
  tail_sampling:
    decision_wait: 10s
    num_traces: 100000
    policies:
      - name: errors-policy
        type: status_code
        status_code:
          status_codes: [ERROR]
      - name: slow-traces-policy
        type: latency
        latency:
          threshold_ms: 500
      - name: baseline-policy
        type: probabilistic
        probabilistic:
          sampling_percentage: 5

6.3 数据管道优化要点

  • 批处理:batch processor 放在管道末端,减少网络往返;
  • 内存保护:memory_limiter 放在管道最前端,先于所有处理逻辑;
  • 属性裁剪:用 attributes / transform processor 删除无用标签,降低存储成本;
  • 队列与重试:导出端配置 sending_queue 与 retry_on_failure,避免后端抖动导致数据丢失;
  • 按信号分管道:traces、metrics、logs 使用独立 pipeline,避免互相拖累。
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheusremotewrite]

7. 总结:如何选择适合自己团队的方案

把两套方案抽象成决策路径:

flowchart TD
  A["团队是否有专职可观测性平台工程师"] -->|是| B["集群数量是否超过 10 个"]
  A -->|否| C["Namespace 数量是否在几十个以内"]
  B -->|是| D["Skyscanner 式:分层 Gateway + GitOps 集中治理"]
  B -->|否| E["单集群 Gateway + DaemonSet"]
  C -->|是| F["Mastodon 式:Namespace 级 Collector"]
  C -->|否| G["先做头部采样,再逐步引入 Gateway"]

核心结论:

  1. 先解决部署模式,再谈采样:部署架构决定了后续治理的上限;
  2. 配置必须版本化:无论规模大小,手工改线上 Collector 配置都是事故源头;
  3. 采样要分层:头部采样控基线成本,尾部采样保关键链路;
  4. 不要过早追求统一:小团队照搬大规模方案,往往先被运维复杂度拖垮。

OpenTelemetry 的价值在于它给了你统一的协议与组件,但架构决策仍然要回到自己的规模、团队与成本约束上。