扩展 Collector

在规划 OpenTelemetry 收集器的可观测性管道时,您应该考虑随着遥测数据收集量的增加,扩展管道的方法。

以下各节将指导您完成规划阶段,讨论要扩展哪些组件,如何确定何时需要扩展,以及如何执行计划。

需要扩展什么

虽然 OpenTelemetry 收集器在一个二进制文件中处理所有遥测信号类型,但实际上每种类型可能有不同的扩展需求,并可能需要不同的扩展策略。首先,分析您的工作负载,确定预计负载最大的是哪种信号类型,以及收集器将接收哪些格式的数据。例如,扩展抓取器集群与扩展日志接收器差异很大。还要考虑工作负载的弹性程度:您是否在一天中的特定时间有高峰,或者负载在 24 小时内是否相似?收集到这些信息后,您将了解需要扩展什么。

例如,假设您有数百个 Prometheus 端点需要抓取,每分钟有来自 fluentd 实例的 TB 级日志,以及一些来自您最新微服务的 OTLP 格式的应用程序指标和跟踪。在这种情况下,您将需要一个能够单独扩展每种信号的架构:扩展 Prometheus 接收器需要抓取器之间的协调,以决定哪个抓取器负责哪个端点。相比之下,我们可以根据需要横向扩展无状态日志接收器。为指标和跟踪设置 OTLP 接收器在第三个收集器集群中,将允许我们隔离故障并更快地迭代,而无需担心重新启动繁忙的管道。鉴于 OTLP 接收器支持所有遥测类型的摄取,我们可以将应用程序指标和跟踪保留在同一实例上,并在需要时对其进行横向扩展。

何时扩展

同样,我们应该了解我们的工作负载来决定何时进行扩展或缩减,但收集器发出的一些指标可以为您提供何时采取行动的良好提示。

当 `memory_limiter` 处理器是管道的一部分时,收集器提供的一个有用的提示是指标 `otelcol_processor_refused_spans`。此处理器允许您限制收集器可以使用的内存量。虽然收集器可能会消耗略高于此处理器中配置的最大量,但新数据最终会被 `memory_limiter` 阻止通过管道,该处理器会将此事实记录在此指标中。所有其他遥测数据类型也存在相同的指标。如果数据被拒绝进入管道的频率太高,您可能需要扩展您的收集器集群。一旦节点之间的内存消耗远低于此处理器中设置的限制,您就可以缩减。

需要关注的另一组指标是与导出器队列大小相关的指标:`otelcol_exporter_queue_capacity` 和 `otelcol_exporter_queue_size`。收集器在等待工作程序可用以发送数据时,会在内存中排队数据。如果没有足够的工作程序或后端速度太慢,数据就会开始堆积在队列中。一旦队列达到其容量(`otelcol_exporter_queue_size` > `otelcol_exporter_queue_capacity`),它就会拒绝数据(`otelcol_exporter_enqueue_failed_spans`)。添加更多工作程序通常会使收集器导出更多数据,这不一定是您想要的(请参阅 不应该扩展的时候)。一般的指导是监控队列大小,并在其达到容量的 60-70% 时考虑扩展,并在容量持续较低时缩减,同时保持最小数量的副本,例如三个,以确保弹性。

熟悉您打算使用的组件也是值得的,因为不同的组件可能会产生其他指标。例如,负载均衡导出器将记录导出操作的时间信息,并将其作为直方图 `otelcol_loadbalancer_backend_latency` 的一部分公开。您可以提取此信息来确定所有后端处理请求所需的时间是否相似:单个后端速度慢可能表示收集器外部存在问题。

对于进行抓取的接收器,例如 Prometheus 接收器,如果完成所有目标抓取所需的时间经常接近抓取间隔,则应扩展或分片抓取。当这种情况发生时,是时候添加更多的抓取器了,通常是新的收集器实例。

不应该扩展的时候

知道何时扩展与了解哪些迹象表明扩展操作不会带来任何好处同样重要。一个例子是,当遥测数据库无法跟上负载时:在不扩展数据库的情况下,向集群添加收集器将无济于事。同样,当收集器和后端之间的网络连接饱和时,添加更多收集器可能会产生有害的副作用。

同样,一种捕捉这种情况的方法是查看指标 `otelcol_exporter_queue_size` 和 `otelcol_exporter_queue_capacity`。如果您发现队列大小一直接近队列容量,这表明导出数据的速度比接收数据的速度慢。您可以尝试增加队列容量,这将导致收集器消耗更多内存,但也会为后端提供一些喘息空间,而不会永久丢失遥测数据。但是,如果您继续增加队列容量而队列大小以相同的比例持续增长,则表明您可能需要关注收集器之外的因素。同样重要的是要注意,在这里添加更多工作程序将无济于事:您只会给已经承受高负载的系统带来更大的压力。

