Grafana 开源了一款 eBPF 采集器 Beyla

Grafana Beyla 是面向 eBPF 可观测性的采集器,可在不侵入业务代码的前提下采集 HTTP 服务的 RED 指标。本文用 Go HTTP 服务演示 Beyla 的安装、运行参数、Prometheus 指标暴露和 Grafana Dashboard 使用方式。

作者 秦晓辉

Grafana Beyla 是一款 eBPF 采集器,适合用来体验无侵入采集 HTTP 服务 RED 指标的工作方式。本文保留原始命令和指标样例,重点说明如何准备测试服务、启动 Beyla、查看 Prometheus 指标,以及后续如何接入采集器和 Grafana Dashboard。

核心要点

  • Beyla 的价值在于通过 eBPF 获取服务请求信息,不需要在示例 Go 服务中手工埋点。
  • 本文使用 RED(Rate、Errors、Duration)指标作为验证目标,重点观察请求量、错误状态码和延迟分布。
  • Beyla 通过 BEYLA_PROMETHEUS_PORT 暴露 Prometheus 格式指标,通过 OPEN_PORT 指定要观测的服务端口。
  • 指标可以被 vmagent、Categraf、Prometheus 等采集器抓取,再进入后端存储并通过 Grafana 分析。
  • 原文示例基于早期 Beyla 版本,生产使用前需要结合当前版本文档复核参数和兼容性。

eBPF 基础介绍可以参考我之前的文章《eBPF Hello world》。理论上,eBPF 可以拿到服务收到的请求信息,比如QPS、延迟、成功率等,这些数据对于应用级监控至关重要,Grafana Beyla 就是为此而生的。

准备一个可观测的 HTTP 服务

