Prometheus 0.14.0 中的高级服务发现

2015年6月1日作者 Fabian Reinartz, Julius Volz

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

在用户侧,Prometheus 现在支持新的服务发现机制。除了 DNS-SRV 记录外,它现在还原生支持 Consul,并且一个基于文件的接口允许您连接自己的发现机制。未来,我们计划向 Prometheus 添加其他常见的服务发现机制。

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

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

Prometheus 与目标

为了更好地理解这篇博客文章,我们首先需要了解 Prometheus 是如何为目标(target)添加标签(label)的。

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

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

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

注意:在内部,即使是目标的地址也存储在一个特殊的 __address__ 标签中。这在高级标签操作(重打标签)中非常有用,我们稍后会看到。以 __ 开头的标签不会出现在最终的时间序列中。

抓取配置与重打标签

除了从 ASCII 协议缓冲区格式迁移到 YAML 之外,Prometheus 配置的一个根本性变化是从按作业(job)配置转变为更通用的抓取配置(scrape configuration)。虽然对于简单的设置,这两者几乎是等效的,但抓取配置在更高级的用例中提供了更大的灵活性。

每个抓取配置都定义了一个作业名称,该名称作为 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'

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

重打标签的工作原理如下:

  • 定义一个源标签列表。
  • 对于每个目标,将这些标签的值用分隔符连接起来。
  • 使用正则表达式匹配生成的字符串。
  • 根据这些匹配结果,将一个新值分配给另一个标签。

可以为每个抓取配置定义多个重打标签规则。一个将两个标签合并为一个的简单规则如下所示:

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 记录名称。如果我们有像 telemetry.<zone>.<job>.srv.example.org 这样的结构化 SRV 记录名称,我们可以从中提取相关的标签:

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 节点的标签(tag)会通过一个可配置的分隔符连接起来,并通过 __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 代理中发现给定的服务。结果是,我们获得了四个作业(authapiload-balancerpostgres)的指标。如果一个节点有 productioncanary 的 Consul 标签,一个相应的 group 标签会被分配给该目标。每个目标的 instance 标签被设置为 Consul 提供的节点名称。

关于通过 Consul 进行服务发现的所有配置参数的完整文档可以在 Prometheus 网站上找到。

自定义服务发现

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

通过这种机制,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 表中。(提示:你可能不想用这种方式来进行服务发现。)

每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 格式。对于较小的配置,我们建议手动升级,以便熟悉新格式并保留注释。