Grafana 使用

Grafana 是一款用 GO 语言开发的开源数据可视化工具,可以做数据监控和数据统计,带有告警功能。

基础概念

组织(Organization) 与用户(User)

Organization 相当于一个 Namespace,一个 Organization 完全独立于另一个 Organization,包括 datasourcedashboard 等,创建一个 Organization 就相当于打开了一个全新的视图,所有的 datasourcedashboard 等都需要重新创建。一个用户(User) 可以属于多个 Organization。

User 是 Grafana 里面的用户,用户可以有以下 角色

  • admin - 管理员权限,可以执行任何操作。
  • editor - p不可以创建用户不可以新增 Datasource可以创建 Dashboard**
  • viewer - 仅可以查看 Dashboard
  • read only editor - 允许用户修改 Dashboard,但是 不允许保存

数据源 Datasource

Grafana 中操作的数据集、可视化数据的来源

Dashboard

在 Dashboard 页面中,可以组织可视化数据图表。

  • Panel - 在一个 Dashboard 中,Panel 是最基本的可视化单元。通过 Panel 的 Query Editor 可以为每一个 Panel 添加查询的数据源以及数据查询方式。每一个 Panel 都是独立的,可以选择一种或者多种数据源进行查询。一个 Panel 中可以有多个 Query Editor 来汇聚多个可视化数据集
  • Row - 在 Dashboard 中,可以定义一个 Row,来组织和管理一组相关的 Panel

Variables

在 Dashboard 的设置页面中,有 Variables 页面,在其中可以为 Dashboard 配置变量,之后可以在 Panel 的 Query Editor 中使用这些预定义的变量。变量的值也可以是通过表达式获取的值。也可以在 Panel 的标题中使用变量

例如以下 Variables 配置

Node    label_values(kubernetes_io_hostname)

在 Dashboard 中定义了这些变量后,可以在 Panel 的 Query Editor 中使用,在 Query Editor 中使用了 Variables 中定义的变量后,在 Dashboard 的顶部下拉菜单中可以选择预定义的变量的值(需要在定义 Variables 时配置 Show on dashboardLabel and Value 以使在 Dashboard 顶部显示下拉菜单),Panel 中的 Query 表达式就会使用这些变量的值进行计算以及显示图表。

变量关联查询

有时候需要在一个变量中使用另一个变量的值来筛选结果。例如以下场景:

假如有 2 个 k8s 集群,第一个 k8s 集群名为 k8s-cluster1,其中有 3 个节点: k8s-cluster1-1, k8s-cluster1-2, k8s-cluster1-3,

第二个 k8s 集群名为 k8s-cluster2, 其中有 3 个节点: k8s-cluster2-1, k8s-cluster2-2, k8s-cluster2-3,

k8s-cluster1 集群采集的指标都有 cluster=k8s-cluster1 的标签,

k8s-cluster2 集群采集的指标都有 cluster=k8s-cluster2 的标签,

grafana 中定义变量 cluster,其值是 cluster 标签的值,

如何定义变量 node,其值根据 cluster 变量来决定,例如当 cluster 的值是 k8s-cluster1 时,node 的值只能选择 k8s-cluster1-1, k8s-cluster1-2, k8s-cluster1-3,

cluster 的值是 k8s-cluster2 时,node 的值只能选择 k8s-cluster2-1, k8s-cluster2-2, k8s-cluster2-3,

要实现以上需求,可以使用如下的表达式定义 node 变量

  1. 添加 Variables,variable type 选择 Query
  2. Data source 选择对应的 Prometheus
  3. Query 中选择 Query TypeLabel valuesLabel * 选择 kubernetes_io_hostname后面的 Metric 中配置表达式 up{cluster=~"$cluster"} 以限制筛选结果

    这里的 主要配置为 Metric 中配置表达式 up{cluster=~"$cluster"},用来限制 Label values 是从 up{cluster=~"$cluster"} 的结果中筛选值

对应的 Variables Definition 为 label_values(up{cluster_name=~"$k8sClusterName"},kubernetes_io_hostname)

