欢迎光临
我们一直在努力

HA8180是什么Prometheus 基础设施监控实用指南(三)

原文:annas-archive.org/md5/eed0067609d974db930559c34e6f291f

译者:飞龙

协议:CC BY-NC-SA 4.0

本节结束后,读者将能够在 Prometheus 中创建有意义的告警和记录规则,充分利用 Grafana,并在 Alertmanager 中设置复杂的告警路由。

本节包括以下章节:

  • 第九章,定义告警和记录规则

  • 第十章,发现和创建 Grafana 仪表板

  • 第十一章,理解和扩展 Alertmanager

记录规则是 Prometheus 中一个非常有用的概念。它们使你能够加速繁重的查询,并在 PromQL 中启用本来非常昂贵的子查询。告警规则类似于记录规则,但具有告警特定的语义。由于测试是任何系统中的基础部分,本章将提供学习如何确保记录和告警规则在部署前按预期工作的机会。理解这些结构将有助于提升 Prometheus 的性能和鲁棒性,并启用其告警能力。

本章将涵盖以下主题:

  • 创建测试环境

  • 规则评估是如何工作的?

  • 在 Prometheus 中设置告警

  • 测试你的规则

在本章中,我们将重点关注 Prometheus 服务器,并且将部署一个新的实例,以便应用所覆盖的概念。

我们从创建一个新的 Prometheus 实例并将其部署到服务器上开始:

  1. 要创建一个新的 Prometheus 实例,请进入正确的仓库路径:
cd chapter09/
  1. 确保没有其他测试环境正在运行,然后启动本章的环境:
vagrant global-status
vagrant up
  1. 使用以下代码验证测试环境是否成功部署:
vagrant status

这将输出以下内容:

Current machine states:

prometheus                   running (virtualbox)

The VM is running. To stop this VM, you can run `vagrant halt` to
shut it down forcefully, or you can run `vagrant suspend` to simply
suspend the virtual machine. In either case, to restart it again,
simply run `vagrant up`.

新实例将可供检查,Prometheus 的 Web 界面将可以通过 http://192.168.42.10:9090 访问。

现在你可以通过执行以下命令访问 prometheus 实例:

vagrant ssh prometheus

现在你已经连接到 prometheus 实例,你可以验证本章中描述的指令。

测试完成后,请确保你位于 chapter09/ 目录内,并执行以下命令:

vagrant destroy -f

不用太担心,如果需要,你可以轻松地重新启动环境。

Prometheus 允许周期性地评估 PromQL 表达式,并存储由这些表达式生成的时间序列;这些被称为 规则。本章中我们将看到这两种规则类型。它们分别是 记录规则和告警规则。它们共享相同的评估引擎,但在目的上有所不同,我们将在接下来的内容中详细探讨。

记录规则的评估结果将作为样本保存到 Prometheus 数据库中,针对配置中指定的时间序列。这类规则可以通过预计算昂贵的查询,将原始数据聚合为时间序列来减轻重型仪表板的负担,随后可以将其导出到外部系统(例如通过联邦机制导出到更高层次的 Prometheus 实例,具体内容请见 第十三章,扩展与联邦化 Prometheus),并且有助于创建复合的范围向量查询(虽然记录规则过去是唯一的实现方式,但新的子查询语法使得这类用例的探索成为可能)。

当规则中评估的 PromQL 表达式产生非空结果时,警报规则会触发。它们是 Prometheus 中进行时间序列警报的机制。警报规则触发时也会生成新的时间序列,但不会将评估结果作为样本;相反,它们会创建一个ALERTS度量,其中包含警报名称和状态作为标签,以及配置中定义的任何其他标签。下一节将进一步分析这一点。

规则与主 Prometheus 配置文件分开定义,并通过rule_files顶级配置键包含在主配置文件中。它们会定期评估,这个间隔可以通过global中的evaluation_interval全局定义(默认为一分钟)。

我们可以通过查看测试环境中提供的配置来看到这一点:

vagrant@prometheus:~$ cat /etc/prometheus/prometheus.yml
global:
...
 evaluation_interval: 1m
...

rule_files:
 - "recording_rules.yml"
...

