logo
logo

DeepFlow Grafana 插件开发实践

振宇 2022-12-08

本文为云杉网络原力释放 - 云原生可观测性分享会第十三期直播实录。回看链接PPT 下载

Grafana 是目前最广泛使用的数据可视化软件之一,DeepFlow 中已有大量基于 Grafana Dashboard 解决的可观测性场景的实战分享。这些场景都是基于 DeepFlow Grafana 插件提供的查询能力来构建的。DeepFlow 社区致力于基于开源生态构建一个完整的可观测性平台,而终端呈现和数据的可视化呈现是其中的重要一环。本文对当前 DeepFlow 提供的 Grafana 插件做一个简单介绍,抛砖引玉,希望大家能了解并创造更多的 DeepFlow 可观测性生态应用,也希望能让大家掌握如何开发一套完整的 Grafana Plugin。

0x0: DeepFlow 插件简介

在最早的 DeepFlow 企业版中,我们提供了一些比较简单的 Grafana 插件。这些插件是基于 DeepFlow 企业版 API 来提供服务的,目的是为了能让用户无缝将 DeepFlow 页面中的视图在自己的 Grafana 环境中搭建起来,避免改变用户的使用习惯。在 DeepFlow 宣布开源以后,我们基于社区版重新设计了若干插件。包括:

  • Data source plugin:DeepFlow Querier,用于为 Grafana 提供 DeepFlow 的数据
  • Panel plugin:DeepFlow AppTracing,用于展示分布式追踪火焰图
  • Panel plugin:DeepFlow Topo,用于展示服务之间的访问关系

这些定制化插件,配合 Grafana 原本提供的一系列标准图表,可以构建出一组完整的 DeepFlow 可观测性视图。可以前往我们的在线 Demo 快速体验。

0x1: Data source plugin

Grafana 的 Data source 插件是用来将数据源接入 Grafana 体系中的核心插件。DeepFlow 架构中,提供了基于 SQL 查询语句的 Querier 接口,因此我们的 Data source 插件会基于这个查询语法,来提供用户完全自由的查询。

DeepFlow 架构DeepFlow 架构

从代码的文件结构,Data source 插件主要有如下部分:

  • ConfigEditor 是在 Grafana 中加入数据源时配置编辑模块,一般用于配置数据源本身连接方式、账户密码等。只需要实现对应的 ConfigEditor 类即可实现对应的编辑界面。
  • QueryEditor 是构建数据源查询界面的主要模块,通过实现 QueryEditor 类即可实现编辑界面,其中可以自由引用 Grafana 自带 UI 库或者其他第三方库进行界面实现。
  • Grafana 在 dashboard 中还可以支持变量。通过实现 VariableQueryEditor 可以实现自定义的变量动态查询,方便 dashboard 中关联使用。
  • datasource.ts 是核心的逻辑模块,用于处理从界面接受的配置项,并从后端进行查询并对返回数据进行处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[zhenyu@dev202 deepflow-gui-grafana]$ tree deepflow-querier-datasource/src/ -L 1
deepflow-querier-datasource/src/
├── components
├── ConfigEditor.tsx
├── consts.ts
├── datasource.ts
├── img
├── index.d.ts
├── module.ts
├── plugin.json
├── QueryEditor.css
├── QueryEditor.tsx
├── types.ts
├── update-dashboards.js
└── utils

模块之间的关系如下图:

插件代码结构插件代码结构

注意其中需要在插件的 plugin.json 中配置相关的 proxy 信息,这样能让 Grafana core 中的 backendsrv 知道该如何转发去往 querier 的请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"path": "auth",
"url": "{{ .JsonData.requestUrl }}",
"headers": [
{
"name": "Content-Type",
"content": "application/x-www-form-urlencoded"
},
{
"name": "authorization",
"content": "Bearer {{ .JsonData.token }}"
}
]
},

从执行的数据流角度,Data source 插件中的数据流如下:

  • 将用户在 QueryEditor 中输入的查询内容,构造为一个标准 JSON 结构
  • 将 JSON 化的查询条件通过 DeepFlow Querier SDK 生成一个有效的查询语句(SQL)
  • 将语句封装为 API 请求
  • 通过 plugin 的 plugin.json 中设置的 proxy 转发至 DeepFlow Querier
  • 将 DeepFlow Querier 返回的数据转换为符合 Panel 需要的数据
  • 在 Panel 中将数据可视化呈现

数据流数据流

其中,DeepFlow Querier SDK 是一个 DeepFlow 的内部转换库,可以将标准化的 JSON 结构转换为 SQL 或者 DeepFlow APP 所需要的参数格式。

上述数据流中最后一步,是将从 Querier 中获取查询到的数据,并可以将这些数据发送给 Panel 模块,用于可视化展示。

0x2: DeepFlow 查询数据逻辑简介

