DeepFlow 利用 eBPF 采集并解析应用协议,实现了零侵扰的分布式追踪和指标数据的采集。DeepFlow 已经内置支持了十多种应用协议的解析,并且还在持续增加中。但我们发现实际业务环境中情况会更加复杂:开发会坚持返回 HTTP 200 同时将错误信息放到自定义 JSON 结构中,大量 RPC 的 Payload 部分使用 Protobuf、Thrift 等依赖 Schema 进行解码的序列化方式,调用的处理流程中发生了跨线程导致 eBPF AutoTracing 断链。
针对这些复杂场景,DeepFlow 实现了一套零侵扰的 WebAssembly 插件机制,使得开发人员可针对自己的业务环境定制化 DeepFlow 的协议解析能力。本文将分享两个案例,来介绍 DeepFlow 中的 Wasm 插件能力。
同时,欢迎大家报名参与 9月16日 DeepFlow开展的线下活动《可观测性 Meetup》!
在本例中,被监控 HTTP API 的响应消息为 JSON 格式,当 API 出错时 HTTP 协议的状态码可能仍然是 200,确切的错误信息通过 JSON 中的 OPT_STATUS 等字段返回:
{
"OPT_STATUS": "AUTH_HEADER_ERROR", // 不等于 SUCCESS 时表示调用失败
"DESCRIPTION": "请传递正确的验证头信息", // 详细错误信息
... // 其他返回字段
}
查阅 API 文档后我们得知,OPT_STATUS的值不等于SUCCESS时表示 API 调用失败。在常规的 DeepFlow 解析流程中,会按照如下方式构造 HTTP 调用日志的各个字段:
response_code:赋值为 HTTP 响应头中的状态码,例如 200、404、500 等
response_status:状态码小于 400 时认为正常,4XX 认为是客户端异常,5XX 认为是服务端异常
response_exception:赋值为 HTTP 异常状态码对应的英文解释,例如 404 时此字段赋值为 Not Found
response_result:当 HTTP 状态码为异常时赋值为整个 HTTP Payload
当我们安装了 Wasm 插件后,我们可以在上述解析的基础上,将失败 API 的调用日志中的如下字段进行覆写,以实现正确体现业务错误的效果:
response_code:当 JSON 中 OPT_STATUS != SUCCESS、且 HTTP 状态码小于 400 时,此值覆写为 500
response_status:按照新的 response_code 重新赋值,例如 500 时赋值为服务端异常
response_exception:当 JSON 中的 OPT_STATUS != SUCCESS时覆写为 DESCRIPTION 字段的值
response_result:当 response_code 大于等于 400 时赋值为整个 JSON Payload
我们将 Wasm 插件代码放到了这个 GitHub 仓库中。上述 API 行为描述的实际上是 DeepFlow 企业版中的 statistics 服务,下面演示将此 Wasm 插件注入到 DeepFlow Agent 以后,对 DeepFlow 企业版服务的自我观测效果。首先我们在命令行中触发一次 statistics 服务的 API 调用:
# 请求
curl https://cloud.deepflow.yunshan.net/api/statistics/v1/stats/querier/DBDescription/ShowDatabases
# HTTP 响应头
HTTP/2 401
date: Tue, 22 Aug 2023 01:44:29 GMT
content-type: application/json
content-length: 152
# HTTP 响应体
{
"DATA": false,
"DESCRIPTION": "请传递正确的验证头信息",
"ERR": null,
"LEVEL": 0,
"OPT_STATUS": "AUTH_HEADER_ERROR"
}
上述 API 响应中,HTTP 的状态码为 401,OPT_STATUS=AUTH_HEADER_ERROR。我们能在 DeepFlow 页面正确的看到客户端异常指标(本例中插件注入在 cloud.deepflow K8s 集群的 deepflow-agent 中):
01-client_error_metrics
在 DeepFlow 调用日志页面,可以看到客户端异常的调用日志的详情信息,整个 JSON body 放在了response_result里面:
02-request_log
对该调用发起追踪,能看到是因为fauths返回的 401 异常:
03-tracing
下面是详细的调用链。第一步发起 DNS 请求:
04-dns
第二步调用后端服务验证 License:
05-license
第三步发起 DNS 请求 fauths 服务的地址:
06-dns
第四步调用 fauth 的 /auth API 验证权限,中间需要访问 Redis 获取用户信息:
07-fauth
08-redis
在金融行业的核心交易系统中,服务之间通常通过在 RPC 中传递一个流水号来实现分布式追踪。本例中我们编写了一个演示 Demo 服务,它演示了一个简单的 gRPC 客户端和服务端。我们知道 gRPC 的消息体是使用 Protobuf 序列化的,本例将演示如何利用 DeepFlow 的 Wasm 插件机制解析这个 Demo 中的 Protobuf 消息,获取其中的流水号,并最终实现分布式追踪。Wasm 插件的代码可以在这个 GitHub 仓库中找到。
本例中的 gRPC 消息定义如下:
service Game{
rpc Game(OrderRequest) returns (OrderResponse);
}
message OrderRequest{
string business_id = 1235;
}
message OrderResponse{
string msg = 1235;
}
在 Wasm 插件中,我们将 gRPC Payload 中的 business_id 字段的值赋值到 trace_id 中,用于分布式调用链追踪。同时会将 business_id 及 msg 等原始字段在调用日志的 Native tag 中存储一份,分别对应 attribute.business_id 及 attribute.msg,可用于业务查看更详细的交易信息。
我们将 gRPC Demo 部署在 cloud.deepflow 环境中 Sandbox K8s 集群里,安装好 Wasm 插件后,在 DeepFlow 页面直接过滤 l7_protocol = Custom 即可看到这个私有协议的指标和调用日志数据:
08-metrics
09-request-log
10-tracing
Wasm 插件可使用多种语言开发,目前 DeepFlow 对 Golang 提供了一个 SDK,开发可以参考文档。其中核心的步骤如下:
新建一个 go 项目, 并且拉取 Golang SDK
go mod init ProjectName && go get github.com/deepflowio/deepflow-wasm-go-sdk
在插件中实现协议解析逻辑
package main
import (
"github.com/deepflowio/deepflow-wasm-go-sdk/sdk"
)
func main(){
sdk.Warn("plugin loaded")
sdk.SetParser(SomeParser{})
}
type SomeParser struct {
}
func (p SomeParser) HookIn() []sdk.HookBitmap {
return []sdk.HookBitmap{
// 一般只需要 hook 协议解析
sdk.HOOK_POINT_PAYLOAD_PARSE,
}
}
func (p dnsParser) OnHttpReq(ctx *sdk.HttpReqCtx) sdk.HttpAction {
return sdk.ActionNext()
}
func (p dnsParser) OnHttpResp(ctx *sdk.HttpRespCtx) sdk.HttpAction {
return sdk.ActionNext()
}
func (p dnsParser) OnCheckPayload(ctx *sdk.ParseCtx) (uint8, string) {
// 这里是协议判断的逻辑, 返回 0 表示失败
// return 0, ""
return 1, "some protocol"
}
func (p dnsParser) OnParsePayload(ctx *sdk.ParseCtx) sdk.ParseAction {
// 这里是解析协议的逻辑
if ctx.L4 != sdk.TCP|| ctx.L7 != 1{
return sdk.ActionNext()
}
return sdk.ActionNext()
}
编译为 Wasm 插件
tinygo build -o wasm.wasm -target wasi -panic=trap -scheduler=none -no-debug *.go
将编译好的插件上传至 deepflow-server
deepflow-ctl plugin create --type wasm --image wasm.wasm --name wasm-demo-1
修改 deepflow-agent 的组配置,添加需要加载的插件
static_config:
ebpf:
# 对于 deepflow-agent 原生不支持的协议, eBPF 数据需要添加端口白名单才能上报
kprobe-whitelist:
port-list: 9999
# 如果配置了 l7-protocol-enabled,别忘了放行 Custom 类型的协议
l7-protocol-enabled:
- Custom
# other protocol
wasm-plugins:
- wasm-demo-1 // 对应 deepflow-ctl 上传插件的名称
注:目前修改此配置后 deepflow-agent 会自动重启。
检查插件是否正确加载
kubectl -n deepflow logs -f deepflow-agent-xxxxx | grep -i plugin
11-check
我们看到插件 main 函数里的 warn 日志正常输出,说明插件加载成功了。
DeepFlow Wasm 插件机制提供了一个可编程的、安全的、资源消耗可控的运行沙箱,它是整个 DeepFlow Pipeline 机制的重要一环。它的使用场景包括:
增强原生支持的协议:在原生协议的解析能力基础之上,提取更多的业务信息
支持私有协议的解析:特别是从 Protobuf、Thrift 等依赖 Schema 的 Payload 内容中提取业务字段
零侵扰分布式追踪:通过解析调用中的事务全局 ID,用于实现分布式追踪
自定义脱敏:对 MySQL、Redis 等协议中的业务敏感信息进行抹除
未来,我们还会基于 Wasm 插件提供更强大的可编程性。例如:
自定义过滤:对调用日志进行基于 URL、Endpoint 等字段的过滤
自定义采样:通过对 TraceID 等追踪字段的分析,决定是否对调用日志进行采样丢弃
DeepFlow 开源项目旨在为复杂的云原生应用提供深度可观测性。DeepFlow 基于 eBPF 实现了零插桩(Zero Code)、全覆盖(Full Stack)的指标、追踪、日志采集,并通过智能标签技术实现了所有观测数据的全关联(Universal Tagging)和高效存取。使用 DeepFlow,可以让云原生应用自动具有深度可观测性,从而消除开发者不断插桩的沉重负担,并为 DevOps/SRE 团队提供从代码到基础设施的监控及诊断能力。
GitHub 地址:https://github.com/deepflowio/deepflow
访问 DeepFlow Demo,体验零插桩、全覆盖、全关联的可观测性。
|