rule_files接受一个路径列表,这些路径可以是相对于主 Prometheus 配置的相对路径或绝对路径。此外,可以使用通配符匹配文件名(而不是目录);例如,/etc/prometheus/rules/*.yml。规则文件中的更改不会被 Prometheus 自动检测到,因此需要重新加载(如第五章《运行 Prometheus 服务器》中所述,Running a Prometheus Server)。如果规则文件中发现错误,Prometheus 将无法重新加载,并将继续使用先前的配置运行。然而,如果服务器被重启,它将无法启动。为了确保这种情况不发生,可以使用promtool提前测试错误(如第八章《故障排除与验证》中所述,Troubleshooting and Validation)– 在使用自动化部署规则时,强烈建议这样做。

就像prometheus.yml配置文件一样,rules文件也是以 YAML 格式定义的。实际格式非常容易理解:

groups:
- name: <group_name_1>
  interval: <evaluation_interval>
  rules:
  - record: <rule_name_1>
    expr: <promql_expression_1>
    labels:
 <label_name>: <label_value>
  ...
  - record: <rule_name_N>
    expr: <promql_expression_N>
...
- name: <group_name_N>
  ...

每个文件在groups键下定义一个或多个规则组。每个组都有一个name,一个可选的评估interval(默认为在主 Prometheus 配置文件中定义的全局评估间隔),以及一个rules列表。每个规则指示 Prometheus 将评估在expr中定义的 PromQL 表达式的结果记录到指定的度量名称中,并可以通过在labels中设置它们来选择性地添加或覆盖存储结果之前设置的系列标签。每个组中的规则按声明的顺序依次评估,这意味着由某个规则生成的时间序列可以安全地在同一组内的后续规则中使用。由规则生成的样本将具有与规则组评估时间相对应的时间戳。下图说明了前述过程:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/74b724c0-a869-455c-8a0d-1382e5fab039.png

图 9.1:规则管理器是 Prometheus 的内部子系统,负责根据规则组的评估间隔定期评估规则,并管理警报生命周期。

让我们来看一下在本章测试环境中可用的录制规则:

vagrant@prometheus:~$ cat /etc/prometheus/recording_rules.yml

该文件有两个规则组,分别命名为recording_rulesdifferent_eval_interval

...
- name: recording_rules
  rules:
  - record: instance:node_cpu:count
    expr: count without (cpu) (count without (mode) (node_cpu_seconds_total))
...

第一个规则组由一个录制规则组成,使用全局评估间隔,采用node_cpu_seconds_total指标,从 Node Exporter 获取该指标,以计算虚拟机VM)中可用的 CPU 核心数,并将结果录制到一个新的时间序列instance:node_cpu:count中。

第二个规则组更为复杂;它为该组显示了自定义评估间隔,并使用该组中先前规则生成的时间序列作为录制规则。我们不会深入讨论这些规则的具体作用,因为它们将作为接下来规则命名约定部分的示例,但可以看到这里的评估间隔:

...
- name: different_eval_interval
  interval: 5s
...

通过在第二个规则组中声明评估间隔,我们正在覆盖prometheus.ymlglobal部分中的配置——此组中的规则将在指定的频率下生成样本。这样做仅用于演示目的;通常不建议设置不同的间隔,原因与抓取作业相同:当使用具有不同采样率的系列时,查询可能会产生错误的结果,而且需要定期跟踪每个系列的不同采样率变得不可管理。

Prometheus 提供了一个状态页面,在 Web用户界面UI)中,用户可以查看已加载的规则组及其包含的录制规则、录制状态、每个规则的最后一次评估所需时间,以及它们上次运行的时间。您可以通过进入顶部栏的状态 | 规则来找到这个页面:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/6d4c3893-2aff-4b18-9bb2-5656f04eed6d.png

图 9.2:Prometheus Web 界面显示/rules 端点

有了这些信息,我们现在掌握了如何创建录制规则的基本知识。接下来,我们将探讨 Prometheus 社区为录制规则约定的命名规则。

录制规则名称的验证遵循与度量名称相同的正则表达式,因此,规则技术上可以与任何其他度量名称相同。然而,制定清晰的命名标准能使得在抓取的度量中更容易识别录制规则,知道它们来源于哪些度量,以及理解应用了哪些聚合。

Prometheus 社区已经趋向于为录制规则制定一个明确的命名约定。这是基于多年的经验,在大规模运行 Prometheus 时积累的。这能在正确使用时提供所有上述优势。

记录规则命名的推荐约定由三个部分组成,使用冒号分隔,格式如下:level:metric:operations。第一部分表示规则的聚合级别,意味着它将列出相关的标签/维度(通常以下划线分隔);第二部分是作为规则基础的指标名称;第三部分列出了应用于指标的聚合操作。

本章介绍的记录规则都遵循这一约定,因此让我们看一下在测试环境中可用的第二条规则组:

- record: handler_instance:prometheus_http_request_duration_seconds_sum:rate5m
  expr: >
    rate(prometheus_http_request_duration_seconds_sum[5m])

- record: handler_instance:prometheus_http_request_duration_seconds_count:rate5m
  expr: >
    rate(prometheus_http_request_duration_seconds_count[5m])

- record: handler:prometheus_http_request_duration_seconds:mean5m
  expr: >
    sum without (instance) (
      handler_instance:prometheus_http_request_duration_seconds_sum:rate5m
    )
    /
    sum without (instance) (
      handler_instance:prometheus_http_request_duration_seconds_count:rate5m
    )

从第一条规则的命名来看,我们可以轻松理解该规则基于prometheus_http_request_duration_seconds_sum指标;rate5m表示对五分钟的时间范围应用了rate()函数,且有两个有趣的标签:handlerinstance

第二条规则应用了相同的逻辑,但这次使用的是prometheus_http_request_duration_seconds_count指标。第三条规则则稍微复杂一些;因为它是将_sum除以延迟事件的_count,它实际上代表了五分钟的延迟平均值,在这种情况下是 Prometheus 提供的 HTTP 请求的延迟。由于我们将instance标签聚合掉了,level部分通过仅保留handler作为相关维度来反映这一点。最后需要注意的是,这条规则的指标名称现在是prometheus_http_request_duration_seconds,因为它既不代表总和也不代表计数,但它仍然可以清楚地理解该规则所基于的指标。

命名记录规则可能是一项困难的任务,需要在精确表示所有相关因素和简洁管理指标名称之间取得平衡。当你遇到这种情况——无法立即清楚如何根据表达式命名记录规则时,一个好的经验法则是确保另一个熟悉这种命名约定的人可以将该规则与使用的指标、应该存在的标签/维度以及应用的变换联系起来。

到目前为止,我们已经讨论了 PromQL 如何在查询收集到的数据时发挥重要作用,但当我们需要一个表达式持续评估,以便在满足定义的条件时触发事件时,我们便进入了告警部分。我们在第一章中解释了告警是监控组件之一,监控基础知识。需要明确的是,Prometheus 并不负责发送电子邮件、Slack 或其他形式的通知;这通常是另一个服务的责任。这个服务通常是 Alertmanager,我们将在第十一章中讨论,理解与扩展 Alertmanager。Prometheus 利用告警规则的强大功能来推送告警,接下来我们将讨论这个话题。

告警规则就像是记录规则,只是有一些额外的定义;它们甚至可以共享同一个规则组而不产生任何问题。最大的区别是,当触发时,它们会通过 HTTP POST 和 JSON 有效负载发送到外部端点,进行进一步处理。扩展一下“激活”这个术语,在这个上下文中,我们指的是当前状态与期望状态不同的情况,简而言之,就是当表达式返回一个或多个样本时。

告警规则,例如记录规则,依赖于在定义的间隔内评估的 PromQL 表达式。这个间隔可以是全局配置的,也可以是特定规则组的本地配置。在每次间隔迭代中,触发的告警会被验证,以确保它们仍然有效;如果不再有效,则认为它们已被解决。

对于每个由我们的表达式返回的样本,它都会触发一个告警。记住这一点非常重要,因为宽松的 PromQL 表达式可能会生成大量的告警,所以尽量将它们聚合在一起。

为了演示如何创建和理解告警规则,我们将引导您完成整个过程。这不仅涉及到主要的 Prometheus 配置文件,还涉及规则文件以及服务器的 Web 界面在处理告警时的表现。

在本章的测试环境中,我们可以找到以下 Prometheus 配置:

vagrant@prometheus:~$ cat /etc/prometheus/prometheus.yml 
global:
...
 evaluation_interval: 1m
...
rule_files:
 - "recording_rules.yml"
 - "alerting_rules.yml"
alerting:
 alertmanagers:
 - static_configs:
 - targets:
 - “prometheus:5001”
...

在这个配置中,有三个组件需要注意:

  • evaluation_interval:它负责定义用于记录和告警规则的全局评估间隔,可以在规则组层级通过使用interval关键字进行覆盖。

  • rule_files:这是 Prometheus 读取已配置的记录和/或告警规则的文件位置。

  • alerting:这是 Prometheus 发送告警进行进一步处理的端点。

在告警部分,我们配置了 “prometheus:5001”。在这个端点背后,实际上只是一个名为 alertdump 的小服务,它在 5001 端口上监听 HTTP POST 请求,并简单地将其有效负载转储到日志文件中。这将有助于分析 Prometheus 在告警触发时发送的内容。

之前,我们查看了 Prometheus 配置文件;接下来,我们将转到提供的告警规则示例,以下是相应代码片段:

vagrant@prometheus:~$ cat /etc/prometheus/alerting_rules.yml 
groups:
- name: alerting_rules
 rules:
 - alert: NodeExporterDown
 expr: up{job="node"} != 1
 for: 1m
 labels:
 severity: "critical"
 annotations:
 description: "Node exporter {{ $labels.instance }} is down."
 link: "https://example.com"

让我们更详细地查看 NodeExporterDown 告警定义。我们可以将配置分为五个不同的部分:alertexprforlabelsannotations。接下来,我们将在下表中逐一介绍这些部分:

部分 描述 是否必需 alert 使用的告警名称 是 expr 要评估的 PromQL 表达式 是 for 发送告警前,确保告警已触发的时间,默认为 0 否 labels 用户定义的键值对 否 annotations 用户定义的键值对 否

Prometheus 社区通常使用驼峰命名法(CamelCase)来命名告警。

Prometheus 不会执行验证来检查告警名称是否已被使用,因此可能会有两个或多个告警共享相同的名称,但评估不同的表达式。这可能会导致问题,例如追踪哪个特定的告警触发,或编写告警测试。

NodeExporterDown 规则仅在 job="node" 选择器的 up 指标超过 1 分钟不为 1 时触发,我们现在通过停止 Node Exporter 服务来测试这一点:

vagrant@prometheus:~$ sudo systemctl stop node-exporter
vagrant@prometheus:~$ sudo systemctl status node-exporter

...
Mar 05 20:49:40 prometheus systemd[1]: Stopping Node Exporter...
Mar 05 20:49:40 prometheus systemd[1]: Stopped Node Exporter.

我们现在强制使告警变为活动状态。这将迫使告警经历三个不同的状态:

顺序 状态 描述 1 非活动 尚未进入待处理或触发状态 2 待处理 尚未足够长时间以变为触发状态 3 触发 活跃时间超过定义的 for 子句阈值

进入 Prometheus 服务器网页界面的 /alerts 端点,我们可以查看 NodeExporterDown 告警的三种不同状态。首先,告警处于非活动状态,正如我们在下图中看到的:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/f1780ac8-5699-40ba-ac51-fe19d917fb04.png

图 9.3:NodeExporterDown 告警处于非活动状态

然后,我们可以看到告警处于待处理状态。这意味着,尽管告警条件已经触发,Prometheus 会继续检查该条件是否在每个评估周期中持续触发,直到for时间段过去。下图展示了待处理状态;请注意,显示注释复选框已被选中,这会展开告警注释:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/b30ea059-23e0-4025-95e2-19505112ae71.png

图 9.4:NodeExporterDown 告警处于待处理状态

最后,我们可以看到告警变为触发状态。这意味着告警已活跃超过 for 子句定义的持续时间——在这种情况下是 1 分钟:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/0e0284b0-8f88-4c81-a54b-f19bb7618cb7.png

图 9.5:NodeExporterDown 告警正在触发

当告警变为触发状态时,Prometheus 会向配置的告警服务端点发送 JSON 负载,在我们的案例中,端点是 alertdump 服务,该服务配置为将日志记录到 /vagrant/cache/alerting.log 文件。这使得我们很容易理解正在发送什么样的信息,并且可以进行如下验证:

vagrant@prometheus:~$ cat /vagrant/cache/alerting.log
[
   {
       "labels": {
           "alertname": "NodeExporterDown",
           "dc": "dc1",
           "instance": "prometheus:9100",
           "job": "node",
           "prom": "prom1",
           "severity": "critical"
       },
       "annotations": {
           "description": "Node exporter prometheus:9100 is down.",
           "link": "https://example.com"
       },
       "startsAt": "2019-03-04T21:51:15.04754979Z",
       "endsAt": "2019-03-04T21:58:15.04754979Z",
       "generatorURL": "http://prometheus:9090/graph?g0.expr=up%7Bjob%3D%22node%22%7D+%21%3D+1&g0.tab=1"
   }
]

现在我们已经了解了如何配置一些告警规则,并验证了 Prometheus 向配置的告警系统发送的内容,接下来让我们探索如何通过使用标签和注释来丰富这些告警的上下文信息。

在告警规则定义中,有两个可选部分:标签和注释。标签定义了告警的身份,并且可以根据它们所在的评估周期发生变化;如果发生变化,将会改变告警的身份。为了说明这一点,我们将介绍 ALERTS 指标,它跟踪所有活动告警及其标签。正如我们在下面的图示中看到的,我们有一个名为 alertstate 的标签,它跟踪告警的状态,并且会从 pending 状态过渡到 firing 状态:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/bb8a64c2-1844-4c5d-8e47-a29cba583986.png

图 9.6:ALERTS 指标

需要注意的是,标签中使用示例值的问题。虽然技术上是可行的,但这是一个非常糟糕的主意。这样做每次值发生变化时都会改变告警的身份,从而始终会重置定义的 for 倒计时,导致告警永远不会进入 firing 状态。

另一方面,注释不属于告警的身份,因此它们不会存储在 ALERTS 指标中。这些注释对于丰富告警的上下文信息非常有用。注释也使用 Go 模板语言进行模板化,就像我们在示例中看到的那样。通过使用 {{ .Labels.instance }} 模板语法,我们可以访问可用的告警标签,选择 instance 标签,并在注释的 description 字段中使用其值。如果需要,还可以通过 {{ .Value }} 来获取触发样本的值。

Golang 模板中的 .Labels.Value 变量也可以作为 $labels$value 使用,以方便操作。

以下代码段展示了我们示例中的告警规则:

   annotations:
     description: "Node exporter {{ .Labels.instance }} is down."
     link: "https://example.com"

当告警处于 firing 状态时,将产生以下渲染结果:

       "annotations": {
           "description": "Node exporter prometheus:9100 is down.",
           "link": "https://example.com"
       },

您可以在 golang.org/pkg/text/template/ 获得更多关于 Golang 模板的信息。

在前面的主题中,我们讨论了警报会经历的三个状态;但是在计算警报变为触发状态所需的总时间时,还需要考虑其他因素。首先是抓取间隔(在我们的示例中是 30 秒,虽然通常抓取和评估间隔应该相同以便于理解),然后是规则评估间隔(在我们的例子中全局定义为 1 分钟),最后是警报规则中 for 子句定义的 1 分钟。如果将这些变量都考虑在内,警报被认为是 firing 的时间,在最坏情况下可能需要 2 分钟 30 秒。下图展示了这个示例情况:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/8973ea05-760e-4131-8a83-38b670f8c308.png

图 9.7:警报延迟可视化

所有这些延迟仅发生在 Prometheus 端。处理发送警报的外部服务可能会有其他约束,这可能导致从发送通知到最终发送的全球延迟更长。

在 Prometheus 2.4.0 之前,pendingfiring 状态在重启后不会持久化,这可能会进一步延长警报的延迟。通过实现一个新的度量指标 ALERTS_FOR_STATE,来存储警报状态,从而解决了这个问题。你可以在 github.com/prometheus/prometheus/releases/tag/v2.4.0 查阅 Prometheus 2.4.0 的发布说明。

在第八章《故障排除与验证》中,我们介绍了 promtool 的一些功能,测试功能除外。test rules 子命令可以模拟多个时间序列的周期性样本摄取,利用这些时间序列评估记录和警报规则,然后测试记录的系列是否与配置的预期结果匹配。现在我们已经了解了记录和警报规则,接下来将通过创建单元测试并使用 promtool 来验证我们的规则,确保它们按预期工作。

Prometheus 二进制分发包中包含的 promtool 工具允许我们定义测试用例来验证我们编写的规则是否按预期工作。本章的测试环境还提供了一套针对我们到目前为止探索的规则的预构建测试。你可以在这里查看配置:

vagrant@prometheus:~$ cat /etc/prometheus/tests.yml

该文件包含本章介绍的所有记录和告警规则的测试。虽然你不需要在一个文件中定义所有的测试(实际上,为了保持组织性,每个规则组使用单独的测试文件更为整洁),但为了简便,这里统一在一个文件中进行了定义。现在,我们只分析记录规则,因为它们更容易理解。测试文件的顶层配置键定义了要加载的规则文件以及测试的默认评估间隔,它决定了当规则没有明确指定自己的评估周期时,记录和告警规则评估的周期性:

rule_files:
  - /etc/prometheus/recording_rules.yml
...
evaluation_interval: 1m

虽然测试文件中的 rule_files 配置键看起来与主 Prometheus 配置文件中的相同,但它不支持通配符(使用文件名通配符)。

在这些全局配置之后,紧接着是 tests 键下的测试用例定义。你可以定义多个测试包,每个包有自己独立的模拟抓取间隔、采集的系列和待测规则。让我们看看文件中定义的第一个测试,我们在其中添加了一些注释以便于理解:

tests:

interval 设置了在我们的模拟时间序列中生成间隔样本的时间:

- interval: 15s

input_series 的列表定义了生成哪些时间序列以及在每次模拟采集间隔的迭代中生成哪些值:

    input_series:
      - series: 'node_cpu_seconds_total{cpu="0",instance="prometheus:9100",job="node",mode="user"}'
        values: '1 1 1 1 1 1 1 1 1 1'
      - series: 'node_cpu_seconds_total{cpu="1",instance="prometheus:9100",job="node",mode="user"}'
        values: '1 1 1 1 1 1 1 1 1 1'
      - series: 'node_cpu_seconds_total{cpu="0",instance="example:9100",job="node",mode="idle"}'
        values: '1 1 1 1 1 1 1 1 1 1'
      - series: 'node_cpu_seconds_total{cpu="0",instance="example:9100",job="node",mode="system"}'
        values: '1 1 1 1 1 1 1 1 1 1'

用于测试的 PromQL 表达式列表被定义为 promql_expr_test

promql_expr_test:

每个 expr 定义了一个特定的表达式:

- expr: instance:node_cpu:count

设置 eval_time 来确定该表达式运行的时间点,预期的样本应该通过运行该表达式返回,称为 exp_samples

        eval_time: 1m
        exp_samples:
          - labels: 'instance:node_cpu:count{instance="prometheus:9100", job="node"}'
            value: 2
          - labels: 'instance:node_cpu:count{instance="example:9100", job="node"}'
            value: 1

在这个测试包中,我们可以看到每 15 秒生成四个时间序列,都是针对相同的指标 node_cpu_seconds_total。由于这些序列的实际值对该记录规则并不重要(它仅计算每个实例的 CPU 数量),因此每个样本的值都设置为 1。请注意标签的变化,具体来说,prometheus:9100 实例报告的是两个 CPU 的指标,而 example:9100 实例报告的是一个 CPU。实际测试只是验证,当 instance:node_cpu:count 表达式在 t=1m 时评估(假设从生成的采集开始经过了 1 分钟),返回的样本应显示每个实例的正确 CPU 数量。

我们现在可以使用以下指令来执行测试:

vagrant@prometheus:/etc/prometheus$ promtool test rules tests.yml 
Unit Testing: tests.yml
 SUCCESS

这确保了配置的记录规则按我们预期的方式运行。你可以通过从 prometheus:9100 实例的 instance:node_cpu:count 测试包中移除一个输入序列来尝试破坏测试。当你重新运行测试时,以下内容将显示出来,因为其中一个测试现在失败了:

vagrant@prometheus:/etc/prometheus$ promtool test rules tests.yml 
Unit Testing: tests.yml
  FAILED:
    expr:'instance:node_cpu:count', time:1m0s, 
        exp:"{__name__="instance:node_cpu:count", instance="example:9100", job="node"} 1E+00, {__name__="instance:node_cpu:count", instance="example:9100", job="node"} 1E+00, {__name__="instance:node_cpu:count", instance="prometheus:9100", job="node"} 2E+00", 
        got:"{__name__="instance:node_cpu:count", instance="example:9100", job="node"} 1E+00, {__name__="instance:node_cpu:count", instance="example:9100", job="node"} 1E+00, {__name__="instance:node_cpu:count", instance="prometheus:9100", job="node"} 1E+00"

这个输出告诉我们,promtool 期待的是已定义的样本集,但返回了一个不同的样本集。你可以看到,正如我们配置的那样,记录规则现在只报告了 prometheus:9100 实例的一个 CPU。这让我们对规则的行为是否如我们所愿充满信心。

第二组记录规则的测试大部分相同,但它们展示了生成更丰富输入序列的强大表示法:

  - interval: 5s
    input_series:
      - series: 'prometheus_http_request_duration_seconds_count{handler="/",instance="localhost:9090",job="prometheus"}'
        values: '0+5x60'
      - series: 'prometheus_http_request_duration_seconds_sum{handler="/",instance="localhost:9090",job="prometheus"}'
        values: '0+1x60'

这称为展开表示法。这是一种紧凑的方式,用于声明生成时间序列值的公式。它的形式可以是A+BxCA-BxC,其中 A 是起始值,B 是每次迭代中序列值应增加(前有 +)或减少(前有 -)的量,C 是这种增加或减少应应用多少次。

回到我们的示例,0+5x60 将展开成以下系列:

0 5 10 15 20 … 290 295 300

在声明输入时间序列的值时,您可以将字面值与展开表示法混合使用。这使得您可以轻松创建复杂的行为。以下是一个例子:

0 1 1 0 1+0x3 0 1 1 0 1 1 0 0 0 1 1 0+0x3 1

这将展开成如下所示:

0 1 1 0 1 1 1 1 0 1 1 0 1 1 0 0 0 1 1 0 0 0 0 1

测试对于避免不可预见的问题至关重要,凭借目前所涵盖的信息,您现在可以为记录规则生成自己的单元测试。接下来,我们将继续处理单元测试,但这次特别针对警报规则。

警报规则的单元测试与记录规则的单元测试非常相似。我们将使用本章之前提供的示例警报来演示如何配置警报测试以及如何验证它们。如前所述,本章的测试环境包含了一套用于测试此处呈现规则的测试,包括我们感兴趣的警报规则。再次提醒,您可以使用以下命令查看测试文件:

vagrant@prometheus:~$ cat /etc/prometheus/tests.yml

专注于警报组件,我们可以看到我们首先定义了警报规则的位置:

rule_files:
  - /etc/prometheus/alerting_rules.yml

默认的规则评估间隔在同一文件中的记录规则和警报规则之间共享:

evaluation_interval: 1m

警报测试便捷地位于自己的测试组中,让我们来看一下它的完整定义:

  - interval: 1m
    input_series:
      - series: 'up{job="node",instance="prometheus:9100"}'
        values: '1 1 1 0 0 0 0 0 0 0'
      - series: 'up{job="prometheus",instance="prometheus:9090"}'
        values: '1 0 1 1 1 0 1 1 1 1'

测试组定义与之前解释的相同,唯一的例外是 alert_rule_test 部分,这里我们定义了警报测试。需要注意的是,在这个示例中,第二个输入序列永远不应被我们的测试规则匹配,因为已定义的警报特别匹配 job="node"

    alert_rule_test:
      - alertname: NodeExporterDown
        eval_time: 3m
      - alertname: NodeExporterDown
        eval_time: 4m
        exp_alerts:
          - exp_labels:
              instance: "prometheus:9100"
              job: "node"
              severity: "critical"
            exp_annotations:
              description: "Node exporter prometheus:9100 is down."
              link: "https://example.com"

alert_rule_testpromql_expr_test 不必放在单独的测试块中;当记录规则和警报规则使用相同的输入时间序列并且具有相同的评估间隔时,您可以将两者放在同一测试组中。

alert_rule_test部分列出了应在模拟测试运行开始时(eval_time)评估哪些警报(alertname)。如果预计该警报会在该时刻触发,则应定义一个额外的exp_alerts部分,列出每个警报实例应包含的预期标签(exp_labels)和注释(exp_annotations)。如果exp_alerts部分为空,意味着该警报在指定的时间不应触发。

第一个警报测试将在第三分钟执行,由于我们之前提供的匹配系列在该时刻返回值1,因此在alerting_rules.yml中定义的警报表达式不会触发——这意味着警报中定义的表达式没有返回数据。

第二个警报规则将在第四分钟执行,并返回数据,因为我们提供的匹配系列在该特定时刻的样本值为0。警报规则返回的所有标签需要明确检查。测试还必须检查警报返回的所有描述,并完全展开任何模板变量。

我们现在可以使用以下指令运行测试:

vagrant@prometheus:~$ promtool test rules /etc/prometheus/tests.yml 
Unit Testing: /etc/prometheus/tests.yml
 SUCCESS

作为额外步骤,尝试将第二个警报的描述从prometheus:9100更改为类似prometheus:9999的内容,然后重新运行测试。你应该会得到以下输出:

vagrant@prometheus:~$ promtool test rules /etc/prometheus/tests.yml 
Unit Testing: /etc/prometheus/tests.yml
  FAILED:
    alertname:NodeExporterDown, time:4m0s, 
        exp:"[Labels:{alertname="NodeExporterDown", instance="prometheus:9100", job="node", severity="critical"} Annotations:{description="Node exporter prometheus:9999 is down.", link="https://example.com"}]", 
        got:"[Labels:{alertname="NodeExporterDown", instance="prometheus:9100", job="node", severity="critical"} Annotations:{description="Node exporter prometheus:9100 is down.", link="https://example.com"}]"

尽管这个警报非常简单,容易确定在何种条件下会触发,但警报规则的测试能够确保在你无法合理重现的环境条件下,警报会触发。

在本章中,我们有机会观察另一种生成衍生时间序列的方法。记录规则通过将重复的重查询预先计算成新的时间序列,帮助提高监控系统的稳定性和性能,这些新时间序列相对便宜,便于查询。警报规则将 PromQL 的强大功能和灵活性带入警报中;它们使得可以为复杂且动态的阈值触发警报,并且使用单个警报规则针对多个实例甚至不同应用进行目标定位。现在,了解警报中如何引入延迟将帮助你根据需求调整它们,但记住,稍微的延迟总比产生噪音的警报要好。最后,我们探索了如何为规则创建单元测试,并在 Prometheus 服务器运行之前验证它们。

下一章将进入监控的另一个组件:可视化。我们将深入探讨 Grafana,这是社区首选的 Prometheus 驱动仪表板工具。

  1. 记录规则的主要用途是什么?

  2. 为什么应避免在规则组中设置不同的评估间隔?

  3. 如果你看到instance_job:latency_seconds_bucket:rate30s指标,你期望看到哪些标签,并且会使用什么表达式来记录它?

  4. 为什么在告警标签中使用告警的样本值是一个不好的主意?

  5. 警报的待处理状态是什么?

  6. 当没有指定 for 子句时,警报从触发到过渡到 firing 状态之间会等待多长时间?

  7. 如何在不使用 Prometheus 的情况下测试你的规则?

  • Prometheus 记录规则: prometheus.io/docs/prometheus/latest/configuration/recording_rules/

  • 规则命名最佳实践: prometheus.io/docs/practices/rules/

  • Prometheus 告警规则: prometheus.io/docs/prometheus/latest/configuration/alerting_rules/

  • Prometheus 单元测试: prometheus.io/docs/prometheus/latest/configuration/unit_testing_rules/

Prometheus 表达式浏览器非常适合执行探索性查询,但有时我们需要预先构建的可视化来帮助我们快速调试问题。在本章中,我们将深入了解 Grafana,这是 Prometheus 项目推荐的用于构建仪表板的工具。Grafana 社区不断壮大,并通过托管大量现成的仪表板,使得重用它们、贡献社区并改进生态系统变得更加容易。在本章中,我们将学习如何查找和使用社区提供的仪表板,以及如何编写自己的仪表板并为社区做出贡献。最后,我们还会简要介绍控制台,这是一种内建于 Prometheus 中的仪表板解决方案,适用于高级用例。

简而言之,本章将涵盖以下主题:

  • 本章的测试环境

  • 如何将 Grafana 与 Prometheus 一起使用

  • 构建你自己的仪表板

  • 发现现成的仪表板

  • 默认 Prometheus 可视化

为了提供一个动手操作的方法,我们将为本章创建一个新的测试环境。我们将使用的设置类似于以下图示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/867a4731-0373-4838-8e98-26aa280b6609.png

图 10.1:测试环境网络

要生成本章的虚拟机VM)基础测试环境,前往代码库根目录下的正确存储库路径:

cd ./chapter10/

确保没有其他测试环境在运行,并启动本章的环境,如下所示:

vagrant global-status
vagrant up

你可以使用以下代码验证测试环境的成功部署:

vagrant status

这将产生以下输出:

Current machine states:

prometheus                 running (virtualbox)
grafana                    running (virtualbox)

This environment represents multiple VMs. The VMs are all listed above with their current state. For more information about a specific VM, run `vagrant status NAME`.

当部署任务完成后,你将能够使用你喜欢的支持 JavaScript 的网页浏览器,验证主机上的以下端点:

服务 端点 Prometheus http://192.168.42.10:9090 Grafana http://192.168.42.11:3000

你应该能够通过以下命令之一访问所需的实例:

实例 命令 Prometheus vagrant ssh prometheus Grafana vagrant ssh grafana

当你完成测试后,只需确保你在 chapter10/ 目录下并执行以下命令:

vagrant destroy -f

不用太担心——如果需要,你可以轻松地重新启动环境。

Grafana 是最为人所知的开源仪表板项目。它有数据源的概念,数据源不过是与数据后端的集成。截止目前,以下是可用的数据源:

  • Prometheus

  • 石墨

  • InfluxDB

  • Elasticsearch

  • Google Stackdriver

  • AWS CloudWatch

  • Azure Monitor

  • Loki(日志可视化)

  • MySQL

  • PostgreSQL

  • Microsoft SQL Server

  • OpenTSDB

  • TestData(用于生成测试的虚拟数据)

已经做了很多努力来提升 Prometheus 在 Grafana 中的集成性——例如,PromQL 自动补全。目前,Grafana 是任何想要可视化 Prometheus 数据的人的首选仪表板解决方案。不过,前面的说法并不完全准确,因为我们知道,对于探索性查询来说,没有什么比 Prometheus 表达式浏览器更好的了。然而,最近在 6.0.0 版本发布后,Grafana 引入了一项名为 Explore 的功能,作为替代的表达式浏览器。

你可以在 grafana.com/grafana/download 下载多个操作系统和发行版的安装文件。

Grafana 在构建时就考虑到了自动化。以下示例将展示你如何在不触碰主要配置文件的情况下几乎完成环境的设置。一个值得注意的好处是,Grafana 本身就已经集成了 Prometheus 的度量指标。

在测试环境运行时,你可以通过 http://192.168.42.11:3000 网址访问 Grafana。用户将看到一个简单的登录界面,如下所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/ac47013b-e9fb-45c2-aba3-f42bf359af55.png

图 10.2:Grafana 登录界面

默认的身份验证凭证如下:

用户名 密码 admin admin

登录成功后,我们将进入 Grafana 的首页,首页会显示一个设置向导。接下来,我们将解释每个配置步骤。以下截图展示了向导,其中一些步骤已完成配置:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/1f250cb1-8e67-4e0e-9eb9-03edbb93f21a.png

图 10.3:Grafana 首页

为了提高可读性,我们将默认的 Grafana 主题改为 Light,而不是 Dark。这可以在 首选项 菜单中的 配置 标签(左侧的小齿轮图标)轻松配置。

为了能够查询数据,我们必须配置一个数据源。在我们的案例中,我们将把 Prometheus 实例添加为默认的数据源。为此,我们需要指明数据源的位置、所需的身份验证/授权信息,以及任何其他特定的数据源配置。

配置数据源有两种方式。一种方式是通过在 Grafana 配置路径中添加一个包含所需配置的 YAML 文件,服务在启动时会自动读取并配置。这就是我们在本章的测试环境中所做的,因为它是自动化部署的更好解决方案。当连接到测试环境中的 grafana 实例时,你可以通过查看默认配置路径,看到我们正在使用的配置,路径如下所示:

vagrant@grafana:~$ cat /etc/grafana/provisioning/datasources/prometheus.yaml 
apiVersion: 1

datasources:
- name: prometheus
 type: prometheus
 access: proxy
 orgId: 1
 url: http://prometheus:9090
 isDefault: True
 version: 1
 editable: True

另一种选择是通过进入配置(左侧的小齿轮图标)| 数据源,手动添加设置选项。在点击保存并测试后,Grafana 将验证设置,并告知你是否存在任何问题。Grafana 提供了两种访问提供 HTTP 基础 API(如 Prometheus)的数据源的选项:是否代理请求。当代理请求时,从仪表板面板或通过探索表达式浏览器发出的每一个查询都会通过 Grafana 后端代理到数据源。尽管这样可以集中管理数据源凭证,并关闭除了受信任的客户端之外的所有直接网络访问,但它会增加 Grafana 实例的负载,因为需要处理更多的流量。不代理请求意味着客户端浏览器每次请求都会直接访问数据源。此配置要求访问 Grafana 的用户也能直接访问使用的数据源,并且该数据源的安全设置允许来自不同来源的请求。在我们所有的示例中,Grafana 都会设置为代理查询。

以下截图展示了我们在测试环境中使用的配置:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/9fe103bc-7af6-41bb-9348-d8f80c95863b.png

图 10.4:数据源配置界面

请注意,访问选项设置为服务器(默认)。这意味着所有的数据源请求将通过 Grafana 实例进行代理。

这个功能是在 Grafana 6 中引入的,开发人员持续改进其与 Prometheus 的紧密集成。在探索模式之前,每次你想执行探索查询时,都需要跳转到 Prometheus 表达式浏览器。

除了这个便利性外,还有一些值得注意的功能,使得探索模式独具特色,如下所示:

  • 指标列表:在左上角,我们可以找到一个名为指标的组合框。它以层次结构的形式列出了指标,按前缀分组,并且在符合双冒号命名规范时,能够自动检测和分组记录规则。

  • 查询字段:除了建议和自动完成指标外,查询字段还会显示有关 PromQL 函数的有用工具提示,甚至可以展开在原始表达式中检测到的记录规则。

  • 上下文菜单:你可以选择直接在探索页面中打开来自任何仪表板面板的查询。

以下截图展示了探索界面,同时显示了正在使用的 PromQL 函数的工具提示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/65766f06-7707-4443-b677-3a5c4f130d0b.png

图 10.5:Grafana 探索页面,显示 label_replace 函数的工具提示

通常可以通过点击左侧的小指南针图标找到探索模式。

类似于管理数据源,添加仪表板有几种方法,列举如下:

  • 通过手动构建你自己的

  • 通过导入 grafana.com 社区驱动的仪表板

  • 通过自动配置之前存储的仪表板

我们现在将处理最后一种方法,因为测试环境正在使用这种方法。我们将在后续章节中关注另外两种方法。

仪表板文件是仪表板的声明性表示,包含所有必要的设置,并使用 JSON 格式。如果你将其放置在预期的配置路径中,Grafana 服务将在启动时加载它。在我们的示例中,我们使用了默认路径,正如以下代码片段所示:

vagrant@grafana:~$ ls /etc/grafana/dashboards/
node_exporter_basics.json

你可以通过进入页面左上角的 首页 菜单并选择 node_exporter_basics 来找到这个仪表板。它在视觉上对应于以下屏幕截图:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/9298eb33-8b04-4ca3-b1a2-8f81dd501573.png

图 10.6:已自动配置的示例仪表板

在 Kubernetes 上部署 Grafana 基本上与在虚拟机上部署的方式相同,因此我们只关注一些操作员应当注意的细节。用于在我们的 Kubernetes 测试环境中启动 Grafana 和 Prometheus 的 Kubernetes 清单可以在代码库根路径的以下位置找到:

cd ./chapter10/provision/kubernetes/

由于 Kubernetes 部署过程与前几章相同(引导 Prometheus Operator,使用 Operator 部署 Prometheus,并部署导出器及其相应的 ServiceMonitor),此处将不再详细介绍。如果需要更多上下文,请随时查看前几章中的测试环境操作说明,例如 第七章,Prometheus 查询语言 – PromQL

以下步骤将确保创建一个新的 Kubernetes 环境,并预配置所有必要的软件,从而使我们能够专注于 Grafana 组件。

  1. 验证没有其他环境正在运行:
minikube status
minikube delete
  1. 使用以下命令启动一个空的 Kubernetes 环境:
minikube start 
  --cpus=2 
  --memory=3072 
  --kubernetes-version="v1.14.0" 
  --vm-driver=virtualbox
  1. 添加 Prometheus Operator 组件并按如下方式进行部署:
kubectl apply -f ./bootstrap/
kubectl rollout status deployment/prometheus-operator -n monitoring
  1. 使用以下命令添加新的 Prometheus 集群,并确保其成功:
kubectl apply -f ./prometheus/
kubectl rollout status statefulset/prometheus-k8s -n monitoring
  1. 将所有目标添加到 Prometheus,并使用以下命令列出它们:
kubectl apply -f ./services/
kubectl get servicemonitors --all-namespaces

现在 Kubernetes 环境已经运行,我们可以继续进行 Grafana 特定的配置。类似于专注于虚拟机的测试环境,我们不仅需要配置 Grafana 本身,还需要配置数据源和仪表板。

对于数据源,由于我们可能将来需要添加敏感信息(如身份验证),我们将使用 Kubernetes 秘密。这也意味着应该有一个 ServiceAccount 用于访问该秘密。

我们可以通过应用以下清单来创建 ServiceAccount:

kubectl apply -f ./grafana/grafana-serviceaccount.yaml

由于我们使用的是秘密,因此数据源配置需要进行 base64 编码。至于配置本身,与虚拟机部署中的配置相同,但我们将使用由服务管理的 Kubernetes 等效 URL 来替换 Prometheus URL。以下是编码前的片段:

...
datasources:
- name: prometheus
...
 url: http://prometheus-service.monitoring.svc:9090
...

应用以下清单后,将会有一个新的秘密,包含所需的 Grafana 数据源:

kubectl apply -f ./grafana/grafana-datasources-provision.yaml

现在,是时候将我们的示例仪表板添加到 Grafana 中了。为此,我们需要向 Grafana 提供一个配置文件,告诉它在哪里查找仪表板定义,然后将我们的示例仪表板定义放到该路径中。这些将作为 ConfigMap 可用于 Grafana 部署。显示仪表板位置配置的相关片段如下:

...
data:
  dashboards.yaml: |-
    {
        "apiVersion": 1,
        "providers": [{
           "folder": "",
           "name": "default",
           "options": {
             "path": "/etc/grafana/dashboards" 
           },
           "orgId": 1,
           "type": "file"
         }]
    }
kind: ConfigMap
...

另一个ConfigMap包含我们的示例仪表板,具体如下所示:

...
data:
  node_exporter_basics.json: |-
    {
...
    }
kind: ConfigMap
...

两个清单可以使用以下命令在 Kubernetes 测试环境中部署:

kubectl apply -f ./grafana/grafana-dashboards-provision.yaml

kubectl apply -f ./grafana/grafana-dashboards.yaml

现在是部署 Grafana 并利用之前所有配置的时候,具体操作如下:

kubectl apply -f ./grafana/grafana-deployment.yaml

这个部署将所有内容整合在一起:它挂载了数据源的秘密,并且将仪表板配置和仪表板 ConfigMap 放置在与虚拟机测试环境相同的位置,如下所示:

... 
        volumeMounts:
        - name: grafana-datasources-provision
          mountPath: /etc/grafana/provisioning/datasources
        - name: grafana-dashboards-provision
          mountPath: /etc/grafana/provisioning/dashboards
        - name: grafana-dashboards
          mountPath: /etc/grafana/dashboards
...

您可以通过以下指令跟踪部署状态:

kubectl rollout status deployment/grafana -n monitoring

最后,我们可以添加一个服务,以便访问新启动的 Grafana 实例,并添加一个 ServiceMonitor,使 Prometheus Operator 配置 Prometheus 来收集指标:

kubectl apply -f ./grafana/grafana-service.yaml

kubectl apply -f ./grafana/grafana-servicemonitor.yaml

现在,您可以使用以下命令访问 Grafana 界面:

minikube service grafana -n monitoring

测试完成后,您可以通过执行以下命令删除这个基于 Kubernetes 的测试环境:

minikube delete

这个设置为您提供了一个关于如何在 Kubernetes 上集成 Grafana 和 Prometheus 的快速概览。它与虚拟机测试环境没有太大区别,但这里展示的细节希望能避免您需要到处搜索如何实现的资料。

在提供的虚拟机测试环境中,您有机会尝试捆绑的仪表板。现在,您应该学习如何创建自己的仪表板,为此,您需要掌握一些概念。在本节中,我们将引导您完成创建仪表板的过程。

仪表板由多个组件组成。我们将在接下来的章节中介绍最重要的概念,包括面板、它们支持的可视化、如何模板化变量,以及如何更改显示数据的时间范围。

面板是仪表板可视化区域中的一个矩形插槽。以下截图展示了一个示例。它可以通过拖放调整其各个维度的大小和位置。你还可以将多个面板放置在一个行中,行只是这些面板的逻辑分组。行可以展开或折叠,以显示或隐藏其中的面板:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/23f6ead4-8460-45e9-87e2-69a122551cbc.png

图 10.7:新面板

一个面板,除了能够查询选定的数据源外,还提供多种可视化选项供选择。这些可视化选项可以以多种方式展示数据,如简单的单值面板、条形图、折线图、表格,甚至热力图。下图展示了可用的内置可视化选项:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/6f3d192a-23d5-4703-b697-111c60593a5c.png

图 10.8:内置可视化选项

在前面的截图中,我们可以看到几种面板类型。以下是四种最常用的面板类型:

  • Graph:这是 Grafana 的主面板。它提供了创建富有表现力的二维图形的工具,这些图形由一个或多个 PromQL 表达式支持。

  • Singlestat:这是一个多用途的单值显示器。因此,PromQL 查询必须返回一个只有一个样本的瞬时向量。

  • Gauge:使用阈值,这个选项表示当前值相对于定义的上下限位置。像 Singlestat 可视化一样,这个选项需要一个只有一个样本的单一瞬时向量。

  • Table:这是以表格格式显示 PromQL 表达式结果的面板,每个标签都在自己的列中,并显示相应的值和时间戳。

对于每种可用的可视化选项,都有大量的设置选项,允许对每个面板的外观进行极为详细的自定义。官方的 Grafana 文档详细解释了每个选项,因此我们在这里将重点介绍最相关的选项。

变量功能非常强大。它允许仪表板配置占位符,可以在表达式中使用,这些占位符可以通过静态或动态列表填充,通常以下拉菜单的形式呈现给仪表板用户。每当选定的值发生变化时,Grafana 将自动更新使用该特定变量的面板中的查询。在我们的示例仪表板中,我们使用此功能让用户选择要展示的节点实例。除了常用于查询之外,它们也可用于面板标题等其他地方。

此功能可以在 仪表板设置 中找到,通过点击右上角的齿轮图标进入,这个图标在任何仪表板内都可以找到。下图展示了 设置 菜单中的 node_exporter_basics 仪表板的 Variables 选项:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/e221b523-da4c-4a4a-b509-d62a7e5ee47f.png

图 10.9:仪表板变量

如你所见,我们使用了一个 PromQL 查询来动态获取$instance变量的可能值。

当视口不够大时,Grafana 的响应式设计会隐藏一些右上角的图标。

时间选择器功能在任何仪表板的右上角都有一个包含时钟图标的按钮。界面分为两个主要区域:快速范围,即预定义的时间范围(大多数是相对于当前时间的),或自定义范围,允许你指定用于所有仪表板面板的确切时间跨度。顾名思义,**每隔:**选项将使仪表板面板在指定的时间间隔内自动重新加载。将其与相对时间范围结合使用时,这对于查看新数据非常有用。

以下截图显示了一系列的快速范围:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/f60cdc9e-774a-4727-a1b8-1c51489f05c6.png

图 10.10:时间选择器

我们将通过引导你完成创建一个基础仪表板的过程来亲自体验 Grafana。你可以通过点击左侧的加号图标 | 仪表板来开始。这将打开一个新的空白仪表板,并且已经有一个新面板,准备进行编辑。因为我们想要一个动态仪表板,所以我们将创建一个新的变量,这个变量将扩展为我们 Prometheus 服务器中可用的 Node Exporter 实例的列表。

使用Shift + *?*的快捷键组合将显示所有可用快捷键的帮助提示。

为了实现我们的目标,我们必须点击右上角的齿轮图标,打开仪表板设置,然后选择变量。下图展示了在创建此类变量时可用的选项:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/96af6a7f-afad-4096-bc80-aaeae5c57b66.png

图 10.11:变量界面

上述截图中显示的预览值展示了基于虚拟机的测试环境中的 Node Exporter 目标。如果你跟随使用 Kubernetes 测试环境,你将看到不同的预览。

在这个示例中,我们正在创建一个名为instance的变量,使用查询类型,这意味着它将从查询数据源的结果中填充其值。我们将数据源指定为prometheus,即在配置时给定的标识符,并且我们希望只有在仪表板加载时才刷新该变量。

现在进入有趣的部分:因为我们有兴趣收集 Node Exporter 实例,我们在查询字段中使用了一个指标,确保能够返回我们所需要的实例,node_exporter_build_infolabel_values()函数实际上不是有效的 PromQL 语法,但它是 Grafana 中的 Prometheus 数据源插件提供的,用于在该字段中使用,以便支持这种表达式。

Regex 字段用于匹配我们想要用于填充变量的查询结果部分。在我们的示例中,我们希望获取实例标签的完整内容,因此我们在正则表达式捕获组 (.+) 中匹配所有内容。我们可以在屏幕底部的 值预览 部分看到匹配是否生效。点击 添加 并保存此仪表板为 example 后,我们现在可以看到包含 instance 变量值的下拉菜单:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/658cc9e1-5956-4698-ae88-29eec1a9a9c1.png

图 10.11:实例值

现在是创建我们第一个面板的时候了。点击右上角的图表图标,在新面板中点击 添加查询。以下截图展示了查询接口:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/d846f364-4fa0-41dd-a32b-63ffbb2e2518.png

图 10.12:查询接口

在这里,我们可以指定要在所需数据源上执行的 PromQL 查询(一个或多个,取决于可视化类型)。在我们的示例中,我们将创建一个按模式划分的 CPU 使用率图,并希望将查询模板化,使其使用我们之前创建的 instance 变量。请注意 $instance,在查询时它将被替换为 instance 下拉框中选择的值。完整表达式如下:

label_replace(avg by (mode, instance) (irate(node_cpu_seconds_total{instance="$instance", mode!="idle"}[5m])), "instance", "$1", "instance", "([^:]+):.+")

label_replace() 函数允许我们从实例值中移除端口,这将在 图例 字段中使用。此字段允许用其中设置的指标标签的值替换 {{ }} 模板标记。这样,在图表图例中将反映出在 查询 菜单之前的内容。以下截图中,我们可以看到应用于仪表板的多个视觉选项,我们将逐一介绍:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/6f5e33df-ee34-442e-bedb-cd1f0689f455.png

图 10.13:可视化接口

部分,我们选择启用哪个图表轴;右侧 Y 配置选项没有产生任何变化,因为该轴未被使用。在 左侧 Y 配置中,我们可以指定 单位。在我们的例子中,我们想要百分比;我们本可以简单地将表达式乘以 100,但不这么做却展示了 Grafana 的一个实用功能。如我们所知,值的范围是从 0 到 1;这种 单位 类型将会把 0-1 范围内的值自动转换为百分比(从 0 到 100)。我们还确保 Y-最小值 设置为 0,这样图表在视觉上更容易理解,因为如果没有此设置,图表的 y 轴会根据查询结果中的最小 Y 值进行适应。此外,为了便于本示例,我们希望 y 轴刻度显示三位小数,因此我们通过 小数位数 字段设置了这一点。在 X 轴 中,我们没有做任何更改,因为我们希望显示时间。

图例部分,我们控制图例的显示方式以及它在面板中的位置。在我们的案例中,我们希望它作为一个表格,放置在图表的右侧,并且我们希望它显示Y的平均值和当前值。

要完成我们的面板,我们需要进入常规菜单,如下图所示,在这里我们可以命名面板并添加描述。描述将在面板的左上角以小i图标形式显示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/3cad9e01-6146-46f0-845a-4570d0e31f8a.png

图 10.14:面板的常规菜单

要保存你的新仪表板,只需点击右上角的小软盘图标。现在,你已经从头创建了一个简单的仪表板!你可以继续添加面板和可视化内容,但主要概念基本相同。你可以在测试环境中探索提供的仪表板,查看更多如何使用不同可视化选项的例子。

创建仪表板时需要记住的一点是避免不必要的杂乱。通常可以看到仪表板上有几十个面板,显示着大量的数据。尽量保持信息的适量,这样例如故障排除时可以快速且无痛。焦点是这里的关键:如果仪表板内有不相关的面板,可能将它们分成单独的仪表板会是一个好主意。

Grafana 使得导出仪表板变得简单。要进行导出,只需打开你想导出的仪表板,点击右上角面板中的小带箭头的方框图标,靠近软盘图标。

将会打开以下表单:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/2b8e6f0d-846b-4dc5-8b5a-865903bf2d97.png

图 10.15:Grafana 仪表板导出

在这里,你会看到几个选项:

  • 外部共享导出:启用数据源名称的模板化,这对公开共享仪表板非常有帮助。为了在grafana.com网站上发布仪表板,这是强制要求的。

  • 查看 JSON:允许你查看仪表板的代码。

  • 保存到文件:将仪表板保存为 JSON 文件。

接下来,我们将看看如何从 Grafana 仪表板画廊下载仪表板,以及如何将自己的仪表板贡献到其中。

由于 Grafana 被广泛使用,并且拥有庞大的社区支持,显然有大量仪表板是由该社区创建的。Grafana 的开发团队提供了一项服务,注册用户可以将他们的仪表板发布到画廊中,任何人都可以下载并在自己的 Grafana 实例中安装它们。在接下来的部分,我们将概述这两项操作,不仅包括如何使用社区制作的仪表板,还包括如何发布自己的仪表板。

社区驱动和官方的仪表盘可以在 grafana.com/dashboards 上找到,正如预期的那样,有很多选择。由于我们对 Prometheus 特定的仪表盘感兴趣,因此在搜索站点时,应该将搜索结果限制为该数据源。通过应用额外的筛选器,我们继续缩小结果范围,正如你在以下截图中看到的那样:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/8813a1bf-1e75-45bc-a419-c33c7ab23bf7.png

10.16:Grafana.com 过滤后的仪表盘结果

然后我们可以选择感兴趣的仪表盘,接着会打开一个屏幕,如下图所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/45d1f4b5-42ff-4330-b110-7f9125e9a2a6.png

图 10.17:Grafana.com 选定的仪表盘信息

在这里,我们可以看到一些关于仪表盘的信息和它的截图。请注意右侧的 ID 9916;它是 Grafana 库中该特定仪表盘的唯一标识符。我们可以通过进入 Grafana 实例(如我们测试环境中的实例),点击左侧主菜单中的加号,选择子菜单中的导入,并将其粘贴到相应的文本框中,如下图所示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/2b84e849-cf97-4e71-a120-a79f7ea7300d.png

图 10.18:导入仪表盘界面

在粘贴 ID 后,会弹出一个新菜单,要求填写该仪表盘的名称、它应放置在哪个文件夹中以及应使用的数据源。如果与已有的仪表盘(例如同名仪表盘)发生冲突,系统会要求你先解决冲突,才能完成导入过程。

发布新创建的仪表盘非常简单。首先,确保你有一个 Grafana 网站的账户,可以通过 grafana.com/signup 上的注册表单进行注册。成功注册后,可以通过个人 | 我的仪表盘进入你的个人资料,或者使用 https://grafana.com/orgs/<user>/dashboards 链接,替换 <user> 为你的 Grafana 用户名。

我的仪表盘中,你现在可以点击上传仪表盘按钮。此操作将打开一个上传表单,要求上传仪表盘。请记住,只有导出时启用了导出以供外部分享选项的仪表盘才能被接受:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/bc4c7fb5-c9ce-43b5-b2a9-73f8ff9a1a99.png

图 10.19:仪表盘上传表单

完成!现在你将获得一个仪表盘的数字 ID,你可以开始使用它或与全球分享。如果你愿意,还可以更新发布的仪表盘,因为这不会改变生成的 ID。相反,它会创建该仪表盘的另一个版本,用户将始终下载最新的版本。

历史上,Prometheus 曾维护自己的工具来创建仪表盘,叫做 PromDash。随着时间的推移,由于 Grafana 增强了对 Prometheus 作为数据源的原生支持,社区开始倾向于使用 Grafana 作为主要的可视化解决方案,以至于 PromDash 被维护 Prometheus 的人弃用,转而支持 Grafana。

你可以在 github.com/prometheus-junkyard/promdash 找到 PromDash 的源代码。

尽管 Grafana 是大多数人推荐的可视化解决方案,Prometheus 也附带了一个名为控制台模板的内部仪表盘功能。这些控制台模板是用原始 HTML/CSS/JavaScript 编写的,并利用 Go 模板语言的强大功能生成由 Prometheus 服务器本身提供的仪表盘(称为控制台)。这使得它们既极其快速,又具有无限的可定制性。控制台模板既强大又复杂。我们将在下一节中通过简要概述如何使用和构建控制台模板来介绍这一功能。

当你解压 Prometheus 发布包时,除了服务器和 promtool 的二进制文件外,还可以找到一些现成的控制台模板。为了更清楚地说明,我们可以查看在测试环境中解压这些模板到系统路径中的内容,如下所示:

vagrant@prometheus:/usr/share/prometheus$ systemctl cat prometheus
...
ExecStart=/usr/bin/prometheus 
    --config.file=/etc/prometheus/prometheus.yml 
    --storage.tsdb.path=/var/lib/prometheus/data 
 --web.console.templates=/usr/share/prometheus/consoles 
 --web.console.libraries=/usr/share/prometheus/console_libraries
...

这两个目录需要正确配置才能使控制台正常工作。控制台库定义了辅助函数,这些函数随后在控制台模板中使用,以减少重复。我们将在下一节中仔细查看这些库,当我们构建自己的模板时。

目前,以下是随 Prometheus 一起提供的控制台模板:

vagrant@prometheus:~$ ls -lh /usr/share/prometheus/consoles 
total 36K
-rw-r--r-- 1 root root 623 Mar 10 16:28 index.html.example
-rw-r--r-- 1 root root 2.7K Mar 10 16:28 node-cpu.html
-rw-r--r-- 1 root root 3.5K Mar 10 16:28 node-disk.html
-rw-r--r-- 1 root root 1.5K Mar 10 16:28 node.html
-rw-r--r-- 1 root root 5.7K Mar 10 16:28 node-overview.html
-rw-r--r-- 1 root root 1.4K Mar 10 16:28 prometheus.html
-rw-r--r-- 1 root root 4.1K Mar 10 16:28 prometheus-overview.html

正如在 index.html.example 中所看到的,这些模板期望 Prometheus 和 Node Exporter 的抓取作业分别命名为 prometheusnode,因此它们可能无法直接在你的 Prometheus 配置中使用。

我们可以通过使用 http://192.168.42.10:9090/consoles/index.html.example 的 Web 界面 URL 访问它,并探索可用的控制台。以下截图展示了 Prometheus 实例的节点 CPU 控制台:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/ec4dbe95-b7b0-4822-8279-bae5419519d6.png

图 10.20:Prometheus 的节点 CPU

从零开始创建控制台模板有一个陡峭的学习曲线。与 Grafana 不同,控制台模板直接用 HTML 和 JavaScript 编写,并且混合了相当多的 Go 模板语言。这意味着控制台在技术上可以采用任何形式,但为了简化,我们将坚持使用内置控制台库提供的结构。

支撑示例控制台模板的库定义了控制台的框架。它们处理的内容包括构建 HTML 结构、包含必要的 CSS 和 JavaScript 以及围绕主控制台内容建模四个部分:顶部的导航栏、左侧的菜单、底部的控制台时间控制和右侧显示摘要统计的表格。让我们通过查看以下代码,看看如何利用它们来构建一个简单的控制台模板:

{{template "head" .}}

{{template "prom_content_head" .}}

head模板展开为定义包含 CSS 和 JavaScript、顶部导航栏以及菜单的 HTML;而prom_content_head模板则定义了时间控制,如以下代码所示:

<h1>Grafana</h1>

<h3>Requests by endpoint</h3>
<div id="queryGraph"></div>
<script>
new PromConsole.Graph({
  node: document.querySelector("#queryGraph"),
  expr: "sum(rate(http_request_total{job='grafana'}[5m])) by (handler)",
  name: '[[ handler ]]',
  yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
  yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
  yUnits: "/s",
  yTitle: "Requests"
})
</script>

本节定义了控制台本身。queryGraph元素用作占位符,图形 JavaScript 库将使用它来生成图表。而 JavaScript 代码片段则配置了图表所使用的选择器(node)、要绘制的表达式(expr)、图例中使用的内容(name)以及几个y轴配置,如以下代码所示:

{{template "prom_content_tail" .}}

{{template "tail"}}

最后两个模板关闭了第一个模板所打开的部分。它们是必需的,以确保生成的 HTML 结构良好。

结果控制台可以在本章的测试环境中访问,可以通过http://192.168.42.10:9090/consoles/grafana.html查看。以下是它应该呈现的截图:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/7e993420-24b6-4226-9b65-5cad8c8e01fe.png

图 10.21:Grafana 每秒请求示例控制台

请注意,左侧的菜单中没有链接到我们新创建的控制台模板。这是因为包含的menu.lib仅支持随 Prometheus 一起提供的示例控制台模板。在部署实际的自定义控制台模板时,您需要用自己的库替换此库。这将使您能够在顶部的导航栏中添加指向其他内部系统的链接,并列出哪些控制台应出现在左侧的导航菜单中。通过利用您可以在模板中执行 PromQL 查询的事实,您应该能够找出该 Prometheus 实例抓取了哪些作业,并生成类似名称控制台的链接。

在本章中,我们探讨了 Prometheus 的标准可视化工具:Grafana。我们学习了如何配置不仅是数据源,还有仪表盘。在了解仪表盘的构建模块后,我们从零开始创建了一个简单的仪表盘,逐步学习了其中的各个环节。我们还学习了如何利用蓬勃发展的社区构建的仪表盘库。回馈社区始终很重要,因此我们经历了导出和发布仪表盘的过程。最后,我们了解了 Prometheus 默认的可视化——控制台——尽管它们有陡峭的学习曲线,但非常强大。

在下一章中,我们将探讨 Alertmanager,如何充分利用其功能,并与 Prometheus 集成。

  1. 如何在 Grafana 中自动配置数据源?

  2. 从 Grafana 图库导入仪表盘的步骤是什么?

  3. Grafana 仪表盘变量是如何工作的?

  4. 仪表盘的构建块是什么?

  5. 当你更新发布到 grafana.com 的仪表盘时,是否会更改其 ID?

  6. 在 Prometheus 中,控制台是什么?

  7. 为什么要使用 Prometheus 控制台模板?

  • Grafana 探索: docs.grafana.org/features/explore/

  • Grafana 模板化: docs.grafana.org/reference/templating/

  • Grafana 时间范围: docs.grafana.org/reference/timerange/

  • 控制台模板官方文档: prometheus.io/docs/visualization/consoles/

  • 控制台模板最佳实践: prometheus.io/docs/practices/consoles/

警报是任何监控堆栈中的关键组件。在 Prometheus 生态系统中,警报及其后续通知是解耦的。Alertmanager 是处理这些警报的组件。在本章中,我们将专注于使用 Alertmanager 将警报转换为有用的通知。从可靠性到自定义,我们将深入探讨 Alertmanager 服务的内部工作原理,提供所需的知识,以配置、排除故障并自定义所有可用选项。我们将确保警报路由、静默和抑制等概念清晰,以便你能够决定如何在自己的堆栈中实现它们。

由于 Alertmanager 是一个关键组件,我们还将探讨高可用性,并解释 Prometheus 与 Alertmanager 之间的关系。我们将自定义通知,并学习如何构建和使用可重用的模板,以确保通知在到达目的地时是适当的,并且传递准确的信息。本章的最后,我们将学习如何监控监控系统,更重要的是,学习当系统部分或完全宕机时如何收到警报。

在本章中,我们将探讨以下主题:

  • 本章的测试环境。

  • Alertmanager 基础知识。

  • Alertmanager 配置。

  • 常见的 Alertmanager 通知集成。

  • 自定义你的警报通知。

  • 谁来监视“监视者”?

为了使用 Alertmanager,我们将新增三个实例来模拟一个高可用的设置。这种方法不仅能让我们暴露所需的配置,还能验证一切如何协同工作。

我们将使用的设置类似于以下图示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/d40bc98a-eaf8-4512-b628-306b0872f0f0.png

图 11.1:测试环境

让我们从部署 Alertmanager 测试环境开始:

  1. 要启动一个新的测试环境,请进入本章相对于仓库根目录的路径:
cd ./chapter11/
  1. 确保没有其他测试环境在运行,然后启动本章的环境:
vagrant global-status
vagrant up
  1. 你可以通过以下命令验证测试环境的成功部署:
vagrant status

你将收到以下输出:

Current machine states:

prometheus running (virtualbox)
alertmanager01 running (virtualbox)
alertmanager02 running (virtualbox)
alertmanager03 running (virtualbox)

This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.

当部署任务完成后,你将能够使用你喜欢的支持 JavaScript 的网页浏览器,在主机上验证以下端点:

服务 端点 Prometheus http://192.168.42.10:9090 Alertmanager01 http://192.168.42.11:9093 Alertmanager02 http://192.168.42.12:9093 Alertmanager03 http://192.168.42.13:9093

你应该能够通过以下命令之一访问所需的实例:

实例 命令 Prometheus vagrant ssh prometheus Alertmanager01 vagrant ssh alertmanager01 Alertmanager02 vagrant ssh alertmanager02 Alertmanager03 vagrant ssh alertmanager03

测试完成后,只需确保你位于 ./chapter11/ 目录下,并执行以下命令:

vagrant destroy -f

不用太担心——如果需要,你可以轻松地再次启动环境。

我们在第九章中讲解了 Prometheus 中告警规则的工作原理,定义告警和记录规则,但仅仅这些规则本身并不十分有用。如我们之前提到的,Prometheus 通过 Webhook 风格的 HTTP 接口将通知处理和路由委托给外部系统。这就是 Alertmanager 的作用所在。

Alertmanager 负责接收由 Prometheus 告警规则生成的告警,并将它们转换为通知。后者可以采取任何形式,例如电子邮件、聊天消息、页面通知,甚至是 Webhook,这些 Webhook 会触发自定义操作,如将告警记录到数据存储中或创建/更新工单。Alertmanager 还是官方栈中唯一一个将其状态分发到多个实例的组件,这样它就能跟踪哪些告警已发送,哪些被静默。

以下图表受到 Alertmanager 架构图的启发,提供了一个概述,展示了告警经过的各个步骤,直到它成功地作为通知发送出去:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/18b36137-36bb-4725-9259-1abbdfc5528b.png

图 11.2:通知管道概览

前面的图表包含了很多内容,因此我们将在接下来的几节中逐一讲解这些步骤。了解告警管道的工作原理将帮助你理解各种配置选项,如何排查丢失的告警,并全面利用 Alertmanager 提供的所有功能。

每当一个告警规则被触发时,Prometheus 会以 JSON 负载的形式将告警发送到 Alertmanager API,并且它会在该规则的每个评估间隔或每分钟(通过 --rules.alert.resend-delay 标志可配置)内继续发送更新,以较长者为准。当告警被 Alertmanager 接收时,它们会经过调度步骤,在此步骤中,告警会根据一个或多个告警标签进行分组,例如 alertname。我们将在本章后面的Alertmanager 配置部分进一步讨论。这样做可以将告警分类,从而减少多个告警合并为一个通知发送的次数,这些合并后的告警会触发通知管道:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/f2fb4c43-294d-41ac-85cf-6457f1cef663.png

图 11.3:Alertmanager 界面通过 alertname 分组告警

当运行多个具有相同配置的 Prometheus 实例时(追求高可用性/冗余性时的常见设置),相同条件的警报规则不一定会在完全相同的时间触发。Alertmanager 通过具有可配置时间间隔来解决这种情况。它会等待其他任何操作,以便将相似的警报分组在一起,从而避免为单一类型的问题发送多个通知。

这种分组是在用户指定的所有标准并行进行的。然后,每个组将触发通知管道,接下来我们将详细介绍它。

一个很好的例子可以帮助我们理解什么是警报抑制,就像想象一个服务器机架,如果顶部交换机失效会发生什么。在这种情况下,该机架中的所有服务器和服务都将开始触发警报,因为它们突然无法访问。为了避免这个问题,我们可以使用顶部交换机的警报,如果触发了,将阻止该机架中所有其他警报的通知发送出去。这有助于操作员更专注于真正的问题,避免洪水般的无法采取行动的警报。

简言之,抑制允许您映射警报之间的依赖关系,因此阻止依赖警报的通知继续在通知管道中传递。这是在 Alertmanager 配置文件中设置的,这意味着如果更改,则需要重新加载服务。

如果在抑制阶段未匹配警报,则会进入静音步骤。

在监控/警报系统中,静音是一个常见概念;它可以在有时间限制的情况下避免警报通知的发送。通常用于在维护窗口期间禁用通知,或在事故期间临时抑制较低重要性的警报。Alertmanager 通过利用警报通常具有一个或多个不同标签的事实来加强这一概念:来自原始警报规则表达式、警报名称、警报的标签字段、alert_relabel_configs,以及 Prometheus external_labels。这意味着可以使用这些标签中的任何一个(或它们的组合),通过直接匹配或正则表达式匹配来临时禁用通知:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/34d02fda-3fe3-4365-a54a-c946cecab22e.png

图 11.4:创建匹配 alertname=NodeExporterDown 的静音

在使用正则表达式匹配时要小心,因为可能会意外地静音超出预期的内容。Alertmanager Web UI 可以帮助预防此类问题,因为在创建新的静音时,它会显示将被抑制的触发警报的预览。

静默是在运行时定义的。可以通过 Alertmanager Web 界面、amtool(稍后将介绍的 Alertmanager 命令行界面)或直接通过 API 设置静默。可以在警报触发时设置静默,比如在事故处理中,或者提前设置以避免计划中的维护干扰值班人员。它不应成为一个永久的解决方案,仅是一个临时措施;因此,创建静默时需要设置到期日期,并且 Web UI 仅识别最长为“天”的持续时间。

由于静默步骤位于抑制之后,如果您对触发抑制规则的警报进行静默,它将继续抑制其他警报。

如果警报未与任何静默匹配,它将进入通知管道的下一步骤,即路由阶段。

当警报批次到达此阶段时,Alertmanager 需要决定将其发送到哪里。由于最常见的使用场景是不同的人对不同的警报感兴趣、对不同严重性的警报使用不同的通知方式,或者这两者的组合,因此此步骤通过路由树实现了这一点。它由多个路由(如果有的话)组成,每个路由指定了一个或多个标签的匹配条件和接收器,以及一个根节点,根节点定义了一个“捕获所有”接收器,以防没有子路由与传递的警报组匹配。子路由可以有自己的路由,从而形成多层树结构。匹配按声明路由的顺序进行,当一个路由匹配时,会进入已定义的子路由,而最深的匹配将决定使用哪个接收器。我们在实际应用中使用 Alertmanager 配置时,这一点会更加清晰。

接收器和通知器的工作方式类似于通讯录联系人。接收器是命名的联系人,可以有一个或多个通知器,通知器类似于联系信息。Alertmanager 支持许多不同的通知器,通常属于以下类别之一:电子邮件、聊天(Slack、微信、HipChat)和页面(PagerDuty、Opsgenie、VictorOps、Pushover)。此外,它还支持 Webhook 通知器,这是一个通用的集成点,可用于支持所有未内置于 Alertmanager 的其他通知系统。

在此路由阶段将警报批次与接收器连接后,Alertmanager 会为该接收器中指定的每个通知器运行通知任务。这个任务负责去重、发送和重试通知。对于去重,它首先检查通知日志(稍后将在本章中讨论),确保该通知尚未发送;如果已经存在,则不执行任何操作。接下来,它会尝试发送通知,如果成功,通知将被记录在通知日志中。如果通知发送失败(例如 API 错误、连接超时等),该任务会再次尝试。

现在我们已经了解了通知管道的基本知识,接下来我们来看看当有多个 Alertmanager 实例时,会发生什么情况,以及如何在它们之间共享警报状态。

通知管道的概述没有涉及 Alertmanager 的高可用性组件。高可用性是通过依赖 gossip(基于 HashiCorp 的 memberlist,github.com/hashicorp/memberlist)来实现的,而不是使用基于共识的协议;这意味着选择集群中实例的奇数数量没有实际意义。通过 gossip,集群在所有 Alertmanager 实例之间共享通知日志nflog),从而让每个实例都能了解到集群在通知方面的整体状态。如果发生网络分区,通知将会从每个分区的一方发送,因为从逻辑上讲,接收更多通知总比完全无法通知要好。

如我们所知,抑制是在配置文件级别设置的,因此它应该在所有 Alertmanager 实例中保持一致。然而,静默也需要在集群中进行 gossip,因为它们是在单个 Alertmanager 实例的运行时设置的。这是验证集群是否按预期工作的一个好方法——确认配置的静默是否在所有实例中都能显示。

Alertmanager /#/status 页面显示了 gossip 集群的状态,以及已知的对等节点。你可以在我们的测试环境中查看这个端点,例如打开 http://192.168.42.11:9093/#/status

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/fc9b2956-1d3e-4687-ae5a-a39c2d29aaa8.png

图 11.5:Alertmanager 集群状态

在 Alertmanager 中,集群工作方式如下:每个 Prometheus 实例将告警发送到它们所知道的所有 Alertmanager 实例。这些实例假设它们都在同一个 HA 集群中,会自己排序,并且排在第一位的实例将处理告警通知。该实例通过传播协议分发通知日志,通知日志会列出已成功发送的通知。其余的 Alertmanager 实例将根据它们在排序中的位置,增加等待通知日志更新的延迟。通知日志中的告警不会被这些实例重新发送——如果通知日志没有说明某个通知在传播延迟结束时已被处理,那么第二个 Alertmanager 实例将处理它,以此类推:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/484c081e-4d9e-488e-9a22-7a584ff688b8.png

图 11.6:Alertmanager 集群概览

Prometheus 实例直接与所有 Alertmanager 实例通信,因为集群成员将负责彼此之间的去重。这意味着不应在 Prometheus 和 Alertmanager 之间放置负载均衡器。

Alertmanager 集群假设每个实例都使用相同的配置文件运行。然而,如果没有做到这一点,只会影响其去重通知的能力。

在第九章,定义告警和记录规则中,我们讨论了 Prometheus 如何生成并推送告警。在已经明确区分告警和通知之后,现在是时候使用 Alertmanager 来处理 Prometheus 发送的告警并将其转化为通知了。

接下来,我们将介绍在 Prometheus 中所需的配置,以及在 Alertmanager 中可用的配置选项,以便我们能够从监控栈中发送通知。

在 Prometheus 中需要做几个配置,以便我们能够开始使用 Alertmanager。首先需要配置外部标签,这些标签会在与外部系统(包括但不限于 Alertmanager)通信时,添加到时间序列数据中(如果数据中没有这些标签)。这些标签用于唯一标识指标的来源,比如 regiondatacenterenvironment 等。一般来说,如果你有将相同的标签名/值添加到每个抓取和记录规则中的冲动,那么该标签更适合做为外部标签,因为它不会在本地的 Prometheus 实例中引入新的维度,但在更高级的系统(例如联合或长期指标存储)中可能会非常有用,正如我们将在接下来的章节中看到的那样。正如我们将在以下示例中看到的,外部标签是在 Prometheus 主配置文件的顶级 global 键内进行配置的。

第二步是配置 Prometheus,使其能够将警报发送到 Alertmanager。正如我们之前在Alertmanager 集群一节中讨论的那样,Prometheus 实例是必需的,以便你可以了解并将警报分别发送到所有 Alertmanager 集群成员。这个配置在 Prometheus 配置文件中,位于一个名为 alerting 的顶级部分中。这个配置的示例可以在我们的测试环境中找到,具体如下:

vagrant@prometheus:~$ cat /etc/prometheus/prometheus.yml
global:
 external_labels:
 dc: dc1
alerting:
 alertmanagers:
 - static_configs:
 - targets:
 - alertmanager01:9093
 - alertmanager02:9093
 - alertmanager03:9093
...

alerting 部分,我们还可以使用 alert_relabel_configs,它的配置语法与 relabel_configsmetric_relabel_configs 相同,正如在第五章《运行 Prometheus 服务器》中所解释的那样,但在这种情况下,它仅适用于传出的警报。使用重标记在这里是有用的,可以防止某些警报完全到达 Alertmanager,修改或丢弃标签以便于分组,甚至可以添加某些警报特有的标签,这些标签在 external_labels 中可能没有意义。由于 alert_relabel_configs 在我们发送警报之前运行,因此外部标签会出现在这些警报中,并因此可以进行操作。以下是一个示例,防止具有名为 environment 且匹配值为 development 的标签的警报被推送到 Alertmanager:

  alert_relabel_configs:
  - source_labels: [environment]
    regex: development
    action: drop

虽然前面的示例说明了如何丢弃警报,但不应将其作为永久解决方案,因为更好的解决方案可能是根本不创建这些警报。

接下来,我们将介绍 Alertmanager 配置文件及其主要部分,并指出一些有用的信息,帮助你入门。

Alertmanager 通过一个单一的配置文件进行配置,并且可以像 Prometheus 一样在运行时重新加载,而无需重启:可以通过向进程发送 SIGHUP 或发送 HTTP POST 请求到 /-/reload 端点来实现。与 Prometheus 一样,格式错误的配置将不会被应用——系统会记录错误信息,并且在其 /metrics 端点中找到的 alertmanager_config_last_reload_successful 指标将被设置为 0

配置文件分为五个顶级部分:globalrouteinhibit_rulesreceiverstemplates。在接下来的章节中,我们将逐一探讨每个部分。

全局部分收集了在文件的其他部分有效的所有配置选项,并作为这些选项的默认设置。由于这些参数可以在其他部分被覆盖,使用它们是保持配置文件尽可能简洁、避免重复的好方法。在这一部分的所有可用参数中,大多数与凭证和令牌作为通知器相关。这里有一个值得注意的参数,叫做resolve_timeout。当 Prometheus 触发警报规则时,它将在每个评估间隔发送产生的警报,并更新 JSON 负载中的EndTime字段。当这些警报被解决时,Alertmanager 会通过更新EndTime来通知这些解决。如果由于某些原因,警报停止定期更新(例如,发送该警报的 Prometheus 实例崩溃并且仍在恢复过程中),Alertmanager 将使用最后接收到的EndTime来解决该警报。resolve_timeout配置用于解决由非 Prometheus 系统创建的警报,这些系统不使用EndTime。需要明确的是,这是一个你不应该修改的设置,因为它与 Prometheus-Alertmanager 警报协议相关;在此解释只是为了完整性。

作为示例,我们测试环境中 Alertmanager 配置的全局部分如下所示:

global:
  smtp_smarthost: 'mail.example.com:25'
  smtp_from: 'example@example.com'
...

这个示例配置为每个使用电子邮件(SMTP)通知器的接收器设置默认的电子邮件smarthostfrom地址。

这无疑是 Alertmanager 最重要的配置部分。在这一部分,我们将定义如何根据标签(group_by)对警报进行分组,等待多长时间才能发送更多通知(group_interval),以及如何重复它们(repeat_interval),但最重要的是,为每个警报批次触发哪些接收器(receiver)。由于每个路由都可以有自己的子路由,这就形成了一棵路由树。顶级路由不能有任何匹配规则,因为它像一个兜底规则,处理所有不匹配任何子路由的警报。除了continue,路由上的每个设置都会以级联方式传递给其子路由。尽管默认行为是在找到最具体的匹配项时停止搜索接收器,但可以将continue设置为true,使匹配过程继续,从而允许触发多个接收器。

你可以在我们的测试环境中找到以下示例路由配置:

route:
  receiver: operations
  group_by: ['alertname', 'job']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

  routes:
  - match_re:
      job: (checkoutService|paymentService)
    receiver: yellow-squad-email
    routes:
    - match:
        severity: pager
      receiver: yellow-squad-pager
...

上述示例中的主路由执行以下操作:

  • 定义operations接收器为默认路由,当没有其他子路由匹配时使用

  • 根据alertnamejob对传入的警报进行分组

  • 在发送第一次通知之前,等待 30 秒钟以便更多警报到达,从而减少相同问题的通知次数

  • 在将新警报添加到批次后,等待五分钟再发送额外的通知

  • 每四小时重新发送一次包含当前触发告警的告警批次的通知

此外,还为 job 标签匹配 checkoutServicepaymentService 的告警设置了一个子路由,指定其接收者为 yellow-squad-email。该子路由反过来定义了一个自己的子路由,如果严重性标签匹配 pager,则应使用 yellow-squad-pager receiver

官方 Prometheus 网站提供了一个路由树编辑器和可视化工具,链接:prometheus.io/webtools/alerting/routing-tree-editor/

group_by 子句也可以采用唯一值 ...,这将指示 Alertmanager 不对传入的告警进行任何分组。这种情况很少使用,因为分组的目的是减少通知的数量,从而提高信噪比。此功能的一个可能用途是将每个告警原样发送到另一个系统,供该系统处理。

在本节中,我们将添加规则来抑制告警。其工作原理是通过在源和目标告警上使用匹配器来匹配源告警并静音目标告警。唯一的要求是目标和源的标签在标签名称和值上都要匹配,例如:

inhibit_rules:
  - source_match:
      job: 'icmp'
    target_match_re:
      alertname: (AlertmanagerDown|NodeExporterDown)
    equal: ['base_instance']

在这个示例中,我们可以读取到以下内容:如果有一个 job 标签设置为 icmp 的告警,且在所有匹配的告警中,base_instance 相同,则会静音所有 alertname 匹配 AlertmanagerDownNodeExporterDown 的告警。换句话说,如果运行 Alertmanager 和 Node Exporter 的实例宕机,则跳过关于这些服务的告警,只发送关于实例本身的告警,让运维人员专注于真正的问题。

如果等式子句中的任何标签在源和目标告警中都不存在,则视为匹配,从而启用抑制。

如果一个告警同时匹配抑制规则定义中的源和目标,该告警将不会被抑制——这是为了防止告警抑制自身。

我们可以在以下截图中看到,当发生此情况时,Alertmanager 的界面:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/2e9ca144-f7bb-42bd-9887-47f351d211e7.png

图 11.7:Alertmanager 界面,仅显示触发的通知

在下面的截图中,我们可以通过选择右上角的抑制选项,看到所有未出现在前一张截图中的被抑制告警:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/f3ac8d8a-c728-403e-a0e7-d75cd3448551.png

图 11.8:Alertmanager 界面,显示包括被抑制告警在内的通知

Alertmanager 界面让你可以俯瞰所有警报,不仅是那些处于激活状态的警报,还有被抑制的警报。默认情况下不会显示被抑制的警报,以减少视觉杂乱;但是,正如我们在前面的截图中所看到的,你可以通过选择抑制复选框来轻松启用显示这些警报,位于筛选器/分组框的右上角。

当路由被匹配时,它将调用一个接收器。接收器包含通知器,我们将在后续章节中更深入地探讨这一点。基本上,接收器是可用集成的命名配置。

在我们的测试环境中,我们可以找到一个使用 Webhook 通知器的接收器示例,具体如下:

route:
  receiver: operations
...
receivers:
- name: 'operations'
  webhook_configs:
  - url: 'http://127.0.0.1:5001'
...

顶级路由,也称为捕获所有或回退路由,将在其他子路由没有匹配到来警报时触发名为operations的接收器。operations接收器是使用单一通知器配置的,该通知器是 Webhook 通知器。这意味着发送到此接收器的警报会被发送到url配置键中指定的 URL。Webhook 通知器将在本章稍后进一步分析。

本节是定义指向可用通知器的多个自定义通知模板的路径列表的地方。与 Prometheus 中的其他文件路径配置类似,每个路径定义允许在最后一个组件上使用 glob 模式,并可以如下定义:

templates:
  - /etc/alertmanager/templates/*.tmpl

我们将在自定义警报通知部分使用这一部分来定义我们的自定义模板。

类似于promtoolamtool是一个易于使用的命令行工具,由 Alertmanager HTTP API 提供支持。除了用于验证 Alertmanager 配置文件的正确性外,它还允许你查询服务器中当前触发的警报,并执行诸如静默警报或创建新警报等操作。amtool的子命令分为四组——alertsilencecheck-configconfig——我们将使用测试环境对每一组进行概述。

为了跟随本节中的示例,确保你连接到其中一个 Alertmanager 实例。由于它们是集群化的,任何一个实例都可以,举例如下:

vagrant ssh alertmanager02

登录后,你可以作为默认用户运行amtool,因为与 Alertmanager API 交互不需要管理员权限。此外,你甚至可以使用amtool连接到任何 Alertmanager 实例,而不仅仅是本地实例,因为与 HTTP API 交互的大多数命令要求你指定实例的 URL。

这个子命令允许你查询 Alertmanager 集群中当前触发的警报,可以通过如下方式实现:

vagrant@alertmanager02:~$ amtool alert --alertmanager.url http://alertmanager02:9093
Alertname Starts At Summary 
deadmanswitch 2019-03-31 14:49:45 UTC 
InstanceDown 2019-03-31 15:48:30 UTC

alert子命令的默认操作是query。与之前的示例等效的命令是amtool alert query --alertmanager.url http://alertmanager02:9093

此子命令的另一个功能是按需创建警报。这对于测试目的非常有用。例如,让我们创建一个名为 ExampleAlert 的新警报,标签为 example="amtool"

vagrant@alertmanager02:~$ amtool alert add alertname="ExampleAlert" example="amtool" --alertmanager.url http://alertmanager02:9093

add 操作期望每个命令参数都有一个标签名称/值对,正如我们在前面的代码中所看到的那样。此操作还会考虑第一个参数,因为警报名称没有标签名称。如果我们省略 alertname 标签,警报也可以创建,但这可能会在 amtool 和 Alertmanager Web UI 中引发一些奇怪的行为,因此对此需要保持谨慎。

我们可以稍等一会儿(在测试环境中,group_wait 定义为 30 秒),然后重新查询当前的警报,来检查它是否已正确添加:

vagrant@alertmanager02:~$ amtool alert --alertmanager.url http://alertmanager02:9093
Alertname Starts At Summary 
deadmanswitch 2019-03-31 14:49:45 UTC 
InstanceDown 2019-03-31 15:48:30 UTC 
ExampleAlert 2019-03-31 15:55:00 UTC 

此操作还允许你指定其他警报字段,例如结束时间、生成器 URL 和注解。你可以通过使用 help 标志来查看 add 操作的命令行界面(参数和选项):

vagrant@alertmanager01:~$ amtool alert add --help

请记住,这个新创建的警报将在五分钟的非活动后被认为已解决(resolve_timeout 的默认值),因此,如果你需要更多时间进行测试,请确保通过运行 add 操作添加此警报的新实例,以保持警报继续存在。

接下来,我们将使用这个新警报作为静默的目标。

使用此子命令,我们可以管理静默。首先,我们可以尝试使用以下指令查询集群中可用的静默:

vagrant@alertmanager02:~$ amtool silence --alertmanager.url http://alertmanager02:9093
ID Matchers Ends At Created By Comment

silence 子命令的默认操作是 query。与前面示例等效的命令是 amtool silence query --alertmanager.url http://alertmanager02:9093

如我们所见,目前没有任何静默被执行。让我们通过匹配其标签 example="amtool" 来为之前生成的警报创建一个新的静默,并再次检查静默:

vagrant@alertmanager02:~$ amtool silence add 'example="amtool"' --comment "ups" --alertmanager.url http://alertmanager02:9093
1afa55af-306a-408e-b85c-95b1af0d7169

vagrant@alertmanager02:~$ amtool silence --alertmanager.url http://alertmanager02:9093
ID Matchers Ends At Created By Comment 
1afa55af-306a-408e-b85c-95b1af0d7169 example=amtool 2019-03-31 16:58:08 UTC vagrant ups

我们现在可以看到新静默已被添加。为了验证它是否已生效,我们可以使用 alert 子命令并检查 ExampleAlert 是否已从当前警报列表中消失:

vagrant@alertmanager02:~$ amtool alert --alertmanager.url http://alertmanager02:9093
Alertname Starts At Summary 
deadmanswitch 2019-03-31 14:49:45 UTC 
InstanceDown 2019-03-31 15:48:30 UTC

让我们通过使用 expire 操作来移除刚刚创建的静默。为此,我们需要静默标识符,可以在列出当前静默时,在 ID 列中看到它:

vagrant@alertmanager02:~$ amtool silence expire 1afa55af-306a-408e-b85c-95b1af0d7169 --alertmanager.url http://alertmanager02:9093

vagrant@alertmanager02:~$ amtool silence --alertmanager.url http://alertmanager02:9093
ID Matchers Ends At Created By Comment

如果我们再次查询当前的警报列表,我们将看到我们的 ExampleAlert 又出现了。

这些是静默功能最常见的使用场景。还有其他可用的操作,比如批量导入静默(在迁移到新集群时很有用)或在需要时更新现有的静默。像往常一样,--help 标志将为你提供如何使用这些操作的指导。

这可能是 amtool 最有用的功能:验证我们 Alertmanager 配置文件及引用的模板文件的语法和模式。你可以通过以下示例来测试 check-config 子命令:

vagrant@alertmanager02:~$ amtool check-config /etc/alertmanager/alertmanager.yml 
Checking '/etc/alertmanager/alertmanager.yml' SUCCESS
Found:
 - global config
 - route
 - 1 inhibit rules
 - 8 receivers
 - 1 templates
  SUCCESS

这种类型的验证非常容易自动化,且应该在任何配置更改后进行,但在重新加载 Alertmanager 实例之前进行,以防止大多数类型的配置问题。

使用config子命令,我们可以查询运行中的 Alertmanager 实例的内部配置,包括所有可配置字段,甚至是配置文件中未明确列出的字段。你可以通过执行以下命令来检查:

vagrant@alertmanager02:~$ amtool config --alertmanager.url http://alertmanager02:9093
global:
  resolve_timeout: 5m
  http_config: {}
  smtp_from: example@example.com
  smtp_hello: localhost
  smtp_smarthost: example.com:25
  smtp_require_tls: true
  slack_api_url: <secret>
...

配置文件中未指定的配置字段将显示其默认值,而涉及秘密信息的字段(如密码和令牌)将被自动屏蔽。

显示了config子命令的默认操作。与前一个示例等效的命令是amtool config show --alertmanager.url http://alertmanager02:9093

下一个子命令操作routes会生成配置的路由树的文本可视化。这个命令可以在运行中的 Alertmanager 实例或本地配置文件上执行。其语法和输出如下:

vagrant@alertmanager02:~$ amtool config routes --alertmanager.url http://alertmanager02:9093
Routing tree:
.
└── default-route receiver: operations
    ├── {job=~"^(?:^(?:(checkoutService|paymentService))$)$"} receiver: yellow-squad-email
    │ └── {severity="pager"} receiver: yellow-squad-pager
    ├── {job="firewall"} receiver: purple-squad-email
    │ ├── {severity="slack"} receiver: purple-squad-slack
    │ └── {severity="pager"} receiver: purple-squad-pager
    └── {alertname=~"^(?:^(?:(AlertmanagerDown|NodeExporterDown))$)$"} receiver: violet-squad-slack
        └── {severity="pager"} receiver: violet-squad-pager

你甚至可以通过为routes test操作提供标签来验证路由树,并检查哪条路由会被触发。在以下示例中,我们可以看到当警报带有job="checkoutService"标签时,触发的接收器是否确实是yellow-squad-email

vagrant@alertmanager02:~$ amtool config routes test 'job="checkoutService"' --config.file /etc/alertmanager/alertmanager.yml 
yellow-squad-email

拥有这个命令行工具可以帮助你简化复杂路由规则的开发,并且无需本地运行 Alertmanager 实例即可验证生成的配置。

在第五章,运行 Prometheus 服务器,我们有机会尝试了 Prometheus Operator。由于 Alertmanager 是 Prometheus 栈的一个核心组件,因此 Operator 也能够管理其实例。除了负责管理 Alertmanager 集群外,Operator 还负责管理记录和告警规则的配置。

为了提供一些如何使用 Operator 管理 Alertmanager 集群的见解,我们将提供一个完整的示例供你尝试。我们 Kubernetes 测试环境中使 Alertmanager 和 Prometheus 运行的 Kubernetes 清单可以在相对于仓库根路径的以下路径中找到:

cd ./chapter11/provision/kubernetes/

以下步骤将确保已经配置好一个新的 Kubernetes 环境,并安装所有必需的软件,以便我们接下来可以专注于 Alertmanager 组件:

  1. 验证没有其他 Kubernetes 环境在运行:
minikube status
minikube delete
  1. 启动一个空的 Kubernetes 环境:
minikube start 
  --cpus=2 
  --memory=3072 
  --kubernetes-version="v1.14.0" 
  --vm-driver=virtualbox
  1. 添加 Prometheus Operator 组件,并按照其部署步骤进行操作:
kubectl apply -f ./bootstrap/

kubectl rollout status deployment/prometheus-operator -n monitoring
  1. 添加新的 Prometheus 集群,确保其成功:
kubectl apply -f ./prometheus/

kubectl rollout status statefulset/prometheus-k8s -n monitoring
  1. 将所有目标添加到 Prometheus 并列出它们:
kubectl apply -f ./services/

kubectl get servicemonitors --all-namespaces

在 Kubernetes 测试环境启动后,我们可以继续进行 Alertmanager 特定的配置。与基于虚拟机的测试环境类似,我们不仅需要配置 Alertmanager 本身,还需要为 Prometheus 提供报警规则。

对于 Alertmanager 配置,由于我们可能需要添加一些敏感信息,比如电子邮件凭证或呼叫令牌,我们将使用 Kubernetes secret。这也意味着应该有一个 ServiceAccount 来访问该 secret。

我们可以通过应用以下清单来创建 ServiceAccount:

kubectl apply -f ./alertmanager/alertmanager-serviceaccount.yaml

由于我们使用了 secret,Alertmanager 配置需要编码为 base64。提供了一个最小配置,可以通过执行以下命令进行部署:

kubectl apply -f ./alertmanager/alertmanager-configuration.yaml

作为参考,编码在 secret 中的最小配置如下:

global:

route:
  receiver: "null"
  group_by:
    - job
  group_interval: 3m
  repeat_interval: 3h
  routes:
    - match:
        alertname: deadmanswitch
      receiver: "null"

receivers:
  - name: "null"

现在,我们可以继续部署,并让 Operator 为我们处理繁重的工作。它会抽象出 StatefulSet 的创建,并让集群正常运行。为此,我们需要应用以下清单:

kubectl apply -f ./alertmanager/alertmanager-deploy.yaml

上述清单中的重要部分可以在以下代码片段中看到:

...
kind: Alertmanager
...
spec:
  baseImage: quay.io/prometheus/alertmanager
...
  replicas: 3
...

我们可以通过执行以下指令来跟踪部署状态:

kubectl rollout status statefulset/alertmanager-k8s -n monitoring

为了确保 Prometheus 实例能够从新创建的 Alertmanager 收集指标,我们将添加一个新的 Service 和 ServiceMonitor。为此,我们需要应用以下清单:

kubectl apply -f ./alertmanager/alertmanager-service.yaml

kubectl apply -f ./alertmanager/alertmanager-servicemonitor.yaml

现在是时候添加报警规则了。为此,你只需要应用以下清单:

kubectl apply -f ./alertmanager/alerting-rules.yaml

如果你打开之前的清单,你将看到几个规则。以下代码片段展示了第一个规则:

...
kind: PrometheusRule
...
spec:
  groups:
  - name: exporter-down
    rules:
    - alert: AlertmanagerDown
      annotations:
        description: Alertmanager is not being scraped.
        troubleshooting: https://github.com/kubernetes-monitoring/kubernetes-mixin/blob/master/runbook.md
      expr: |
        absent(up{job="alertmanager-service",namespace="monitoring"} == 1)
      for: 5m
      labels:
        severity: page
...

这些规则将被添加到 Prometheus 实例中,Operator 会处理重新加载配置,而不会导致服务停机。

最后,你可以访问 Prometheus 和 Alertmanager 的 web 界面,并通过执行以下指令验证到目前为止所做的所有配置,这将打开几个浏览器标签:

minikube service alertmanager-service -n monitoring

minikube service prometheus-service -n monitoring

测试完成后,你可以通过执行以下命令删除这个基于 Kubernetes 的环境:

minikube delete

这个设置为你提供了一个快速概览,说明如何在 Kubernetes 上将 Alertmanager 集成到 Prometheus 中。再说一遍,Prometheus Operator 抽象了大部分复杂性,让你可以专注于最重要的部分。

用户和/或组织对通知方式有不同的需求;有些可能使用 HipChat 作为通讯工具,而其他则依赖于电子邮件,值班通常需要使用类似 PagerDuty 或 VictorOps 的寻呼系统,等等。幸运的是,Alertmanager 提供了多种开箱即用的集成选项,能够满足大部分的通知需求。如果没有,始终可以使用 Webhook 通知器,它支持与自定义通知方法的集成。接下来,我们将探索最常见的集成方式以及如何配置它们,并提供基本的示例帮助你入门。

在考虑与聊天系统集成时,需要记住的是,这些系统是为人类设计的,对于低优先级警报,建议使用工单系统。当创建警报的过程变得简单且自助时,管理警报可能很快失控。工单确保责任归属:与在聊天频道中发送警报相比,使用工单的主要优势是,它们可以跟踪、优先排序,并进行适当的跟进,以确保警报问题不再发生。这种方法还隐式确保了通知的归属,并避免了常见的 谁是这个警报的负责人? 问题。归属感使服务维护人员能够筛选他们收到的警报,并且作为副作用,也有助于减少警报疲劳。

如果你正在使用 JIRA 进行任务跟踪,可以通过 Webhook 通知器实现一个名为 JIRAlert 的自定义集成,具体内容请参考 github.com/free/jiralert

所有通知器都有一个共同的配置项,叫做 send_resolved。它接受一个布尔值(true 或 false),用于声明是否在警报被解决时发送通知。默认情况下,PagerDuty、Opsgenie、VictorOps、Pushover 和 Webhook 集成启用了此选项,但其他通知器则禁用了它,这是你应该防止不必要垃圾邮件的主要原因。

电子邮件是大多数组织的标准通讯方式,因此 Alertmanager 支持电子邮件也就不足为奇了。在配置方面,设置相当简单;然而,由于 Alertmanager 本身并不直接发送电子邮件,它需要使用一个实际的电子邮件中继。我们以一个现实世界的例子为例,适用于快速测试和低预算设置,这就是使用一个电子邮件提供商(在此例中是 Google 的 Gmail)提供的 SMTP 服务:

global:
  smtp_smarthost: 'smtp.gmail.com:587'
  smtp_from: 'alertmanager@example.com'
  smtp_auth_username: 'alertmanager@example.com'
  smtp_auth_identity: 'alertmanager@example.com'
  smtp_auth_password: '<generated_token>'

route: 
  receiver: 'default'

receivers:
- name: 'default'
  email_configs:
  - to: 'squad@example.com'

在这个特定的示例中,由于直接使用你的主 Gmail 密码在网络安全方面不太安全,你需要一个启用了双因素认证的账户,并生成一个应用密码来使用 smtp_auth_password 字段。

你可以在 使用应用密码登录 支持页面找到如何为 Gmail 帐号生成应用密码,页面地址是 support.google.com/accounts/answer/185833

在撰写时,有三种聊天服务的集成:Slack、WeChat 和 HipChat。以下示例表示 Slack 集成的配置;本章稍后我们将提供更多关于这种集成的定制化概述:

global:
  slack_api_url: 'https://hooks.slack.com/services/TOKEN'

route:
  receiver: 'default'

receivers:
- name: 'default'
  slack_configs:
  - channel: '#alerting'

slack_api_url 应指向 Slack Incoming Webhooks 的 URL。你可以通过访问他们的文档了解更多内容,网址是 api.slack.com/incoming-webhooks。由于 slack_configs 是一个列表,你可以在单个接收器中指定多个频道。

值班通常意味着携带一个呼叫器,无论是物理的还是虚拟的。在撰写时,Alertmanager 支持四种呼叫器样式的服务集成:PagerDuty、Opsgenie、VictorOps 和 Pushover。这些服务的配置相对简单,主要涉及 API URL 和身份验证令牌。然而,它们也支持更深层次的定制,比如添加图片、链接以及配置服务特定的字段,如严重性。这些高级配置选项已在 Alertmanager 的官方文档中描述,因此这里不再赘述。以下示例展示了 PagerDuty 的基本配置:

global:
  pagerduty_url: 'https://events.pagerduty.com/v2/enqueue'

route:
  receiver: 'default'

receivers:
- name: 'default'
  pagerduty_configs:
  - service_key: 'PAGERDUTYSQUADTOKENEXAMPLE'

与之前的通知器类似,由于 pagerduty_configs 配置是一个列表,你可以在单个接收器中触发多个服务路由。你可以在这里了解更多有关 PagerDuty 与 Alertmanager 集成的内容:www.pagerduty.com/docs/guides/prometheus-integration-guide/

Webhook 集成为自定义集成提供了广阔的可能性。此功能允许 Alertmanager 发出带有通知 JSON 负载的 HTTP POST 请求到你选择的端点。请记住,URL 不能被模板化,且目标端点必须设计为处理 JSON 负载。例如,它可以用于将所有通知推送到日志系统(如 Elasticsearch),以便你进行报告和统计分析。如果你的团队使用 IRC,这也可以是与其集成的解决方案。另一个示例是我们为本书创建的 alertdump 工具。它以前在 第九章中,定义警报和记录规则,用于展示 Prometheus 在触发警报规则时发送的内容,但它也可以用来展示 Alertmanager 发送的通知负载。

可以在以下代码中看到一个简单的配置:

global:

route:
  receiver: 'default'

receivers:
- name: 'default'
  webhook_configs:
  - url: 'http://127.0.0.1:5001'

该配置会将 Alertmanager 接收到的每个警报原封不动地发送到 alertdump,后者会将有效负载追加到以 alertdump 运行所在主机命名的日志文件中。该日志文件位于一个路径中,该路径在我们测试环境中的每个虚拟机内均可访问(/vagrant/cache/alertmanager*.log),同时在外部(相对于仓库根目录的 ./cache/alertmanager*.log)也可访问。

这本身不是一个通知器,而是一个常用的模式,用于丢弃通知。其配置方式是指定一个接收者而不指定通知器,这样就会导致通知被丢弃。以下示例确保永远不会发送任何通知:

global:

route:
  receiver: 'null'

receivers:
- name: 'null'

这有时对于演示目的很有用,但除此之外并无太多作用;不应触发通知的警报应该在源头被丢弃,而不是在 Alertmanager 中丢弃,唯一的例外是用作禁止的源头的警报。

始终需要关注的是 alertmanager_notifications_failed_total 这个 Alertmanager 指标,因为它跟踪每个集成的通知发送失败次数。

现在我们了解了 Alertmanager 通知器的基本知识,接下来可以学习如何自定义告警通知,以便将最重要的信息适当呈现。

对于每个可用的集成,Alertmanager 已经包含了内置的通知模板。然而,这些模板可以根据用户和/或组织的具体需求进行定制。类似于我们在第九章《定义告警和记录规则》中探讨的告警规则注解,告警通知使用 Go 模板语言进行模板化。我们以 Slack 集成为例,了解消息是如何构建的,以便根据你的需求进行定制。

为了了解没有任何自定义的通知是什么样子,我们将使用一个非常简单的示例。以我们在 Prometheus 实例中定义的以下告警规则为例:

  - alert: deadmanswitch
    expr: vector(42)

一旦此警报开始触发,警报有效负载将被发送到 Alertmanager。以下代码片段演示了有效负载的发送。注意其中的标签,包括来自 Prometheus 实例的 alertnameexternal_labels

    {
        "labels": {
            "alertname": "deadmanswitch",
            "dc": "dc1"
        },
        "annotations": {},
        "startsAt": "2019-04-02T19:11:30.04754979Z",
        "endsAt": "2019-04-02T19:14:30.04754979Z",
        "generatorURL": "http://prometheus:9090/graph?g0.expr=vector%2842%29&g0.tab=1"
    }

在 Alertmanager 端,我们将有这个最小化配置,确保能够发送 Slack 通知(将 TOKEN 替换为实际的 Slack token):

global:
  slack_api_url: 'https://hooks.slack.com/services/TOKEN'

route:
  receiver: 'default'

receivers:
- name: 'default'
  slack_configs:
  - channel: '#alerting'

最终结果会是一个类似于以下的 Slack 消息:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/6e072e2e-499d-4e06-a92c-52073b206923.png

图 11.9:Slack 默认通知格式

如我们所见,默认的通知格式包含了大量信息。但问题仍然存在,这到底是如何生成的?为了回答这个问题,我们可以查看由我们基础的 Alertmanager 配置生成的 default 接收器的运行时配置,以下片段展示了这一点:

...
receivers:
- name: default
  slack_configs:
  - send_resolved: false
    http_config: {}
    api_url: <secret>
    channel: '#alerting'
    username: '{{ template "slack.default.username" . }}'
    color: '}danger}good{{ end }}'
    title: '{{ template "slack.default.title" . }}'
    title_link: '{{ template "slack.default.titlelink" . }}'
    pretext: '{{ template "slack.default.pretext" . }}'
    text: '{{ template "slack.default.text" . }}'
...

可以通过使用amtool config或访问 Alertmanager Web 界面的/#/status端点来检查 Alertmanager 的运行时配置。

如我们所见,每个可自定义的字段都是使用 Go 模板配置的。我们将以username字段为例,说明 Slack 消息中的用户名是如何生成的,因为这是一个相当简单的模板。所有其他使用的模板都遵循相同的逻辑,复杂度有所不同。

Alertmanager 使用的默认模板无法在测试环境实例中本地查看,因为它们已经被编译并包含在 Alertmanager 的二进制文件中。不过,我们可以通过查看 Alertmanager 代码库中的 templates/default.tmpl 文件来查阅所有默认的通知集成模板。本文撰写时,当前版本是 0.16.2,因此,为了方便起见,我们在此提供了链接:github.com/prometheus/alertmanager/blob/v0.16.2/template/default.tmpl

如果我们查看 default.tmpl 文件,我们将找到 slack.default.username 模板的定义:

{{ define "slack.default.username" }}{{ template "__alertmanager" . }}{{ end }}

如我们所见,该模板使用了另一个模板作为其定义。因此,如果我们查找 __alertmanager 模板的定义,我们会发现以下内容:

{{ define "__alertmanager" }}AlertManager{{ end }}

现在,你明白了 AlertManager 是如何出现在 Slack 通知中的。其余各个模板的追踪任务留给你作为练习。在下一部分中,我们将学习如何创建自己的模板,并将它们用于自定义我们的警报通知。

在深入创建模板之前,我们首先需要了解发送到通知模板的数据结构。下表显示了可以使用的变量:

变量 描述 Alerts 一个警报结构列表,每个警报都有其自己的状态、标签、注解、StartsAt、EndsAt 和 GeneratorURL CommonAnnotations 所有警报共有的注解 CommonLabels 所有警报共有的标签 ExternalURL 发送警报的 Alertmanager 的 URL GroupLabels 用于警报分组的标签 receiver 将处理通知的接收器 status 只要警报处于该状态,就会触发该变量,或者当警报解决时,它会变为已解决

所有可用的数据结构和函数的全面参考可以在prometheus.io/docs/alerting/notifications/找到。

演示如何构建模板的最佳方法是提供一个示例。我们构建了以下模板,它已经在测试环境中准备好,目的正是如此。我们将在详细讲解每一部分的同时,将 Alertmanager 配置精简为只包含重要的部分。

我们希望创建的最终结果可以在以下截图中看到:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/23660965-21b3-44b9-a32c-eddadb0dd050.png

图 11.10:示例 Slack 通知模板

现在,我们将解析示例配置,该配置将生成类似于前面截图所示的通知:

route:
  group_by: [alertname, job]
  receiver: null
  routes:
  - match:
      severity: slack
    receiver: purple-squad-slack

出于这个示例的考虑,我们将警报按alertnamejob进行分组。这一点很重要,因为它将影响CommonAnnotationsCommonLabels,正如我们稍后会看到的那样:

receivers:
- name: null
- name: purple-squad-slack
  slack_configs:
  - api_url: 'https://hooks.slack.com/TOKEN'
    channel: '#alerting'
    title: >
      [{{ .Alerts | len }}x ALERT}S{{end}}] {{ .CommonLabels.alertname }}

正如我们在前面的表格中看到的,.Alerts 是所有警报的列表,因此我们希望获取该列表的长度(len)来为消息创建一个标题,从触发的警报数量开始。注意if语句,它确保在有多个警报时使用复数形式。最后,由于我们按alertname分组警报,我们会在方括号后面打印alertname

    text: >
      :link: <{{ .CommonAnnotations.troubleshooting }}/{{ .CommonLabels.alertname }}|Troubleshooting Guide>

对于消息正文,我们希望生成一个指向此类警报故障排除指南的链接。我们的警报发送了一个名为troubleshooting的注解,包含一个基本 URL。如果我们遵循惯例,使得指南名称与警报名称匹配,我们就可以使用这两个字段轻松生成链接。

为了提供更多关于触发警报的上下文,我们将把所有可用的警报标签添加到消息中。为了实现这个目标,我们必须遍历列表中的每个警报:

      {{ range .Alerts }}

对于每个警报,我们将打印该警报可用的描述,它作为该警报的注解存在:

       *Description:* {{ .Annotations.description }}
      *Details:*

我们还将打印每个警报的标签/值对。为此,我们将遍历SortedPairs的结果,它返回一个排序后的标签/值对列表:

        {{- range .Labels.SortedPairs }}

}
• *{{ .Name }}:* `{{ .Value }}`
{{- end}}
{{- end }}
{{ end }}

就是这样。你甚至可以通过将此模板从 Alertmanager 配置中提取出来并放入自己的模板文件中,使其更加易于管理。我们在测试环境中已经这么做了,其中接收器配置仅如下所示:

- name: violet-squad-slack
  slack_configs:
  - channel: '#violet-squad-alerts'
    title: '{{ template "slack.example.title" . }}'
    text: '{{ template "slack.example.text" . }}'

所有模板定义都可以在 Alertmanager 实例中的以下路径找到:

/etc/alertmanager/templates/example.tmpl

通知模板化一开始是相当难以理解和构建的。幸运的是,Prometheus 的联合创始人之一和核心维护者 Julius Volz 创建了一个工具,可以帮助你快速迭代 Slack 通知模板。这是理解它们如何工作及如何生成它们的最佳方式。你可以在juliusv.com/promslack/找到它。

监控系统是任何基础设施的关键组件。我们依赖它来监视一切——从服务器和网络设备到服务和应用程序——并期待在出现问题时收到通知。然而,当问题出现在监控堆栈本身,或者甚至是通知提供者上,导致警报被生成但未能到达我们时,作为运维人员,我们该如何得知?

保证监控堆栈正常运行,并确保通知能够到达接收者,是一个常常被忽视的任务。在本节中,我们将深入探讨如何采取措施来减轻风险因素,并提高对监控系统的整体信任度。

从广义上讲,你不能让监控系统监控自己;如果系统遭遇严重故障,它将无法发出相关通知。虽然让 Prometheus 自己抓取数据是常见做法(你可能会在大多数教程中看到),但显然不能依赖它来发出关于自己的警报。这时,元监控就派上用场了:它是指监控系统被监控的过程。

你应该考虑的第一个选项来缓解这个问题,是拥有一组 Prometheus 实例,监控它们数据中心/区域中的所有其他 Prometheus 实例。由于 Prometheus 自身生成的指标相对较少,这将转化为一个相当轻量的抓取任务,进行元监控的实例甚至不需要专门致力于此:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/7181b1e2-83d3-45f8-a939-c1c4c3096af1.png

图 11.11:元监控——Prometheus 组互相监控

然而,你可能会想知道,这些实例该如何被监控。我们可以不断添加更高层次的实例,以分层方式进行元监控——先在数据中心级别,再在区域级别,最后在全球级别——但我们仍然会面临一组没有被监控的服务器。

一种补充的技术来缓解这一不足被称为交叉监控。这种方法涉及让同一责任层级的 Prometheus 实例相互监控。这样,每个实例至少会有另一个 Prometheus 在监视它,并在其失败时生成警报:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/8341ddba-98dc-4dff-832f-231728abb27a.png

图 11.12:Prometheus 组自我监控

那么,如果问题出现在 Alertmanager 集群中会怎样?或者如果外部连接问题阻止了通知到达通知提供者呢?甚至如果通知提供者本身发生故障呢?在接下来的部分,我们将提供这些问题的可能解决方案。

想象一下,你有一组 Prometheus 实例,使用 Alertmanager 集群进行警报。当某种原因导致这两个服务之间发生网络分区时,即使每个 Prometheus 实例都检测到它无法再连接任何 Alertmanager 实例,它们也没有办法发送通知。

在这种情况下,关于问题的任何警报都不会被发送:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/998721d6-ecb3-4f19-b8ba-4186455fba9e.png

图 11.13:Prometheus 与 Alertmanager 之间的网络分区

死人开关的原始概念指的是一个机制,它在停止触发/按下时会被激活。这个概念在软件世界中以多种方式得到了应用;就我们的目的而言,我们可以通过创建一个应该始终触发的警报来实现——从而持续发送通知——然后检查它是否停止触发。通过这种方式,我们可以全面测试从 Prometheus、通过 Alertmanager,到通知提供者,最终到达通知接收者的警报路径,以确保端到端的连接性和服务可用性。当然,这与我们了解的警报疲劳背道而驰,我们不希望一直收到关于总是触发的警报的页面或电子邮件。你可以实现自己的自定义服务,使用看门狗定时器,但那样你也需要监控这个服务。理想情况下,你应该利用第三方服务,这样可以减轻该服务遭遇与阻止通知发送的故障相同问题的风险。

为此,有一个围绕死人的开关类型警报构建的服务,名字也非常有趣,叫做Dead Man’s Snitch(deadmanssnitch.com)。这是一个第三方服务,位于你的基础设施之外,负责通过电子邮件或 Webhook 接收你的始终触发的通知,并在该通知停止接收超过可配置时间后,发出页面、Slack 消息或 Webhook。这个设置减轻了我们之前提出的问题——即使整个数据中心起火,你仍然会收到警报!

Dead Man’s Snitch 与 VictorOps 和 PagerDuty 集成的完整配置指南可以在help.victorops.com/knowledge-base/victorops-dead-mans-snitch-integration/www.pagerduty.com/docs/guides/dead-mans-snitch-integration-guide/找到。

在本章中,我们深入探讨了 Prometheus 堆栈中的告警组件——Alertmanager。这个服务的设计考虑了高可用性,我们有机会了解它的工作原理,从生成更好的通知到避免被无用的通知淹没。通知管道是理解 Alertmanager 内部工作原理的一个很好的起点,但我们也了解了它的配置,并通过示例更好地巩固了这些知识。我们还介绍了 amtool 及其提供的所有功能,如通过命令行直接添加、删除和更新静默。

Alertmanager 提供了多种通知集成方式,我们已经涵盖了所有这些方式,帮助你挑选出感兴趣的部分。既然我们都希望获得更好的通知,我们深入探讨了如何自定义默认通知,使用 Slack 作为示例。解决的一个难题是如何监控监控系统;在本章中,我们学习了如何确保在通知未正常发送时能够及时告警。

在一个不断变化的基础设施中,追踪哪些服务在运行以及它们运行在哪里并非易事。在下一章中,我们将深入探讨 Prometheus 如何处理服务发现,并为你自动化这些任务。

  1. 如果在同一集群中 Alertmanager 实例之间发生网络分区,通知会发生什么?

  2. 一个告警可以触发多个接收者吗?为此需要满足什么条件?

  3. group_intervalrepeat_interval 有什么区别?

  4. 如果告警与任何配置的路由都不匹配,会发生什么?

  5. 如果 Alertmanager 原生不支持你需要的通知提供者,你该如何使用它?

  6. 在编写自定义通知时,CommonLabelsCommonAnnotations 是如何填充的?

  7. 你能做些什么来确保整个告警路径从头到尾都正常工作?

  • 官方 Alertmanager 页面: prometheus.io/docs/alerting/alertmanager/

  • Alertmanager 通知指南: prometheus.io/docs/alerting/notifications/

  • Alertmanager 配置细节: prometheus.io/docs/alerting/configuration/

伟大的事物往往从小事开始,但随着时间的推移,它们很快会变得难以控制。本节将介绍通过动态设置目标、分片和联邦来处理规模的几种选项。

以下章节包含在本节中:

  • 第十二章,选择合适的服务发现

  • 第十三章,扩展与联邦化 Prometheus

  • 第十四章,将长期存储与 Prometheus 集成

在处理动态环境时,手动维护目标文件是不可行的。服务发现会为您处理复杂的不断变化的基础设施,确保没有任何服务或主机被遗漏。本章重点介绍如何利用 Prometheus 服务发现来减少在应对不断变化的基础设施管理工作中的繁琐工作。

简而言之,本章将涵盖以下主题:

  • 本章节的测试环境

  • 运行服务发现选项

  • 使用内建的服务发现

  • 构建自定义服务发现

本章将重点介绍服务发现。为此,我们将部署两个新的实例,以模拟一个场景,其中 Prometheus 使用流行的服务发现软件动态生成目标。这个方法不仅能暴露所需的配置,还能验证所有内容如何协同工作。

我们将使用的设置类似于以下图示:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/0046acd0-7265-423c-aab8-96f281292177.png

图 12.1:本章节的测试环境

Consul 的常见部署模式是在基础设施的每个节点上以客户端模式运行一个代理,然后与以服务器模式运行的 Consul 实例进行通信。此外,客户端实例充当 API 代理,因此 Prometheus Consul 服务发现通常会使用本地主机进行配置。然而,为了使各自的职责更加明确,我们在测试环境中选择仅在一台虚拟机上运行 Prometheus 实例,并在另一台虚拟机上运行作为服务器的 Consul。

在接下来的章节中,我们将解释如何启动并运行测试环境。

要启动一个新的测试环境,请进入本章节的路径,相对于仓库根目录:

cd ./chapter12/

确保没有其他测试环境在运行,并启动本章节的环境:

vagrant global-status
vagrant up

您可以使用以下命令验证测试环境的成功部署:

vagrant status

这将为您提供以下输出:

Current machine states:

prometheus                running (virtualbox)
consul                    running (virtualbox)

This environment represents multiple VMs. The VMs are all listed above with their current state. For more information about a specific VM, run `vagrant status NAME`.

部署任务结束后,您将能够使用您最喜欢的启用了 JavaScript 的网页浏览器,在主机机器上验证以下端点:

服务 端点 Prometheus http://192.168.42.10:9090 Consul http://192.168.42.11:8500

您应该能够通过以下命令之一访问所需的实例:

实例 命令 Prometheus vagrant ssh prometheus Consul vagrant ssh consul

测试完成后,只需确保您位于 ./chapter12/ 目录中并执行以下命令:

vagrant destroy -f

不用太担心——如果需要,您可以轻松地重新启动环境。

Prometheus 开箱即用地提供了多个发现集成。这些集成覆盖了大多数主流数据源,用于应用和机器清单,例如公共和私有云计算 API、虚拟机和容器编排系统、独立的服务注册和发现系统等。对于 Prometheus 不直接支持的发现机制,可以通过通用的发现系统进行集成,利用文件系统和一些粘合代码,就像我们在本章稍后看到的那样。

每个集成都以相同的方式工作——通过将所有发现的地址作为目标,并将其相关的元数据作为临时标签(在没有某些重新标签的情况下,这些标签不会持久化)。对于每个被发现的目标,__address__ 标签通常被设置为服务地址和端口。这一点很重要,因为这个标签是 Prometheus 用来连接抓取目标的标签;instance 标签在没有显式定义时默认使用 __address__ 的值,但它可以设置为任何其他便于识别目标的值。

服务发现集成提供的元数据标签遵循 __meta_<service discovery name>_<key> 的模式。还有一些标签是 Prometheus 添加的,例如 __scheme____metrics_path__,它们分别定义是否应使用 HTTP 或 HTTPS 执行抓取,以及配置的抓取端点。

metrics_path 抓取配置中不支持 URL 参数。相反,这些参数需要在 params 配置中设置。相关内容将在 第五章 中介绍,运行 Prometheus 服务器

以下章节提供了可用的发现选项概述,并展示了如何配置它们的一些示例,附带生成数据的截图。

随着云基础设施的崛起,在这些环境中运行工作负载变得越来越常见。这带来了新的挑战,例如短暂且高度动态的基础设施配置。可扩展性易于实现也是需要考虑的因素:过去,可能需要几个月的时间来协商、达成支持合同、购买、部署和配置新硬件;而现在,只需几秒钟即可启动并运行一组新的实例。随着像自动扩展这样的技术出现,它可以在你甚至不知道的情况下部署新实例,变化的速度让人难以跟上。为了减轻对这种云原生动态基础设施的监控负担,Prometheus 开箱即用地与一些 基础设施即服务 (IaaS) 市场中的大玩家进行了集成,例如 Amazon Web Services、Microsoft Azure Cloud、Google Cloud Platform、OpenStack 和 Joyent。

以 Amazon 弹性计算 (EC2) 为虚拟机发现的例子,抓取任务的配置可以像下面这样简单:

scrape_configs:
 - job_name: ec2_sd
   ec2_sd_configs:
    - region: eu-west-1
      access_key: ACCESSKEYTOKEN
      secret_key: 'SecREtKeySecREtKey+SecREtKey+SecREtKey'

其他云服务提供商的设置可能有所不同,但逻辑基本相同。基本上,我们需要设置适当的凭据级别来查询云服务提供商的 API,以便 Prometheus 发现集成能够获取生成目标所需的所有数据以及其相关的元数据。以下截图演示了如何将类似于之前列出的配置但带有实际凭据的设置转化为一组目标:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/2fba1ca1-18c8-4268-8237-3f5ab2c62fbd.png

图 12.2:Prometheus */*service-discovery 端点,展示 ec2_sd 数据

