Prometheus 通过 consul 分布式集群实现自动服务发现
文章目录
1、Consul 介绍
Consul 是基于 GO 语言开发的开源工具,主要面向分布式,服务化的系统提供服务注册、服务发现和配置管理的功能。Consul 提供服务注册/发现、健康检查、Key/Value存储、多数据中心和分布式一致性保证等功能。Prometheus 通过 Consul 可以很方便的实现服务自动发现和维护,同时 Consul 支持分布式集群部署,将大大提高了稳定性,通过 Prometheus 跟 Consul 集群二者结合起来,能够高效的进行数据维护同时保证系统稳定。
2、环境、软件准备
本次演示环境,我是在虚拟机上安装 Linux 系统来执行操作,以下是安装的软件及版本:
- Oracle VirtualBox: 5.1.20 r114628 (Qt5.6.2)- System: CentOS Linux release 7.3.1611 (Core)- Docker: 18.06.1-ce- Prometheus: v2.11.1- Consul: 1.6.1
注意:这里为了方便启动 Prometheus,我使用 Docker 方式启动,所以本机需要安装好 Docker 环境,这里忽略 Docker 的安装过程。其中 Prometheus 安装配置,可以参照之前文章 ,这里着重介绍一下如何配置 Consul 分布式集群,以及使用 nginx 来负载均衡 Consul 集群,最后分别验证配置 Prometheus 基于 Consul 集群来实现自动服务发现。
3、Consul 分布式集群搭建
Consul 单机安装很方便, 提供各个系统版本二进制安装包,解压安装即可,可以参照之前文章 文章来安装。这里我们要搭建 Consul 分布式集群,既然是分布式集群,那么肯定至少得部署到三台机器上,组成一个集群,苦于手上没有那么多的机器,我们只能在一台机器上部署三个 Consul 服务来模拟 “分布式” 集群,Consul 默认以 client 方式启动,这里我们采用 Server 方式来启动实例,通过不同的端口来区分不同的服务,集群实例如下:
- 172.30.12.100:8500 node1 leader-
- 172.30.12.100:8510 node2 follower
- 172.30.12.100:8520 node2 follower
下载最新版二进制安装包,解压后并拷贝到/usr/local/bin
目录下
$ cd /root/prometheus/consul
$ wget https://releases.hashicorp.com/consul/1.6.1/consul_1.6.1_linux_amd64.zip
$ unzip consul_1.6.1_linux_amd64.zip
$ mv consul /usr/local/bin
集群启动三个实例,这里因为在一台机器上,直接使用 consul anget -server -bootstrap-expect=3 -data-dir=xxx...
命令来启动,稍显麻烦且不直观,这里我们采用配置文件的方式分别启动实例。配置 node1 实例,新建配置文件 consul01.json
如下 :
{
"datacenter": "dc1",
"data_dir": "/root/prometheus/consul/consul01",
"log_level": "INFO",
"server": true,
"node_name": "node1",
"ui": true,
"bind_addr": "172.30.12.100",
"client_addr": "0.0.0.0",
"advertise_addr": "172.30.12.100",
"bootstrap_expect": 3,
"ports":{
"http": 8500,
"dns": 8600,
"server": 8300,
"serf_lan": 8301,
"serf_wan": 8302
}
}
说明一下参数:
- datacenter:数据中心名称
- data_dir:数据存放本地目录
- log_level:输出的日志级别
- server:以 server 身份启动实例,不指定默认为 client
- node_name:节点名称,集群中每个 node 名称不能重复,默认情况使用节点 hostname- ui:指定是否可以访问 UI 界面
- bind_addr:监听的 IP 地址,指 Consul 监听的地址,该地址必须能够被集群中的所有节点访问,默认监听 0.0.0.0
- client_addr:客户端监听地址,0.0.0.0 表示所有网段都可以访问
- advertise_addr:集群广播地址
- bootstrap_expect:集群要求的最少成员数量
- ports:该参数详细配置各个服务端口,如果想指定其他端口,可以修改这里。
后台启动一下 node1 Consul 服务,执行如下命令启动:
nohup consul agent -config-dir=consul01.json > ./consul01.log 2>&1 &
查看服务启动日志会发现如下输出:
ootstrap_expect > 0: expecting 3 servers
==> Starting Consul agent...
Version: 'v1.6.1'
Node ID: '18450af8-64a3-aa0f-2a6c-4c2fc6e35a4b'
Node name: 'node1'
Datacenter: 'dc1' (Segment: '<all>')
Server: true (Bootstrap: false)
Client Addr: [0.0.0.0] (HTTP: 8500, HTTPS: -1, gRPC: -1, DNS: 8600)
Cluster Addr: 172.30.12.100 (LAN: 8301, WAN: 8302)
Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false, Auto-Encrypt-TLS: false
==> Log data will now stream in as it occurs:
2020/03/30 17:03:18 [INFO] raft: Initial configuration (index=0): []
2020/03/30 17:03:18 [INFO] raft: Node at 172.30.12.100:8300 [Follower] entering Follower state (Leader: "")
2020/03/30 17:03:18 [INFO] serf: EventMemberJoin: node1.dc1 172.30.12.100
2020/03/30 17:03:18 [INFO] serf: EventMemberJoin: node1 172.30.12.100
2020/03/30 17:03:18 [INFO] agent: Started DNS server 0.0.0.0:8600 (udp)
2020/03/30 17:03:18 [INFO] consul: Handled member-join event for server "node1.dc1" in area "wan"
2020/03/30 17:03:18 [INFO] consul: Adding LAN server node1 (Addr: tcp/172.30.12.100:8300) (DC: dc1)
2020/03/30 17:03:18 [INFO] agent: Started DNS server 0.0.0.0:8600 (tcp)
2020/03/30 17:03:18 [INFO] agent: Started HTTP server on [::]:8500 (tcp)
2020/03/30 17:03:18 [INFO] agent: started state syncer
==> Consul agent running!
==> Newer Consul version available: 1.7.2 (currently running: 1.6.1)
2020/03/30 17:03:23 [WARN] raft: no known peers, aborting election
2020/03/30 17:03:25 [ERR] agent: failed to sync remote state: No cluster leader
我们会看到类似 [ERR] agent: failed to sync remote state: No cluster leader
这样的报错,这是因为目前还没有任何其他实例加入,无法构成一个集群。接下来,按照上边的配置,启动第二个实例 node2,并加入到 node1 集群中,新建配置文件 consul02.json
如下 :
{
"datacenter": "dc1",
"data_dir": "/root/prometheus/consul/consul02",
"log_level": "INFO",
"server": true,
"node_name": "node2",
"ui": true,
"bind_addr": "172.30.12.100",
"client_addr": "0.0.0.0",
"advertise_addr": "172.30.12.100",
"bootstrap_expect": 3,
"ports":{
"http": 8510,
"dns": 8610,
"server": 8310,
"serf_lan": 8311,
"serf_wan": 8312
}
}
注意:由于这里是一台服务器上启动了多个实例,所以必须修改各个服务端口号,否则会报错,这里新启动的 Consul 服务端口为 8510
。启动 node2 Consul 服务,并加入到 node1 Consul 服务中组成集群,启动命令如下:
nohup consul agent -config-dir=consul02.json -join 172.30.12.100:8301 > ./consul02.log 2>&1 &
启动完毕后,此时会发现 node1 的日志输出中会打印如下日志:
2020/03/30 17:14:54 [INFO] serf: EventMemberJoin: node2 172.30.12.100
2020/03/30 17:14:54 [INFO] consul: Adding LAN server node2 (Addr: tcp/172.30.12.100:8310) (DC: dc1)
2020/03/30 17:14:54 [INFO] serf: EventMemberJoin: node2.dc1 172.30.12.100
2020/03/30 17:14:54 [INFO] consul: Handled member-join event for server "node2.dc1" in area "wan"
可以看到 node2 成功加入到 node1 集群,不过接下来它还会一直报错 agent: failed to sync remote state: No cluster leader
,这是为什么?日志明明已经显示 node2 成功加入了,怎么还一直报错呢?这是因为我们创建时指定了 bootstrap_expect
为 3
,那么就必须至少存在 3 个实例才能组成该 Consul 集群。那么,依照以上步骤,继续启动 node3 Consul 服务并添加到 node1 集群中,新建配置文件 consul03.json
如下:
{
"datacenter": "dc1",
"data_dir": "/root/prometheus/consul/consul03",
"log_level": "INFO",
"server": true,
"node_name": "node3",
"ui": true,
"bind_addr": "172.30.12.100",
"client_addr": "0.0.0.0",
"advertise_addr": "172.30.12.100",
"bootstrap_expect": 3,
"ports":{
"http": 8520,
"dns": 8620,
"server": 8320,
"serf_lan": 8321,
"serf_wan": 8322
}
}
启动 node3 Consul 实例命令如下:
nohup consul agent -config-dir=consul03.json -join 172.30.12.100:8301 > ./consul03.log 2>&1 &
此时,我们会发现 node1 日志输出中会打印如下日志:
2020/03/30 17:24:39 [INFO] serf: EventMemberJoin: node3 172.30.12.100
2020/03/30 17:24:39 [INFO] consul: Adding LAN server node3 (Addr: tcp/172.30.12.100:8320) (DC: dc1)
2020/03/30 17:24:39 [INFO] consul: Found expected number of peers, attempting bootstrap: 172.30.12.100:8300,172.30.12.100:8310,172.30.12.100:8320
2020/03/30 17:24:39 [INFO] serf: EventMemberJoin: node3.dc1 172.30.12.100
2020/03/30 17:24:39 [INFO] consul: Handled member-join event for server "node3.dc1" in area "wan"
2020/03/30 17:24:40 [WARN] raft: Heartbeat timeout from "" reached, starting election
2020/03/30 17:24:40 [INFO] raft: Node at 172.30.12.100:8300 [Candidate] entering Candidate state in term 2
2020/03/30 17:24:40 [INFO] raft: Election won. Tally: 2
2020/03/30 17:24:40 [INFO] raft: Node at 172.30.12.100:8300 [Leader] entering Leader state
2020/03/30 17:24:40 [INFO] raft: Added peer b7212be5-7d33-a5b6-0ca8-522f05ab9eeb, starting replication
2020/03/30 17:24:40 [INFO] raft: Added peer 0afe6fcb-3a07-e443-3802-f822ae9df422, starting replication
2020/03/30 17:24:40 [INFO] consul: cluster leadership acquired
2020/03/30 17:24:40 [INFO] consul: New leader elected: node1
2020/03/30 17:24:40 [WARN] raft: AppendEntries to {Voter 0afe6fcb-3a07-e443-3802-f822ae9df422 172.30.12.100:8320} rejected, sending older logs (next: 1)
2020/03/30 17:24:40 [INFO] raft: pipelining replication to peer {Voter 0afe6fcb-3a07-e443-3802-f822ae9df422 172.30.12.100:8320}
2020/03/30 17:24:40 [WARN] raft: AppendEntries to {Voter b7212be5-7d33-a5b6-0ca8-522f05ab9eeb 172.30.12.100:8310} rejected, sending older logs (next: 1)
2020/03/30 17:24:40 [INFO] raft: pipelining replication to peer {Voter b7212be5-7d33-a5b6-0ca8-522f05ab9eeb 172.30.12.100:8310}
2020/03/30 17:24:40 [INFO] consul: member 'node1' joined, marking health alive
2020/03/30 17:24:40 [INFO] consul: member 'node2' joined, marking health alive
2020/03/30 17:24:40 [INFO] consul: member 'node3' joined, marking health alive
2020/03/30 17:24:42 [INFO] agent: Synced node info
node3 成功加入到 node1 集群,整个集群已选出 node1 作为新的 Leader,此时集群状态为 health,那么整个 “分布式” 集群就搭建完毕了。此时,浏览器访问以下任意一个 http://127.0.0.1:8500
、http://127.0.0.1:8510
、http://127.0.0.1:8520
地址,均可打开 Consul Web 管理页面。
我们也可以通过命令行查看集群状态、以及集群成员状态。
# 查看集群状态
$ consul operator raft list-peers
Node ID Address State Voter RaftProtocol
node1 18450af8-64a3-aa0f-2a6c-4c2fc6e35a4b 172.30.12.100:8300 leader true 3
node2 b7212be5-7d33-a5b6-0ca8-522f05ab9eeb 172.30.12.100:8310 follower true 3
node3 0afe6fcb-3a07-e443-3802-f822ae9df422 172.30.12.100:8320 follower true 3
# 查看集群成员状态
$ consul members
Node Address Status Type Build Protocol DC Segment
node1 172.30.12.100:8301 alive server 1.6.1 2 dc1 <all>
node2 172.30.12.100:8311 alive server 1.6.1 2 dc1 <all>
node3 172.30.12.100:8321 alive server 1.6.1 2 dc1 <all>
4、配置 Prometheus 实现自动服务发现
接下来,我们配置 Prometheus 来使用 Consul 集群来实现自动服务发现,目的就是能够将添加的服务自动发现到 Prometheus 的 Targets 中,详细配置说明可以参考之前文章 中的配置,在修改 Prometheus 配置之前,我们需要往 Consul 集群中注册一些数据。首先,我们注册一个 node-exporter-172.30.12.100
的服务,新建 consul-1.json
如下:
{
"ID": "node-exporter",
"Name": "node-exporter-172.30.12.100",
"Tags": [
"node-exporter"
],
"Address": "172.30.12.100",
"Port": 9100,
"Meta": {
"app": "spring-boot",
"team": "appgroup",
"project": "bigdata"
},
"EnableTagOverride": false,
"Check": {
"HTTP": "http://172.30.12.100:9100/metrics",
"Interval": "10s"
},
"Weights": {
"Passing": 10,
"Warning": 1
}
}
# 调用 API 注册服务
$ curl --request PUT --data @consul-1.json http://172.30.12.100:8500/v1/agent/service/register?replace-existing-checks=1
然后,我们再注册一个 cadvisor-exporter-172.30.12.100
的服务,新建 consul-2.json
并执行如下命令:
{<!-- -->
"ID": "cadvisor-exporter",
"Name": "cadvisor-exporter-172.30.12.100",
"Tags": [
"cadvisor-exporter"
],
"Address": "172.30.12.100",
"Port": 8080,
"Meta": {
"app": "docker",
"team": "cloudgroup",
"project": "docker-service"
},
"EnableTagOverride": false,
"Check": {
"HTTP": "http://172.30.12.100:8080/metrics",
"Interval": "10s"
},
"Weights": {
"Passing": 10,
"Warning": 1
}
}
# 调用 API 注册服务
$ curl --request PUT --data @consul-2.json http://172.30.12.100:8500/v1/agent/service/register?replace-existing-checks=1
注册完毕,通过 Consul Web 管理页面可以查看到两服务已注册成功。注意:这里需要启动 node-exporter 及 cadvisor-exporter,否则即使注册成功了,健康检测也不通过,在后边 Prometheus 上服务发现后状态也是不健康的,这里我就不在演示了,可以直接拿 Docker 启动二者即可。