后端可能存在问题的另一个迹象是 `otelcol_exporter_send_failed_spans` 指标的增加:这表明发送到后端的数据永久失败。当这种情况持续发生时,扩展收集器很可能会使情况变得更糟。

如何扩展

此时,我们知道我们管道的哪些部分需要扩展。关于扩展,我们有三种类型的组件:无状态、抓取器和有状态。

大多数收集器组件都是无状态的。即使它们在内存中持有某些状态,对于扩展目的来说也是不重要的。

抓取器(如 Prometheus 接收器)被配置为从外部位置获取遥测数据。然后,接收器将逐个目标抓取,将数据放入管道。

像尾部采样处理器这样的组件不容易扩展,因为它们会在内存中保留一些相关状态以供业务使用。这些组件在扩展之前需要仔细考虑。

扩展无状态收集器并使用负载均衡器

好消息是,大多数时候,扩展收集器很容易,因为它只是添加新副本并通过负载均衡器在它们之间分配流量的问题。

当您需要时,负载均衡器至关重要:

  • 将传入的遥测流量分配到多个无状态收集器实例,以防止任何单个实例过载。
  • 提高收集管道的可用性和容错能力。如果一个收集器实例发生故障,负载均衡器可以将流量重定向到健康的实例。
  • 根据需求横向扩展您的收集器层。

在 Kubernetes 环境中运行时,利用服务网格(如 Istio 或 Linkerd)或云提供商负载均衡器提供的强大现成负载均衡和速率限制解决方案。这些系统提供成熟的流量管理、弹性和可观测性功能,通常超越了基本的负载分配。

当使用 gRPC 接收数据时(OTLP 的常见场景),请使用理解 gRPC 的负载均衡器(L7 负载均衡器)。标准的 L4 负载均衡器可能会与单个后端收集器实例建立持久连接,从而否定扩展的好处,因为客户端总是会命中相同的后端收集器。您仍应考虑在可靠性方面分离您的收集管道。例如,当您的工作负载在 Kubernetes 上运行时,您可能希望使用 DaemonSets 在与您的工作负载相同的物理节点上放置一个收集器,并使用一个远程的中央收集器在将数据发送到存储之前负责预处理数据。当节点数量少而 Pod 数量多时,Sidecars 可能更合适,因为您可以在收集器层之间获得更好的 gRPC 连接负载均衡,而无需 gRPC 特定负载均衡器。使用 Sidecar 还可以避免在 DaemonSet Pod 发生故障时导致节点上所有 Pod 的关键组件崩溃。

Sidecar 模式包括将一个容器添加到工作负载 Pod 中。 OpenTelemetry Operator 可以自动为您添加它。要实现这一点,您需要一个 OpenTelemetry Collector CR,并且需要注释您的 PodSpec 或 Pod,告知 Operator 注入一个 Sidecar。

---
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
  name: sidecar-for-my-workload
spec:
  mode: sidecar
  config: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
    processors:

    exporters:
      # Note: Prior to v0.86.0 use the `logging` instead of `debug`.
      debug:

    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: []
          exporters: [debug]
---
apiVersion: v1
kind: Pod
metadata:
  name: my-microservice
  annotations:
    sidecar.opentelemetry.io/inject: 'true'
spec:
  containers:
    - name: my-microservice
      image: my-org/my-microservice:v0.0.0
      ports:
        - containerPort: 8080
          protocol: TCP

如果您希望绕过 Operator 手动添加 Sidecar,以下是一个示例

apiVersion: v1
kind: Pod
metadata:
  name: my-microservice
spec:
  containers:
    - name: my-microservice
      image: my-org/my-microservice:v0.0.0
      ports:
        - containerPort: 8080
          protocol: TCP
    - name: sidecar
      image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector:0.69.0
      ports:
        - containerPort: 8888
          name: metrics
          protocol: TCP
        - containerPort: 4317
          name: otlp-grpc
          protocol: TCP
      args:
        - --config=/conf/collector.yaml
      volumeMounts:
        - mountPath: /conf
          name: sidecar-conf
  volumes:
    - name: sidecar-conf
      configMap:
        name: sidecar-for-my-workload
        items:
          - key: collector.yaml
            path: collector.yaml

扩展抓取器

