PAYATHON 2026

为什么在 Grafana 中看不到来自 Alloy 的 traces?

排障赵师傅

先检查 Beyla 是否发现并插桩了目标应用,以及应用是否收到了可采集的请求。如果测试流量产生期间,otelcol.processor.batch 仍未收到 spans,就应集中排查 Beyla 到 batch 这一段链路。

最初的 eBPF 权限错误已经解决,应用是否插桩成功还需要确认。network_flow 日志只能说明 Beyla 观察到了网络流量,无法证明它已生成或发送 OTLP traces。现有日志还不足以确定唯一根因。

1. 确认 Alloy 能看到目标应用的进程

在 Kubernetes 中,Beyla 需要先发现目标应用进程,才能进行应用级自动插桩。以 DaemonSet 方式部署时,先检查:

  • 目标应用所在节点上是否运行了 Alloy Pod。
  • Alloy Pod 是否启用了 hostPID。
  • 容器实际生效的权限是否与配置一致。
kubectl -n production-hm-alloy get pods -o wide
kubectl -n <application-namespace> get pods -o wide
kubectl -n production-hm-alloy get pod <alloy-pod> -o yaml

查看生成的 Pod 配置中是否有:

spec:
  hostPID: true

如果当前 Pod 没有共享宿主机 PID namespace,Beyla 可能无法发现同节点其他 Pod 中的目标进程。即使能够采集网络流量,这个问题也可能存在。

Alloy Helm Chart 对应的配置项是 controller.hostPID。可以将以下配置合并到现有 values,再通过 helm template 或 Argo CD 的渲染结果,确认最终生成了 spec.template.spec.hostPID: true:

controller:
  type: daemonset
  hostPID: true

alloy:
  stabilityLevel: public-preview
  securityContext:
    appArmorProfile:
      type: Unconfined
    capabilities:
      add:
        - SYS_ADMIN
        - SYS_PTRACE

上述权限配置已经解决了当前环境中的报错。其他内核、容器运行时和安全策略所需的权限可能不同,不能直接套用这个结论。

如果目标应用所在节点有 taint,或 Alloy 配置了 nodeSelector,还要确认 DaemonSet 确实覆盖了该节点。

2. 核对 Beyla 选择的进程和端口

更新后的配置使用:

open_port = 8080

这个值需要匹配目标应用进程实际监听的端口。Kubernetes Service 的 port、targetPort 和容器内监听端口可能不同。例如:

ports:
  - port: 80
    targetPort: 8080

此时应检查应用实际监听的 8080。

原配置还同时使用了:

discovery {
  services {
    exe_path = "http"
    open_ports = "80"
  }
}

exe_path 匹配的是可执行文件路径,含义并非“采集 HTTP 请求”。如果应用进程是 java、node、python 或其他二进制,需要核对这个条件是否匹配。同一条服务选择规则中的多个条件也可能进一步缩小匹配范围。

调试时,先选定一个应用及其监听端口,减少发现规则的干扰。可以保留现有 batch 和 exporter,暂时将 Beyla 部分简化为:

logging {
  level  = "debug"
  format = "logfmt"
}

beyla.ebpf "hm_beyla" {
  debug     = true
  open_port = 8080

  attributes {
    kubernetes {
      enable = "true"
    }
  }

  output {
    traces = [otelcol.processor.batch.hm_beyla.input]
  }
}

将示例中的 8080 替换为目标应用实际监听的端口。排查期间可以移除空的 discovery.services 和网络指标配置,方便阅读日志。

Helm Chart 版本与 Alloy 二进制版本不同。应检查实际使用的容器镜像,再按对应的 Alloy 版本确认参数是否受支持。

3. 主动产生应用请求,检查插桩日志

Beyla 可以通过自动插桩生成 spans,应用无需先通过 SDK 向 Alloy 发送 traces,但必须收到 Beyla 能够识别的请求。

从能够访问应用的环境发起请求:

curl -v http://<application-service>.<application-namespace>.svc:<service-port>/<known-path>

请求地址使用 Service 暴露的端口;Beyla 选择进程时,仍需匹配应用实际监听的端口。

产生流量的同时,查看目标应用所在节点上的 Alloy Pod 日志:

kubectl -n production-hm-alloy logs <alloy-pod> -c alloy --since=10m

具体日志措辞随版本而异,主要检查:

  • 是否发现目标 PID、可执行文件和监听端口。
  • 是否尝试向该进程附加探针。
  • 是否出现权限、内核能力、协议或运行时支持方面的错误。
  • 请求发生后,是否出现与应用请求有关的调试输出。

metrics.network.enable = true 产生的网络流量记录与应用 traces 走的是不同的数据路径。即使观察到 Alloy 与 Tempo 的 4317 端口通信,也无法据此确认连接中已经传输了 spans。

Alloy 组件图显示健康,主要表示组件没有报告相应的健康错误,组件是否正在处理业务数据还需单独确认。

4. 按数据经过的位置判断故障范围

观察测试请求发生时的数据变化,避免仅凭一张 live debugging 截图下结论。