在如何将查询数据结果进行可视化展现之前,需要简单的介绍下 DeepFlow 的数据查询逻辑。

DeepFlow 中的数据表,一般会分为双端表和单端表两种:

  • 单端表是对单个服务或实例的统计数据,数据本身没有方向。例如某个 K8s 服务的 RED 指标数据,某个进程的网络性能指标等等。这种数据一般适合用常规的折线、柱图、表格等展示。
  • 双端表是对 A 服务到 B 服务的访问路径的统计数据,有源、目的、方向等区分。例如,A -> BB -> A 是两条不同的数据。这种数据一般适合用拓扑图等表征访问关系的视图进行展示。目前双端表有:
    • flow_metrics.vtap_app_edge_port:服务之间的应用访问关系和性能指标
    • flow_metrics.vtap_flow_edge_port:服务之间的网络访问关系和性能指标
    • flow_log.l7_flow_log:应用调用日志
    • flow_log.l4_flow_log:网络流日志

除此之外,DeepFlow 的存储数据和其他主流 TSDB/OLAP 也一样,所有的表都会有时间列、有 Tag 和 Metric 的区分,以及支持数据的分组聚合能力。

分组聚合分组聚合

图中每个小圆点代表一行数据。查询到的原始数据,经过分组后,每组中有若干条数据,再对每组数据中进行聚合计算,每个分组得到一列数据。

这样的查询方式,可以很方便的查询出如下场景的数据:

  • 每分钟的平均请求数 SELECT AVG(request) ... GROUP BY INTERVAL(time, 60)
  • 每个 K8s Pod 在每分钟中的平均 TCP 重传比率 SELECT AVG(retrans_ratio) ... GROUP BY INTERVAL(time, 60), pod

从 QueryEditor UI 收集到的用户输入,会通过统一模块转换为 querier 认识的 SQL 语句,或者 API 约定的参数结构。我们提供了一个 SDK deepflow-sdk-js,可以按需将结构化的数据生成指定的文本输出。

这个 SDK 中,会首先对结构数据补充相关的信息,例如:

  • 实例分组转换为实例 ID 的分组,避免实例之间的重名
  • 自动增加 node_typeicon_id 等算子,用于附加实例的类型和对应图标等信息
  • 实现对枚举类型的自动翻译,例如 protocol 会自动附加 Enum(protocol)
  • 自动进行别名转换,以可读的方式展示每一列数据名称

SDK 中还会通过指定函数或者操作符的序列化,按照定制方式生成 SQL 语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[OP.EQ]: (a, b) => `${a}=${escape(b)}`,
[OP.NEQ]: (a, b) => `${a}!=${escape(b)}`,
[OP.LT]: (a, b) => `${a}<${escape(b)}`,
[OP.LTE]: (a, b) => `${a}<=${escape(b)}`,
[OP.GT]: (a, b) => `${a}>${escape(b)}`,
[OP.GTE]: (a, b) => `${a}>=${escape(b)}`,
[OP.IN]: (a, b) => formatIn(a, b),
[OP.NOT_IN]: (a, b) => formatIn(a, b, true),
[OP.REGEXP]: (a, b) => `${a} REGEXP ${escape(b)}`,
[OP.NOT_REGEXP]: (a, b) => `${a} NOT REGEXP ${escape(b)}`,
[OP.AS]: (a, b) => `${a} AS \`${b}\``,
[OP.SELF]: a => `${a}`,
[OP.INTERVAL]: (a, b) => `time(${a}, ${b})`,
[OP.LIKE]: (a, b) => formatLike(a, b),
[OP.NOT_LIKE]: (a, b) => formatLike(a, b, true),

其中还会对一些逻辑运算符进行自动的化简和合并。例如条件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let a = and(
or(
and(
not(
and(
eq("vm_name", 'ab c" bla"'),
eq("ip", "10.1.1.1"),
eq("subnet", "subnet_1"),
oneOf("subnet", ["subnet_1", "subnet_2"]),
like("host", "host.*")
)
),
or(and(lt("bps", 1), lte("rps", 1)))
),
lt("bps", 1),
and(not(or(lt("rps", 3)))),
and(and(lt("bps", 1), lte("rps", 1)), falseOp())
)
)

输出的条件结果为:

1
2
3
4
5
6
(
(vm_name!='ab c" bla"' OR ip!='10.1.1.1' OR subnet!='subnet_1'
OR subnet NOT IN ('subnet_1','subnet_2') OR host NOT REGEXP 'host.*')
AND bps<1 AND rps<=1
)
OR bps<1 OR rps>=3

0x3: Data source plugin 对数据的处理

通过上面的 Data source 插件,我们已经成功从 Querier 中查询出一组数据。接下来需要将这些数据进行展示。很简单的,通过 Grafana 列表插件,我们可以得到这组数据的列表:

数据列表数据列表