一些接收器会主动获取遥测数据放入管道,例如 hostmetrics 和 prometheus 接收器。虽然获取主机指标通常不是我们需要扩展的,但我们可能需要拆分抓取数千个 Prometheus 接收器端点的任务。我们不能简单地添加具有相同配置的新实例,因为每个收集器都会尝试抓取集群中其他收集器的相同端点,导致更多问题,例如样本乱序。

解决方案是按收集器实例对端点进行分片,这样如果我们添加另一个收集器副本,每个副本将处理一组不同的端点。

一种方法是为每个收集器拥有一个配置文件,这样每个收集器只会发现该收集器相关的端点。例如,每个收集器可以负责一个 Kubernetes 命名空间或工作负载上的特定标签。

另一种扩展 Prometheus 接收器的方法是使用 Target Allocator:这是一个额外的二进制文件,可以作为 OpenTelemetry Operator 的一部分部署,并将给定配置的 Prometheus 抓取目标分布到收集器集群中。您可以使用自定义资源 (CR) 如下来利用 Target Allocator。

apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
  name: collector-with-ta
spec:
  mode: statefulset
  targetAllocator:
    enabled: true
  config: |
    receivers:
      prometheus:
        config:
          scrape_configs:
          - job_name: 'otel-collector'
            scrape_interval: 10s
            static_configs:
            - targets: [ '0.0.0.0:8888' ]

    exporters:
      # Note: Prior to v0.86.0 use the `logging` instead of `debug`.
      debug:

    service:
      pipelines:
        metrics:
          receivers: [prometheus]
          processors: []
          exporters: [debug]

在协调后,OpenTelemetry Operator 将把 Collector 的配置转换为以下内容:

exporters:
   # Note: Prior to v0.86.0 use the `logging` instead of `debug`.
   debug: null
 receivers:
   prometheus:
     config:
       global:
         scrape_interval: 1m
         scrape_timeout: 10s
         evaluation_interval: 1m
       scrape_configs:
       - job_name: otel-collector
         honor_timestamps: true
         scrape_interval: 10s
         scrape_timeout: 10s
         metrics_path: /metrics
         scheme: http
         follow_redirects: true
         http_sd_configs:
         - follow_redirects: false
           url: http://collector-with-ta-targetallocator:80/jobs/otel-collector/targets?collector_id=$POD_NAME
service:
   pipelines:
     metrics:
       exporters:
       - debug
       processors: []
       receivers:
       - prometheus

请注意,Operator 如何向 `otel-collector` 抓取配置添加了 `global` 部分和 `new http_sd_configs`,指向它配置的 Target Allocator 实例。现在,要扩展收集器,请更改 CR 的“replicas”属性,Target Allocator 将通过为每个收集器实例(Pod)提供自定义的 `http_sd_config` 来相应地分配负载。

扩展有状态收集器

某些组件可能会在内存中保存数据,扩展它们会产生不同的结果。例如尾部采样处理器,它会在给定的一段时间内将 spans 保存在内存中,仅在跟踪被认为完成后才评估采样决策。通过添加更多副本扩展收集器集群意味着不同的收集器将接收给定跟踪的 spans,导致每个收集器评估该跟踪是否应被采样,并可能得出不同的答案。这种行为会导致跟踪缺少 spans,从而歪曲了该事务中的实际情况。

在使用 span-to-metrics 处理器生成服务指标时,也会发生类似的情况。当不同的收集器接收与同一服务相关的数据时,基于服务名称的聚合将不准确。

为了克服这个问题,您可以部署一个由负载均衡导出器组成的收集器层,置于进行尾部采样或 span-to-metrics 处理的收集器前面。负载均衡导出器将一致地哈希跟踪 ID 或服务名称,并确定哪个后端收集器应接收该跟踪的 spans。您可以配置负载均衡导出器使用给定 DNS A 记录(例如 Kubernetes headless service)后面的主机列表。当该服务后面的部署被扩展或缩减时,负载均衡导出器最终会看到更新的主机列表。或者,您可以指定一个静态主机列表供负载均衡导出器使用。您可以通过增加副本数量来扩展配置了负载均衡导出器的收集器层。请注意,每个收集器可能会在不同时间执行 DNS 查询,导致集群视图在几秒钟内存在差异。我们建议降低间隔值,以便在高度弹性的环境中,集群视图仅在很短的时间内发生差异。

这是一个使用 DNS A 记录(Kubernetes 服务 `otelcol` 在 `observability` 命名空间中)作为后端信息输入的配置示例:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

processors:

exporters:
  loadbalancing:
    protocol:
      otlp:
    resolver:
      dns:
        hostname: otelcol.observability.svc.cluster.local

service:
  pipelines:
    traces:
      receivers:
        - otlp
      processors: []
      exporters:
        - loadbalancing