Prometheus 0.14.0 中的高级服务发现

本周我们发布了 Prometheus v0.14.0 —— 一个包含许多期待已久的新增功能和改进的版本。

在用户方面,Prometheus 现在支持新的服务发现机制。除了 DNS-SRV 记录外,它现在还开箱即用地支持 Consul,并且基于文件的接口允许您连接自己的发现机制。随着时间的推移,我们计划向 Prometheus 添加其他常见的服务发现机制。

除了许多较小的修复和改进外,您现在还可以通过向 Prometheus 进程发送 SIGHUP 在运行时重新加载您的配置。有关更改的完整列表,请查看 此版本的更新日志

在这篇博文中,我们将仔细研究内置的服务发现机制,并提供一些实际示例。作为额外的资源,请参阅 Prometheus 的配置文档

Prometheus 和目标

为了正确理解这篇博文,我们首先需要了解 Prometheus 如何标记目标。

在配置文件中有多个地方可以设置目标标签。它们按以下顺序应用,后面的阶段会覆盖早期阶段设置的任何标签

  1. 全局标签,分配给 Prometheus 实例抓取的每个目标。
  2. job 标签,配置为每个抓取配置的默认值。
  3. 在抓取配置中为每个目标组设置的标签。
  4. 通过 重新标记 进行高级标签操作。

每个阶段都会覆盖早期阶段的任何冲突标签。最终,我们得到一组扁平的标签,用于描述单个目标。这些标签随后附加到从此目标抓取的每个时间序列。

注意:在内部,甚至目标的地址也存储在特殊的 __address__ 标签中。这在高级标签操作(重新标记)期间可能很有用,我们稍后会看到。以 __ 开头的标签不会出现在最终的时间序列中。

抓取配置和重新标记

除了从 ASCII 协议缓冲区格式迁移到 YAML 之外,Prometheus 配置的一个根本性变化是从基于 job 的配置更改为更通用的抓取配置。虽然对于简单的设置来说,两者几乎是等效的,但抓取配置在更高级的用例中允许更大的灵活性。

每个抓取配置定义一个 job 名称,该名称用作 job 标签的默认值。然后可以为整个目标组或单个目标重新定义 job 标签。例如,我们可以定义两个目标组,每个目标组都为一个 job 定义目标。为了使用相同的参数抓取它们,我们可以将它们配置如下

scrape_configs:
- job_name: 'overwritten-default'

  scrape_interval: 10s
  scrape_timeout:  5s

  target_groups:
  - targets: ['10.1.200.130:5051', '10.1.200.134:5051']
    labels:
      job: 'job1'

  - targets: ['10.1.200.130:6220', '10.1.200.134:6221']
    labels:
      job: 'job2'

通过名为 重新标记 的机制,可以在每个目标级别删除、创建或修改任何标签。这实现了细粒度的标签,还可以考虑来自服务发现的元数据。重新标记是标签分配的最后阶段,会覆盖之前设置的任何标签。

重新标记的工作方式如下

  • 定义源标签列表。
  • 对于每个目标,这些标签的值用分隔符连接起来。
  • 针对结果字符串匹配正则表达式。
  • 基于这些匹配项,为另一个标签分配新值。

可以为每个抓取配置定义多个重新标记规则。一个将两个标签压缩为一个标签的简单规则如下所示

relabel_configs:
- source_labels: ['label_a', 'label_b']
  separator:     ';'
  regex:         '(.*);(.*)'
  replacement:   '${1}-${2}'
  target_label:  'label_c'

此规则将具有以下标签集的目标转换

{
  "job": "job1",
  "label_a": "foo",
  "label_b": "bar"
}

...转换为具有以下标签集的目标

{
  "job": "job1",
  "label_a": "foo",
  "label_b": "bar",
  "label_c": "foo-bar"
}

然后,您还可以在额外的重新标记步骤中删除源标签。

您可以在配置文档中阅读有关重新标记以及如何使用它来过滤目标的更多信息。

在接下来的章节中,我们将看到如何在使用服务发现时利用重新标记。

通过 DNS-SRV 记录进行发现

从一开始,Prometheus 就支持通过 DNS-SRV 记录进行目标发现。相应的配置如下所示

job {
  name: "api-server"
  sd_name: "telemetry.eu-west.api.srv.example.org"
  metrics_path: "/metrics"
}

Prometheus 0.14.0 允许您在单个抓取配置中指定要查询的多个 SRV 记录,并提供服务发现特定的元信息,这些信息在重新标记阶段很有用。

在查询 DNS-SRV 记录时,名为 __meta_dns_name 的标签会附加到每个目标。其值设置为返回该目标的 SRV 记录名称。如果我们有结构化的 SRV 记录名称,例如 telemetry.<zone>.<job>.srv.example.org,我们可以从中提取相关的标签名称

scrape_configs:
- job_name: 'myjob'

  dns_sd_configs:
  - names:
    - 'telemetry.eu-west.api.srv.example.org'
    - 'telemetry.us-west.api.srv.example.org'
    - 'telemetry.eu-west.auth.srv.example.org'
    - 'telemetry.us-east.auth.srv.example.org'

  relabel_configs:
  - source_labels: ['__meta_dns_name']
    regex:         'telemetry\.(.+?)\..+?\.srv\.example\.org'
    target_label:  'zone'
    replacement:   '$1'
  - source_labels: ['__meta_dns_name']
    regex:         'telemetry\..+?\.(.+?)\.srv\.example\.org'
    target_label:  'job'
    replacement:   '$1'