正如我们在前面的截图中看到的,EC2 发现将相当多的元数据标签附加到每个发现的目标。这些标签在重新标记阶段可用,您可以利用它们只抓取正在运行的目标,将抓取地址从私有 IP 地址更改为公共 IP 地址,或者将实例标签重命名为更友好的名称。

从发现过程中收集的信息要么是定期刷新(刷新间隔可以在服务发现级别进行配置),要么通过监视自动刷新,使得 Prometheus 能够意识到目标的创建或删除。

容器编排器是提取正在运行的服务及其位置的完美场所,因为它们的工作正是管理这些信息。因此,Prometheus 的发现机制支持一些最广泛使用的容器编排平台,如 Kubernetes 和 Marathon,这是 Mesos 和 DC/OS 的容器编排平台。由于我们在本书的大部分示例中使用了 Kubernetes,因此我们将重点介绍该平台,以解释这些系统是如何工作的。

和 Prometheus 一样,Kubernetes 也是云原生计算基金会CNCF)的一个毕业项目。虽然这并不意味着一个是专门为了与另一个一起工作的,但二者之间的联系是不可否认的。Google 的容器编排和监控系统 Borg 和 Borgmon,分别是 Kubernetes 和 Prometheus 的灵感来源。为了应对 Kubernetes 等云原生平台的监控,其中变化的速度几乎令人难以承受,必须具备一套特别的功能。Prometheus 符合这些需求,例如高效处理容器的短暂性。

