如何使用 InfluxDB 和 Flux 查询语言创建 Grafana 告警
引言
Grafana Alerting 代表了一种强大的新方法,用于系统可观测性和事件响应管理。虽然该告警平台可能最知名的是其与 Prometheus 的紧密集成,但该系统与包括 InfluxDB 在内的许多流行的188体育直播平台源配合使用。在本教程中,我们将学习如何使用 InfluxDB 和较新的 Flux 查询语言创建 Grafana 告警。我们将涵盖从最基本到最复杂的五种常见场景。总的来说,这五种场景将为您提供几乎任何您希望使用 Grafana 和 Flux 创建的告警查询的极佳指南。
在我们深入探讨告警场景之前,值得回顾一下 InfluxDB 的两种流行的查询语言的发展:InfluxQL 和 Flux。最初,InfluxDB 使用 InfluxQL 作为其查询语言,该语言使用类似 SQL 的语法。但从 InfluxDB v1.8 开始,公司引入了 Flux,“这是一种开源函数式数据脚本语言,旨在用于查询、分析和处理数据。”其官方文档继续阐述,“Flux 将用于查询、处理、写入和处理数据的代码统一到单一语法中。该语言设计为易于使用、易于阅读、灵活、可组合、可测试、可贡献和可共享。”
在接下来的五个示例中,我们将看到新的 Flux 查询语言可以多么强大和灵活。我们还将看到 Flux 与 Grafana Alerting 的结合效果有多好。
示例 1:创建当值高于或低于设定的阈值时触发的告警
我们的第一个示例使用了 InfluxDB 和 Grafana Alerting 的一个常见现实场景。InfluxDB 在物联网和边缘应用中很受欢迎,擅长现场、实时可观测性。在本示例以及后续许多示例中,我们将考虑一个假设的场景:我们正在监控制造工厂中的一些流体储罐。这个场景基于 InfluxDB 和 Alerting 的实际应用,将使我们能够逐步完成 Grafana 的各种告警设置,从最简单到最复杂。
对于示例 1,让我们考虑以下场景:我们正在监控一个储罐 A5
,并存储其实时温度数据。我们需要确保该储罐中的温度始终大于 30 °C 且小于 60 °C。
我们希望编写一个 Grafana 告警,当储罐 A5
中的温度低于 30 °C 或高于 60 °C 的阈值时触发。
为此,我们将:创建 Grafana 告警规则,添加 Flux 查询,然后向告警规则添加表达式。
创建 Grafana 告警规则
- 打开 Grafana 告警菜单,选择 告警规则。
- 点击 新建告警规则。
- 为您的告警规则命名,然后选择 Grafana 管理的告警。对于 InfluxDB,您始终创建 Grafana 管理的规则。
向告警规则添加初始 Flux 查询
仍在告警规则页面的 步骤 2 部分,您将看到三个框:一个查询编辑器 (A
),以及两个标记为 B
和 C
的部分。您将使用这三个部分来构建您的规则。让我们逐一进行。
首先,我们需要查询我们假想的 InfluxDB 实例中的数据,以获取储罐 A5 温度的时间序列图。为此,您可以从下拉菜单中选择您的 InfluxDB 数据源,然后编写如下查询:
```
from(bucket: "RetroEncabulator")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "TemperatureData")
|> filter(fn: (r) => r["Tank"] == "A5")
|> filter(fn: (r) => r["_field"] == "Temperature")
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|> yield(name: "mean")
```
这是一个相当典型的 Flux 查询。让我们逐个函数进行分析。我们首先使用 from()
函数选择数据所在的正确桶。然后我们使用 range()
函数根据时间限制过滤行。接着,我们通过三个 filter()
函数来缩小结果范围。我们选择一个特定的 measurement
(InfluxDB 中的特殊关键字),然后是我们关注的储罐(A5
),以及一个特定的 field
(InfluxDB 中的另一个特殊关键字)。之后,我们将数据传递给 aggregateWindow()
函数,该函数将我们的数据下采样到特定时间段,最后传递给 yield()
函数,该函数指定我们想要的最终结果:mean
(平均值)。
这个 Flux 查询将生成如下所示的时间序列图:
向您的 Grafana 告警规则添加表达式
数据现在已出现在我们的规则设置中,下一步是创建一个表达式。移至部分 B
。对于这个场景,我们想创建一个 Reduce 表达式,将上述结果缩减为一个单一值。在此图片中,您可以看到我们选择将时间序列数据缩减为来自输入 A
的 Last
值。在这种情况下,它返回储罐 A5 的温度值 53 摄氏度。
最后,我们需要创建一个 Grafana 将触发告警的数学表达式。在我们的例子中,我们将编写一个包含两个条件并用 OR ||
运算符分隔的表达式。我们希望在 section B
中的结果小于 30 或大于 60 时触发告警。这看起来像 $B < 30 || $B > 60
。
将告警条件设置为 C - 表达式
。我们现在可以预览我们的告警了。这是告警状态为 正常
时的预览:
这是告警状态为 告警中
时的预览:
请注意,上述 Reduce 表达式是必需的。如果没有它,在预览结果时,Grafana 将显示 invalid format of evaluation results for the alert definition B: looks like time series data, only reduced data can be alerted on
。
💡提示:如果您的区域设置仍然顽固地使用华氏温度,我们可以修改上面的 Flux 查询,在 aggregateWindow 语句之前添加一个 map() 函数,将值从 °C 转换为 °F。请注意,我们没有创建一个新字段。我们只是重新映射现有值。
|> map(fn: (r) => ({r with _value: r._value * 1.8 + 32.0}))
结论
使用这三个步骤,您可以创建一个基于 Flux 的 Grafana 告警,该告警将根据单个数据源中的两个阈值中的任何一个触发。但是,如果您需要基于多个条件和多个时间序列触发告警怎么办?在示例二中,我们将涵盖这个场景。
示例 2:如何从两个查询和两个条件创建 Grafana 告警
让我们在示例二中稍微改变一下场景,离开我们假想的制造工厂。想象您是《回到未来》中伟大的埃米特·布朗博士的助手,而布朗博士交给您以下挑战:“我希望每当时间旅行的两个条件都满足时,就向我发送告警:当车辆速度达到每小时 88 英里,并且一个物体产生 1.21 吉瓦的电力时。”
我们假设我们正在 InfluxDB 和 Grafana 中跟踪这些数据。我们还假设上述每个数据源都来自不同的桶。我们如何对此进行告警?如何使用 Grafana 和 Flux 对来自两个不同数据源的两个不同条件进行告警?
向您的 Grafana 告警规则添加两个 Flux 查询
就像我们在示例 1 中那样,我们首先模拟我们的查询。我们的车辆数据查询与我们上次的查询非常相似。我们使用 from()
、range()
和一系列 filter()
函数。然后我们使用 AggregateWindow()
和 yield()
进一步缩小我们的数据范围。在这种情况下,结果是一个跟踪我们 1983 年 DeLorean 车辆速度的时间序列。
from(bucket: "vehicles")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "VehicleData")
|> filter(fn: (r) => r["VehicleType"] == "DeLorean")
|> filter(fn: (r) => r["VehicleYear"] == "1983")
|> filter(fn: (r) => r["_field"] == "velocity")
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|> yield(name: "mean")
我们的第二个查询将在我们的电力资源(希尔谷钟楼上的闪电击中)达到所需的 1.21 吉瓦时触发告警。像这样的查询将与我们的车辆速度查询非常相似。
from(bucket: "HillValley")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "ElectricityData")
|> filter(fn: (r) => r["Location"] == "clocktower")
|> filter(fn: (r) => r["Source"] == "lightning")
|> filter(fn: (r) => r["_field"] == "power")
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|> yield(name: "mean")
我们现在可以使用表达式来修改这些数据了。
向您的 Grafana 告警规则添加表达式
现在,让我们使用相同的步骤将每个查询缩减为最新值。将查询
A
缩减为单个值可能如下所示:这是我们正在缩减查询
B
:现在,在 section
C
中,我们需要创建一个数学表达式进行告警。在这种情况下,我们将使用 AND&&
运算符来指定必须满足两个条件:C
的值(查询A
的缩减值)必须大于 88.0,同时D
的值(查询B
的缩减值)必须大于 1.21。我们将其写为$C > 88.0 && $D > 1.21
。
这是我们告警的预览:
💡提示:如果您在 InfluxDB 中的数据小数点右侧有不必要的大量数字(例如上面显示的 1.2104705741732575),并且您希望 Grafana 告警更易读,请尝试使用 {{ printf “%.2f” $values.D.Value }}。例如,在注释摘要中,我们可以编写以下内容:
{{ $values.D.Labels.Source }} at the {{ $values.D.Labels.Location }} has generated {{ printf "%.2f" $values.D.Value }} jigowatts.`
这将显示如下: )
您可以参考我们的文档 告警消息模板,了解更多关于此强大功能的信息。
结论
在本示例中,我们展示了如何创建一个基于 Flux 的告警,该告警使用来自两个不同查询的两个不同条件,这些查询使用了来自两个不同数据源的数据。在示例三中,我们将转换思路,解决另一个常见的告警场景:如何基于汇总(每日)值创建告警。
示例 3:如何基于汇总(每日)值创建 Grafana 告警
Grafana 社区论坛中最常见的请求之一涉及绘制每日电量消耗和产量图。这类数据通常存储在 InfluxDB 中。在本示例中,我们将看到如何将时间序列数据汇总为每日值,然后对其进行告警。
假设我们的电表每小时向 InfluxDB 发送一次读数,其中包含该小时的总千瓦时使用量。我们希望编写一个查询,将这些每小时值汇总为每日值,然后创建告警,当电量消耗(千瓦时)超过每天 5,000 千瓦时时触发。
向您的 Grafana 告警规则添加初始 Flux 查询
让我们首先检查一个典型查询以及由此产生的 7 天期间的每小时数据时间图。下面的查询所示:
from(bucket: "RetroEncabulator") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r["_measurement"] == "ElectricityData") |> filter(fn: (r) => r["Location"] == "PlantD5") |> filter(fn: (r) => r["_field"] == "power_consumed") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "power")
我们在这里可以看到与示例 1 和 2 中相同的 Flux 函数模式。像这样的查询会生成类似于下图的图形:
现在让我们调整查询以计算每日用量。对于许多数据源来说,这可能是一个相当复杂的操作。但使用 Flux,只需更改 aggregateWindow 函数的参数,我们就可以计算同一 7 天期间的每日用量:
from(bucket: "RetroEncabulator") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r["_measurement"] == "ElectricityData") |> filter(fn: (r) => r["Location"] == "PlantD5") |> filter(fn: (r) => r["_field"] == "power_consumed") |> aggregateWindow(every: 1d, fn: sum) |> yield(name: "power")
请注意我们如何将
aggregateWindow()
函数调整为aggregateWindow(every: 1d, fn: sum)
。这会生成如下所示的图形:向您的 Grafana 告警规则添加表达式。
现在我们的每日查询已经正确,我们可以继续使用之前相同的模式,添加表达式来缩减结果并对其执行数学运算。
如前所述,让我们将查询缩减为一个单一值:
现在创建一个数学表达式进行告警,并设置评估行为。在这种情况下,我们需要编写
$B > 5000
。现在,当我们的日用电量超过 5000 千瓦时时,我们就会收到告警。这是告警预览:
结论
绘制和汇总电量消耗是结合使用 InfluxDB 和 Grafana 的常见用例。使用 Flux,我们看到了将数据按天分组并基于该日值触发告警是多么容易。在接下来的两个示例中,我们将探讨更复杂的 Grafana 告警形式:多维告警。
示例 4:使用 Flux 创建动态(多维)Grafana 告警
让我们回到示例 1 中的流体储罐,但这次假设我们有 5 个储罐(A5、B4、C3、D2 和 E1)。我们现在正在跟踪五个储罐的温度:A5、B4、C3、D2 和 E1。
我们希望创建一个多维告警,当任何储罐的温度低于 30 °C 或高于 60 °C 时通知我们。
向您的 Grafana 告警规则添加初始 Flux 查询
我们一如既往地从编写初始查询开始。这与示例 1 中的查询非常相似,但请注意,我们的第三个 filter()
函数如何捕获所有五个储罐的数据,而不仅仅是 A5
。
from(bucket: "HyperEncabulator")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "TemperatureData")
|> filter(fn: (r) => r["MeasType"] == "actual")
|> filter(fn: (r) => r["Tank"] == "A5" or r["Tank"] == "B4" or r["Tank"] == "C3" or r["Tank"] == "D2" or r["Tank"] == "E1")
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|> yield(name: "mean")
💡提示:如果储罐每晚从 23:00 到 07:00 关闭,它们可能会降到 30 °C 的阈值以下。如果不想在这些时间段内接收告警,可以使用 Flux 函数 hourSelection(),该函数根据指定小时范围内的188体育直播平台值过滤行。
|> hourSelection(start: 7, stop: 23)`
像上面这样的查询将产生这样的时间序列图:
向您的 Grafana 告警规则添加表达式
我们创建一个 Reduce 表达式,将每个储罐的时间序列缩减为单个值。这给我们五个不同的温度值:
)
创建要触发告警的数学表达式。这与示例 1 中的表达式完全相同:
$B < 30 || $B > 60
。
正如我们所见,有三个储罐在可接受的阈值范围内,而有两个储罐越过了上限。这将触发对储罐 D2
和 E1
的告警。
结论
借助多维告警,我们可以避免重复。但是如果场景更复杂呢?在下一个也是最后一个示例中,我们将探讨如何使用多维告警来创建最动态的告警。
示例 5:如何使用 Flux 从多个查询和多个阈值创建动态(多维)Grafana 告警
对于最后一个示例,让我们继续使用我们的五个流体储罐及其五个数据集。让我们再次假设每个储罐都有一个温度控制器,其设定值存储在 InfluxDB 中。让我们改变一下,假设每个储罐都有不同的设定值,并且我们始终需要保持在设定值的 3 度范围内。
我们想要创建一个多维告警,该告警将涵盖每个储罐的每个独特场景,当任何储罐的温度超出其独特的允许范围时触发告警。
为了更好地可视化这个挑战,这是一个表示我们的五个储罐、它们的温度设定值和它们的允许范围的表格:
储罐 | 设定值 | 允许范围 (±3) |
---|---|---|
A5 | 45 | 42 到 48 |
B4 | 55 | 52 到 58 |
C3 | 60 | 57 到 63 |
D2 | 72 | 69 到 75 |
E1 | 80 | 77 到 83 |
使用 Grafana Alerting,我们可以创建一个单一的多维规则来覆盖所有 5 个储罐,并且可以使用 Flux 比较每个储罐的设定值和实际值。换句话说,一个多维告警可以监控 5 个独立的储罐,每个储罐都有不同的设定值和实际值,但都拥有一个共同的“允许阈值”(即温度差为 ±3 度)。
向您的 Grafana 告警规则添加初始 Flux 查询
让我们从数据查询开始。它类似于我们过去的查询,只是现在更复杂。我们必须添加额外的函数以将数据格式化到正确的形式,包括 pivot()
、map()
、rename()
、keep()
和 drop()
函数。
from(bucket: "HyperEncabulator")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "TemperatureData")
|> filter(fn: (r) => r["MeasType"] == "actual" or r["MeasType"] == "setpoint")
|> filter(fn: (r) => r["Tank"] == "A5" or r["Tank"] == "B4" or r["Tank"] == "C3" or r["Tank"] == "D2" or r["Tank"] == "E1")
|> filter(fn: (r) => r["_field"] == "Temperature")
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|> pivot(rowKey:["_time"], columnKey: ["MeasType"], valueColumn: "_value")
|> map(fn: (r) => ({ r with _value: (r.setpoint - r.actual)}))
|> rename(columns: {_value: "difference"})
|> keep(columns: ["_time", "difference", "Tank"])
|> drop(columns: ["actual", "setpoint"])
|> yield(name: "mean")
请注意,在上面我们计算了实际值与设定值之间的差值。Grafana 解析来自 InfluxDB 的结果的方式是,如果找到 _value 列,则假定它是时间序列。快速解决办法是添加以下 rename()
函数:
|> rename(columns: {_value: "something"})
上面的查询会产生这个时间序列:
向您的 Grafana 告警规则添加表达式
再次,我们为上述查询创建一个 Reduce 表达式,将每个值缩减为单个值。此值表示每个储罐的设定值与其实际实时温度之间的温差:
现在我们创建一个用于触发告警的数学表达式。这次我们将创建一个条件,检查我们计算的绝对值是否大于 3,即
abs($(B))>3.0
。
我们现在可以看到两个储罐,D2
和 E1
,正在评估为 true。当我们预览告警时,我们可以看到这两个储罐将触发通知,并将其状态从 Normal
更改为 Alerting
。
结论
Flux 查询和 Grafana 统一告警是识别数据集或整个系统中几乎任何可告警条件的强大组合。有关 Grafana Alerting 的更多信息,请访问此处文档。有关 Flux 查询语言的更多信息,您也可以访问其文档。