但如果我们进一步想要画出这个列表对应的折线图时,会遇到一个问题:如何将这些数据整理为折线图 Panel 所需要的数据结构呢?在折线图中,需要每条线有单独的 Series,而我们目前只有一个 data: Record<string, any>[] 结构的数据。当我们需要展示多条线时,是无法画出想要的图的。

因此我们在 Data source plugin 中提供了一个输出格式的转换选择,可以根据选择的 Panel 转换为不同的数据结构:

格式化数据格式化数据

当选择折线图时,会把数据转换为 data: [{time: timestamp, 组1: number}, {time: timestamp, 组2: number}, ...}] 的结构,这样折线图 Panel 会按组绘制出对应的折线。

在数据转换时,会根据分组的 Tag 中所有可能取值进行分组。例如当分组条件为 Pod 时,就能绘制出每个 Pod 的折线:

分组折线图分组折线图

其中组X 的名称,可以通过自定义的形式进行格式化:

格式化名称格式化名称

同样,在选择拓扑图时,也需要对返回的数据做二次处理,将数据处理为 Panel 识别的格式。

0x4: DeepFlow Topo panel

DeepFlow 中大量数据都在描述微服务之间的访问关系,因此需要一个流量拓扑图来进行展示这些数据。我们基于 d3.js 来构建流量拓扑,主要的考虑点为:

  • 自由度更高
  • 更能满足产品经理的各种要求

绘制流量拓扑时,我们使用客户端优先的模式进行宽度优先搜索,将单纯的客户端节点作为第一层,然后遍历去构建出整个拓扑。

在普通拓扑的基础上,我们还提供了瀑布拓扑的展现形式,可以有序的呈现拓扑各节点的关系:

瀑布拓扑瀑布拓扑

瀑布拓扑会提供额外的分组能力,能够对拓扑做二次分组:

瀑布拓扑的二次分组能力瀑布拓扑的二次分组能力

瀑布拓扑使用普通拓扑嵌套的方式构建,可以实现在分组聚合情况下的拓扑展示。

0x5: DeepFlow AppTracing panel

AppTracing panel 用来展现应用追踪数据。这些数据是从应用访问中通过 eBPF、cBPF、OpenTelemetry 等采集到的一组结构化日志数据,有起始时间,也有从属关系。通过时间先后关系和从属关联,构建出应用访问的火焰图。

分布式追踪分布式追踪

除了使用 DeepFlow AppTracing panel 来可视化应用追踪数据,我们也支持使用 Grafana Tempo 来显示 DeepFlow 的 Tracing 数据。

我们设计了一个完全模拟 Grafana Tempo backend 的模块,通过实现 Tempo 的 API,可以无缝将 DeepFlow 中的统计数据注入 Tempo UI 中显示,而且不需要额外部署 Tempo 后端:

DeepFlow 使用 Tempo Panel, 通过 deepflow-server 替代 tempo 的 backend :

DeepFlow-TempoDeepFlow-Tempo

其中需要实现的 API:

Tempo-APITempo-API

0x6: DeepFlow-Vis

上述的两个 Panel 插件,核心都是一组基于 d3.js 的可视化库。这些功能我们整合到一个独立的可视化库中,为 Panel 提供绘图能力。

DeepFlow-VisDeepFlow-Vis

DeepFlow-Vis 提供如下能力:

  • 元素的抽象,提供了应用层的抽象元素
  • 几何计算,提供元素本身的几何运算,并提供一些简单的布局模型
  • 提供属性和绘图元素的直接获取,调用层可以很方便的获取数据以及基础的 dom/svg 等对象,方便自行修改
  • 提供了一个独立的渲染层,可以使用不同的方式进行渲染输出,如使用 canvas 替换 svg

这些能力是以一个比较松散的结构组合在一起,因此在人力有限的情况下,可以更快的实现不同需求场景下的不同适配。

在此之上,提供了一些封装好的绘图,并实装在 AppTracing 和 Topo panel 中。

0x7: Next

我们计划在 DeepFlow 后续版本中,将对应的 Grafana 插件及相关库全部开源,并完成在 Grafana 官方的注册。也欢迎大家能够一起加入,丰富 DeepFlow 客观性的生态。

0x8: 什么是 DeepFlow

DeepFlow 是云杉网络开发的一款可观测性产品,旨在为复杂的云基础设施及云原生应用提供深度可观测性。DeepFlow 基于 eBPF 实现了应用性能指标、分布式追踪、持续性能剖析等观测信号的零侵扰Zero Code)采集,并结合智能标签SmartEncoding)技术实现了所有观测信号的全栈Full Stack)关联和高效存取。使用 DeepFlow,可以让云原生应用自动具有深度可观测性,从而消除开发者不断插桩的沉重负担,并为 DevOps/SRE 团队提供从代码到基础设施的监控及诊断能力。

GitHub 地址:https://github.com/deepflowio/deepflow

访问 DeepFlow Demo,体验高度自动化的可观测性新时代。