代码生成的Grafana仪表板:以代码为基础创建Grafana仪表板
我试用了通过代码生成Grafana仪表板的Dashboards-as-Code,并进行了介绍。
背景- Background
最近,我们对Grafana这个非常方便的可视化工具提出了以下两个问题。
课题1:难以审核仪表盘的JSON文件…
由于Grafana的仪表板可以以JSON格式进行导入/导出,因此我认为大多数人都会选择以JSON文件的形式来对仪表板进行Git管理。
在这种情况下,我们会在Grafana的用户界面中创建仪表板,然后将其以JSON格式导出并发起拉取请求。但是,Grafana的JSON文件包含了大量元数据,对于人类来说阅读起来实在是有些困难。
因此,我认为大多数情况下我们更愿意通过查看实际的仪表板界面来进行审核,而不是仅仅看JSON文件。
任务2:创建多个类似的图表太麻烦了…
在Grafana中,通常我们需要在用户界面上编辑仪表板。
当我们创建相似的图表时,只有指标名称稍有不同,就需要逐个在UI上编辑标题和指标名称,这非常麻烦。有时候可以使用Grafana的变量功能来切换值,但是当需要查看列表等情况时,我们仍然需要复制并进行编辑。
有能够解决这些问题的方法是使用代码作为仪表板。
代码作为仪表板是一种通过编程生成仪表板的方法,并且有多个开源软件可以实现这一目标。
本次我们验证了在GrafanaCon EU 2018中介绍的以下两个软件。
-
- grafanalib
- grafonnet
创建仪表板
作为仪表盘样本,在Kubernetes和Prometheus环境中,我们将创建一个按照命名空间标签计数kube_{k8s资源名}_created指标的图表,针对每个k8s资源。
{k8s资源名}部分将包含pod、service等资源名称。
如果在UI上创建,则需要复制每个资源并逐步修改{k8s资源名}部分。
grafanalib → Grafana代码库
Grafana库是Weaveworks公司开源的Python库。
安装
当使用pip进行安装时,您将可以使用generate-dashboard命令。
$ pip install grafanalib
编码
我使用Python进行编写。
这次我准备了一个函数,当给定 Kubernetes 资源名称时,它将返回相应的图形。
由于API非常直观,所以即使不经常使用Python的人也可以很快上手。
from grafanalib.core import *
def resource_row(resource):
return Row(panels=[
Graph(
title='Number of %s by namespace' % resource,
dataSource='prometheus',
targets=[
Target(
expr='count(kube_%s_created) by (namespace)' % resource,
legendFormat='{{namespace}}',
),
],
yAxes=[
YAxis(format=NO_FORMAT),
YAxis(format=SHORT_FORMAT),
]
)
])
dashboard = Dashboard(
title="grafanalib: Kubernetes resource count",
rows=[
resource_row('deployment'),
resource_row('daemonset'),
resource_row('job'),
resource_row('cronjob'),
resource_row('pod'),
resource_row('configmap'),
resource_row('secret'),
resource_row('service'),
resource_row('endpoint'),
],
).auto_panel_ids()
如果要用UI制作这个,很明显会发现需要重复地复制图表并修改指标和标题,这是一项繁琐的工作,因此现在会更轻松。
生成JSON
使用generate-dashboard指令,从Python代码生成JSON。
$ generate-dashboard -o kube-state-metrics.json kube-state-metrics.dashboard.py
從36行程式碼生成了1008行的JSON。
$ wc -l kube-state-metrics.dashboard.py
36 kube-state-metrics.dashboard.py
$ wc -l kube-state-metrics.json
1008 kube-state-metrics.json
导入生成的JSON后,将会得到如下所示的图表。
印象
实际应用中,我们可以使用git对*.py文件进行版本管理,并通过CI/CD生成JSON,并将生成的JSON导入或进行配置以创建容器映像。我认为这种流程会更方便进行审查,因为审查对象从1008行的JSON简化为了仅有36行的Python代码,改善了审查的易用性。
让我疑虑的是生成的JSON中的”schemaVersion”为12,有点过时。
我对Grafana今后的更新支持速度有些担忧。
Grafonnet: 图形编程语言
Grafonnet是一个用于生成JSON的Jsonnet语言的Grafana库。
生成的代码使用Jsonnet编写。
安装
首先安装Jsonnet。
$ brew install jsonnet
接下来,我们将克隆grafonnet库作为备份。
$ git clone git@github.com:grafana/grafonnet-lib.git
在Jsonnet代码的开头进行导入。
local grafana = import 'grafonnet/grafana.libsonnet';
当您在执行jsonnet命令时指定克隆grafonnet的路径,即可使用。
jsonnet -J /path/to/grafonnet dashboard.jsonnet
编码
我写了几乎与grafanalib时相同的代码。
local grafana = import 'grafonnet/grafana.libsonnet';
local dashboard = grafana.dashboard;
local graphPanel = grafana.graphPanel;
local prometheus = grafana.prometheus;
local resourcePanel(resource) =
graphPanel.new(
title='Number of %s count by namespace' % resource,
datasource='prometheus',
).addTarget(
prometheus.target(
expr='count(kube_%s_created) by (namespace)' % resource,
legendFormat='{{namespace}}',
)
);
local gridPos = {
x: 0,
y: 0,
w: 24,
h: 8,
};
dashboard.new(
'grafonnet: Kubernetes resource count',
)
.addPanel(resourcePanel('deployment'), gridPos)
.addPanel(resourcePanel('daemonset'), gridPos)
.addPanel(resourcePanel('job'), gridPos)
.addPanel(resourcePanel('cronjob'), gridPos)
.addPanel(resourcePanel('pod'), gridPos)
.addPanel(resourcePanel('configmap'), gridPos)
.addPanel(resourcePanel('secret'), gridPos)
.addPanel(resourcePanel('service'), gridPos)
.addPanel(resourcePanel('endpoint'), gridPos)
由于是第一次使用Jsonnet,并且错误信息相当晦涩难懂,所以我觉得相较于grafanalib,Jsonnet的学习曲线更陡峭。
我建议先阅读https://jsonnet.org/,掌握最基本的语法知识,然后再开始编写代码。
幸好有Vim的插件,可以进行语法高亮和保存时的自动格式化,非常方便。
https://github.com/google/vim-jsonnet
生成JSON
使用jsonnet命令,并指定grafonnet的路径进行执行。
$ jsonnet -J /path/to/grafonnet kube-state-metrics.jsonnet -o grafonnet-kube-state-metrics.json
这段代码生成了800行的JSON数据。
% wc -l kube-state-metrics.jsonnet
35 kube-state-metrics.jsonnet
$ wc -l grafonnet-kube-state-metrics.json
800 grafonnet-kube-state-metrics.json
根据书写方式而异,代码量应该与grafanalib相当。
导入生成的JSON后,图表会呈现这样的效果。
印象
我认为使用Jsonnet的人不多,与使用Python编写的grafanalib相比,它的学习曲线更高。只要掌握最基本的语法,处理仪表盘的JSON生成应该还是可以应付的。
同时,我在研究Jsonnet时发现它具有其他语言所没有的特点。实际上,Jsonnet是JSON的超集。换句话说,现有的仪表盘JSON可以完全被解释为Jsonnet格式。
从迁移现有仪表盘的角度来看,这个特点将成为一个重要的优势。
# JSONをそのままJsonnetとして利用可能
$ cp a.json a.jsonnet
$ jsonnet -J /path/to/grafonnet a.jsonnet -o a-generated.json
# 生成されるJSONは同じ
$ diff a-generated.json a.json
$ diff a-generated.json a.jsonnet
# 拡張子は.jsonnetじゃなくてもOK
$ jsonnet -J path/to/grafonnet a.json -o a-generated.json
通过以上的例子,我们可以得出结论:我们可以直接使用现有的JSON文件并使用Jsonnet实现CI/CD的迁移工作。首先,我们可以建立一个生成JSON的CI/CD流程,然后进行重构,将仪表盘的相似部分进行共享,这样就可以实现整合。
...
local resourcePanel(resource) = graphPanel.new(
...
{
...
panels: [
{
aliasColors: {},
bars: false,
dashLength: 10,
...
},
resourcePanel('cronjob'), # 元のJSONの一部分だけ共通化した生成コードに置き換え
resourcePanel('statefulset'),
...
{
aliasColors: {},
bars: false,
...
},
...
}
我成功地使用Grafonnet将仪表盘的某个部分生成的JSON文件进行替换。
总结
我测试了两个Dashboards-as-Code库。起初我觉得用Python编写的grafanalib更容易上手,但是grafonnet(也可以说是Jsonnet)可以直接使用JSON,这就让我觉得grafonnet更适合现有项目的引入。我认为未来Grafana的JSON模式可能会发生变化,如果使用grafonnet,就可以立即使用最新的JSON。如果使用Python或其他语言生成,除非库与最新的Grafana JSON模式兼容,否则将无法使用最新的功能,所以我觉得使用Jsonnet的grafonnet在这方面有很大优势。