← 文章 / 未分类
signoz 2小时前 · 2026-09-17 07:14:58 · 1 阅读

使用 OpenTelemetry 实现 eBPF 零代码插桩

OpenTelemetry Go 自动插桩通过 eBPF 探针挂载到运行中的 Go 进程上,并将 trace 导出至 SigNoz。eBPF 是 Linux 内核的一项特性,可以在进程中的指定位置运行小型程序。该 agent 以独立进程的形式运行在你的应用旁边,你的二进制文件无需重新构建。

本项目仍在开发中

OpenTelemetry 官方将这个 eBPF agent 标记为开发中状态。它目前只支持追踪四个库,需要提升权限,并且在新的探针 API 就绪之前不接受新增的插桩探针。如果你能重新构建应用,建议改用 Go 编译时插桩,它更稳定、覆盖的库更多,而且不需要特殊权限。

提示

当你无法修改构建过程时,可以使用 eBPF agent。常见场景包括:厂商提供的二进制文件、冻结的发布产物,或不归你管理的容器镜像。如果能修改构建,OpenTelemetry SDK 和编译时插桩能覆盖更多库。Go 插桩概览对这三种方式做了对比。

该 agent 只产出 trace,不产出 metrics 和 logs。SigNoz 会基于 span 派生出速率、错误和延迟指标。

使用自托管的 SigNoz?

大部分步骤相同,只需按照 Cloud -> Self-Hosted 的说明更新 endpoint 并移除 ingestion key 请求头即可。

前提条件

  • 一台 amd64 或 arm64 架构的 Linux 主机,内核版本为 4.19 或更高。在 macOS 和 Windows 上,请在 Linux 容器或虚拟机中运行该 agent。
  • 拥有以 root 运行特权容器的权限。
  • 一个至少使用了 受支持库 之一的 Go 应用。
  • 应用二进制文件的路径,且该路径对 agent 可读。
  • 一个 SigNoz 实例(CloudSelf-Hosted 均可)。

工作原理

该代理以独立进程形式与你的应用并行运行。它会读取目标二进制文件,识别其支持的函数,并为其附加 uprobe(用户态探针)。uprobe 是一种内核钩子,当进程执行到指定指令时触发。

代理容器与应用共享进程命名空间,将 uprobe 附加到 Go 二进制文件,从内核读取 span 事件,并将数据以 OTLP 格式导出至 SigNoz
代理向运行中的二进制文件附加探针并直接导出 span

eBPF 程序将事件写入内核环形缓冲区(Ring Buffer)。代理读取该缓冲区,构建 span(链路追踪单元),并将其导出至 SigNoz。正因如此,代理需要共享的进程命名空间、root 权限以及特权容器:它需通过 /proc 读取其他进程信息,并将程序加载至内核。

将追踪数据发送至 SigNoz

步骤 1. 将代理部署在应用旁侧

代理通过 OTEL_GO_AUTO_TARGET_EXE 定位目标进程,该变量存储二进制文件的完整路径。由于代理与应用共享进程命名空间,因此可以读取该路径。

DockerKubernetesLinux host

docker-compose.yaml 中将代理配置为服务。它需要挂载宿主机的进程命名空间、/proc 目录,并与应用保持一致的二进制文件路径:

docker-compose.yaml
services:
  myapp:
    image: <your-image>
    volumes:
      - ./bin:/app:ro
    command: /app/myapp
 
  go-auto:
    image: otel/autoinstrumentation-go:v0.24.0
    privileged: true
    pid: "host"
    environment:
      OTEL_GO_AUTO_TARGET_EXE: /app/myapp
      OTEL_SERVICE_NAME: "<service-name>"
      OTEL_EXPORTER_OTLP_ENDPOINT: "https://ingest.<region>.signoz.cloud:443"
      OTEL_EXPORTER_OTLP_HEADERS: "signoz-ingestion-key=<your-ingestion-key>"
    volumes:
      - ./bin:/app:ro
      - /proc:/host/proc
    depends_on:
      - myapp

在两个容器中将二进制文件挂载到相同路径。由于代理通过 /proc 读取路径,两者的路径必须一致。

启动服务栈:

Copy
docker compose up -d

在应用所在的同一 Pod 中,将代理作为第二个容器运行。

将摄入密钥(Ingestion Key)存储在 Secret 中:

Copy
kubectl create secret generic signoz-ingestion \
  --from-literal=otlp-headers="signoz-ingestion-key=<your-ingestion-key>"

添加 agent 容器,并在 Pod 中设置 shareProcessNamespace

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      # agent 和应用程序必须共享进程命名空间。
      shareProcessNamespace: true
      containers:
        - name: myapp
          image: <your-image>
          ports:
            - containerPort: 8080
 
        - name: autoinstrumentation-go
          image: otel/autoinstrumentation-go:v0.24.0
          imagePullPolicy: IfNotPresent
          env:
            - name: OTEL_GO_AUTO_TARGET_EXE
              value: /usr/local/bin/myapp
            - name: OTEL_SERVICE_NAME
              value: "<service-name>"
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: "https://ingest.<region>.signoz.cloud:443"
            - name: OTEL_EXPORTER_OTLP_HEADERS
              valueFrom:
                secretKeyRef:
                  name: signoz-ingestion
                  key: otlp-headers
          securityContext:
            runAsUser: 0
            privileged: true

