wasm在可观测领域的运用

Wasm在DeepFlow可观测平台的运用

我们知道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(&paramBuf[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(&paramBuf[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即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值