Tars微服务框架 在从腾讯开源出去的过程中,运营体系被剥离了两个很重要的能力,一个是容器管理,一个是监控告警。

为了让开源的Tars重新拥有这两项能力,而能力的支持尽量对Tars本身的侵入性小,我前段时间分别做了一个tars和k8s生命周期融合的插件、一个tars grafana插件,来分别支持容器管理和监控告警。后续看看能不能整理后开源出来吧。

此文主要讲讲grafana告警插件,因为开发的过程中还是踩了不少坑,先回忆一下,记录一些要点,供未来的自己或网友参考。

grafana插件开发文档

技术选型背景

首先必须得承认,grafana是一个非常优秀的看板&监控开源项目。最开始我本来是想用Prometheus来做这个告警的,后来发现Prometheus自带看板比较差,官方也采用了grafana这个项目,深入了解之后发现grafana更加契合tars这种,已经有了上报数据,没有告警能力的情况。

理论上来说,数据不需要重复做上报/收集了,这样Prometheus的意义就失去了一大半;而grafana则更好是读取数据源,配置告警项,完美。

然而后来的开发过程中发现,项目本身的优秀,并不代表对开发者友好。它的文档给开发工作带来了太多的阻碍了。最终设计:

image.png

无法配置告警的场景

文档中并没有描述:

  • 带模板变量的Panel无法配置告警
  • 应用了Transform的Panel无法配置告警

轻飘飘的两个特性,却会在开发过程中造成血案,因为文档中压根没有提及(至少截止v7.3版本未提及)这两点特性,只有在付出了大量工作量后,才能发现这俩特性,导致之前的方案被推翻

说干就干,一开始基于tars的mysql表写了一堆sql,以及针对时间序列数据格式的转换。Dashboard做完之后,才发现依赖模板变量的mysql数据源方式行不通。

转换方向,后来采用自己开发后端数据源插件的方案,第一版接口返回了比较通用的格式,在Panel中使用Transform来转换数据格式,做完后才发现应用了Transform的Panel无法配置告警。

插件后端读取前端的配置数据

插件配置有两块,一块是后端数据源配置
image.png

另一块是Panel查询配置
image.png

这两个配置在前端设置后,golang后台如何获取配置的值呢?其中Panel查询配置,在脚手架工程代码中能比较容易的看到。而后端数据源配置,试了下面几种渠道都没找到,印象深刻:

  • 找遍google、baidu
  • 翻遍官网文档
  • 加了四五个grafana技术交流群
  • 咨询公司内曾经做过插件开发的同事

均没有得到正确答案。最终解决的方法是,看后端插件SDK的代码 + 加log定位,找到了如何读取这个配置。 入口并不深,但是对于首次接触的开发者来说,真的不好找。

// 返回 datasource.ServeOpts.结构对象
func newDatasource() datasource.ServeOpts {
    // 创建插件的manager对象(instance manager)
    // 当实例第一次被创建或者数据源配置更改时,传入 `NewInstanceManger` 的函数 newDataSourceInstance 被调用
    im := datasource.NewInstanceManager(newDataSourceInstance)
    //创建 data source结构对象
    ds := &TarsDatasource{
        im: im,
    }
    //创建并返回sdk的datasource.ServeOpts结构对象,传入datasource对象作为查询和监控检查的handler
    return datasource.ServeOpts{
        QueryDataHandler:   ds,
        CheckHealthHandler: ds,
    }
}
func (td *TarsDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
}
func (td *TarsDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
}
func newDataSourceInstance(setting backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
}

贴一下关键代码。
真正的方式是在QueryData或者CheckHealth函数里边,通过req.PluginContext.DataSourceInstanceSettings.JSONDatareq.PluginContext.DataSourceInstanceSettings. DecryptedSecureJSONData来获取。入口不深,但是藏在PluginContext里边,文档又没有描述,天知道呢?

可能有人会奇怪,其实生命周期函数已经传递了这个DataSourceInstanceSettings参数。为何第一时间没发现呢? 这又涉及到了下一个环环相扣的坑。

插件后端的生命周期不生效

首先,官网本身并没有对插件生命周期很细致的描述。下面是脚手架工程中的函数注释:

// 这个函数描述的是第一次实例化或者配置变化时被调用,但是貌似并不会真的被调用
func newDataSourceInstance(setting backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
    PluginConfigObj.SetConfigBySettings(&setting)
    TarCheckIns.StartCheckTarsDashboard()
    return &instanceSettings{
        httpClient: &http.Client{},
    }, nil
}

这个函数其实 并不会 生效,这也是为啥第一时间死活找不到如何获取数据源配置。

用协程启动Webhook监听失败

尝试在插件加载起来的时候,同时开启供发送自定义方式告警的Webhook。然而很蛋疼的发现,main函数入口、datasource.Serve(newDatasource()) 入口逻辑中,用协程启动端口监听都不可行,插件进程拉起后会立即被杀掉。
这种奇葩的现象,文档中更不可能说明原因了。最终给放到了CheckHealth中去做首次拉起,以及一个定时任务中去做check拉起。无奈。

☞ 参与评论