OTEL_GO_AUTO_TARGET_EXE 指向应用容器内的二进制文件路径。agent 通过共享的进程命名空间解析该路径。

应用配置:

Copy
kubectl apply -f deployment.yaml

如果集群启用了 Pod 安全准入控制,该 namespace 必须允许 privileged Pod。在应用 Deployment 前先给 namespace 打上标签:

Copy
kubectl label namespace <your-namespace> pod-security.kubernetes.io/enforce=privileged

针对宿主机上的进程运行 agent 容器。将二进制文件以只读方式挂载到与宿主机相同的路径:

Copy
docker run --rm \
  --privileged \
  --pid=host \
  -v /usr/local/bin/myapp:/usr/local/bin/myapp:ro \
  -v /proc:/host/proc \
  -e OTEL_GO_AUTO_TARGET_EXE=/usr/local/bin/myapp \
  -e OTEL_SERVICE_NAME="<service-name>" \
  -e OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443" \
  -e OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>" \
  otel/autoinstrumentation-go:v0.24.0

如果不使用 Docker,可以从源码构建 agent。构建需要 Linux 环境以及 clangllvmlibbpf-dev,具体见构建说明。该项目不提供预编译的二进制文件。

请确认以下参数:

  • <region>:你的 SigNoz Cloud 区域
  • <your-ingestion-key>:你的 SigNoz ingestion key
  • <service-name>:服务在 SigNoz 中显示的名称,例如 payment-service
  • OTEL_GO_AUTO_TARGET_EXE:应用程序二进制文件的完整路径,而不是进程名。

如果应用尚未启动,agent 会等待它启动。

第 2 步:确认 agent 已附加

查看 agent 日志,成功启动时会以这几行结尾:

Copy
loading probe
instrumentation loaded successfully, starting...

此外,agent 在从二进制文件读取每个字段时会记录 Offset not cached, analyzing directly。当你的 Go 版本或依赖库版本比 agent 发布版本更新时,就会看到这条日志。此时 agent 会直接从二进制文件中的 DWARF 数据读取偏移量。

这种回退机制依赖调试信息。如果二进制文件是用 -ldflags "-s -w" 构建的,其中不含调试信息,agent 就会加载探针失败并直接退出。解决办法是构建时不做裁剪,或者使用缓存覆盖了你所用版本的 agent 发行版。

验证

向应用发送几个请求,然后打开 SigNoz。

进入 Services 页面,一两分钟内你的服务就会出现,并显示请求速率、错误率和延迟。

进入 Traces 页面,按服务名过滤。HTTP server span 会以路由作为名称,例如 GET /rolldice

Spans from a Go service instrumented with the OpenTelemetry eBPF agent, listed in the SigNoz Traces Explorer
从未经修改的 Go 二进制文件中捕获的 HTTP 和数据库 Spans

打开一条 Trace,数据库调用和出站 HTTP 请求会作为该请求的子 Span 展示。

Trace detail for a Go service instrumented with the OpenTelemetry eBPF agent
由 eBPF 探针生成的完整请求树,应用层无需引入 SDK

支持的库

该 Agent 支持追踪以下四个库:

Span 类型
net/http(客户端和服务端)HTTP
google.golang.org/grpc(客户端和服务端)RPC
database/sql数据库客户端
github.com/segmentio/kafka-go消息队列

在新探针 API 就绪之前,Agent 暂不添加新的探针,因此预期不会有太多新增支持。关于本指南所固定版本的兼容性矩阵,请参阅 v0.24.0 的兼容性矩阵

需要支持更多库?

编译时插桩支持 13 个库,包括 gin、go-redis、MongoDB 和 AWS SDK。这种方式需要重新编译,但无需特殊权限。

记录 SQL 语句

默认情况下,database/sql Span 不包含查询文本,且名称仅为 DB。可以通过以下两个变量更改这一行为:

Copy
OTEL_GO_AUTO_INCLUDE_DB_STATEMENT=true
OTEL_GO_AUTO_PARSE_DB_STATEMENT=true

OTEL_GO_AUTO_INCLUDE_DB_STATEMENT 用于在 Span 中添加 SQL 文本。OTEL_GO_AUTO_PARSE_DB_STATEMENT 还会根据操作类型和表名来命名 Span,例如 SELECT rolls。第二个变量仅在第一个变量已设置时才生效。

查询文本可能包含个人数据。只有在确保查询内容可以安全存储时,才开启这些功能。

捕获 OpenTelemetry API 的 span

如果应

原始来源: signoz

评论 (0)