这将根据每个目标来自的 SRV 记录,将 zonejob 标签附加到每个目标。

通过 Consul 进行发现

现在原生支持通过 Consul 进行服务发现。可以通过定义 Consul 代理的访问参数以及我们想要查询目标的 Consul 服务列表来配置它。

每个 Consul 节点的标签都通过可配置的分隔符连接起来,并通过 __meta_consul_tags 标签公开。还提供了各种其他 Consul 特定的元标签。

可以使用简单的 consul_sd_config 和重新标记规则来实现抓取给定服务列表的所有实例

scrape_configs:
- job_name: 'overwritten-default'

  consul_sd_configs:
  - server:   '127.0.0.1:5361'
    services: ['auth', 'api', 'load-balancer', 'postgres']

  relabel_configs:
  - source_labels: ['__meta_consul_service']
    regex:         '(.*)'
    target_label:  'job'
    replacement:   '$1'
  - source_labels: ['__meta_consul_node']
    regex:         '(.*)'
    target_label:  'instance'
    replacement:   '$1'
  - source_labels: ['__meta_consul_tags']
    regex:         ',(production|canary),'
    target_label:  'group'
    replacement:   '$1'

这会从本地 Consul 代理发现给定的服务。结果,我们获得了四个 job(authapiload-balancerpostgres)的指标。如果节点具有 productioncanary Consul 标签,则会将相应的 group 标签分配给目标。每个目标的 instance 标签都设置为 Consul 提供的节点名称。

有关通过 Consul 进行服务发现的所有配置参数的完整文档,请访问 Prometheus 网站

自定义服务发现

最后,我们添加了一个基于文件的接口,用于集成您的自定义服务发现或其他尚未开箱即用的常见机制。

通过此机制,Prometheus 监视一组定义目标组的目录或文件。每当这些文件中的任何一个发生更改时,都会从文件中读取目标组列表,并提取抓取目标。现在,我们的工作是编写一个小型的桥接程序,作为 Prometheus 的助手运行。它从任意服务发现机制检索更改,并将目标信息作为目标组列表写入受监视的文件。

这些文件可以是 YAML 格式

- targets: ['10.11.150.1:7870', '10.11.150.4:7870']
  labels:
    job: 'mysql'

- targets: ['10.11.122.11:6001', '10.11.122.15:6002']
  labels:
    job: 'postgres'

...或 JSON 格式

[
  {
    "targets": ["10.11.150.1:7870", "10.11.150.4:7870"],
    "labels": {
      "job": "mysql"
    }
  },
  {
    "targets": ["10.11.122.11:6001", "10.11.122.15:6002"],
    "labels": {
      "job": "postgres"
    }
  }
]

现在,我们配置 Prometheus 以在其工作目录中监视 tgroups/ 目录中的所有 .json 文件

scrape_configs:
- job_name: 'overwritten-default'

  file_sd_configs:
  - names: ['tgroups/*.json']

现在缺少的是一个向此目录写入文件的程序。为了本示例的目的,我们假设我们在一个非规范化的 MySQL 表中拥有不同 job 的所有实例。(提示:您可能不想以这种方式进行服务发现。)

每 30 秒,我们从 MySQL 表中读取所有实例,并将生成的目标组写入 JSON 文件。请注意,我们不必保持状态,无论任何目标或其标签是否已更改。Prometheus 将自动检测更改并将其应用于目标,而不会中断其抓取周期。

import os, time, json

from itertools import groupby
from MySQLdb import connect


def refresh(cur):
    # Fetch all rows.
    cur.execute("SELECT address, job, zone FROM instances")

    tgs = []
    # Group all instances by their job and zone values.
    for key, vals in groupby(cur.fetchall(), key=lambda r: (r[1], r[2])):
        tgs.append({
            'labels': dict(zip(['job', 'zone'], key)),
            'targets': [t[0] for t in vals],
        })

    # Persist the target groups to disk as JSON file.
    with open('tgroups/target_groups.json.new', 'w') as f:
        json.dump(tgs, f)
        f.flush()
        os.fsync(f.fileno())

    os.rename('tgroups/target_groups.json.new', 'tgroups/target_groups.json')


if __name__ == '__main__':
    while True:
        with connect('localhost', 'root', '', 'test') as cur:
            refresh(cur)
        time.sleep(30)

虽然 Prometheus 不会将任何格式错误的文件更改应用于文件,但最佳实践是通过重命名原子地更新文件,就像我们在示例中所做的那样。还建议根据逻辑分组将大量目标组拆分为多个文件。

结论

借助 DNS-SRV 记录和 Consul,Prometheus 现在原生支持两种主要的服务发现方法。我们已经看到,重新标记是利用服务发现机制提供的元数据的强大方法。

请务必查看新的配置文档,以将您的 Prometheus 设置升级到新版本,并了解其他配置选项,例如基本 HTTP 身份验证和通过重新标记进行目标过滤。

我们提供了一个迁移工具,可将您现有的配置文件升级到新的 YAML 格式。对于较小的配置,我们建议手动升级以熟悉新格式并保留注释。