观察结果优先检查
没有发现目标进程hostPID、节点覆盖、端口和进程选择条件
发现进程,但插桩失败权限、内核、运行时及协议支持
插桩后 batch 仍无 spans测试请求是否命中目标进程、过滤条件、Beyla 输出
batch 已有 spans,exporter 报错OTLP 地址、TLS、租户、压缩及网络
Tempo 已接收 spans,Grafana 查不到查询租户、数据源、时间范围及 Tempo 查询链路

也可以查看 Alloy 和 Tempo 暴露的 /metrics,检查 exporter 发送、发送失败以及 distributor 接收 spans 的计数是否随测试请求增长。指标名称和标签以实际版本暴露的内容为准。

要独立验证 Tempo,可以用能够生成 OTLP traces 的测试工具直接发送一条已知 trace,再通过 trace ID 查询。这能验证接收和查询链路,Beyla 的自动插桩仍需另外验证。

5. 有 spans 后,再检查 OTLP 发送和租户

当前 exporter 使用 OTLP/gRPC:

endpoint = "hm-tempo-distributor.production-hm-tempo.svc:4317"

这个地址形式符合 gRPC exporter 的要求。Grafana 数据源访问的 query-frontend 3100 端口用于查询,两者用途不同。

调试时可以先简化 exporter 配置:

otelcol.exporter.otlp "hm_tempo_for_hm_beyla" {
  client {
    endpoint = env("TEMPO_ENDPOINT")

    tls {
      insecure = true
    }
  }
}

这个示例适用于集群内明文连接。如果 Tempo 启用了多租户,应保留现有认证配置:

auth = otelcol.auth.headers.hm_tempo_for_hm_beyla.handler

insecure = true 表示不使用 TLS,此时 insecure_skip_verify 没有实际作用。原配置中的 compression = "zstd" 可以等基础链路正常后再启用。只有 exporter 出现相关错误时,才需要排查压缩兼容性。

更新后的配置还发送了:

X-Scope-OrgID: hm

如果 Tempo 启用了多租户,Grafana 查询时也必须使用同一租户。可以在 Tempo 数据源中配置:

jsonData:
  httpHeaderName1: X-Scope-OrgID
secureJsonData:
  httpHeaderValue1: hm

如果 Tempo 未启用多租户,则按实际运行配置处理。仅凭 exporter 中存在这个 header,无法认定数据已写入租户 hm。

6. 单独检查 Service Graph 的指标链路

Service Graph 还依赖服务关系指标。即使已经能查询 traces,只要指标未生成、写入失败,或 Grafana 指向了其他指标后端,图仍可能为空。

当前配置有两条不同的指标路径:

Beyla → prometheus.scrape → Mimir
Tempo metrics-generator → remote_write → Prometheus

Grafana 的 serviceMap.datasourceUid 则指向 hm-prometheus。

如果使用 Tempo metrics-generator 生成服务关系指标,需要检查:

  • 目标租户的有效配置中是否启用了 service-graphs processor。
  • metrics-generator 是否实际收到 spans。
  • remote write 是否成功。
  • Prometheus 是否启用了 remote write receiver。
  • Prometheus 中能否查询到 traces_service_graph_request_total 等相关指标。

如果使用 Beyla 写入 Mimir 的指标,则需确认 Mimir 中已有 Grafana Service Graph 所需的指标和标签,并将 serviceMap 指向对应的数据源。只启用 application_service_graph,无法保证当前 Grafana 配置能读到这些数据。

还要核对数据源 UID。以下配置只指定了名称:

name: hm-prometheus

它不会自动将 UID 设为 hm-prometheus。应检查实际 UID,或在 provisioning 中显式指定:

- name: hm-prometheus
  uid: hm-prometheus
  type: prometheus
  url: http://hm-prometheus-kube-pr-prometheus.production-hm-prometheus:9090

Loki、Tempo 及其他通过 datasourceUid 引用的数据源,也应确保引用值与实际 UID 一致。nodeGraph.enabled 和 serviceMap 用于展示与关联,不会自行生成 traces 或服务关系指标。

7. 如何理解 Tempo 和 S3 的现有日志

启动日志最后出现了:

joining memberlist cluster succeeded

这说明前面的 gossip-ring DNS 失败至少在本次启动中已经恢复。只有后续持续出现解析失败或 ring 异常,才需要继续排查这个方向。

Overrides config type mismatch 需要单独处理。检查 Helm 渲染后的静态配置、运行时 overrides 和目标租户配置,确认 processors 是否实际生效。这个警告可能影响指标生成,但无法解释 Beyla 上游未向 batch 输出 spans 的原因。

S3 中存在 tempo_cluster_seed.json,只能证明相关组件完成了对该对象的操作。配置中的 admin bucket 与 trace bucket 分开使用,因此该对象的存在无法证明 ingester 已收到 spans,也无法证明 trace bucket 写入正常。trace 数据还需要经过接收、缓冲和 block 刷写过程。

当前先确认 hostPID 和节点覆盖,再选定一个监听端口明确的应用,主动发送请求,并在同节点 Alloy 日志中检查进程发现与插桩过程。等 batch 收到 spans 后,再检查 exporter、Tempo 查询和 Service Graph。

备注:内容仅供参考。