要测试使用 Beyla 采集服务的 RED(Rate-Errors-Duration) 指标,那首先得有个服务,这里我用的是 answer( https://answer.flashcat.cloud ) 论坛,你也可以自己搞一个简单的 http 服务,比如:

package main

import (
    "net/http"
    "strconv"
    "time"
)

func handleRequest(rw http.ResponseWriter, req *http.Request) {
    status := 200
    for k, v := range req.URL.Query() {
        if len(v) == 0 {
            continue
        }
        switch k {
        case "status":
            if s, err := strconv.Atoi(v[0]); err == nil {
                status = s
            }
        case "delay":
            if d, err := time.ParseDuration(v[0]); err == nil {
                time.Sleep(d)
            }
        }
    }
    rw.WriteHeader(status)
}

func main() {
    http.ListenAndServe(":8080",
                 http.HandlerFunc(handleRequest))
}

上面这个代码,保存成 server.go,然后用 go run server.go 即可运行,当然,前提是你机器上有 go 开发环境。这个小服务,可以接收两个参数,一个是 status,用来指定返回的 http 状态码,另一个是 delay,用来指定延迟多久返回,比如:

curl -v "http://localhost:8080/foo?status=404"

上面的命令,会返回 404 状态码,如果想延迟 1 秒返回,可以这样:

curl -v "http://localhost:8080/foo?delay=1s"

接下来,我们就可以使用 Beyla 采集这个服务的 RED 指标了。这个示例服务的好处是输入、状态码和延迟都可控,方便验证 Beyla 是否真的采集到了请求维度的数据。

下载 Beyla

我的机器上有 go 开发环境,所以我直接使用 go install 安装了,你也可以去 Beyla 的 release 页面下载二进制包,然后解压缩使用。

go install github.com/grafana/beyla/cmd/beyla@latest

运行 Beyla

使用下面的命令运行 Beyla:

$ BEYLA_PROMETHEUS_PORT=8999 PRINT_TRACES=true OPEN_PORT=8080 sudo -E beyla

或者直接使用 root 账号运行,比如我是这么跑的:

$ BEYLA_PROMETHEUS_PORT=8999 PRINT_TRACES=true OPEN_PORT=8080 beyla

解释一下这几个参数:

  • BEYLA_PROMETHEUS_PORT: Beyla 要监听的端口,通过这个端口暴露 metrics 指标数据
  • PRINT_TRACES: 是否打印 trace 日志
  • OPEN_PORT: Beyla 采集的目标服务监听的端口,这里是 8080,上面给出的那段 go server 的代码就是监听在 8080,我的机器上 answer 论坛程序也是监听在 8080,你要监控的程序如果不是监听在 8080,可以在换成你自己的端口
环境变量 作用 示例值
BEYLA_PROMETHEUS_PORT Beyla 暴露 Prometheus 指标的端口 8999
PRINT_TRACES 控制是否在终端打印 trace 日志 true
OPEN_PORT 被 Beyla 观测的目标服务端口 8080

查看 Prometheus 指标

运行之后,可以通过 curl 查看指标:

curl http://localhost:8999/metrics

返回的内容如下:

# HELP http_client_duration_seconds duration of HTTP service calls from the client side, in seconds
# TYPE http_client_duration_seconds histogram
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0"} 0
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.005"} 0
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.01"} 0
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.025"} 0
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.05"} 0
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.075"} 0
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.1"} 0
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.25"} 0
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.5"} 0
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.75"} 0
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="1"} 0
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="2.5"} 1
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="5"} 1
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="7.5"} 1
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="10"} 1
http_client_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="+Inf"} 1
http_client_duration_seconds_sum{http_method="GET",http_status_code="200",service_name="answer"} 1.668771575
http_client_duration_seconds_count{http_method="GET",http_status_code="200",service_name="answer"} 1
# HELP http_client_request_size_bytes size, in bytes, of the HTTP request body as sent from the client side
# TYPE http_client_request_size_bytes histogram
http_client_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0"} 1
http_client_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="32"} 1
http_client_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="64"} 1
http_client_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="128"} 1
http_client_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="256"} 1
http_client_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="512"} 1
http_client_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="1024"} 1
http_client_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="2048"} 1
http_client_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="4096"} 1
http_client_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="8192"} 1
http_client_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="+Inf"} 1
http_client_request_size_bytes_sum{http_method="GET",http_status_code="200",service_name="answer"} 0
http_client_request_size_bytes_count{http_method="GET",http_status_code="200",service_name="answer"} 1
# HELP http_server_duration_seconds duration of HTTP service calls from the server side, in seconds
# TYPE http_server_duration_seconds histogram
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0"} 0
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.005"} 201
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.01"} 789
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.025"} 799
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.05"} 799
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.075"} 799
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.1"} 799
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.25"} 799
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.5"} 799
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0.75"} 799
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="1"} 799
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="2.5"} 800
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="5"} 800
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="7.5"} 800
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="10"} 800
http_server_duration_seconds_bucket{http_method="GET",http_status_code="200",service_name="answer",le="+Inf"} 800
http_server_duration_seconds_sum{http_method="GET",http_status_code="200",service_name="answer"} 5.752096697000003
http_server_duration_seconds_count{http_method="GET",http_status_code="200",service_name="answer"} 800
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="0"} 0
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="0.005"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="0.01"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="0.025"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="0.05"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="0.075"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="0.1"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="0.25"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="0.5"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="0.75"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="1"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="2.5"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="5"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="7.5"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="10"} 1
http_server_duration_seconds_bucket{http_method="GET",http_status_code="302",service_name="answer",le="+Inf"} 1
http_server_duration_seconds_sum{http_method="GET",http_status_code="302",service_name="answer"} 0.001523002
http_server_duration_seconds_count{http_method="GET",http_status_code="302",service_name="answer"} 1
# HELP http_server_request_size_bytes size, in bytes, of the HTTP request body as received at the server side
# TYPE http_server_request_size_bytes histogram
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="0"} 800
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="32"} 800
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="64"} 800
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="128"} 800
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="256"} 800
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="512"} 800
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="1024"} 800
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="2048"} 800
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="4096"} 800
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="8192"} 800
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="200",service_name="answer",le="+Inf"} 800
http_server_request_size_bytes_sum{http_method="GET",http_status_code="200",service_name="answer"} 0
http_server_request_size_bytes_count{http_method="GET",http_status_code="200",service_name="answer"} 800
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="302",service_name="answer",le="0"} 1
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="302",service_name="answer",le="32"} 1
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="302",service_name="answer",le="64"} 1
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="302",service_name="answer",le="128"} 1
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="302",service_name="answer",le="256"} 1
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="302",service_name="answer",le="512"} 1
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="302",service_name="answer",le="1024"} 1
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="302",service_name="answer",le="2048"} 1
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="302",service_name="answer",le="4096"} 1
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="302",service_name="answer",le="8192"} 1
http_server_request_size_bytes_bucket{http_method="GET",http_status_code="302",service_name="answer",le="+Inf"} 1
http_server_request_size_bytes_sum{http_method="GET",http_status_code="302",service_name="answer"} 0
http_server_request_size_bytes_count{http_method="GET",http_status_code="302",service_name="answer"} 1
# HELP promhttp_metric_handler_errors_total Total number of internal errors encountered by the promhttp metric handler.
# TYPE promhttp_metric_handler_errors_total counter
promhttp_metric_handler_errors_total{cause="encoding"} 0
promhttp_metric_handler_errors_total{cause="gathering"} 0