常用配置示例

Gauge 类型

Gauge 类型的数据是一个标量值,代表了当前的值。

选择图标显示的数据格式

例如要配置百分比类型的图标,可以在 Panel 配置中的 Standard options 中选择 UnitMisc -> Percent(0-100)

配置 Gauge 图标显示渐变色

要配置 Gauge 仪表盘的渐变色,可以在 Panel 配置中的 Thresholds 中添加渐变颜色

要使 Gauge 仪表盘内部显示度量值,需要配置 Standard options –> MinStandard options –> MaxStandard options –> Decimals

Time series

自定义图形的 Legend

默认情况下,Legend 会显示指标中的所有标签,会比较长,为了缩短简化 Legend,需要自定义 Legend。在 图形的编辑界面,表达式下的 Options 中,选择 Legend 进行自定义,可以使用已有的标签 {{label}},或者 Variables $variable 来格式化 Lengend

下图为默认的 Legend

下图为为自定义后的 Legend

Grafana 展示 Zabbix 监控数据

安装 Zabbix 插件

在 Grafana 的 Administration –> Plugins 中搜索 Zabbix 插件并 install。安装后在插件中找到 Zabbix 插件并 Enable

添加 Data source

直接使用 Zabbix 的 API 性能较差,建议让 Zabbix 的 Data Source 直连 Zabbix 数据库。

  1. 首先要添加一个 Mysql 的数据源(此处示例 Zabbix 数据库为 Mysql)

  2. Mysql 配置成功后,选择 Zabbix 添加为 Data Source,使用 Zabbix 的 API 链接及认证用户名和密码配置 Data source,配置 Direct DB ConnectionEnable 并选择数据库为第一步配置的 Mysql Data source

常用查询

因为 Grafana 直连了 Zabbix 数据库,有些查询可以直接查询 Zabbix 的 Mysql 数据库

Zabbix 监控的主机数量

SQL : SELECT COUNT(*) AS host_count FROM hosts;

使用 Prometheus 数据源展示 Kubernetes 中 Ingress Nginx Controller 监控数据

Prometheus 监控 Kubernetes 中 Ingress Nginx Controller 指标配置参考.

在 Grafana Dashboard 中配置以下 Variables

  • k8sClusterName label_values(cluster_name)
  • k8sNamespace label_values(kube_namespace_labels{cluster_name=~"$k8sClusterName"},namespace)
  • Ingress label_values(kube_ingress_info{cluster_name=~"${k8sClusterName}", namespace=~"${k8sNamespace}"},ingress)
  • Host label_values(kube_ingress_path{cluster_name=~"$k8sClusterName",namespace=~"$k8sNamespace",ingress=~"$Ingress"},host)

Ingress Nginx Controller 发送的数据总和

使用表达式: sum(rate(nginx_ingress_controller_bytes_sent_sum{cluster_name=~"$k8sClusterName", namespace=~"$k8sNamespace", ingress=~"$Ingress",host=~"$Host"}[2m]))

Ingress Nginx Controller 各个实例的连接数

sum by (instance)(rate(nginx_ingress_controller_nginx_process_connections{cluster_name=~"$k8sClusterName"}[2m]))

Ingress Nginx Controller 请求成功率

sum(rate(nginx_ingress_controller_requests{cluster_name=~"$k8sClusterName",ingress=~"$Ingress",namespace=~"$k8sNamespace",status!~"[4-5].*"}[2m])) / sum(rate(nginx_ingress_controller_requests{cluster_name=~"$k8sClusterName",ingress=~"$Ingress",namespace=~"$k8sNamespace"}[2m]))

Ingress Nginx Controller 请求持续时间

计算平均请求持续时间:
sum by (namespace, ingress) (nginx_ingress_controller_request_duration_seconds_sum) / sum by (namespace, ingress) (nginx_ingress_controller_request_duration_seconds_count)

计算平均上游占用时间:
sum by (namespace, ingress) (nginx_ingress_controller_ingress_upstream_latency_seconds_sum) / sum by (namespace, ingress) (nginx_ingress_controller_ingress_upstream_latency_seconds_count)

