普罗米修斯:对警报规则进行单元测试
我在 Prometheus 添加的单元测试功能中尝试了警报规则和记录规则的测试。由于此文章的撰写时间还是最新版本的 Prometheus v2.4.3 并没有包含此功能,因此我使用了 master 分支的 Docker 镜像。由于尚未发布,因此可能会有规格更改,请注意。
背景 – (background)
此前,有关 Prometheus 警报规则的单元测试的要求已经在问题(#1695)中提出。该要求也被列为 2018 年 Google Summer of Code 的项目想法,并且 Ganesh Vernekar 先生已经对此进行了实现。
- 実装の PR: Unit testing for rules by codesome · Pull Request #4350 · prometheus/prometheus · GitHub
使用promtool test命令
在 Prometheus 的实用工具命令 “promtool” 中,Alert Rule 的单元测试作为 “test rules” 子命令被实现。
因为promtool包含在Prometheus的Docker镜像中,所以我们可以使用它来验证命令。由于默认的Entrypoint是prometheus二进制文件,我们将其更改为/bin/sh以执行shell并在其中进行验证。我们希望使用本地的单元测试定义文件,因此需要将当前目录挂载到Docker容器中。
# master の最新バージョンを pull する
$ docker pull prom/prometheus:master
# Prometheus のイメージでシェルを実行
$ docker run -it -v $PWD:/work -w /work --entrypoint /bin/sh prom/prometheus:master
以下操作在容器内的Shell中执行。基本上,您只需将包含单元测试定义的yaml文件作为参数传递给promtool test rules命令。在此,我们将测试定义文件放在当前目录中进行准备。
考试执行(成功)
test.yaml是一个单元测试的定义文件(如下所述)。如果测试成功,则显示“SUCCESS”,并以0作为返回值返回。
/work $ promtool test rules test.yaml
Unit Testing: test.yaml
SUCCESS
# 戻り値は 0
/work $ echo $?
0
考试执行(失败)
如果测试失败,将显示“FAILED”,并显示期望值(exp)和实际值(got)。返回值将为1。
/work $ promtool test rules failed.yaml
Unit Testing: failed.yaml
FAILED:
alertname:NodeNotReady, time:10m0s,
exp:"[Labels:{alertname=\"NodeNotReady\", condition=\"Ready\", node=\"mynode\", severity=\"notice\", status=\"true\"} Annotations:{text=\"node mynode is not ready for more than 10 minutes.\"}]",
got:"[Labels:{alertname=\"NodeNotReady\", condition=\"Ready\", node=\"othernode\", severity=\"notice\", status=\"true\"} Annotations:{text=\"node othernode is not ready for more than 10 minutes.\"}]"
# 戻り値は 1
/work $ echo $?
1
请帮助我
# promtool test のヘルプを実行
/work $ promtool test --help
usage: promtool test <command> [<args> ...]
Unit testing.
Flags:
-h, --help Show context-sensitive help (also try --help-long and --help-man).
--version Show application version.
Subcommands:
test rules <test-rule-file>...
Unit tests for rules.
警报规则的测试定义
我来定义单元测试。首先,我们准备一个用于单元测试的警报规则文件。在这个例子中,我们将测试 Kubernetes 中的节点在10分钟内未准备好的警报规则(KubeNotReady)。文件名为alerts.yaml。
groups:
- name: kube-state-metrics
rules:
- alert: NodeNotReady
expr: kube_node_status_condition{condition="Ready",status="true"} == 0
for: 10m
labels:
severity: notice
annotations:
text: node {{ $labels.node }} is not ready for more than 10 minutes.
下面将说明单元测试的定义。输入的时间序列(input_series)可以使用特殊的展开符号(后述)。
# テスト対象のルールファイルを指定します
rule_files:
- alerts.yaml
# テストケースを記載します
tests:
- interval: 1m # input series の values の timeseries を記録する間隔
# 想定する入力の timeseries を定義します
input_series:
- series: 'kube_node_status_condition{condition="Ready",status="true", node="mynode"}'
values: 0+0x15 # 0 の値を 15 回 interval の間隔で繰り返す
# テストケースを定義します
alert_rule_test:
- eval_time: 10m # 評価するタイミング (テスト起動時を起点とした時間)
alertname: NodeNotReady # アラート名
exp_alerts: # 期待するアラートの状態
- exp_labels: # 期待するアラートのラベル。すべて記載する
severity: notice
node: mynode
condition: Ready
status: true
exp_annotations: # 期待するアラートのアノテーション
text: "node mynode is not ready for more than 10 minutes."
我将在容器内运行测试,并确认测试通过。
/work $ promtool test rules test.yaml
Unit Testing: test.yaml
SUCCESS
试着将input_series的值更改为1+0x15,以确认测试无法通过。
# アラートの条件を満たさないのでアラートが上がらない
/work $ promtool test rules test.yaml
Unit Testing: test.yaml
FAILED:
alertname:NodeNotReady, time:10m0s,
exp:"[Labels:{alertname=\"NodeNotReady\", condition=\"Ready\", node=\"mynode\", severity=\"notice\", status=\"true\"} Annotations:{text=\"node mynode is not ready for more than 10 minutes.\"}]",
got:"[]"
对于尝试过后的感受如何?
-
- いくつか既存のアラートのテストを書いてみたところ、複雑な PromQL はやはりテストがあると安心できました
特に他人が書いたアラートだとテストを書くことで意味が理解しやすくなりました
アラートが上がらない境界値のテストをどうやるか
現状 exp_alerts: [] とするとアラートが上がらない場合と一致しますが仕様なのかは不明でした
录音规则的测试定义
您可以像警报规则一样使用类似的方法来进行录制测试。这里以计算空闲节点的CPU时间为例进行录制规则的测试。文件名为recording.yaml。
groups:
- name: example-node-exporter-rules
rules:
# CPU in use by CPU.
- record: instance_cpu:node_cpu_seconds_not_idle:rate5m
expr: sum(rate(node_cpu_seconds_total{mode!="idle"}[5m])) without (mode)
在警报规则中,我们使用 alert_rule_test 来定义警报的期望值。但是在记录规则中,我们将使用 promql_expr_test 来使用 PromQL 进行测试。
rule_files:
- recording.yaml # レコーディングルールのファイル
tests:
- interval: 1m
# 想定する入力の timeseries を定義します
input_series:
- series: 'node_cpu_seconds_total{mode="system", cpu="0"}'
values: 0+20x10
- series: 'node_cpu_seconds_total{mode="user", cpu="0"}'
values: 0+40x10
- series: 'node_cpu_seconds_total{mode="system", cpu="1"}'
values: 0+10x10
- series: 'node_cpu_seconds_total{mode="user", cpu="1"}'
values: 0+20x10
# PromQL で値をテストします
promql_expr_test:
- eval_time: 10m
expr: 'instance_cpu:node_cpu_seconds_not_idle:rate5m' # PromQL
exp_samples: # 期待するする結果
- labels: 'instance_cpu:node_cpu_seconds_not_idle:rate5m{cpu="0"}'
value: 1
- labels: 'instance_cpu:node_cpu_seconds_not_idle:rate5m{cpu="1"}'
value: 0.5
我将确保测试通过。
/work/recoding $ promtool test rules recording-test.yaml
Unit Testing: recording-test.yaml
SUCCESS
测试更改input_series的值,以确认测试无法通过。
/work/recoding $ promtool test rules recording-test.yaml
Unit Testing: recording-test.yaml
FAILED:
expr:'instance_cpu:node_cpu_seconds_not_idle:rate5m', time:10m0s,
exp:"{__name__=\"instance_cpu:node_cpu_seconds_not_idle:rate5m\", cpu=\"0\"} 1E+00, {__name__=\"instance_cpu:node_cpu_seconds_not_idle:rate5m\", cpu=\"0\"} 1E+00, {__name__=\"instance_cpu:node_cpu_seconds_not_idle:rate5m\", cpu=\"1\"} 5E-01",
got:"{__name__=\"instance_cpu:node_cpu_seconds_not_idle:rate5m\", cpu=\"0\"} 2.3333333333333335E+00, {__name__=\"instance_cpu:node_cpu_seconds_not_idle:rate5m\", cpu=\"0\"} 2.3333333333333335E+00, {__name__=\"instance_cpu:node_cpu_seconds_not_idle:rate5m\", cpu=\"1\"} 5E-01"
尝试后的感受
- レコーディングルールの場合、どのように記録されるかがテストの期待値として記載できわかりやすかったです
输入系列的表示方法
输入系列的值有两种表示方法:直接写下数值和使用展开表示法。
直接表示
以下是一种将数值排列在values中的方式。数值将以interval的间隔记录timeseries(类似于抓取间隔)。在下面的例子中,最初的值是0,每1分钟出现5次,然后每1分钟出现1次,持续10分钟,形成了一组指标数据。
- interval: 1m
input_series:
- series: 'kube_node_status_condition{condition="Ready",status="true", node="mynode"}'
values: 0 0 0 0 0 1 1 1 1 1
展开记法 jì fǎ)
如果一个数以相同的值或固定的值递增,可以用展开表示法来表示该值。前面提到的值可以用 0+0x5 和 1+0x5 这两种形式来表示。
- interval: 1m
input_series:
- series: 'kube_node_status_condition{condition="Ready",status="true", node="mynode"}'
values: 0+0x5 1+0x5
文件中有说明,其记载如下。增加值对于计数型指标非常方便。
a = 初期値、b = 増加値、 c = 回数 とすると下記のように展開されます (x は繰り返しの意味)
'a+bxc' : 'a a+b a+(2*b) a+(3*b) … a+(c*b)'
'a-bxc' : 'a a-b a-(2*b) a-(3*b) … a-(c*b)'
例:
'-2+4x3' : '-2 2 6 10'
'1-2x4' : '1 -1 -3 -5 -7'
0+0x5 の場合増加値が 0 なので: '0 0 0 0 0' と 0 が5回になります
参考:用于 PromQL 的测试 DSL
在 GitHub 上查看由 codesome 发起的用于规则的 PR 单元测试 · Pull Request #4350 · prometheus/prometheus,其中的单元测试功能通过核心部分的 unittest.go 实现,代码行数不超过 500 行,非常精简。
因为它使用了 Prometheus 内部使用的 PromQL 用 DSL 结构(promql/test.go)。例如,1+0x10 等特殊表示法也是受此 DSL 表示方式的影响。Prometheus 中使用的测试用 DSL 可作为参考来了解 PromQL 的测试方法。
PromQL 的测试用DSL示例(increase()函数的一部分)
# Tests for increase().
load 5m
http_requests{path="/foo"} 0+10x10
http_requests{path="/bar"} 0+10x5 0+10x5
# Tests for increase().
eval instant at 50m increase(http_requests[50m])
{path="/foo"} 100
{path="/bar"} 90
总结
promtool test rules でアラートルール、レコーディングルールのテストが実行できるようになりました
PromQL のテスト用 DSL がベースとなっています
メトリクスの想定入力値を書いて、それに対して期待するアラートもしくは PromQL の結果をテストできます
複雑なアラートやレコーディングルールはテストを書いてみると安心感があります
请参考
- prometheus/unit_testing_rules.md at master · prometheus/prometheus · GitHub