使用 OBI 进行分布式追踪

了解 OBI 的分布式追踪支持。

简介

OBI 支持应用程序的分布式追踪,但存在一些限制和内核版本限制。

分布式追踪是通过传播 W3C traceparent 头部值来实现的。traceparent 上下文传播是自动的,不需要任何操作或配置。

OBI 读取任何传入的追踪上下文头部值,跟踪程序执行流程,并通过自动将 traceparent 字段添加到传出的 HTTP/gRPC 请求中来传播追踪上下文。如果应用程序已在传出的请求中添加了 traceparent 字段,OBI 将使用该值进行追踪,而不是使用其自身生成的追踪上下文。如果 OBI 找不到传入的 traceparent 上下文值,它将根据 W3C 规范生成一个。

实现

追踪上下文传播通过两种不同的方式实现

  1. 通过在网络层面写入传出的头部信息
  2. 通过在 Go 的库层面写入头部信息

根据您的服务是用什么编程语言编写的,OBI 会使用一种或两种上下文传播方法。我们使用这些多种方法来实现上下文传播,因为使用 eBPF 进行内存写入取决于内核配置以及授予 OBI 的 Linux 系统能力。有关此主题的更多详细信息,请参阅我们 KubeCon NA 2024 的演讲 So You Want to Write Memory with eBPF?

网络层面的上下文传播默认是禁用的,可以通过设置环境变量 OTEL_EBPF_BPF_CONTEXT_PROPAGATION=all 或修改 OBI 配置文件来启用。

ebpf:
  context_propagation: 'all'

网络层面的上下文传播

网络层面的上下文传播通过在 HTTP 传出头部和 TCP/IP 数据包层面写入追踪上下文信息来实现。HTTP 上下文传播与任何其他基于 OpenTelemetry 的追踪库完全兼容。这意味着 OBI 仪器化的服务在与使用 OpenTelemetry SDK 仪器化的服务进行通信时,可以正确地传播追踪信息。我们使用 Linux Traffic Control (TC) 来调整网络数据包,这要求其他使用 Linux Traffic Control 的 eBPF 程序与 OBI 进行适当的链式处理。关于 Cilium CNI 的特殊注意事项,请参考我们的 Cilium 兼容性指南。

对于 TLS 加密流量 (HTTPS),OBI 无法将追踪信息注入到传出的 HTTP 头部中,而是将其注入到 TCP/IP 数据包层面。由于这个限制,OBI 只能将追踪信息发送到其他 OBI 仪器化的服务。L7 代理和负载均衡器会干扰 TCP/IP 上下文传播,因为原始数据包会被丢弃并在下游重放。解析来自 OpenTelemetry SDK 仪器化服务的传入追踪上下文信息仍然有效。

gRPC 和 HTTP/2 目前不受支持。

这种类型的上下文传播适用于任何编程语言,并且不需要 OBI 以 privileged 模式运行或拥有 CAP_SYS_ADMIN 权限。有关更多详细信息,请参阅 分布式追踪与上下文传播 配置部分。

Kubernetes 配置

在 Kubernetes 上部署 OBI 并启用网络层面的分布式追踪支持的推荐方式是使用 DaemonSet

必须使用以下 Kubernetes 配置

  • OBI 必须作为 DaemonSet 部署,并启用主机网络访问 (hostNetwork: true)。
  • 主机上的 /sys/fs/cgroup 路径必须作为本地 /sys/fs/cgroup 路径进行卷挂载。
  • 必须授予 OBI 容器 CAP_NET_ADMIN 能力。

以下 YAML 片段显示了一个 OBI 部署配置示例