Grafana Alerting and Notification Policies

Grafana Notification Template 语法总结

  • 使用版本 Grafana v10.4.1

Grafana 集成的 Alert Rule 和 Notification Policies 在发送告警时一般是以 Group 的形式发送告警信息。 [1]

.Alerts

告警和恢复消息存放在 .Alerts 中,为 切片(slice) 类型

  • .Alerts.Firing - 存放了告警消息对象
  • .Alerts.Resolved - 存放了告警恢复消息对象

以下语句打印出所有的告警及其标签,标签以 , 分割: [2]

{{ range .Alerts }}
{{ .Labels }}
{{ end }}

参考以下语句打印出所有的告警以及对应实例的标签,标签按行分割 [2]

{{ range .Alerts }}
********************************
{{ range .Labels.SortedPairs }}
{{ .Name }} = {{ .Value }}
{{ end }}
{{ range .Annotations.SortedPairs }}
{{ .Name }} = {{ .Value }}
{{ end }}
{{ end }}

参考以下语句可以获取到告警列表中指定的 Labels 或者 Annotations 的所有值 [2]

{{ range .Alerts }}
The name of the alert is {{ index .Labels "instance" }}
{{ end }}

{{ range .Alerts }}
The name of the alert is {{ index .Annotations "summary" }}
{{ end }}

参考以下语句获取告警消息的数量

{{ len .Alerts }}
{{ len .Alerts.Firing }}
{{ len .Alerts.Resolved }}

使用以下语句获取告警及恢复告警的时间

# UTC 时间
StartTime: {{ .StartsAt }}
EndTime: {{ .EndsAt }}

