使用 Grafonnet 管理 Grafana 仪表盘

维护 Grafana 面板或更新面板 JSON 文件有时会变得混乱,因为 JSON 文件的大小变得太大而难以调试。正常的 Grafana 面板 json 文件的平均代码行数有时超过 700-800 行。大型 json 文件通常包含冗余属性,这些属性在整个文件中通常具有相似的值。事实上,我们收到的大部分外部贡献都集中在这个领域,因此,在不费力的情况下维护 Grafana 面板文件变得至关重要。在本博客中,您可以了解如何使用 Grafonnet 将 Grafana 面板作为代码进行管理。

关于 Grafonnet ¶
最初,在致力于寻找一种使 Grafana 面板不那么复杂的方法时,我遇到了 Grafanalib - 一个用于维护 Grafana 面板的 Python 库。当我浏览 grafanalib 时,我发现它缺少一些功能,例如 - vonage-status-panel 和 grafana-piechart-panel。我也浏览了 ceph-mixins,它已经存在于 Ceph 仓库中,并使用纯 jsonnet 提供 Ceph 特定的 Prometheus 规则文件,但我没有在那里找到任何关于 Grafana 面板的参考。因此,作为替代方案,我开始探索 Grafonnet,Grafana 官方维护它。如果您已经知道 Grafonnet 是什么,那么使用 grafonnet 会更容易。对于那些不知道的人来说:Grafonnet 是一个 Jsonnet 库 - Jsonnet 是 JSON 的扩展,用于使用变量、函数、条件等抽象创建文件。Grafonnet 包含不同的函数,我们可以使用这些函数来创建用于组合 Grafana 面板或面板的 JSON 对象。然后,将生成的 JSON 文件导入到 grafana 中。
使用 Grafonnet 创建面板 ¶
grafonnet 库公开了我们可以用来定义 Grafana 面板的函数。首先,我们需要 导入 Grafonnet。Grafana.libsonnet 作为入口点,并公开了库文件中所需的所有函数和接口。以下代码是创建仅需要标题的面板所需的最小代码。
local grafana = import 'grafonnet/grafana.libsonnet';
local dashboard = grafana.dashboard;
dashboard.new(
title='Demo dashboard'
)
可以使用 $ jsonnet -o new.json new.jsonnet 编译上述代码,并将生成的 json 文件(new.json)导入到 Grafana 中。
添加模板变量 ¶
Grafonnet 库提供了函数 addTemplate() 和 addTemplates(),它们分别接收模板对象或模板数组。
local template = grafana.template;
dashboard.new(
title='Demo Dashboard'
)
.addTemplate(
template.new(
name='service',
datasource='LocalPrometheus',
query='label_values(service)',
allValues=null,
current='all',
refresh='load',
includeAll=true,
multi=true,
)
)
添加面板 ¶
面板对象的使用方式与模板变量类似,通过使用 addPanel() 或 addPanels() 添加。除了创建面板对象之外,我们还通过添加 gridPos 属性来指定它们的位置。以下是添加一个文本面板和一个图形面板的附加代码。通过使用面板对象公开的 addTarget(),我们可以添加目标对象。
local prometheus = grafana.prometheus;
local template = grafana.template;
dashboard.new(
title='Demo Dashboard'
)
.addAnnotation(addAnnotationSchema(1, '-- Grafana --', true, true, 'rgba(0, 211, 255, 1)', 'Annotations & Alerts', 'dashboard'))
.addRequired(type='grafana', id='grafana', name='Grafana', version='5.0.0')
// template object
.addTemplate(
template.new(name='service',datasource='LocalPrometheus',query='label_values(service)',allValues=null,current='all',refresh='load',includeAll=true,
multi=true)
)
// dashboard panel
.addPanels([
textPanel.new(
mode='html',
content='<p2 align=\"center\">Services</p2>',
) + {gridPos: {h: 1, w: 0, x: 8, y: 0}},
graphPanel.new(
title='RGW Sync Overview',
fill=2,
formatY1='percentunit',
datasource='Prometheus',
)
.addTargets([
prometheus.target(
expr='ceph_health_status == 2',
intervalFactor=1,
legendFormat='time_series',
)
]) + {gridPos: {h: 0, w: 0, x: 8, y: 9}},
])
以下是上述 jsonnet 代码的等效 json 代码
显示 json 代码
{
"__inputs": [ ],
"__requires": [
{
"id": "grafana",
"name": "Grafana",
"type": "grafana",
"version": "5.0.0"
},
{
"id": "graph",
"name": "Graph",
"type": "panel",
"version": "5.0.0"
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"showIn": 0,
"tags": [ ],
"type": "dashboard"
}
]
},
"editable": false,
"gnetId": null,
"graphTooltip": 0,
"hideControls": false,
"id": null,
"links": [ ],
"panels": [
{
"aliasColors": { },
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 0
},
"id": 2,
"legend": {
"alignAsTable": false,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": false,
"show": true,
"sideWidth": null,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"repeat": null,
"seriesOverrides": [ ],
"spaceLength": 10,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum by (source_zone) (rate(ceph_data_sync_from_zone_fetch_bytes_sum[30s]))",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "Replication (throughput) from Source Zone",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "Bps",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
}
]
},
],
"refresh": "15s",
"rows": [ ],
"schemaVersion": 16,
"style": "dark",
"tags": [
"overview"
],
"templating": {
"list": [
{
"allValue": null,
"current": { },
"datasource": "$datasource",
"hide": 2,
"includeAll": true,
"label": null,
"multi": false,
"name": "rgw_servers",
"options": [ ],
"query": "prometehus",
"refresh": 1,
"regex": "",
"sort": 1,
"tagValuesQuery": "",
"tags": [ ],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {
"text": "default",
"value": "default"
},
"hide": 0,
"label": "Data Source",
"name": "datasource",
"options": [ ],
"query": "prometheus",
"refresh": 1,
"regex": "",
"type": "datasource"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"15s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "",
"title": "RGW Sync Overview",
"uid": "rgw-sync-overview",
"version": 0
}
正如您从上面的示例中清楚看到的,使用 Grafonnet 可以将编码行数从大约 200 行减少到大约 40 行,从而减少约 85-90%。
为了可重用性而自定义函数 ¶
使用可重用的函数不仅可以使代码更易于阅读,而且更易于维护。以下是一个创建自定义函数的示例,这些函数接收诸如标题、uid 等属性,如果希望通过仅传递参数来创建许多面板,则可以在许多面板中重用这些函数。
local dashboardSchema(title, uid, time_from, refresh, schemaVersion, tags,timezone, timepicker) =
g.dashboard.new(title=title, uid=uid, time_from=time_from, refresh=refresh, schemaVersion=schemaVersion, tags=tags, timezone=timezone, timepicker=timepicker);
local graphPanelSchema(title, nullPointMode, stack, formatY1, formatY2, labelY1, labelY2, min, fill, datasource) =
g.graphPanel.new(title=title, nullPointMode=nullPointMode, stack=stack, formatY1=formatY1, formatY2=formatY2, labelY1=labelY1, labelY2=labelY2, min=min, fill=fill, datasource=datasource);
迁移 ¶
如果您希望从现有的 Grafana JSON 面板迁移到 Grafonnet,您只需要在应用程序中安装 jsonnet 包。您还需要做的事情是克隆 Grafonnet 库,我已经在关于部分中提到了该库的链接。对如何用 jsonnet 编写代码有一定的了解将大有裨益。如果不是,您可以从此链接获取一些想法 - jsonnet。您可以开始用 jsonnet 编写面板,并从该面板生成等效的 json 文件并将其导入到现有的 Grafana 面板中。
结果 ¶
如果使用 Grafana UI 生成面板过于繁琐或耗时,当使用许多类似的面板或面板时,Grafonnet 可能值得考虑,前提是您能在自定义和重用自定义模板之间找到合适的平衡。另一方面,所有更改都需要在 Jsonnet 文件中进行,因为 UI 中的更改不会更改相应的文件。