Prometheus 服务发现集成通过 Kubernetes API 检索所有所需的数据,保持与集群状态的同步。由于可查询的 API 对象数量庞大,Prometheus 的发现配置中引入了角色(role)概念,这些角色可以是 nodeservicepodendpointingress。虽然解释 Kubernetes 核心概念超出了本书的范围,但我们可以快速了解每个角色的用途:node 用于收集形成 Kubernetes 集群的实际节点(例如,运行 kubelet 代理的虚拟机),因此可以用来监控集群本身以及其底层基础设施;Kubernetes 中的服务对象充当负载均衡器,service 将提供每个配置服务的每个端口的单个端点,无论它是由一个还是多个应用实例支持——并且仅用于黑盒监控;pod 用于发现独立的 Pod,无论它们是否属于某个服务;endpoint 发现支持特定服务的 Pod 中的主要进程;最后,ingress 类似于 service,返回一个应用实例集合的外部负载均衡器,因此仅应在端到端探测中使用。

以下代码片段提供了一个查询 Pods 的示例,匹配具有标签 app 且值为 hey 的 Pods:

scrape_configs:
  - job_name: kubernetes_sd
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - action: keep
        regex: hey
        source_labels:
          - __meta_kubernetes_pod_label_app

前面的配置生成了以下截图中所示的数据,在其中我们可以看到通过 Kubernetes API 收集的所有元数据:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/cd652109-78b2-4fea-9769-d636a2398e68.png

