为什么在 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-graphsprocessor。 - 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。
备注:内容仅供参考。
