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 如何标记目标。
在配置文件中有多种设置目标标签的地方。它们按以下顺序应用,后一个阶段会覆盖前一个阶段设置的任何标签。
- 全局标签,分配给 Prometheus 实例抓取的每个目标。
job标签,配置为每个抓取配置的默认值。- 在抓取配置中为每个目标组设置的标签。
- 通过重标记进行的高级标签操作。
每个阶段都会覆盖前面阶段的任何冲突标签。最终,我们得到一个描述单个目标的扁平标签集。然后,这些标签会附加到从该目标抓取的每个时间序列上。
注意:在内部,即使是目标的地址也存储在一个特殊的 __address__ 标签中。这在进行高级标签操作(重标记)时可能很有用,我们稍后会看到。以 __ 开头的标签不会出现在最终的时间序列中。
抓取配置和重标记
除了从 ASCII Protocol Buffer 格式迁移到 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 记录名称。如果我们有像 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 记录的来源,将 zone 和 job 标签附加到每个目标。
通过 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(auth、api、load-balancer 和 postgres)的指标。如果节点具有 production 或 canary 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']
现在缺少的是一个将文件写入此目录的程序。出于示例的目的,假设我们将所有不同 job 的实例都放在一个反规范化的 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 格式。对于较小的配置,我们建议手动升级以熟悉新格式并保留注释。