图 12.3:Prometheus /service-discovery 端点,展示 kubernetes_sd 数据

这是一个可以完成的非常小的示例。使用 Kubernetes 服务发现的配置通常广泛使用 relabel_configs 来过滤目标,重写 job 标签以匹配容器名称,并且通常基于 Kubernetes 注解的约定进行巧妙的自动配置。

随着服务数量的增加,越来越难以将所有内容联系在一起——无论是在服务正确配置以相互通信方面,还是在操作员能看到系统行为的方面。解决这些问题的常见方法是实现一个服务发现系统,它充当注册表,软件客户端以及监控系统可以查询该注册表。

Prometheus 无缝集成了几个主流的服务发现系统,目前支持 Consul、Nerve 和 ServerSets。直接与发现服务集成使 Prometheus 始终能够获取当前运行的服务及其位置,从而使服务实例在创建后能够被自动监控,直到它们被销毁。

Consul 目前是最受欢迎的,它提供了一整套功能来实现服务发现,并且拥有强大且易于使用的命令行工具和 API,且易于扩展。我们以以下内容作为示例:

scrape_configs:
  - job_name: 'consul_sd'
    consul_sd_configs:
      - server: http://consul.prom.inet:8500
        datacenter: dc1
    relabel_configs:
      - source_labels: [__meta_consul_service]
        target_label: job
      - source_labels: [job, __address__]
        regex: "consul;([^:]+):.+"
        target_label: __address__
        replacement: ${1}:9107