spec:
  serviceAccount: obi
  hostPID: true # <-- Important. Required in DaemonSet mode so OBI can discover all monitored processes
  hostNetwork: true # <-- Important. Required in DaemonSet mode so OBI can see all network packets
  dnsPolicy: ClusterFirstWithHostNet
  containers:
    - name: obi
      resources:
        limits:
          memory: 120Mi
      terminationMessagePolicy: FallbackToLogsOnError
      image: 'docker.io/otel/ebpf-instrument:main'
      imagePullPolicy: 'Always'
      env:
        - name: OTEL_EXPORTER_OTLP_ENDPOINT
          value: 'http://otelcol:4318'
        - name: OTEL_EBPF_KUBE_METADATA_ENABLE
          value: 'autodetect'
        - name: OTEL_EBPF_CONFIG_PATH
          value: '/config/obi-config.yml'
      securityContext:
        runAsUser: 0
        readOnlyRootFilesystem: true
        capabilities:
          add:
            - BPF # <-- Important. Required for most eBPF probes to function correctly.
            - SYS_PTRACE # <-- Important. Allows OBI to access the container namespaces and inspect executables.
            - NET_RAW # <-- Important. Allows OBI to use socket filters for http requests.
            - CHECKPOINT_RESTORE # <-- Important. Allows OBI to open ELF files.
            - DAC_READ_SEARCH # <-- Important. Allows OBI to open ELF files.
            - PERFMON # <-- Important. Allows OBI to load BPF programs.
            - NET_ADMIN # <-- Important. Allows OBI to inject HTTP and TCP context propagation information.
      volumeMounts:
        - name: cgroup
          mountPath: /sys/fs/cgroup # <-- Important. Allows OBI to monitor all newly sockets to track outgoing requests.
        - mountPath: /config
          name: obi-config
  tolerations:
    - effect: NoSchedule
      operator: Exists
    - effect: NoExecute
      operator: Exists
  volumes:
    - name: obi-config
      configMap:
        name: obi-config
    - name: cgroup
      hostPath:
        path: /sys/fs/cgroup

如果 /sys/fs/cgroup 未作为 OBI DaemonSet 的本地卷路径挂载,则某些请求可能不会传播其上下文。我们使用此卷路径来监听新创建的套接字。

内核版本限制

网络层面上下文传播的传入头部解析通常需要内核 5.17 或更高版本才能添加和使用 BPF 循环。

一些已打补丁的内核,例如 RHEL 9.2,可能已将此功能移植回去。在您的内核包含此功能但版本低于 5.17 的情况下,设置 OTEL_EBPF_OVERRIDE_BPF_LOOP_ENABLED 会跳过内核检查。

通过在库级别进行仪器化来实现 Go 上下文传播

这种类型的上下文传播仅支持 Go 应用程序,并使用 eBPF 用户内存写入支持 (bpf_probe_write_user)。这种方法的优点是它适用于 HTTP/HTTP2/HTTPS 和 gRPC(有一些限制),但是使用 bpf_probe_write_user 需要 OBI 被授予 CAP_SYS_ADMIN 权限或被配置为以 privileged 容器运行。

与 Go 手动仪器化的集成

OBI 可以通过 Auto SDK 自动集成手动跨度。请参阅 Auto SDK 的文档了解更多信息。

内核完整性模式限制

为了将 traceparent 值写入传出的 HTTP/gRPC 请求头部,OBI 需要使用 bpf_probe_write_user eBPF 帮助函数来写入进程内存。自内核 5.14(以及反向移植到 5.10 系列的修复程序)以来,如果 Linux 内核处于 integrity lockdown 模式,此帮助函数就会受到保护(并且 BPF 程序无法使用)。如果内核启用了 Secure Boot,内核完整性模式通常会默认启用,但也可以手动启用。

OBI 会自动检查是否可以使用 bpf_probe_write_user 帮助函数,并且仅在内核配置允许的情况下启用上下文传播。通过运行以下命令来验证 Linux 内核的 lockdown 模式

cat /sys/kernel/security/lockdown

如果该文件存在且模式不是 [none],则 OBI 无法执行上下文传播,分布式追踪将被禁用。

容器化环境(包括 Kubernetes)中的 Go 分布式追踪

由于内核 lockdown 模式的限制,Docker 和 Kubernetes 配置文件应将 OBI docker 容器/sys/kernel/security/ 卷从主机系统挂载。这样 OBI 就可以正确确定 Linux 内核的 lockdown 模式。以下是一个 Docker compose 配置示例,它确保 OBI 拥有足够的信息来确定 lockdown 模式

services:
  ...
  obi:
    image: 'docker.io/otel/ebpf-instrument:main'
    environment:
      OTEL_EBPF_CONFIG_PATH: "/configs/obi-config.yml"
    volumes:
      - /sys/kernel/security:/sys/kernel/security
      - /sys/fs/cgroup:/sys/fs/cgroup

如果未挂载 /sys/kernel/security/ 卷,OBI 将假定 Linux 内核未处于完整性模式。


最后修改日期 2025 年 10 月 29 日:ISSUE 8251 将 obi 镜像标签更新为 main (#8252) (2a818d51)