这段输出里已经能看到 http_server_duration_secondshttp_client_duration_seconds、请求大小、状态码、服务名等信息。对于应用监控来说,最先关注的通常是三类指标:

观测目标 对应指标样例 排障价值
请求延迟 http_server_duration_seconds_buckethttp_server_duration_seconds_sum 判断服务响应是否变慢,并支持按桶计算延迟分布
状态码 http_status_code 标签 区分 2xx、3xx、4xx、5xx 请求,定位错误趋势
请求数量 http_server_duration_seconds_count 估算请求量,结合错误和延迟判断服务健康度

这些指标就可以用采集器来抓了,比如 vmagent、categraf、prometheus 等,完事之后入库,使用 Grafana 展示分析即可,经常关注本公众号的读者对于这些知识应该比较熟悉了,这里不再赘述。Beyla 默认提供了一个 Grafana Dashboard,可以导入测试:https://github.com/grafana/beyla/tree/main/grafana

常见问题

Beyla 适合替代应用内埋点吗?

从本文示例看,Beyla 更适合先解决“无需改业务代码也能采集请求 RED 指标”的问题。它可以降低接入门槛,但是否替代应用内埋点,要看你是否还需要业务语义、代码级 span、用户维度等更细粒度信息。

为什么示例里需要指定 OPEN_PORT

OPEN_PORT 用来告诉 Beyla 观测哪个本地服务端口。本文的 Go 服务和 answer 示例都监听 8080,所以命令中使用 OPEN_PORT=8080

采集到指标后下一步做什么?

下一步是让 vmagent、Categraf 或 Prometheus 抓取 http://localhost:8999/metrics,写入时序库,再导入 Beyla Dashboard 或自建 Grafana 面板分析延迟、错误和请求量。

结语

Beyla 的核心价值是把 eBPF 的无侵入能力带到应用级可观测场景中。本文演示的是最小闭环:准备 HTTP 服务、启动 Beyla、查看 Prometheus 指标、再交给采集器和 Grafana 使用。对于想理解 eBPF 应用监控路径的同学,这已经足够作为一次可复现的尝鲜实验。

需要注意的是,本文原始实践基于 Beyla 早期阶段,生产落地前应复核当前版本的参数、权限要求和运行限制。

联系我们交流

延伸路径

继续看解决方案和产品对比

如果你正在做监控、可观测性或故障定位相关选型,建议从解决方案和产品对比继续往下看。

标签 eBPF
快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云