上面的示例转换成如下截图,我们可以看到不仅是生成的标签,还有目标的定义:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/1e139a67-bffa-4703-8efa-3715a6c9ad83.jpg

图 12.4:Prometheus /service-discovery 端点展示 consul_sd 数据

上面的示例展示了一个工作配置,用于从 Consul 服务器中所有可用服务收集数据,使用 relabel_configs 将目标的 job 标签重写为服务名称,而不是 job_name。这意味着在 Consul 中注册的每个应用实例都会被自动拾取为抓取目标,并正确分配适当的作业名称。此外,最后的重标签规则会在服务名为 Consul 时将目标端口更改为 9107,从而将目标从 Consul 本身更改为其导出器。

这种类型的服务发现依赖于 DNS 来收集数据。它通过定义一系列需要定期查询的域名来获取目标。用于解析的名称服务器会在 /etc/resolv.conf 中查找。除了支持 A 和 AAAA DNS 记录外,该发现集成还能够查询 SRV 记录,SRV 记录还提供服务的端口:

~$ dig SRV hey.service.example.inet
...
;; QUESTION SECTION:
;hey.service.example.inet. IN SRV

;; ANSWER SECTION:
hey.service.example.inet. 0 IN SRV 1 1 8080 server01.node.example.inet.

;; ADDITIONAL SECTION:
server01.node.example.inet. 0 IN A 192.168.42.11
server01.node.example.inet. 0 IN TXT "squad=purple"
...