接下来,我们修改 Prometheus 配置如下:
...
- job_name: 'consul-node-exporter'
consul_sd_configs:
- server: '172.30.12.100:8500'
services: []
relabel_configs:
- source_labels: [__meta_consul_tags]
regex: .*node-exporter.*
action: keep
- regex: __meta_consul_service_metadata_(.+)
action: labelmap
- job_name: 'consul-cadvisor-exproter'
consul_sd_configs:
- server: '172.30.12.100:8510'
services: []
- server: '172.30.12.100:8520'
services: []
relabel_configs:
- source_labels: [__meta_consul_tags]
regex: .*cadvisor-exporter.*
action: keep
- action: labelmap
regex: __meta_consul_service_metadata_(.+)
...
启动一下 Prometheus 服务,通过 web 界面看下 Target 中是否能够发现 Consul 中注册的服务。
可以看到,妥妥没有问题,这里 consul-node-exporter
我配置指向了 node1 Consul 服务地址,consul-cadvisor-exproter
配置指向了 node2、node3 Consul 服务,二者都能够正确发现之前注册的服务,因为 Consul 集群数据是保持同步的,无论连接哪一个节点,都能够获取到注册的服务信息,同理,我们也可以指定 consul_sd_configs
分别指向集群所有节点,这样即使某个节点挂掉,也不会影响 Prometheus 从 Consul 集群其他节点获取注册的服务,从而实现服务的高可用。
5、配置 nginx 负载均衡 Consul 集群
虽然我们可以将整个 Consul 集群 IP 添加到 Prometheus 的配置中,从而实现 Prometheus 从 Consul 集群获取注册的服务,实现服务的高可用,但是这里有个问题,如果 Consul 集群节点新增或者减少,那么 Prometheus 配置也得跟着修改了,这样不是很友好,我们可以在 Consul 集群前面使用 nginx 反向代理将请求负载均衡到后端 Consul 集群各节点服务上,这样 Prometheus 只需要配置代理地址即可,后期不需要更改了。
这里为了方便,nginx 也使用容器方式启动,新增配置文件 default.conf
如下:
upstream service_consul {
server 172.30.12.100:8500;
server 172.30.12.100:8510;
server 172.30.12.100:8520;
ip_hash;
}
server {
listen 80;
server_name 172.30.12.100;
index index.html index.htm;
#access_log /var/log/nginx/host.access.log main;
location / {
add_header Access-Control-Allow-Origin *;
proxy_next_upstream http_502 http_504 error timeout invalid_header;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://service_consul;
}
access_log /var/log/consul.access.log;
error_log /var/log/consul.error.log;
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Docker 启动 nginx 服务,启动命令如下:
$ docker run --name nginx-consul -p 80:80 -v /root/prometheus/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro -d nginx
这里就没啥好说的了,启动完成后,可以浏览器访问一下 http://172.30.12.100/ui
,妥妥也是没有问题的。

最后,我们修改一下 Prometheus 配置文件,将 server 指向 172.30.12.100
即可,修改配置如下:
...
- job_name: 'consul-node-exporter'
consul_sd_configs:
- server: '172.30.12.100'
services: []
relabel_configs:
- source_labels: [__meta_consul_tags]
regex: .*node-exporter.*
action: keep
- regex: __meta_consul_service_metadata_(.+)
action: labelmap
- job_name: 'consul-cadvisor-exproter'
consul_sd_configs:
- server: '172.30.12.100'
services: []
relabel_configs:
- source_labels: [__meta_consul_tags]
regex: .*cadvisor-exporter.*
action: keep
- action: labelmap
regex: __meta_consul_service_metadata_(.+)
...
重启 prometheus 服务,验证也是妥妥没问题的。
6. 配置 relabel_configs 实现自定义标签及分类
Prometheus 允许用户在采集任务设置中,通过 relabel_configs
来添加自定义的 Relabeling 的额过程,来对标签进行指定规则的重写。 Prometheus 加载 Targets 后,这些 Targets 会自动包含一些默认的标签,Target 以 __
作为前置的标签是在系统内部使用的,这些标签不会被写入到样本数据中,每次增加 Target 时会自动增加一个 instance 标签,而 instance 标签的内容刚好对应 Target 实例的 __address__
值,这是因为实际上 Prometheus 内部做了一次标签重写处理,默认 __address__
标签设置为 地址,经过标签重写后,默认会自动将该值设置为 instance 标签,所以我们能够在页面看到该标签。
详细 relabel_configs
配置及说明可以参考 官网说明,这里我简单列举一下里面每个 relabel_action
的作用,方便下边演示。
- replace: 根据 regex 的配置匹配
source_labels
标签的值(注意:多个source_label
的值会按照 separator 进行拼接),并且将匹配到的值写入到target_label
当中,如果有多个匹配组,则可以使用 ${1}, ${2} 确定写入的内容。如果没匹配到任何内容则不对target_label
进行重新, 默认为 replace。- keep: 丢弃source_labels
的值中没有匹配到 regex 正则表达式内容的 Target 实例- drop: 丢弃source_labels
的值中匹配到 regex 正则表达式内容的 Target 实例- hashmod: 将target_label
设置为关联的source_label
的哈希模块- labelmap: 根据 regex 去匹配 Target 实例所有标签的名称(注意是名称),并且将捕获到的内容作为为新的标签名称,regex 匹配到标签的的值作为新标签的值- labeldrop: 对 Target 标签进行过滤,会移除匹配过滤条件的所有标签- labelkeep: 对 Target 标签进行过滤,会移除不匹配过滤条件的所有标签
6.2 过滤
我们可以配置 relabel_configs
来实现标签过滤,只加载符合规则的服务。以上边为例,可以通过过滤 __meta_consul_tags
标签为 test
的服务,relabel_config
向 Consul 注册服务的时候,只加载匹配 regex 表达式的标签的服务到自己的配置文件。修改 prometheus.yml
配置如下:
...
- job_name: 'consul-prometheus'
consul_sd_configs:
- server: '172.30.12.167:8500'
services: []
relabel_configs:
- source_labels: [__meta_consul_tags]
regex: .*test.*
action: keep
解释下,这里的 relabel_configs
配置作用为丢弃源标签中 __meta_consul_tags
不包含 test
标签的服务,__meta_consul_tags
对应到 Consul 服务中的值为 "tags": ["test"]
,默认 consul 服务是不带该标签的,从而实现过滤。重启 Prometheus 可以看到现在只获取了 node-exporter-172.30.12.167
这个服务了。
6.1 可视化标签
系统默认标签或者用户自定义标签转换成可视化标签,方便查看及后续 Alertmanager 进行告警规则匹配分组。不过要实现给服务添加自定义标签,我们还得做一下修改,就是在注册服务时,将自定义标签信息添加到 Meta Data 数据中,具体可以参考 这里 官网说明
注册到consul的信息
{
"ID": "250-docker-export",
"Name": "docker_exporter",
"Tags": ["cadvisor-exporter"],
"Address": "192.168.2.250",
"Port": 18104,
"Meta": { ##自定义标签
"group": "docker",
"env": "dev"
},
"EnableTagOverride": false,
"Check": {
"Http": "http://192.168.2.250:18104/metrics",
"Interval": "15s"
}
}
修改 prometheus.yml
配置如下:
...
- job_name: 'consul-prometheus'
consul_sd_configs:
- server: '172.30.12.167:8500'
services: []
relabel_configs:
- source_labels: [__meta_consul_tags]
regex: .*test.*
action: keep
- regex: __meta_consul_service_metadata_(.+)
action: labelmap
解释一下,增加的配置作用为匹配 __meta_consul_service_metadata_
开头的标签,将捕获到的内容作为新的标签名称,匹配到标签的的值作为新标签的值,而我们刚添加的2个自定义标签,系统会自动添加 __meta_consul_service_metadata_group=docker
、__meta_consul_service_metadata_env=dev标签,经过 relabel 后,Prometheus 将会新增
group=docker、
env=dev、
2个标签。重启 Prometheus 服务,可以看到新增了对应了2个自定义标签。