Prometheus服务发现全解析:静态配置、文件 / Consul/DNS 动态发现实战
Prometheus是基于Pull模式抓取监控数据首先要能够发现需要监控的目标对象target那么Prometheus如何获取监控目标呢有两种方式静态手工配置和动态服务发现配置。对于小型的系统环境来说通过static_configs静态指定各Target便能解决问题这就是最简单静态手工配置。但是Prometheus最开始设计是一个面向云原生应用程序的云原生、容器场景下按需的资源使用方式对于监控系统而言就意味着没有一个固定的监控目标所有的监控对象(基础设施、应用、服务)都在动态的变化。因此对于有较强动态性的云环境来说静态配置显然难以适用于是就出现了动态服务发现机制。为了适应静态和动态发现机制Prometheus引入一个中间的代理人服务注册中心这个代理人掌握着当前所有监控目标的访问信息Prometheus只需要向这个代理人询问有哪些监控目标即可 这种模式被称为服务发现(service discovery)。如上图SD模块专门负责去发现需要监控的target信息Prometheus去从SD模块订阅该信息有target信息时会推送给Prometheus然后Prometheus拿到target信息后通过pull http协议去拉取监控指标数据。prometheus支持多种服务发现目前已支持多达二十多种服务发现协议常见的有:kubernetes_sd_configs: kubernetes服务发现,让prometheus动态发现kubernetes中被监控的目标。static_configs: 静态服务发现,基于prometheus配置文件指定监控目标dns_sd_configs: DNS服务发现监控目标consul_sd_configs: Consul服务发现,基于consul服务动态发现监控目标file_sd_configs: 基于指定的文件事先服务发现,prometheus定时读取文件如果发生变化则加载新目标1、静态配置的局限性静态服务发现机制配置简单但是监控目标是写死在了配置文件中/usr/local/prometheus/prometheus.yml如果要新增、修改、删除监控节点时需要每次都去修改配置文件然后再通知prometheus重新加载这太麻烦了。于是出现了动态服务发现。静态服务发现机制适合小型的系统环境。2、基于文件的服务发现通过定义一组资源“子”配置文件yaml格式文件里面只存储需要采集的targets信息此种方式可以被prometheus实时动态获取而不需要重启prometheus服务。先前准备创建文件服务发现目录mkdir -p /usr/local/prometheus/targets首先定义一个JSON格式文件的服务发现文件/usr/local/prometheus/targets/node.json[ { targets: [ 192.168.38.148:9100 ], labels: { env: dev_gameserver } } ]或者也可以定义一个YAML格式文件的服务发现文件/usr/local/prometheus/targets/node.yaml- targets: - 192.168.38.148:9100 labels: app: node-exporter job: nodes接着将这两个文件放到当前的targets目录下先创建targets目录最后修改prometheus.yml 配置文件增加如下内容- job_name: node_service_discovery file_sd_configs: - files: - targets/*.json refresh_interval: 60s - files: - targets/*.yaml refresh_interval: 60s配置文件说明file_sd_configs指定prometheus基于文件的服务发现配置。files自定义的和prometheus程序同级目录的targets目录要被自动加载的所有.json格式、yaml格式的文件。当然也可以单独指定某一个JSON格式的文件。refresh_interval: 60m自定义刷新间隔时间为60秒。最后重载配置验证curl -X POST http://192.168.38.148:9090/-/reload3、Prometheus中的标签重写机制1、Prometheus中标签的概念在Prometheus所有的Target监控实例中都包含一些默认的Metadata标签信息。可以通过Prometheus UI的Targets页面中查看这些实例的Metadata标签的内容默认情况下当Prometheus加载Target实例完成后这些Target都会包含一些默认的标签__address__当前Target实例的访问地址lt;hostgt;:lt;portgt;__scheme__采集目标服务访问地址的HTTP SchemeHTTP或者HTTPS__metrics_path__采集目标服务访问地址的访问路径__param_lt;namegt;采集任务目标服务的中包含的请求参数上面这些标签将会告诉Prometheus如何从Target实例中获取监控数据。除了这些默认的标签以外我们还可以为Target添加自定义的标签。一般来说Target以__作为前置的标签是在系统内部使用的因此这些标签不会被写入到样本数据中。不过这里有一些例外,比如指标上的instance标签的默认值就来自于_address__标签的值。这里实际上是发生了一次标签的重写处理。这种发生在采集样本数据之前对Target实例的标签进行重写的机制在Prometheus被称为Relabeling。Prometheus允许用户在采集任务中设置通过relabel_configs来添加自定义的Relabeling过程。2、relabel_configs的使用relabel_configs常用的几个参数如下source_labels: 源标签,没有经过relabel处理之前的名字target_labels: 通过action处理之后的新标签名字regex: 正则表达式,匹配源标签replacement: 通过分组替换后标签(target_label)对应的值action执行的动作。支持: replace,drop,keep,labelmap,labeldrop,labelkeep,hashmod下面我简单列举一下action里面每个 relabel_action 的作用relabel_action参数含义replace根据 regex 的配置匹配 source_labels 标签的值并且将匹配到的值写入到 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 标签进行过滤会移除不匹配过滤条件的所有标签例子1给target增加标签修改prometheus.yml增加如下内容- job_name: prometheus static_configs: - targets: [ localhost:9090] labels: env: prod __hostname__: localhost然后执行# curl -X POST http://192.168.38.148:9090/-/reload此操作会在原来的标签基础之上又增加了两个标签。注意,内部标签默认是不显示的只有把鼠标移动到Labels那里才会显示。同时要注意__开头的标签是不会写到metrics指标里面的因为这属于系统内置标签。env这种没有__作为前缀的是可以写到metrics指标中的。例子2将target的初始标签里面的值替换到新的标签中修改prometheus.yml增加如下内容- job_name: prometheus static_configs: - targets: [ localhost:9090] relabel_configs: - source_labels: [ __address__ ] target_label: addr - source_labels: [ __metrics_path__ ] target_label: path然后执行 # curl -X POST http://192.168.38.148:9090/-/reload上面直接替换了两个标签如果只替换一个标签就不用写两个source_lables了还有一点要注意目标标签不能用__标签名 __。如果要替换成指定值可做如下修改relabel_configs: - source_labels: [ __address__ ] target_label: addr replacement: localhost上面就是将__address__替换成addr然后再让addrlocalhost。如果要使用正则匹配替换可做如下修改relabel_configs: - source_labels: [ __address__ ] target_label: addr regex: (.*):(.*) replacement: $1上面就是将localhost:9090通过正则截成两段然后将第一段交给replacement去替换addr的值。当然也可以使用下面的写法relabel_configs: - action: replace source_labels: [ __address__ ] regex: (.*):(.*) replacement: $1 target_label: addr例子3使用labelmap标签名替换labelmap: 根据 regex 去匹配 Target 实例所有标签的名称注意是名称并且将捕获到的内容作为为新的标签名称regex 匹配到标签的的值作为新标签的值.当然这个新的标签也会加到样本数据中。relabel_configs: - source_labels: [ __metrics_path__ ] regex: __metrics_(.)__ #这相当于将path截取出来作为新的标签名称 action: labelmap或者下面的方式relabel_configs: - regex: __metrics_(.)__ action: labelmap最终会新增一个path/metrics的新标签。4、基于consul的服务发现Consul 是基于GO语言开发的开源工具提供服务注册、服务发现、健康检查、Key/Value存储、多数据中心和分布式一致性保证等功能。之前我们通过 Prometheus 实现监控当新增一个 Target 时需要变更服务器上的配置文件即使使用 file_sd_configs 配置也需要登录服务器修改对应 Json 文件会非常麻烦。不过 Prometheus 官方支持多种自动服务发现的类型其中就支持 Consul。(1)、安装consulConsul安装很方便官网https://www.consul.io/downloads 提供各个系统版本二进制安装包解压安装即可同时也可以通过 Docker 来快速安装。这里我在192.168.38.148主机上执行如下命令docker方式安装$ docker run -d -p 8500:8500 --restartalways -v /data/consul/data:/consul/data --nameconsul hashicorp/consul:1.15.2 agent -server -bootstrap -ui -nodedc1 -client0.0.0.0参数含义agent: 表示启动 Agent 进程。server 表示启动 Consul Server 模式client 表示启动 Consul Cilent 模式。bootstrap 表示这个节点是 Server-Leader 每个数据中心只能运行一台服务器。技术角度上讲 Leader 是通过 Raft 算法选举的但是集群第一次启动时需要一个引导 Leader在引导群集后建议不要使用此标志。ui 表示启动 Web UI 管理器默认开放端口 8500所以上面使用 Docker 命令把 8500 端口对外开放。node 节点的名称集群中必须是唯一的默认是该节点的主机名。client consul服务侦听地址这个地址提供HTTP、DNS、RPC等服务默认是127.0.0.1所以不对外提供服务如果你要对外提供服务改成0.0.0.02、通过API 注册服务到 ConsulConsul服务注册提供了两种注册方法一种是定义配置文件服务注册方法即在配置文件中定义服务来进行注册一种是HTTP API服务注册方法即在启动服务后自身通过调用API进行自我注册。这里介绍第二种consul主要是添加和删除命令都是使用接口调用。先创建test.json文件内容如下{ ID: node-exporter, Name: node-exporter-192.168.38.148, Address: 192.168.38.148, Port: 9100, Tags: [node-exporter], Meta: { app: spring-boot, team: appgroup, project: bigdataserver }, Check: { HTTP: http://192.168.38.148:9100/metrics, Interval: 10s } }然后执行如下命令curl --request PUT --data test.json http://192.168.38.148:8500/v1/agent/service/register?replace-existing-checks1执行完毕后刷新一下 Consul Web 控制台页面可以看到成功注册到 Consul 中。要删除这个服务可执行如下命令curl --request PUT http://192.168.38.148:8500/v1/agent/service/deregister/node-exporter注意url最后的node-exporter是注册对应的ID删除哪个服务就指定哪个ID。3、配置 Prometheus 实现自动服务发现现在 Consul 服务已经启动完毕并成功注册了一个服务接下来我们需要配置 Prometheus 来使用 Consul 自动服务发现目的就是能够将上边添加的服务自动发现到 Prometheus 的 Targets 中增加prometheus.yml配置如下- job_name: consul-node-exporter consul_sd_configs: - server: 192.168.38.148:8500 services: []说明一下consul_sd_configs表示使用 Consul 服务发现类型server 为 Consul 的服务地址这里跟上边要对应上。services: []这个表示匹配consul中所有的service。配置完毕后重启 Prometheus 服务此时可以通过 Prometheus UI 页面的 Targets 下查看是否配置成功。可以看到在 Targets 中能够成功的自动发现 Consul 中的 Services 信息后期需要添加新的 Targets 时只需要通过 API 往 Consul 中注册服务即可Prometheus 就能自动发现该服务非常方便。但是存在问题。。。4、使用relabel_configs自定义标签修改刚才的test.json文件内容如下{ ID: node-exporter, Name: node-exporter-192.168.38.148, Tags: [ test ], Address: 192.168.38.148, Port: 9100, Meta: { app: spring-boot, team: appgroup, project: bigdataserver }, EnableTagOverride: false, Check: { HTTP: http://192.168.38.148:9100/metrics, Interval: 10s }, Weights: { Passing: 10, Warning: 1 } }参数含义replace-existing-checks在重新注册服务时替换现有的检查。Name指定服务名称必选。ID指定服务ID如果未指定默认为服务名称。Tags指定标记用于筛选。Address指定服务的地址。Port指定服务的端口。Meta指定 KV 元数据。Weights指定服务的权重。Check指定健康检测。说明该 Json 文件为要注册的服务信息同时往 Meta 信息中添加了appspring-bootteamappgroupprojectbigdataserver三组自定义标签目的就是为了方便告警分组使用。执行如下命令进行注册$ curl --request PUT --data test.json http://192.168.38.148:8500/v1/agent/service/register?replace-existing-checks1注册完毕通过 Consul Web 管理页面可以查看到已注册成功并且包含了 Meta 信息。然后修改prometheus.yml配置如下- job_name: consul-prometheus consul_sd_configs: - server: 192.168.38.148:8500 services: [] relabel_configs: - source_labels: [__meta_consul_tags] regex: .*test.* action: keep - regex: __meta_consul_service_metadata_(.) action: labelmap5、使用relabel_configs将自动发现的服务进行分类重新修改test.json文件此类服务实现node-exporter自动注册内容如下{ ID: node-exporter, Name: node-exporter-192.168.38.148, Tags: [ node-exporter ], Address: 192.168.38.148, Port: 9100, Meta: { app: spring-boot, team: appgroup, project: bigdataserver }, EnableTagOverride: false, Check: { HTTP: http://192.168.38.148:9100/metrics, Interval: 10s }, Weights: { Passing: 10, Warning: 1 } }然后更新注册服务$ curl --request PUT --data test.json http://192.168.38.148:8500/v1/agent/service/register?replace-existing-checks1新增另一个文件docker.json,用于docker类服务的自动发现内容如下{ ID: cadvisor-exporter, Name: cadvisor-exporter-192.168.38.148, Tags: [ cadvisor-exporter ], Address: 192.168.38.148, Port: 8080, Meta: { app: docker, team: cloudgroup, project: docker-service }, EnableTagOverride: false, Check: { HTTP: http://192.168.38.148:8080/metrics, Interval: 10s }, Weights: { Passing: 10, Warning: 1 } }继续注册服务到consul$ curl --request PUT --data docker.json http://192.168.38.148:8500/v1/agent/service/register?replace-existing-checks1最后我们修改prometheus.yml配置如下- job_name: consul-node-exporter consul_sd_configs: - server: 192.168.38.148: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: 192.168.38.148:8500 services: [] relabel_configs: - source_labels: [__meta_consul_tags] regex: .*cadvisor-exporter.* action: keep - regex: __meta_consul_service_metadata_(.) action: labelmap这里通过relabel_configs中配置的 Tags 来做匹配区分对注册的服务进行分类重启 Prometheus 服务可以看到服务已经按照类型分类了方便查看。虽然prometheus 使用consul的服务发现做监控不对prometheus 做修改了但是我们每次还要手动去注册服务感觉很麻烦于是呢来个终极方法分享一个脚本自动去consul注册服务自动检测服务状态还能卸载consul里面的服务#!/bin/bash #usage: #param1check_service or register_service or deregister_service #param2: an exist file,and the file content format is as follows,pay attention to remove the first # #group_name cluster_name service_ip service_port #注册服务 function register_service() { cat $1 | while read line do group_name$(echo $line | awk {print $1}) cluster_name$(echo $line | awk {print $2}) service_ip$(echo $line | awk {print $3}) service_port$(echo $line | awk {print $4}) cat gt;service-$service_ip-$service_port.jsonlt;lt;EOF { id:service-${service_ip}-${service_port}, name:service-${service_ip}-${service_port}, address:${service_ip}, port:${service_port}, tags:[$cluster_name], meta:{group:$group_name,cluster:$cluster_name,instance_name:$service_ip:$service_port}, checks:[{ http:http://${service_ip}:$service_port/metrics, interval:5s }], Weights: { Passing: 10, Warning: 1 }, EnableTagOverride: false } EOF curl --request PUT --data service-$service_ip-$service_port.json \ http://${consul_host}:${consul_http_api_port}/v1/agent/service/register?replace-existing-checks1 rm -rf service-$service_ip-$service_port.json done } #注销服务 function deregister_service() { cat $1 | while read line do service_ip$(echo $line | awk {print $3}) service_port$(echo $line | awk {print $4}) #let service_portredis_port10000 consul_service_idservice-$service_ip-$service_port curl --request PUT http://${consul_host}:${consul_http_api_port}/v1/agent/service/deregister/${consul_service_id} done } #检查服务 check_service() { cat $1 | while read line do service_ip$(echo $line | awk {print $3}) service_port$(echo $line | awk {print $4}) #let service_portredis_port10000 consul_service_idservice-$service_ip-$service_port ret1$(curl -s http://${consul_host}:${consul_http_api_port}/v1/agent/service/$consul_service_id?pretty) if [[ $ret1 ~ unknown proxy service ID ]];then echo 不存在服务$consul_service_id else ret2$(curl -s http://${consul_host}:${consul_http_api_port}/v1/health/service/service-$service_ip-$service_port?pretty \ | grep $service_ip:$service_port/metrics: 200 OK) if [[ -z $ret2 ]];then echo -e \033[5;34m服务$consul_service_id已注册,但服务状态异常 lt;---\033[0m else echo 服务$consul_service_id已注册,且服务状态正常 fi fi done } function main() { if [[ $# -ne 2 ]];then echo -e must two parameters:\nfirst exec_command(check_service or register_service or deregister_service)\nsecond filename exit else case $1 in check_service) shift 1 check_service $1 ;; register_service) shift 1 register_service $1 ;; deregister_service) shift 1 deregister_service $1 ;; *) echo 无效的指令 esac fi } consul_host192.168.38.148 consul_http_api_port8500 main $1 $2将上面脚本命名为consul-register.sh脚本执行方式[rootdocker-server consul]# ./consul-register.sh must two parameters: first exec_command(check_service or register_service or deregister_service) second filename第一个参数可从check_service or register_service or deregister_service选择对应检测服务、注册服务、注销服务。第二个参数对应一个文件文件名随便内容格式为group_name cluster_name service_ip service_port例如consul-service文件内容如下dbgroup mysql-exports 192.168.38.148 9104每个部分用空格分开。下面是一个执行例子[rootdocker-server consul]# ./consul-register.sh check_service consul-service 服务service-192.168.38.148-9104已注册,且服务状态正常5、基于DNS的服务发现首先需要部署好一个DNS服务并且配置好A记录与SRV记录接着将prometheus-server所在主机的dns设置为刚刚部署好的dnsdns配置好解析记录如下dnf install bind bind-utils -y systemctl start named systemctl enable named _prometheus._tcp.iivey.com service 10 10 9100 node1.iivey.com. _prometheus._tcp.iivey.com service 10 10 9100 master.iivey.com.然后修改prometheus.yml文件[rootlocalhost ~]# vi /usr/local/prometheus/prometheus.yml ...... - job_name: dns-server-monitor dns_sd_configs: - names: [_prometheus._tcp.iivey.com]然后重启prometheus服务curl -X POST http://192.168.38.148:9090/-/reload当变更DNS解析记录时不用重启prometheus,注册节点信息自动变更。综上从传统静态 Target 配置到 file_sd、consul_sd、dns_sd 多种动态服务发现方案再到 Relabel 标签重写与自动化注册脚本落地本次实验完整展示了 Prometheus 在不同架构下的目标管理能力。在微服务与容器集群场景中依靠注册中心、DNS 实现监控目标自动上下线配合标签管理完成业务分组大幅降低了人工运维成本。基于 192.168.38.148 搭建的这套实验环境为生产环境大规模监控架构设计提供了可复用的实践参考。