我们可以看到,通过查询 hey.service.example.inet 的 SRV 记录,在此示例中,我们得到服务位置 server01.node.example.inet 和端口 8080。我们还得到了包含服务 IP 地址的 A 记录和包含一些元数据的 TXT 记录。

以下代码片段展示了使用此 DNS 服务发现集成的示例抓取配置。它通过使用之前提到的域名 hey.service.example.inet 来实现:

scrape_configs:
  - job_name: 'dns_sd'
    dns_sd_configs:
      - names:
        - hey.service.example.inet

返回的 SRV 记录将转换为新的目标。Prometheus 不支持 RFC 6763 中指定的高级 DNS-SD,它允许通过相关的 TXT 记录传输元数据(如之前在 dig 命令中看到的)。这意味着只能使用此方法发现服务地址和端口。我们可以在以下截图中看到发现的标签:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/261b8bcc-e022-46fc-95dc-b4b0c45c0ab5.png

图 12.5:Prometheus /service-discovery 端点展示 dns_sd 数据

在所有发现集成中,这是提供的元数据最少的一个。此外,使用 DNS 进行服务发现很难做到正确——需要规划慢速收敛,考虑多个不同的缓存层,这些缓存层可能会或可能不会尊重记录的 TTL 等问题。这种方法应该仅在高级用例中考虑。

类似于 webhook 通知器为集成不受支持的通知系统提供解决方案(如 第十一章《理解与扩展 Alertmanager》中所解释的),基于文件的集成为服务发现提供了相同类型的解决方案。它的工作原理是加载有效的 JSON 或 YAML 文件列表,这些文件用于生成所需的目标及其标签。文件发现更改后不需要重载或重启 Prometheus,因为它们会被监控并自动重新读取,具体取决于操作系统。此外,作为回退,发现文件也会按照预定计划(默认每 5 分钟)被读取。

以下 JSON 片段展示了一个有效的 Prometheus 服务发现文件。正如我们所看到的,它包含了标签列表和一个目标数组,这些标签适用于目标:

[
    {
        "labels": {
            "job": "node"
        },
        "targets": [
            "192.168.42.11:9100"
        ]
    }
]

以下抓取配置使用了 file_sd 服务发现,它加载了之前展示的 file_sd.json 文件:

scrape_configs:
  - job_name: 'file_sd'
    file_sd_configs:
      - files:
        - file_sd.json

files 列表还允许对路径的最后一个元素进行 glob 匹配,即对文件级别进行匹配。

从该配置中发现的目标可以在下图中看到,我们可以检查文件提供的元数据以及 Prometheus 自动生成的标签:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/04711801-62dc-4225-8c1d-78288a7c1091.png

图 12.6:Prometheus /service-discovery 端点展示 file_sd 数据

很容易看出,这种集成打开了无数可能性:这些文件可以通过一个常驻的守护进程或者一个定时任务(cron job)创建,使用 shell 脚本(甚至是简单的 wget)或完备的编程语言,或者通过配置管理工具来部署。我们将在本章后面讨论如何构建自定义服务发现时,深入探讨这个话题。

为了理解 Prometheus 和服务发现提供者之间的集成方式,我们将依赖于我们的测试环境。进一步来说,我们将提供一个在 Kubernetes 中运行的 Prometheus 工作示例,依赖于该平台的原生服务发现。这些动手示例将展示如何将一切结合起来,帮助你不仅理解其优点,更重要的是理解这些机制的简单性。

在本章中,我们将 Consul 配置为虚拟机基础的测试环境中的示例服务发现系统——Consul 的设置非常简单,这使得它非常适合我们的示例。它的工作方式是在每个节点上运行一个客户端模式的代理,以及在服务器模式下运行的一个奇数个代理,后者维护服务目录。客户端节点上可用的服务会直接传递给服务器节点,而集群成员身份则通过集群中每个节点之间使用的 gossip 协议(随机点对点消息传递)进行传播。由于我们的主要目标是展示使用 Consul 进行 Prometheus 服务发现,我们将测试环境配置为在开发模式下运行的代理,从而启用一个内存中的服务器来进行实验。当然,这完全忽视了安全性、可扩展性、数据安全性和弹性;关于如何正确配置 Consul 的文档可以在learn.hashicorp.com/consul/找到,在部署和维护 Consul 于生产环境中时应予以考虑。

要查看测试环境中如何配置此项,我们需要连接到运行 Consul 的实例:

vagrant ssh consul

从这里,我们可以开始探索 Consul 是如何配置的。例如,以下片段展示了正在使用的 systemd 单元文件,在其中我们可以看到正在使用的配置标志——它被配置为在开发模式下以代理身份运行,并且必须将其端口绑定到实例的外部 IP 地址:

vagrant@consul:~$ systemctl cat consul.service 
...
[Service]
User=consul
ExecStart=/usr/bin/consul agent 
 -dev 
 -bind=192.168.42.11 
 -client=192.168.42.11 
 -advertise=192.168.42.11
...

如果我们运行ss并过滤其输出,只显示属于 Consul 的行,我们可以找到它正在使用的所有端口:

vagrant@consul:~$ sudo /bin/ss -lnp | grep consul
udp UNCONN 0 0 192.168.42.11:8301 0.0.0.0:* users:(("consul",pid=581,fd=8))
udp UNCONN 0 0 192.168.42.11:8302 0.0.0.0:* users:(("consul",pid=581,fd=6))
udp UNCONN 0 0 192.168.42.11:8600 0.0.0.0:* users:(("consul",pid=581,fd=9))
tcp LISTEN 0 128 192.168.42.11:8300 0.0.0.0:* users:(("consul",pid=581,fd=3))
tcp LISTEN 0 128 192.168.42.11:8301 0.0.0.0:* users:(("consul",pid=581,fd=7))
tcp LISTEN 0 128 192.168.42.11:8302 0.0.0.0:* users:(("consul",pid=581,fd=5))
tcp LISTEN 0 128 192.168.42.11:8500 0.0.0.0:* users:(("consul",pid=581,fd=11))
tcp LISTEN 0 128 192.168.42.11:8502 0.0.0.0:* users:(("consul",pid=581,fd=12))
tcp LISTEN 0 128 192.168.42.11:8600 0.0.0.0:* users:(("consul",pid=581,fd=10))

如我们所见,Consul 监听了多个端口,包括 TCP 和 UDP。我们关心的端口是提供 HTTP API 的端口,默认是 TCP 端口8500。如果我们在浏览器中访问http://192.168.42.11:8500,我们将看到类似以下内容:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/e34662e2-2048-4bba-b590-7753c24c4dec.png

图 12.7:Consul Web 界面显示其默认配置

默认配置了一个服务,即 Consul 服务本身。

为了让这个示例更加有趣,我们还在consul实例中部署了consul_exporter(一个由 Prometheus 项目提供的导出器)。这个导出器无需在 Consul 端进行任何额外配置,因此应该能够直接工作。我们可以在 systemd 单元文件中找到运行该服务所使用的配置,像这样:

