说明:
OpenTelemetry 是工具、API 和 SDK 的集合,用于检测、生成、收集和导出遥测数据(指标、日志和跟踪),帮助用户分析软件的性能和行为。关于 OpenTelemetry 的更多信息请参考 OpenTelemetry 官方网站。
eBPF(扩展伯克利包过滤器)允许在内核空间运行安全的用户定义代码,而不需要修改内核源代码或加载内核模块。eBPF 与 OpenTelemetry 结合,使得用户无需在代码中手动埋点就可实现完整的监控能力。
腾讯云 eBPF 探针基于开源社区 opentelemetry-go-instrumentation 项目二次开发,功能和特性仍在快速迭代中。
本文将介绍如何通过腾讯云 eBPF 探针接入 Go 应用。
前提条件
说明:
eBPF 目前仅支持 Linux,内核版本需要在 4.19 及以上。
暂不支持 macOS 和 Windows 的系统(包括运行在这类宿主机中的 Linux 容器)。
Go 版本需在 1.18 及以上,支持的库和框架如下:
库/框架 | 版本 |
database/sql | go1.12 to go1.23.0 |
github.com/segmentio/kafka-go | v0.4.1 to v0.4.47 |
google.golang.org/grpc | v1.14.0 to v1.66.0 |
net/http | go1.12 to go1.22.6 |
注意:
基于
net/http
的 Web 框架,如 Gin 等都支持。接入流程
步骤1:获取接入点和 Token
1. 登录 腾讯云可观测平台 控制台。
2. 在左侧菜单栏中选择应用性能监控,单击应用列表 > 接入应用。
3. 在右侧弹出的数据接入抽屉框中,单击 Go 语言。
4. 在接入 Go 应用页面,选择您所要接入的地域以及业务系统。
5. 选择接入协议类型为 OpenTelemetry。
6. 上报方式选择您所想要的上报方式,获取您的接入点和 Token。
说明:
内网上报:使用此上报方式,您的服务需运行在腾讯云 VPC。通过 VPC 直接联通,在避免外网通信的安全风险同时,可以节省上报流量开销。
外网上报:当您的服务部署在本地或非腾讯云 VPC 内,可以通过此方式上报数据。请注意外网通信存在安全风险,同时也会造成一定上报流量费用。
步骤2:下载探针
linux/amd64 探针下载。
linux/arm64 探针下载。
赋予探针执行权限:
chmod +x otel-go-instrumentation
步骤3:接入并上报
1. 确认应用的运行路径
将需要接入的应用编译成二进制文件,确认应用启动时的完整路径。
2. 运行探针
运行探针需要有系统的 root 权限,使用以下命令接入:
sudo OTEL_GO_AUTO_TARGET_EXE=</path/to/executable_binary> \\OTEL_SERVICE_NAME=<serviceName> \\OTEL_EXPORTER_OTLP_PROTOCOL=grpc \\OTEL_TRACES_EXPORTER=otlp \\OTEL_EXPORTER_OTLP_ENDPOINT=<endpoint> \\OTEL_RESOURCE_ATTRIBUTES=token=<token>,host.name=<hostName> \\./otel-go-instrumentation
对应字段的说明如下:
</path/to/executable_binary>
:应用可执行文件的路径,这里一定要是绝对路径。<serviceName>
:应用名,多个使用相同 serviceName 接入的应用进程,在 APM 中会表现为相同应用下的多个实例。应用名最长63个字符,只能包含小写字母、数字及分隔符“ - ”,且必须以小写字母开头,数字或小写字母结尾。<endpoint>
:前置步骤中拿到的接入点,注意这里必须添加http://
前缀。<token>
:前置步骤中拿到业务系统 Token。<hostName>
:该实例的主机名,是应用实例的唯一标识,通常情况下可以设置为应用实例的 IP 地址。下述内容以应用名为
myService
,运行路径为/root
,业务系统 Token 为 myToken
,主机名为 192.168.0.10
,接入点以https://ap-guangzhou.apm.tencentcs.com:4317
为例,完整的启动命令为:sudo OTEL_GO_AUTO_TARGET_EXE=/root/myService \\OTEL_SERVICE_NAME=myService \\OTEL_EXPORTER_OTLP_PROTOCOL=grpc \\OTEL_TRACES_EXPORTER=otlp \\OTEL_EXPORTER_OTLP_ENDPOINT=https://ap-guangzhou.apm.tencentcs.com:4317 \\OTEL_RESOURCE_ATTRIBUTES=token=myToken,host.name=192.168
.0.10
\\./otel-go-instrumentation
3. 运行应用
运行应用,当发生接口调用时,探针会输出日志。如果应用停止运行,探针不需要停止,应用下次启动时会自动埋点。
4. 接入验证
接入侧
探针的日志输出中含有
instrumentation loaded successfully
字段表示接入成功:{"level":"info","ts":1725609047.2234442,"logger":"go.opentelemetry.io/auto","caller":"cli/main.go:119","msg":"starting instrumentation..."}{"level":"info","ts":1725609047.2235398,"logger":"Instrumentation.Manager","caller":"instrumentation/manager.go:195","msg":"loading probe","name":"net/http/server"}{"level":"info","ts":1725609047.388379,"logger":"go.opentelemetry.io/auto","caller":"cli/main.go:115","msg":"instrumentation loaded successfully"}
APM 控制台
在发生接口调用的情况下,应用性能监控 > 应用列表 中将展示接入的应用,点击应用名称/ID 进入应用详情页,再选择实例监控,即可看到接入的应用实例。由于可观测数据的处理存在一定延时,如果接入后在控制台没有查询到应用或实例,请等待30秒左右。
接入错误排查
探针仅有一条日志输出:
{"level":"info","ts":1725609014.2038825,"logger":"go.opentelemetry.io/auto","caller":"cli/main.go:86","msg":"building OpenTelemetry Go instrumentation ...","globalImpl":false}
一般是如下两种情况:
1. 应用没有启动;
2. 应用的启动路径(
OTEL_GO_AUTO_TARGET_EXE
)错误。探针日志报错:
traces export: failed to exit idle mode: dns resolver: missing address
一般是接入点没有添加
http://
前缀导致。eBPF 接入代码示例
说明:
以下将给出多组使用 eBPF 探针接入 APM 的代码示例,帮助用户更方便地把业务的链路数据上报到 APM。
使用 eBPF 探针上报数据时,几乎不需要更改已有的业务代码,但是需要一些细节,例如代码中 context 的传递。
项目一:对外提供接口的数据库操作程序
本项目的主要功能是对外提供一个 HTTP 接口,调用此接口可以操作 sqlite 数据库。基于 Go 语言的标准库
net/http
和 database/sql
实现。package mainimport ("database/sql""fmt""net/http""os"_ "github.com/mattn/go-sqlite3""go.uber.org/zap")const (sqlQuery = "SELECT * FROM contacts"dbName = "test.db"tableDefinition = `CREATE TABLE contacts (contact_id INTEGER PRIMARY KEY,first_name TEXT NOT NULL,last_name TEXT NOT NULL,email TEXT NOT NULL,phone TEXT NOT NULL);`tableInsertion = `INSERT INTO 'contacts'('first_name', 'last_name', 'email', 'phone') VALUES('Moshe', 'Levi', 'moshe@gmail.com', '052-1234567');`)type Server struct {db *sql.DB}// 初始化数据库func CreateDb() {file, err := os.Create(dbName)if err != nil {panic(err)}err = file.Close()if err != nil {panic(err)}}func NewServer() *Server {CreateDb()database, err := sql.Open("sqlite3", dbName)if err != nil {panic(err)}_, err = database.Exec(tableDefinition)if err != nil {panic(err)}return &Server{db: database,}}func (s *Server) queryDb(w http.ResponseWriter, req *http.Request) {ctx := req.Context()conn, err := s.db.Conn(ctx)if err != nil {panic(err)}// 注意这里一定要传递 `ctx` 或者 `req.Context()`,使 HTTP 请求和数据库的操作记录可以保持在一条链路中。// 如果在这里没有传递请求的 context,写成了 `s.db.Exec(tableInsertion)`,就会导致链路中断,HTTP span 和 database span 出现在两条链路中!_, err = s.db.ExecContext(ctx, tableInsertion)if err != nil {panic(err)}rows, err := conn.QueryContext(req.Context(), sqlQuery)if err != nil {panic(err)}logger.Info("queryDb called")for rows.Next() {var id intvar firstName stringvar lastName stringvar email stringvar phone stringerr := rows.Scan(&id, &firstName, &lastName, &email, &phone)if err != nil {panic(err)}fmt.Fprintf(w, "ID: %d, firstName: %s, lastName: %s, email: %s, phone: %s\\n", id, firstName, lastName, email, phone)}}
注意:
请关注代码中的注释说明,一定要正确传递 context 才能正确构造 span 之间的父子关系,保证链路不会中断。