我们知道deepflow是当前业内比较流行的无侵入可观测平台,为了提高deepflow的扩展性,deepflow也引入了wasm插件机制来让用户自己对观测内容的处理。
wasm实现机制
下面我们来看下deepflow的具体实现
wasm-go-sdk
deepflow也定义了自己的一套wasm abi接口,并提供了go版本的sdk:
https://github.com/deepflowio/deepflow-wasm-go-sdk
//export on_http_req
func onHttpReq() bool {
if vmParser == nil {
return false
}
paramBuf := [PARSE_PARAM_BUF_SIZE]byte{}
ctxSize := vmReadCtxBase(¶mBuf[0], len(paramBuf))
if ctxSize == 0 {
return false
}
reqInfo := [HTTP_REQ_BUF_SIZE]byte{}
reqCtxSize := vmReadHttpReqInfo(&reqInfo[0], len(reqInfo))
if reqCtxSize == 0 {
return false
}
ctx := deserializeHttpReqCtx(paramBuf[:ctxSize], reqInfo[:reqCtxSize])
if ctx == nil {
return false
}
act := vmParser.OnHttpReq(ctx)
if act == nil {
return false
}
info, err := act.getParsePayloadResult()
if err != nil {
Error("on http req encounter error: %v", err)
return act.abort()
}
if len(info) > 1 {
Error("on http req return multi info")
return act.abort()
}
data := serializeL7ProtocolInfo(info, DirectionRequest)
if len(data) == 0 {
return act.abort()
}
hostReadL7ProtocolInfo(&data[0], len(data))
return act.abort()
}
//export on_http_resp
func onHttpResp() bool {
if vmParser == nil {
return false
}
paramBuf := [PARSE_PARAM_BUF_SIZE]byte{}
ctxSize := vmReadCtxBase(¶mBuf[0], len(paramBuf))
if ctxSize == 0 {
return false
}
respInfo := [HTTP_RESP_BUF_SIZE]byte{}
respCtxSize := vmReadHttpRespInfo(&respInfo[0], len(respInfo))
if respCtxSize == 0 {
return false
}
ctx := deserializeHttpRespCtx(paramBuf[:ctxSize], respInfo[:respCtxSize])
if ctx == nil {
return false
}
act := vmParser.OnHttpResp(ctx)
if act == nil {
return false
}
info, err := act.getParsePayloadResult()
if err != nil {
Error("on http req encounter error: %v", err)
return act.abort()
}
if len(info) > 1 {
Error("on http resp return multi info")
return act.abort()
}
if len(info) > 0 && info[0].Resp.Status == nil {
// preserve the status if not rewrite
info[0].Resp.Status = &ctx.Status
}
data := serializeL7ProtocolInfo(info, DirectionResponse)
if len(data) == 0 {
return act.abort()
}
hostReadL7ProtocolInfo(&data[0], len(data))
return act.abort()
}同样的可以看到,这些接口都有相应的export标签,也是遵循wasi的规范,可以编译成wasm文件,使用任意的wasm虚拟机解析并调用
agent处理
那么deepflow是怎么使用wasm的呢?
我们知道,deepflow分为server和agent两大块,
server主要是管理面
agent是数据面,主要用来无侵入的对应用进行可观测能力。所以对wasm的使用主要是在agent里面
下面我们简单来看看,因为agent是rust语言写的,我对这个语言不熟悉,只能看个大概:
pub(super) const EXPORT_FUNC_CHECK_PAYLOAD: &str = "check_payload";
pub(super) const EXPORT_FUNC_PARSE_PAYLOAD: &str = "parse_payload";
pub(super) const EXPORT_FUNC_ON_HTTP_REQ: &str = "on_http_req";
pub(super) const EXPORT_FUNC_ON_HTTP_RESP: &str = "on_http_resp";
pub(super) const EXPORT_FUNC_GET_HOOK_BITMAP: &str = "get_hook_bitmap";上面是对abi接口的函数名定义
// get all vm export func
let vm_func_on_http_req =
get_instance_export_func::<(), i32>(&instance, &mut *store, EXPORT_FUNC_ON_HTTP_REQ)?;
let vm_func_on_http_resp =
get_instance_export_func::<(), i32>(&instance, &mut *store, EXPORT_FUNC_ON_HTTP_RESP)?;
let vm_func_check_payload =
get_instance_export_func::<(), i32>(&instance, &mut *store, EXPORT_FUNC_CHECK_PAYLOAD)?;
let vm_func_parse_payload =
get_instance_export_func::<(), i32>(&instance, &mut *store, EXPORT_FUNC_PARSE_PAYLOAD)?;定义获取wasm文件中导出接口的函数
pub fn on_http_req(
&mut self,
payload: &[u8],
param: &ParseParam,
info: &HttpInfo,
) -> Option<CustomInfo> {
if self.instance.len() == 0 {
return None;
}
let _ = self
.store
.data_mut()
.parse_ctx
.insert(VmParseCtx::HttpReqCtx(VmHttpReqCtx::from((
param, info, payload,
))));
let mut ret = None;
for ins in self.instance.iter() {
if ins.hook_point_bitmap.skip(HOOK_POINT_HTTP_REQ) {
continue;
}
let start_time = SystemTime::now();
let start_time = start_time.duration_since(UNIX_EPOCH).unwrap();
self.store
.data_mut()
.parse_ctx
.as_mut()
.unwrap()
.set_ins_name(ins.name.clone());
let abort = ins.on_http_req(&mut self.store);
ins.on_http_req_counter
.mem_size
.swap(ins.get_mem_size(&mut self.store) as u64, Ordering::Relaxed);
if abort.is_err() {
wasm_error!(ins.name, "wasm on http req fail: {}", abort.unwrap_err());
ins.on_http_req_counter
.fail_cnt
.fetch_add(1, Ordering::Relaxed);
continue;
}
ins.on_http_req_counter.exe_duration.swap(
{
let end_time = SystemTime::now();
let end_time = end_time.duration_since(UNIX_EPOCH).unwrap();
// Local timestamp may be modified
if end_time > start_time {
(end_time - start_time).as_micros() as u64
} else {
0
}
},
Ordering::Relaxed,
);
if !abort.unwrap() {
continue;
}
ret = self
.store
.data_mut()
.parse_ctx
.as_mut()
.unwrap()
.take_l7_info_result()
.map_or(None, |mut r| r.pop());
break;
}
// clean the ctx
drop(self.store.data_mut().parse_ctx.take());
ret
}具体的wasm接口调用处理
下面来简单总结下deepflow对wasm的处理流程:
VmHttpReqCtx:在 http 请求解析完成返回之前,会调用 Export 函数 on_http_req,host 会序列化 VmCtxBase 和 VmHttpReqCtx 到 instance 的线性内存
VmHttpRespCtx:在 http 响应解析完成返回之前,会调用 Export 函数 on_http_resp,host 会序列化 VmCtxBase 和 VmHttpRespCtx 到 instance 的线性内存
Trace,[]KeyVal- 在 Export 函数 on_http_req/on_http_resp 返回之前,instance 会将 Trace 和 []KeyVal 序列化到线性内存
示例
下面我们来看一个示例
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 SomeParser) OnHttpReq(ctx *sdk.HttpReqCtx) sdk.Action {
return sdk.ActionNext()
}
func (p SomeParser) OnHttpResp(ctx *sdk.HttpRespCtx) sdk.Action {
return sdk.ActionNext()
}
func (p SomeParser) OnCheckPayload(ctx *sdk.ParseCtx) (uint8, string) {
// 这里是协议判断的逻辑, 返回 0 表示失败
// return 0, ""
return 1, "some protocol"
}
func (p SomeParser) OnParsePayload(ctx *sdk.ParseCtx) sdk.Action {
// 这里是解析协议的逻辑
if ctx.L4 != sdk.TCP|| ctx.L7 != 1{
return sdk.ActionNext()
}
return sdk.ActionNext()
}以上是一个简单的插件示例,展示了如何使用 "deepflowio/deepflow-wasm-go-sdk" 库来开发自定义的插件,并实现不同的回调函数来处理网络数据包。在实际的业务场景中,我们可以根据自己的需求进一步扩展和修改这些回调函数的实现逻辑,以满足自身的场景诉求。
编译为 Wasm Plugin
建议 go 版本不低于1.21,tinygo 版本不低于 0.29
tinygo build -o wasm.wasm -target wasi -gc=precise -panic=trap -scheduler=none -no-debug *.go执行命令后,将会生成名为"wasm.wasm"的文件
接下来我们需要将其部署到deepflow中
1、将编译好的插件上传至 Deepflow-Server
deepflow-ctl plugin create --type wasm --image wasm.wasm --name wasm-devops执行命令后,deepflow-ctl 将会创建一个名为"wasm-devops"的插件
2、Agent 端加载 Wasm Plugin
static_config:
ebpf:
# 对于 deepflow-agent 原生不支持的协议, eBPF 数据需要添加端口白名单才能上报
kprobe-whitelist:
port-list: 9999
# 如果配置了 l7-protocol-enabled,别忘了放行 Custom 类型的协议
l7-protocol-enabled:
- Custom
# other protocol
wasm-plugins:
- wasm-devops // 对应 deepflow-ctl 上传插件的名称
重启下agent即可
Wasm在DeepFlow可观测平台的运用

被折叠的 条评论
为什么被折叠?