vagrant@consul:~$ systemctl cat consul-exporter.service 
...
[Service]
User=consul_exporter
ExecStart=/usr/bin/consul_exporter --consul.server=consul:8500
...

consul_exporter的源代码和安装文件可在github.com/prometheus/consul_exporter找到。

为了验证导出器是否正确地联系到 Consul 并解析其指标,我们可以运行以下指令:

vagrant@consul:~$ curl -qs localhost:9107/metrics | grep "^consul"
consul_catalog_service_node_healthy{node="consul",service_id="consul",service_name="consul"} 1
consul_catalog_services 1
consul_exporter_build_info{branch="HEAD",goversion="go1.10.3",revision="75f02d80bbe2191cd0af297bbf200a81cbe7aeb0",version="0.4.0"} 1
consul_health_node_status{check="serfHealth",node="consul",status="critical"} 0
consul_health_node_status{check="serfHealth",node="consul",status="maintenance"} 0
consul_health_node_status{check="serfHealth",node="consul",status="passing"} 1
consul_health_node_status{check="serfHealth",node="consul",status="warning"} 0
consul_raft_leader 1
consul_raft_peers 1
consul_serf_lan_members 1
consul_up 1

当 exporter 成功连接并从 Consul 收集指标时,它会将 consul_up 指标设置为 1。我们还可以看到 consul_catalog_services 指标,它告诉我们 Consul 知道有一个服务,与我们在 Web 界面中看到的内容一致。

现在我们可以断开与 consul 实例的连接,并使用以下命令连接到 prometheus 实例:

exit
vagrant ssh prometheus

如果我们查看 Prometheus 服务器配置,我们将发现以下内容:

vagrant@prometheus:~$ cat /etc/prometheus/prometheus.yml 
...

 - job_name: 'consul_sd'
 consul_sd_configs:
 - server: http://consul:8500
 datacenter: dc1
 relabel_configs:
 - source_labels: [__meta_consul_service]
 target_label: job
...

该配置允许 Prometheus 连接到 Consul API 地址(可通过 http://192.168.42.11:8500 访问),并通过 relabel_configs 重写 job 标签,使其与服务名称(如 __meta_consul_service 标签中所示)匹配。如果我们检查 Prometheus 的 Web 界面,我们可以看到以下信息:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/2db261a5-76d8-4f3e-ad1f-30889debfefc.png

图 12.8:Prometheus /service-discovery 端点显示 Consul 默认服务

现在,到了有趣的部分:让我们通过将 consul_exporter 定义为 Consul 中的一个服务,自动添加一个抓取目标。该章节的资源中提供了一个包含 Consul 服务配置的 JSON 有效负载,我们可以通过 Consul API 将其添加。有效负载位于以下路径:

vagrant@prometheus:~$ cat /vagrant/chapter12/configs/consul_exporter/payload.json 
{
 "ID": "consul-exporter01",
 "Name": "consul-exporter",
 "Tags": [
 "consul",
 "exporter",
 "prometheus"
 ],
 "Address": "consul",
 "Port": 9107
}

使用以下指令,我们将通过 HTTP API 将此新服务添加到 Consul 的服务目录中:

vagrant@prometheus:~$ curl --request PUT 
--data @/vagrant/chapter12/configs/consul_exporter/payload.json 
http://consul:8500/v1/agent/service/register

运行此命令后,我们可以通过查看 Consul Web 界面来验证新服务是否已被添加,该界面将显示类似如下内容:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/0250a371-7438-4945-b9b1-b19b321477d4.png

图 12.9:Consul Web 界面显示 consul-exporter 服务

最后,我们可以检查 Prometheus /service-discovery 端点,查看是否有一个新目标,从而证明 Consul 服务发现功能正常:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/6953826d-6c14-489b-9767-5af2237bc548.png

图 12.10:Prometheus /service-discovery 端点显示 consul-exporter 目标

如果我们再次查看 consul_catalog_services 指标,我们会发现它已经变为 2。由于我们现在正在 Prometheus 中收集 consul_exporter 指标,我们可以使用 promtool 查询其当前值:

vagrant@prometheus:~$ promtool query instant http://localhost:9090 'consul_catalog_services'
consul_catalog_services{instance="consul:9107", job="consul-exporter"} => 2 @[1555252393.681]

Consul 标签可以用于通过 relabel_configs 配置抓取任务,适用于有不同要求的服务,例如在某个标签存在时更改指标路径,或者使用一个标签来标记是否使用 HTTPS 进行抓取。__meta_consul_tags 标签的值在开始和结束时有逗号分隔符,以便更容易进行匹配;这样,你就不需要根据你尝试匹配的标签在字符串中的位置来特别处理正则表达式。一个实际的例子如下:

...
    relabel_configs: 
      - source_labels: [__meta_consul_tags]
        regex: .*,exporter,.*
        action: keep
...

这将仅保留在 Consul 中注册的带有 exporter 标签的服务,丢弃其他所有服务。

在这个例子中,我们将不再使用前几章中的 Prometheus Kubernetes Operator,而是将重点放在 Prometheus 原生服务发现集成上,以便与这个容器编排平台配合使用。用于在 Kubernetes 测试环境中启动 Prometheus 的清单文件可以在以下路径中找到,相对于代码仓库的根路径:

cd ./chapter12/provision/kubernetes/

以下步骤将确保在新 Kubernetes 环境中配置所有所需的软件,从而使我们能够专注于服务发现组件。

验证没有其他环境在运行:

minikube status
minikube delete

启动一个空的 Kubernetes 环境:

minikube start 
  --cpus=2 
  --memory=3072 
  --kubernetes-version="v1.14.0" 
  --vm-driver=virtualbox 
  --extra-config=kubelet.authentication-token-webhook=true 
  --extra-config=kubelet.authorization-mode=Webhook

我们提供给 minikube 的额外配置是必需的,以便 Prometheus 能够通过服务帐户令牌与 kubelets 进行交互。当前一个命令完成时,一个新的 Kubernetes 环境将准备就绪。然后我们可以按照以下说明部署我们的配置:

kubectl apply -f ./bootstrap/
kubectl rollout status deployment/prometheus-deployment -n monitoring

前面的命令应用了几个清单文件,其中包括创建一个名为 monitoring 的命名空间、一个 ServiceAccount,以及所有所需的 RBAC 配置,以便 Prometheus 可以查询 Kubernetes API。还包括一个包含 Prometheus 服务器配置的 ConfigMap,该配置可以在 bootstrap/03_prometheus-configmap.yaml 中找到。它定义了多个 Kubernetes 组件的抓取任务,这些任务通过服务发现功能进行目标定位,正如以下片段所示:

$ cat bootstrap/03_prometheus-configmap.yaml
...
data:
 prometheus.yml: |
 scrape_configs:
...
 - job_name: kubernetes-pods
 kubernetes_sd_configs:
 - role: pod
 relabel_configs:
 - action: keep
 regex: hey
 source_labels:
 - __meta_kubernetes_pod_label_app
...

我们可以通过执行以下命令打开 Prometheus Web 界面:

minikube service prometheus-service -n monitoring

通过进入 /service-discovery 端点的服务发现部分,我们可以看到,尽管发现了几个 pod,但没有一个匹配 app 标签的 hey 标签值,因此它们被丢弃:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/17bff879-85f8-4b7b-b064-e3584277df90.png

图 12.11:Prometheus /service-discovery 端点显示 kubernetes-pods 任务丢弃的目标

现在是时候添加一些带有正确标签/值对的新 pod,以触发我们的服务发现配置。我们可以通过运行以下命令来继续部署 hey 应用程序,然后跟踪部署状态:

kubectl apply -f ./services/
kubectl rollout status deployment/hey-deployment -n default

在成功部署后,我们可以再次访问 Prometheus Web 界面,在 /service-discovery 端点查看,发现现在在 kubernetes-pods 抓取任务中有三个活动目标。以下截图展示了其中一个目标及其所有由 Kubernetes API 提供的标签:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/b49d8dc0-ea63-4617-a91c-9020c6e8b6d1.png

图 12.12:Prometheus /service-discovery 端点显示为 kubernetes-pods 任务发现的目标

测试完成后,您可以通过执行以下命令删除此 Kubernetes 环境:

minikube delete

这种服务发现方式使我们能够自动跟踪多个 Kubernetes 对象,而无需手动更改 Prometheus 配置。该环境使我们能够测试各种设置,并为定制 Kubernetes 服务发现以满足我们的特定需求提供了基础。

即便有许多现成的服务发现选项,仍有许多其他系统/提供者不被默认支持。在这些情况下,我们有几种选择:

  • 提交一个功能请求,请求 Prometheus 支持该特定的服务发现,并依赖社区和/或维护者来实现它。

  • 在 Prometheus 中自行实现服务发现集成,并维护一个分支或将其贡献回项目。

  • 找到一种方法,以最少的维护工作和时间成本将所需的目标导入 Prometheus 实例,并且无需依赖 Prometheus 路线图来完成这项工作。

前两种选项不太理想,因为它们要么超出了我们的控制范围,要么维护起来很麻烦。此外,在没有较大兴趣和支持社区的情况下将额外的服务发现集成到 Prometheus 中,会给维护者带来过多的支持负担,而维护者目前并未接受任何新的集成。幸运的是,有一种方法可以轻松与任何类型的服务或实例目录进行集成,而无需维护昂贵的分支或创造性的黑客解决方案。在接下来的部分,我们将探讨如何将我们自己的服务发现与 Prometheus 集成。

集成自定义服务发现的推荐方式是依赖基于文件的服务发现file_sd。集成实现的方式是通过一个进程(本地或远程、定时或持续运行)查询数据源(目录/API/数据库/配置管理数据库CMDB)),然后将所有目标及其相应标签写入一个 Prometheus 可访问的路径上的 JSON 或 YAML 格式文件。然后,Prometheus 会通过磁盘监控或定时任务自动读取该文件,从而使你能够动态更新可供抓取的目标。

以下图表展示了上述工作流程:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/aedacf33-2fb8-4089-b3c2-81c1062d1ec8.png

图 12.13:自定义服务发现流程

这种方法足够通用,可以满足大多数(如果不是全部)所需的用例,使得以简单明了的方式构建自定义服务发现机制成为可能。

社区驱动的file_sd集成可以在 prometheus.io/docs/operating/integrations/#file-service-discovery 找到。

现在我们已经知道了这种集成方式的工作原理,接下来让我们直接动手,开始构建我们自己的服务发现。

如前所述,构建自定义服务发现似乎是一个可管理的任务。我们需要查询数据并将数据按照标准格式写入文件。为了简化我们的生活,Prometheus 团队提供了一个适配器,可以消除创建新服务发现集成的大部分样板代码。这个适配器仅适用于 Go 编程语言,因为它重用了来自 Prometheus 本身的一些代码。这种设计使得一些维护较少的服务发现集成可以不太费力地迁移到独立服务中,同时也简化了使用适配器构建的外部发现集成迁移到主 Prometheus 二进制文件中的过程,这些集成都已经被证明是有效的。请注意,并没有阻止您使用您选择的语言来构建这样的集成,但出于遵循推荐方法的考虑,我们将坚持使用 Go 和这个发现适配器。关于如何在 Go 中编程的详细说明超出了本书的范围。

在主 Prometheus 代码库中,我们可以找到适配器的代码,以及一个使用 Consul 的示例,有趣的是,我们已经在我们的测试环境中设置了 Consul。正如我们现在所知,Consul 集成在 Prometheus 中得到了原生支持;然而,假装它没有得到支持,我们需要与之集成。在接下来的主题中,我们将详细讨论如何将所有内容整合在一起并构建一个自定义的服务发现。

自定义服务发现示例的代码可在github.com/prometheus/prometheus/tree/v2.9.1/documentation/examples/custom-sd找到。

作为高层次概述,适配器负责启动和管理我们自定义服务发现代码,消费它生成的目标组,并将它们转换为file_sd格式,并确保在需要时将 JSON 数据写入文件。使用这个适配器编写服务发现集成时,不需要修改其代码,因此它可以作为一个库直接导入。为了更清楚地解释适配器的行为,我们将解释一些低级细节,以便在实现我们自己的发现时其行为变得清晰。

以下代码片段说明了我们需要从我们的代码中调用的适配器的Run函数。这个函数将负责在它自己的 goroutine 中启动discovery.Managera.manager.Run),指示它运行我们的发现实现(a.disc),最后在另一个 goroutine 中运行适配器本身(a.runCustomSD):

// Run starts a Discovery Manager and the custom service discovery implementation.
func (a *Adapter) Run() {
    go a.manager.Run()
    a.manager.StartCustomProvider(a.ctx, a.name, a.disc)
    go a.runCustomSD(a.ctx)
}

启动后,适配器从 Manager 提供的通道消费,更新我们的代码将生成的目标组。当有更新时,它将把目标组转换为 file_sd 格式,并验证自上次更新以来是否有任何更改。如果有更改,它将保存新的目标组以备将来比较,并将它们以 JSON 格式写入输出文件。这意味着应该在每次更新中发送完整的目标组列表;没有通过通道发送的组将从生成的发现文件中删除。

file_sd 适配器源代码可在 github.com/prometheus/prometheus/blob/v2.9.2/documentation/examples/custom-sd/adapter/adapter.go 找到。

现在我们已经了解了适配器的工作原理,让我们看看我们需要实现哪些内容来使我们的自定义服务发现工作。正如我们之前看到的,适配器使用了 discovery.Manager,因此我们需要提供一个 Discoverer 接口的实现,以便它可以运行我们的发现。该接口的样式如下:

type Discoverer interface 

Discoverer 接口文档可在 godoc.org/github.com/prometheus/prometheus/discovery#Discoverer 找到。

这意味着我们只需要实现 Run 函数,在其中我们将在循环中运行我们发现的逻辑,生成适当的目标组,并将它们通过 up 通道发送到适配器。ctx 上下文存在是为了让我们知道何时需要停止。我们实现的代码将定期收集来自我们数据源的所有可用目标/元数据。在本例中,我们使用的是 Consul,它要求我们首先获取服务列表,然后针对每个服务查询支持它的实例及其元数据以生成标签。如果发生故障,我们不会通过通道发送任何更新,因为提供过时的数据比提供不完整或不正确的数据更好。

最后,在我们的 main 函数中,我们只需要实例化一个新的适配器,并提供一个后台上下文、输出文件的名称、我们发现实现的名称、实现 Discoverer 接口的发现对象,以及一个 log.Logger 实例:

func main() {
...
  sdAdapter := adapter.NewAdapter(ctx, *outputFile, "exampleSD", disc, logger)
  sdAdapter.Run()
...
}

此适配器实现的工作示例可在 github.com/prometheus/prometheus/blob/v2.9.2/documentation/examples/custom-sd/adapter-usage/main.go 找到。

下一步是部署并集成这个新创建的服务发现提供者与 Prometheus,这是我们将在接下来的部分中完成的。

为了亲自观察自定义服务发现的表现,我们将依赖于我们的测试环境。custom-sd 二进制文件作为自定义服务发现的示例,重新创建了 Consul 服务发现集成,已经与 Prometheus 一起部署并准备使用。加上 Consul 部署,我们在测试环境中具备了所有必要的组件,可以看到一切如何协同工作。

custom-sd 可以在配置了 Go 开发环境的机器上通过执行以下命令构建:go get github.com/prometheus/prometheus/documentation/examples/custom-sd/adapter-usage

首先,我们需要确保我们已连接到 prometheus 实例。我们可以使用以下命令:

vagrant ssh prometheus

接下来,我们可以修改 Prometheus 配置,使用 file_sd 作为我们的集成方式。为此,我们必须将原本配置为使用 consul_sd 的抓取任务替换为新的任务。为了简化操作,我们已经将这个更改写入了 /etc/prometheus/ 下的配置文件。要使用它,你只需要将当前配置替换为新的配置:

vagrant@prometheus:~$ sudo mv /etc/prometheus/prometheus_file_sd.yml /etc/prometheus/prometheus.yml

我们感兴趣的抓取任务如下:

- job_name: 'file_sd'
    file_sd_configs:
      - files:
        - custom_file_sd.json

为了让 Prometheus 知道这些更改,我们必须重新加载它:

vagrant@prometheus:~$ sudo systemctl reload prometheus

我们还应该确保 Consul 服务器已配置了我们之前添加的 consul-exporter。如果你错过了这一步,可以通过运行以下代码来添加:

vagrant@prometheus:~$ curl --request PUT 
--data @/vagrant/chapter12/configs/consul_exporter/payload.json 
http://consul:8500/v1/agent/service/register

如果我们查看 Prometheus 的 Web 界面,我们会看到类似如下的内容:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/c1006783-1072-469f-9c55-4cea4df74adf.png

12.14:Prometheus /service-discovery 端点没有任何 file_sd 目标

现在我们可以开始尝试 custom-sd 应用程序了。我们需要指定 Consul API 地址和 Prometheus 配置为读取的输出文件路径。以下命令将完成这项工作,并确保正确的用户创建文件,以便 Prometheus 进程能够访问它:

vagrant@prometheus:~$ sudo -u prometheus -- custom-sd --output.file="/etc/prometheus/custom_file_sd.json" --listen.address="consul:8500"

现在我们已经启动了自定义服务发现。如果我们返回到 Prometheus 的 Web 界面,并访问 /service-discovery 端点,我们将能够看到已发现的目标:

https://github.com/OpenDocCN/freelearn-devops-pt4-zh/raw/master/docs/hsn-infra-mnt-prom/img/63410416-ab64-4dd4-8180-69d269bc10dc.png

12.15:Prometheus /service-discovery 端点显示已发现的目标

我们还可以检查由 custom-sd 创建的文件,并验证其内容,如下所示(输出已被压缩以节省空间):

vagrant@prometheus:~$ sudo cat /etc/prometheus/custom_file_sd.json 
[
 }]

就这样!你现在已经成功搭建了一个自定义服务发现系统,并且与 Prometheus 完全集成,使用基于文件的服务发现机制。更为严谨的部署方式是将 custom-sd 服务作为守护进程运行。如果你更喜欢使用脚本语言,可以选择编写一个生成发现文件并退出的服务发现脚本,在这种情况下,将其作为定时任务运行是一个可行的选择。最后的建议是,你可以让你的配置管理软件按计划动态生成发现文件。

在本章中,我们有机会了解为什么服务发现对于以合理的方式管理日益增长的基础设施至关重要。Prometheus 提供了几种开箱即用的服务发现选项,这些选项可以帮助你以非常快速和友好的方式开始使用。我们介绍了 Prometheus 提供的可用服务发现选项,并向你展示了从中可以期待的内容。然后,我们通过使用 Consul 和 Kubernetes 的几个例子,具体化了之前介绍的概念。最后,我们介绍了如何通过使用推荐的方法和依赖 file_sd 将自定义服务发现与 Prometheus 集成。

在下一章中,我们将介绍如何扩展和联邦 Prometheus。

  1. 为什么在 Prometheus 中使用服务发现机制?

  2. 当你使用云服务提供商的服务发现时,设置集成的主要要求是什么?

  3. 基于 DNS 的服务发现集成支持哪些类型的记录?

  4. 在 Kubernetes 服务发现集成中,角色概念的作用是什么?

  5. 当你构建自定义服务发现时,你将依赖哪些可用的集成?

  6. file_sd 中配置的目标文件更新时,你需要重新加载 Prometheus 吗?

  7. 构建自定义服务发现的推荐方式是什么?

  • Prometheus 服务发现配置prometheus.io/docs/prometheus/latest/configuration/configuration
赞(0)
未经允许不得转载:上海聚慕医疗器械有限公司 » HA8180是什么Prometheus 基础设施监控实用指南(三)

登录

找回密码

注册