# 修改为东八区的时间并指定时间显示格式
StartTime: {{ (.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}
EndTime:{{ (.EndsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}

如果 启用了 Grafana Image Renderer Plugin,在告警消息模板中使用变量 {{ .ImageURL }} 引用图片。

  • 格式化消息小技巧:
    • 在发送给如 Telegram 的消息中,可以使用 Emoji(如 🔴),直接在 消息模板 或者 Alert Rule 的 Summary 或者 Description 中直接使用 Emoji(如 🔴) 即可

Grafana 告警中发送图片

版本信息

  • Grafana v11.2

为了在告警中直观的展示告警发生时的监控项的图形,Grafana 支持在告警中发送 Panel 对应的图片(PNG 图片)。当告警产生时,Grafana 会生成对应的临时 PNG 图片并保存在文件系统中($GRAFANA_DATA_DIR/data/png/),后台定时任务每隔 10 分钟会删除这些临时图片。要自定义多久删除这些临时图片,可以通过修改配置 temp_data_lifetime 实现 [3]

此功能由 Grafana Image Renderer plugin 实现,使用之前需要先 安装

grafana-cli plugins install grafana-image-renderer

插件将会安装到 Grafana 插件目录,默认为 /var/lib/grafana/plugins

Docker Compose 部署的 Prometheus 和 Grafana 的基础上配置 Grafana Image Renderer [4]

version: "3"

services:
prometheus:
image: prom/prometheus
ports:
- "9099:9090"
volumes:
- "/opt/Prometheus/config/prometheus/:/etc/prometheus/"
- "/opt/Prometheus/data/prometheus/tsdb/:/prometheus/"
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=8760h'
networks:
- prometheus-network

grafana:
image: grafana/grafana
ports:
- "3000:3000"
volumes:
- "/opt/Prometheus/config/grafana/:/etc/grafana/"
- "/opt/Prometheus/data/grafana/:/var/lib/grafana/"

environment:
GF_RENDERING_SERVER_URL: http://grafana_image_renderer:8081/render
GF_RENDERING_CALLBACK_URL: http://grafana:3000/
GF_LOG_FILTERS: rendering:debug

networks:
- prometheus-network

grafana_image_renderer:
image: grafana/grafana-image-renderer:latest
ports:
- 8081
volumes:
- ./config/grafana_image_renderer/config.json:/usr/src/app/config.json

networks:
- prometheus-network

blackbox_exporter:
image: bitnami/blackbox-exporter
command:
- "--config.file=/etc/blackbox/blackbox.yml"
ports:
- "9115:9115"
volumes:
- "/opt/Prometheus/config/blackbox_exporter/blackbox.yml:/etc/blackbox/blackbox.yml"
environment:
- LOG_LEVEL=debug
networks:
- prometheus-network

networks:
prometheus-network:

./config/grafana_image_renderer/config.json 文件内容参考官网示例

  • 要自定义图片中显示的时区,可以修改 timezone 选项,如 Asia/Shanghai

Grafana Image Renderer plugin 配置选项说明

Grafana Image Renderer plugin 会比较消耗内存,因为其工作机制为在后台启动浏览器实例进行图片获取。

配置完成后,重启 grafana,可以通过 clicking Panel > Share > Direct link rendered image in the Link tab. 来测试是否正常。

要支持在告警中发送图片,需要在 Grafana 配置中启用以下配置,并且不同告警通道对 Image Renderer 的支持情况也不不同,具体可参考官网说明 [5]

[unified_alerting.screenshots]
capture = true

在告警消息模板中,可以通过变量 {{ .ImageURL }} 引用图片链接。相关限制参考官网说明 [5]

常见错误

部署 Grafana Image Renderer plugin 后生成的图像空白

在部署 Grafana Image Renderer plugin 后,通过点击 Panel 的 Share > Direct link rendered image 测试,显示生成的图片空白。检查 Grafana Image Renderer 的容器日志,报错如下:

grafana_image_renderer-1  | {"failure":"net::ERR_CONNECTION_REFUSED","level":"error","message":"Browser request failed","method":"GET","url":"http://grafana:3000/d-solo/bduz8o5q2tcsgc/cdn-domains-traffic-data?orgId=1&from=1725247462795&to=1725269062795&panelId=1&width=1000&height=500&scale=1&tz=Asia%2FShanghai&render=1"}
grafana_image_renderer-1 | {"err":"Error: net::ERR_CONNECTION_REFUSED at http://grafana:3000/d-solo/bduz8o5q2tcsgc/cdn-domains-traffic-data?orgId=1&from=1725247462795&to=1725269062795&panelId=1&width=1000&height=500&scale=1&tz=Asia%2FShanghai&render=1\n at navigate (/usr/src/app/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Frame.js:114:23)\n at async Deferred.race (/usr/src/app/node_modules/puppeteer-core/lib/cjs/puppeteer/util/Deferred.js:82:20)\n at async Frame.goto (/usr/src/app/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Frame.js:80:21)\n at async CDPPage.goto (/usr/src/app/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Page.js:651:16)\n at async ClusteredBrowser.takeScreenshot (/usr/src/app/build/browser/browser.js:286:13)\n at async /usr/src/app/build/browser/clustered.js:66:32","level":"error","message":"Error while trying to prepare page for screenshot","url":"http://grafana:3000/d-solo/bduz8o5q2tcsgc/cdn-domains-traffic-data?orgId=1&from=1725247462795&to=1725269062795&panelId=1&width=1000&height=500&scale=1&tz=Asia%2FShanghai&render=1"}
grafana_image_renderer-1 | {"file":"/tmp/292de270.png","level":"debug","message":"Deleting temporary file"}

错误原因

根据错误提示,是因为 grafana_image_renderer 无法连通 http://grafana:3000,测试发现的确如此,因主机防火墙限制,防火墙放通限制即可

常见错误

Grafana 集成的 Telegram 发送消息失败

  • Grafana v10.4.1

Grafana 配置 Contact pointsIntegration 选择 TelegramParse Mode 选择 HTML,使用以下模板发送消息失败 Failed to send test alert.: failed to send telegram message: webhook response status 400 Bad Request

{{ define "MyNotificationTemplate" }}
<html>
<body>
There are {{ len .Alerts.Firing }} firing alert(s), and {{ len .Alerts.Resolved }} resolved alert(s).

</body>
</html>
{{ end }}

修改为以下内容后(去掉 htmlbody 标签),可以正常发送消息

{{ define "MyNotificationTemplate" }}

There are {{ len .Alerts.Firing }} firing alert(s), and {{ len .Alerts.Resolved }} resolved alert(s).


{{ end }}

经测试,在 Grafana notification template 中使用 HTML 格式发送消息到 Telegram,不能使用以下标签,否则报错: Failed to send test alert.: failed to send telegram message: webhook response status 400 Bad Request

  • html
  • body
  • br
  • p

支持以下标签:

  • <b>粗体</b>
  • <i>斜体</i>
  • <u>下划线</u>
  • <s>删除线</s>

常见问题

Dashboard 顶部的选择下拉单初始值无法固定

使用 Variables 后,Dashboard 顶部的选择下拉单中想让初始值为 All(假设所有变量都有 All 选项),在 Dashboard 选择所有的下拉选项选择 All,然后保存 Dashboard。重新刷新页面或者重新登陆后,很多下拉选项都不是保存是设置的 All 选项。

要解决此问题,参考以下方法:

  1. 在 Dashboard 选择所有的下拉选项选择 All
  2. 点击 Dashboard settings,在设置页面中选择 JSON Model,点击左下角的 Save changes 。重新刷新或者重新登陆后,Dashboard 顶部的选择下拉选项默认值都为 All

Grafana Table 中对字段排序

Grafana 中的 Table 类型的数据,默认的列顺序是和查询语句返回的顺序一致,像 Prometheus 返回的列不支持通过 ProQL 调整结果中标签的顺序,此种情况下要在 Grafana Table 中调整列的排列顺序,需要使用 Grafana 提供的 Transform data 功能,具体操作步骤如下:

  1. Transform data 标签页中点击 Add transformation
  2. 在弹出的子页面中找到或者搜索 Organize fields by name
  3. 在页面中拖动调整字段的顺序,即可完成调整 Table 中列顺序的目的。

Grafana Table 中组合多个查询到同一个 Table 中

若需要将多个查询表的内容整合到一个表中,参考以下思路:

  1. 首先确保每个查询得到想要的数据
  2. Transform data 标签页中点击 Add transformation
  3. 在弹出的子页面中找到或者搜索 Merge series/tables
  4. 多个表的内容会被合并到一张表中,如果多张表中的字段名重复,会自动合并成一个字段。若要修改字段名和属性,可以使用 overrides 功能。

Grafana 统一告警发送的通知中时区修改为东八区

Grafana 统一告警发送的通知中时区固定为 UTC 时间,要修改为东八区,参考以下模板((.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05")将时间改为东八区时间并指定时间显示的格式:

{{ define "MyNotificationTemplate" }}
There are <b><u>{{ len .Alerts.Firing }}</u> firing</b> alert(s), and <b>{{ len .Alerts.Resolved }} <s>resolved</s></b> alert(s).

{{ if .Alerts.Firing }}
<code>Firing Alerts</code>:
{{ range .Alerts.Firing }}
<pre>
Name: {{ index .Labels "alertname" }}
StartTime: {{ (.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}
Severity: {{ index .Labels "Severity" }}
Status: {{ .Status }}
Summary: {{ index .Annotations "summary" }}
Details:
{{ index .Annotations "description" }}
</pre>
GeneratorURL: {{ .GeneratorURL }}
PanelURL: {{ .PanelURL }}

{{- end }}
{{- end }}

{{ if .Alerts.Resolved }}
<code>Resolved Alerts</code>:
{{ range .Alerts.Resolved }}
<pre>
Name: {{ index .Labels "alertname" }}
EndTime:{{ (.EndsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}
Severity: {{ index .Labels "Severity" }}
Status: {{ .Status }}
Summary: {{ index .Annotations "summary" }}
Details:
{{ index .Annotations "description" }}
</pre>
GeneratorURL: {{ .GeneratorURL }}
PanelURL: {{ .PanelURL }}

{{- end }}
{{- end }}
{{- end }}

脚注