Prometheus自定义监控 && 服务发现 && 联邦模式

Prometheus自定义监控 && 服务发现 && 联邦模式
[TOC]
环境规划
Prom01 打通了 Linux 主机 + 主流中间件的监控,Prom02 用 exporter 监控了 MySQL/MongoDB/Redis/Nginx/Tomcat 本篇进阶三个方向:
- 自定义监控:pushgateway(推送式)+ 自定义 exporter(Go)
- 服务发现:file_sd(文件)+ consul_sd(注册中心)
- 联邦模式:3 台 Prometheus server 分布式采集、集中汇总
| 虚拟机 | IP | 系统 | 本篇角色 |
|---|---|---|---|
| Prom | 10.0.0.10 | Ubuntu | 主 Prometheus server + Grafana(联邦汇总端) |
| node1 | 10.0.0.2 | Rocky | node-exporter + consul 集群 |
| node2 | 10.0.0.11 | Rocky | node-exporter + pushgateway + consul + consul_exporter + 联邦子 server(32) |
| node3 | 10.0.0.12 | Rocky | node-exporter + Go exporter + consul + 联邦子 server(33) |
pushgateway 概述
Prometheus 的默认采集方式是 pull(拉取):server 定期去目标端抓 /metrics
但有些任务存活时间太短(批处理、定时脚本),server 还没来得及拉,进程就结束了,指标就丢了
pushgateway 就是为了让这类任务能主动 push(推送) 指标而存在的中间件
- 说白了,就是 Prometheus 官方用来监控存活时间短的任务的组件
- 进程一直活着的服务,建议运维开发编写相应的 exporter(本篇后半部分会做)
- GitHub 项目:https://github.com/prometheus/pushgateway
📌 pushgateway 的定位
判断用 pushgateway 还是 exporter,就看进程是常驻还是短命:
- 进程跑一下就死(定时脚本、批处理)→ pushgateway,因为 pull 抓不到
- 进程一直活着(服务)→ 写 exporter,因为随时能拉
类比:pushgateway 像个留言板,短命任务把”我这次跑了多少数据”写在上面,Prometheus 定期来抄
部署
pushgateway 部署在 node2,采用二进制 + systemd 托管
1)下载软件包(官方源,宿主机)jiuzhao@Ubuntu ~$ wget -P /home/jiuzhao/下载 https://github.com/prometheus/pushgateway/releases/download/v1.11.3/pushgateway-1.11.3.linux-amd64.tar.gzjiuzhao@Ubuntu ~$ scp /home/jiuzhao/下载/pushgateway-1.11.3.linux-amd64.tar.gz node2:/tmp/
2)解压二进制到 /usr/local/bin/[root@node2 ~]# tar tf /tmp/pushgateway-1.11.3.linux-amd64.tar.gzpushgateway-1.11.3.linux-amd64/pushgateway-1.11.3.linux-amd64/LICENSEpushgateway-1.11.3.linux-amd64/NOTICEpushgateway-1.11.3.linux-amd64/pushgateway[root@node2 ~]# tar xf /tmp/pushgateway-1.11.3.linux-amd64.tar.gz -C /usr/local/bin/ pushgateway-1.11.3.linux-amd64/pushgateway --strip-components=1# 只解压这一个二进制,其余 LICENSE/NOTICE 不要[root@node2 ~]# /usr/local/bin/pushgateway --versionpushgateway, version 1.11.3✅️ 二进制可执行,版本 1.11.3
3)编写 systemd 服务文件[root@node2 ~]# mkdir -p /var/lib/pushgateway# 数据目录放 /var/lib/(FHS 规范),pushgateway 不会自动创建父目录,必须先建[root@node2 ~]# cat > /etc/systemd/system/pushgateway.service <<"EOF"[Unit]# 服务描述Description=Kpyun pushgateway server# 官方文档地址Documentation=https://github.com/prometheus/pushgateway# 等待网络就绪后再启动After=network-online.target# 主动声明依赖网络在线目标Wants=network-online.target
[Service]# 前台常驻进程:ExecStart 拉起即算成功(不 fork 不后台化)Type=simple# 异常退出时自动重启Restart=on-failure# 重启间隔 3 秒RestartSec=3# 文件描述符上限LimitNOFILE=65535# 启动命令(无日志重定向,直接写二进制即可,日志自动进 journald)ExecStart=/usr/local/bin/pushgateway --web.telemetry-path=/metrics --web.listen-address=:9091 --web.enable-admin-api --persistence.file=/var/lib/pushgateway/pushgateway.data
[Install]# 开机自启WantedBy=multi-user.targetEOF启动参数
| 参数 | 含义 |
|---|---|
--web.telemetry-path=/metrics | 暴露指标的路径 |
--web.listen-address=:9091 | 监听地址和端口 |
--web.enable-admin-api | 开启管理 API(wipe 清空指标用,默认关闭) |
--persistence.file=/var/lib/pushgateway/pushgateway.data | 持久化文件,重启后指标不丢 |
📌 —persistence.file` 是 pushgateway 的”救命参数”
- pushgateway 默认把指标存在内存里,进程一重启,所有指标全部清空
- 加上
--persistence.file,指标会落盘,重启后自动恢复 --web.enable-admin-api不开启的话,后面wipe(清空所有指标)这个 API 会返回 404
📌 为什么不用 bash -c 包裹?
- Prom01 里 prometheus.service 用
/bin/bash -c "...",是因为它要&>> 日志重定向到文件 - pushgateway 这里没有日志重定向,直接写二进制就行,更干净
- 日志自动进 journald,
journalctl -u pushgateway -f查看
4)启动并验证[root@node2 ~]# systemctl daemon-reload[root@node2 ~]# systemctl enable --now pushgateway.service[root@node2 ~]# systemctl is-active pushgatewayactive[root@node2 ~]# ss -ntl | grep 9091LISTEN 0 4096 *:9091 *:*✅️ 9091 端口已监听
5)访问 pushgateway 的 WebUIhttp://10.0.0.11:9091/
API 管理方式
pushgateway 用 HTTP API 完成推送、查询、删除
推送数据
1)使用 curl 推送单值(echo 管道)[root@node1 ~]# echo "student_online 35" | curl -s --data-binary @- http://10.0.0.11:9091/metrics/job/kpyun_student/instance/10.0.0.2# @- 表示从标准输入读取数据体=======================================================`末尾的 35 是 value,必须是数字(字符串信息只能放标签里)`student_name kpyun# ❌ 错误:value 是字符串,会报 parse error为什么必须是数字?'Prometheus 本质是时序数据库,底层存的是一条条 (时间戳, 数值) 的样本'=======================================================`URL 路径拆解:` /metrics 推送端点 /job/kpyun_student → 指标标签 job="kpyun_student" /instance/10.0.0.2 → 指标标签 instance="10.0.0.2"💡 'URL 路径里的每一段会被 pushgateway 自动解析成指标标签'=======================================================[root@node1 ~]# curl -s http://10.0.0.11:9091/metrics | grep student_online# TYPE student_online untypedstudent_online{instance="10.0.0.2",job="kpyun_student"} 35`👆 instance 标签来自 URL 的 /instance/10.0.0.2,job 标签来自 /job/kpyun_student`
2)使用 cat 推送多值(带 HELP/TYPE)[root@node1 ~]# cat <<EOF | curl -s --data-binary @- http://10.0.0.11:9091/metrics/job/kpyun_disk/instance/10.0.0.2# HELP linux_cloud_native Number of people learning cloud native# TYPE linux_cloud_native counterlinux_cloud_native{auther="jiuzhao","project"="k8s"} 59linux_cloud_native{auther="jiuzhao","project"="Prometheus"} 56linux_cloud_native{auther="jiuzhao","project"="ceph"} 52linux_cloud_native{auther="jiuzhao","project"="elasticstack"} 55linux_cloud_native{auther="jiuzhao","project"="docker"} 80# HELP disk_usage Disk usage.# TYPE disk_usage gaugedisk_usage 92.56EOF[root@node1 ~]# curl -s http://10.0.0.11:9091/metrics | grep cloud# HELP linux_cloud_native Number of people learning cloud native# TYPE linux_cloud_native counterlinux_cloud_native{auther="jiuzhao",project="k8s",job="kpyun_disk",instance="10.0.0.2"} 59`👆 auther/project 是数据体自带的标签,job/instance 是 URL 自动加的标签`📌 推送 URL 的路径即标签
推送命令分两部分:--data-binary @- 读进来的指标文本叫数据体;http://IP:9091/... 叫 URL 路径
URL 路径里 /job/<job名> 和 /instance/<实例名> 会被自动解析成指标标签
🔥 当数据体推送的 job/instance 标签,和 URL 路径解析出的 job/instance 冲突时,以 URL 路径解析的标签为准
3)使用 echo 发送多行数据`和上面 cat 等价,只是生成多行文本的语法不同,都能发多值`[root@node1 ~]# echo """# HELP student_online Number of students online# TYPE student_online gaugestudent_online 150""" | curl -s --data-binary @- http://10.0.0.11:9091/metrics/job/kpyun_student/instance/10.0.0.2[root@node1 ~]# curl -s http://10.0.0.11:9091/metrics | grep student_online# HELP student_online Number of students online# TYPE student_online gaugestudent_online{instance="10.0.0.2",job="kpyun_student"} 150删除数据
1)只指定 job 删除(删不掉!)[root@node1 ~]# curl -X DELETE http://10.0.0.11:9091/metrics/job/kpyun_disk[root@node1 ~]# curl -s http://10.0.0.11:9091/metrics | grep -c "kpyun_disk"# grep -c 统计行数8⚠️ 还有 8 行!没删干净!📌 貌似删除不掉,其实是 grouping key 不匹配:
- 推送时我们带了
instance=10.0.0.2,所以指标实际的 grouping key 是{job="kpyun_disk", instance="10.0.0.2"} - 但删除时只写了
/job/kpyun_disk,它的 grouping key 是{job="kpyun_disk"}(不含 instance) - 两组 key 对不上,所以删不掉
- 正确姿势:删除时要把完整的分组标签都写进路径 →
/job/kpyun_disk/instance/10.0.0.2
🌰 一句话:推送时带了多少标签,删除时就要带上多少标签,少一个都删不掉
2)指定 job + instance 删除(才删干净)`删除整个分组 Group,必须把 job 和 instance(推送时带了多少标签)都写全`[root@node1 ~]# curl -X DELETE http://10.0.0.11:9091/metrics/job/kpyun_disk/instance/10.0.0.2[root@node1 ~]# curl -s http://10.0.0.11:9091/metrics | grep -c "kpyun_disk"0✅️ 删干净了!
3)删除 pushgateway 的所有指标(wipe,需 --web.enable-admin-api)[root@node1 ~]# curl -X PUT http://10.0.0.11:9091/api/v1/admin/wipe[root@node1 ~]# curl -s http://10.0.0.11:9091/metrics | egrep -c "student_online|cloud|disk"0✅️ 全部清空💡 清空所有指标有两种方式:
① curl -X PUT /api/v1/admin/wipe(需 --web.enable-admin-api)
② 直接重启 pushgateway(没配 --persistence.file 时重启即清空)
配了 --persistence.file 就只能走 wipe 了,重启数据还在
自定义指标案例
用脚本 + crontab 定时推送自定义指标,模拟”在线学习人数”的实时变化
推送脚本
1)编写推送脚本(node1)[root@node1 ~]# vim /tmp/push-student-online.sh#!/bin/bash
NUMBER_OF_STUDENT=("$((RANDOM%51+50))" "$((RANDOM%51+50))" "$((RANDOM%51+50))" "$((RANDOM%51+50))" "$((RANDOM%51+50))")# 每门课程随机生成 50~100 之间的在线人数
cat <<METRICS | curl -s --data-binary @- http://10.0.0.11:9091/metrics/job/student_online/instance/10.0.0.2# HELP linux_cloud_native Number of people learning cloud native# TYPE linux_cloud_native counterlinux_cloud_native{auther="jiuzhao","project"="k8s"} ${NUMBER_OF_STUDENT[0]}linux_cloud_native{auther="jiuzhao","project"="Prometheus"} ${NUMBER_OF_STUDENT[1]}linux_cloud_native{auther="jiuzhao","project"="ceph"} ${NUMBER_OF_STUDENT[2]}linux_cloud_native{auther="jiuzhao","project"="elasticstack"} ${NUMBER_OF_STUDENT[3]}linux_cloud_native{auther="jiuzhao","project"="docker"} ${NUMBER_OF_STUDENT[4]}METRICS[root@node1 ~]# chmod +x /tmp/push-student-online.sh[root@node1 ~]# bash /tmp/push-student-online.sh
2)配置 crontab 每分钟推送一次[root@node1 ~]# echo "*/1 * * * * /tmp/push-student-online.sh" | crontab -# crontab - 表示从标准输入读取并加载为定时任务[root@node1 ~]# crontab -l*/1 * * * * /tmp/push-student-online.sh💡 为什么用 crontab - 而不是直接编辑文件?
crontab 实际存的位置随系统而变(Rocky 是 /var/spool/cron/root,Ubuntu 是 /var/spool/cron/crontabs/root)
用 echo "..." | crontab - 把路径差异交给 crontab 命令自己处理,跨系统通用,且立即生效
💡 周期性任务最快每分钟一次
如果想要精确到秒推送,可以写个死循环永久执行:
[root@node1 ~]# vim while_true.sh#!/bin/bashwhile true ;do sleep 3 && /tmp/push-student-online.shdone[root@node1 ~]# nohup bash while_true.sh &> /tmp/test.log &监控 pushgateway
1)修改 prometheus.yml 追加 pushgateway 的 job(Prom)root@Prom ~# vim /etc/prometheus/prometheus.yml... - job_name: "kpyun-pushgateway" metrics_path: "/metrics" scheme: "http" honor_labels: true static_configs: - targets: - 10.0.0.11:9091⚠️ honor_labels 决定标签冲突时谁覆盖谁
采集时,Prometheus 本地会自动加 job/instance 标签,而 pushgateway 指标本身也自带 job/instance(推送时 URL 里指定的),两边 key 相同、值不同 → 冲突
| 配置 | 冲突时谁赢 | 结果 |
|---|---|---|
honor_labels: false(默认) | 本地赢 | 远程标签被改名,加 exported_* 前缀 |
honor_labels: true | 远程赢 | 远程标签覆盖本地,不加前缀 ✅ |
- 本地 = Prometheus server 自己加的
job(来自 job_name)、instance(来自 target 地址) - 远程 = 被采集端指标自带的
job/instance(这里就是 pushgateway 推送时 URL 里的)
用本实验数据对照:
# 远程(pushgateway 指标自带)job="student_online" instance="10.0.0.2"
# 本地(Prometheus 采集时自动加)job="kpyun-pushgateway" instance="10.0.0.11:9091"
# false:本地赢 → job="kpyun-pushgateway"、instance="10.0.0.11:9091"(远程变 exported_*)# true :远程赢 → job="student_online"、instance="10.0.0.2"(覆盖本地)🌰 为什么 pushgateway 场景必须 true?pushgateway 汇聚了多台机器推来的指标 如果让本地标签赢,所有指标的 instance 都变成 pushgateway 自己的 IP(10.0.0.11<9091>9091>),就分不清数据到底来自哪台机器了 改成 true,instance 保留推送端的真实 IP(10.0.0.2),这才是我们要的
2)检查语法 + 热加载root@Prom ~# promtool check config /etc/prometheus/prometheus.ymlChecking /etc/prometheus/prometheus.yml SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntaxroot@Prom ~# curl -X POST http://localhost:9090/-/reload
3)验证 targets 状态root@Prom ~# curl -s "http://localhost:9090/api/v1/query" --data-urlencode "query=up" | jq -r ".data.result[] | \"\(.metric.job) | \(.metric.instance) | \(.value[1])\"" | grep pushgatewaykpyun-pushgateway | 10.0.0.11:9091 | 1`✅️ pushgateway 已纳入监控`root@Prom ~# curl -s "http://localhost:9090/api/v1/query" --data-urlencode "query=linux_cloud_native" | jq -r ".data.result[].metric.instance" | sort -u10.0.0.2# 👆 instance 是推送端的 10.0.0.2,而不是 pushgateway 的 10.0.0.11
# WebUIhttp://10.0.0.10:9090/targetsGrafana 出图展示
# 自定义模板1)添加新行:学生在线人数]
2)添加变量 --> 查询 --> 名称:subject --> 标签(显示名称):学科 --> 变量编辑器 查询类型: Label values(标签值) 标签:project # 剩下的不用填值预览: k8s,Prometheus,elasticstack,ceph,docker
3)添加面板 --> 配置可视化# PromQLlinux_cloud_native{project="$subject"}
# 自定义 Legend$subject课程在线人数

监控 TCP 的 12 种状态
用脚本统计 Linux 的 TCP 连接状态,推送到 pushgateway
1)是查看系统里所有 TCP 套接字(socket)连接[root@node1 ~]# ss -tan-t # 只看 TCP 连接-a # 显示所有状态(含 LISTEN 和已建立连接,默认只显示已建立的)-n # 用数字显示端口/IP,不做域名反解(否则会很慢)State Recv-Q Send-Q Local Address:Port Peer Address:PortLISTEN 0 128 0.0.0.0:9100 0.0.0.0:*ESTAB 0 0 10.0.0.2:22 10.0.0.1:53210TIME-WAIT 0 0 10.0.0.2:9091 10.0.0.11:52000`最左边 State 那一列,就是 TCP 连接的状态`
2)编写 TCP 状态监控脚本(Prom 上,统计本机)root@Prom ~# vim /usr/local/bin/tcp_status.sh#!/bin/bash
url="http://10.0.0.11:9091/metrics/job/tcp_status/instance/10.0.0.10"
echo """# HELP tcp_connections Number of TCP state connections# TYPE tcp_connections gauge""" > /tmp/tcp.txt
for i in SYN-SENT SYN-RECV FIN-WAIT-1 FIN-WAIT-2 TIME-WAIT CLOSE CLOSE-WAIT LAST-ACK LISTEN CLOSING ESTAB UNKNOWN; do count=$(ss -tan | grep -c "$i ") echo "tcp_connections{state=\"$i\"} $count" >> /tmp/tcp.txtdone
cat /tmp/tcp.txt | curl -s --data-binary @- $url
3)调用脚本root@Prom ~# bash /usr/local/bin/tcp_status.sh
4)验证数据root@Prom ~# curl -s http://10.0.0.11:9091/metrics | grep "tcp_connections"# HELP tcp_connections Number of TCP state connections# TYPE tcp_connections gaugetcp_connections{instance="10.0.0.10",job="tcp_status",state="CLOSE"} 0tcp_connections{instance="10.0.0.10",job="tcp_status",state="CLOSE-WAIT"} 0tcp_connections{instance="10.0.0.10",job="tcp_status",state="CLOSING"} 0tcp_connections{instance="10.0.0.10",job="tcp_status",state="ESTAB"} 10tcp_connections{instance="10.0.0.10",job="tcp_status",state="FIN-WAIT-1"} 0tcp_connections{instance="10.0.0.10",job="tcp_status",state="FIN-WAIT-2"} 0tcp_connections{instance="10.0.0.10",job="tcp_status",state="LAST-ACK"} 0tcp_connections{instance="10.0.0.10",job="tcp_status",state="LISTEN"} 6tcp_connections{instance="10.0.0.10",job="tcp_status",state="SYN-RECV"} 0tcp_connections{instance="10.0.0.10",job="tcp_status",state="SYN-SENT"} 2tcp_connections{instance="10.0.0.10",job="tcp_status",state="TIME-WAIT"} 1tcp_connections{instance="10.0.0.10",job="tcp_status",state="UNKNOWN"} 0✅️ 12 种 TCP 状态全部采集📌 TCP 的 12 种状态来自 ss -tan 的输出,脚本逐个 grep 统计数量
ESTAB(已建立连接)、LISTEN(监听中)、TIME-WAIT(等待关闭)是运维最常关注的- 大量
TIME-WAIT堆积说明短连接频繁断开(常见于高并发 Nginx 反代) SYN-RECV堆积可能是 SYN Flood 攻击的前兆
1)添加变量 --> 查询 --> 名称:state --> 标签(显示名称):TCP状态 --> 变量编辑器 查询类型: Label values(标签值) 标签:state # 剩下的不用填值预览: SYN-SENT,SYN-RECV,FIN-WAIT-1,FIN-WAIT-2,TIME-WAIT,CLOSE,CLOSE-WAIT,LAST-ACK,LISTEN,CLOSING,ESTAB,UNKNOWN
2)添加面板 --> 配置可视化# PromQLtcp_connections{state="$state"}
# 自定义 Legend$state连接数

📌 pushgateway 存的是”最后一次推送的值”
pushgateway 是被动存储,不是实时采集:
- 脚本每跑一次,就把那一刻的 TCP 状态推上去,覆盖上次的值
- 脚本跑完就不动了,pushgateway 里就一直存着那个值,自己不会刷新
- Prometheus 每次从 pushgateway 拉到的,就是这个定格的值
想让 TCP 状态随时间变化,脚本就得持续跑,两种方式:
# 方式 A:crontab 定时(和前面 push-student-online.sh 一样)echo "*/1 * * * * /usr/local/bin/tcp_status.sh" | crontab -
# 方式 B:死循环while true; do sleep 3 && /usr/local/bin/tcp_status.shdone📌 pushgateway vs exporter 的本质区别
| 数据怎么来 | 值会自己变吗 | |
|---|---|---|
| exporter | 常驻进程,Prometheus 每次拉都现场读系统状态 | ✅ 每次拉都是最新值 |
| pushgateway | 脚本推一次存一次,被动存储 | ❌ 不推就不变 |
🌰 一句话:exporter 是”体温计”(随时量随时报),pushgateway 是”留言板”(写一次定格一次,除非你再来改)
systemd timer 定时执行
前面演示了 crontab、死循环两种让脚本持续跑的方式,这里演示第三种——systemd timer 它把定时任务也纳入 systemd 统一管理:日志集中到 journald、可精确到秒、错过触发还能补跑,比 crontab 更现代
💡 timer 管时间,service 管执行,脚本干活
1)编写 service 文件(管执行)root@Prom ~# cat > /etc/systemd/system/tcp_status.service <<"EOF"[Unit]# 服务描述Description=Push TCP connection status to pushgateway
[Service]# oneshot:运行一次就退出,正好匹配"跑完就退"的脚本Type=oneshot# 执行脚本(绝对路径 + 脚本要有执行权限)ExecStart=/usr/local/bin/tcp_status.shEOFroot@Prom ~# chmod +x /usr/local/bin/tcp_status.sh
2)编写 timer 文件(管时间)root@Prom ~# cat > /etc/systemd/system/tcp_status.timer <<"EOF"[Unit]# timer 强依赖同名 serviceDescription=Run tcp_status every minuteRequires=tcp_status.service
[Timer]# 每分钟的第 0 秒触发(显式日历语法,等价于 minutely 这个内置别名)OnCalendar=*-*-* *:*:00# 错过触发时间(如关机/休眠),系统启动后立即补执行一次Persistent=true# 触发目标(默认就是同名 service,显式写出更清晰)Unit=tcp_status.service
[Install]# 开机自启的是 timer(触发器),不是 serviceWantedBy=timers.targetEOF
3)启动并验证root@Prom ~# systemctl daemon-reloadroot@Prom ~# systemctl enable --now tcp_status.timerroot@Prom ~# systemctl list-timers tcp_status.timerNEXT LEFT LAST PASSED UNIT ACTIVATESSun 2026-08-16 17:21:00 CST 10s - - tcp_status.timer tcp_status.service✅️ 定时任务已生效,每分钟触发一次 tcp_status.service📌 几个关键点
Type=oneshot:跑一次就退出,正好匹配”跑完就退”的脚本- 要开机自启的是 timer,不是 service:
WantedBy=timers.target写在 timer 里;service 是被动激活的,不用 enable - timer 强依赖 service,默认同名关联:
.timer文件名必须与.service前缀一致;不一致时也可用Unit=显式指定 Persistent=true:即使系统在触发时间关机/休眠,开机后也会立即补执行一次
💡 日志:脚本里 curl -s 已静默、echo 都重定向到 /tmp/tcp.txt,journald 里比较干净,想看就用 journalctl -u tcp_status.service(或 journalctl -u tcp_status.timer)
网络丢包率监控
用脚本 ping 一个目标主机,统计丢包率推送到 pushgateway
1)编写丢包率监控脚本(Prom 上)root@Prom ~# vim /usr/local/bin/loss_packet.sh#!/bin/bash
host="www.baidu.com"url="http://10.0.0.11:9091/metrics/job/kpyun_loss_packet/instance/${host}"loss=$(ping "$host" -c 10 | grep -oP "[0-9]+(?=% packet loss)")
echo """# HELP loss_packet ${host} Packet Loss Rate# TYPE loss_packet gaugeloss_packet $loss""" | curl --data-binary @- ${url}⚠️ 丢包率提取的坑
用 ping $host -c 10 | grep packet | awk '{print $6}' | tr -d '%'
但不同 Linux 发行版 ping 的输出格式不一样,awk '{print $6}' 很容易取错字段
- 更稳的写法是用
grep -oP正则精确匹配百分比数字:grep -oP "[0-9]+(?=% packet loss)" -o只输出匹配部分、-P用 Perl 正则、(?=% packet loss)是零宽断言,只取%前面的数字- 这样不管 ping 输出格式怎么变,都能准确抓到丢包率
2)调用脚本root@Prom ~# bash /usr/local/bin/loss_packet.sh
3)验证数据root@Prom ~# curl -s http://10.0.0.11:9091/metrics | egrep "loss_packet"# HELP loss_packet www.baidu.com Packet Loss Rate# TYPE loss_packet gaugeloss_packet{instance="www.baidu.com",job="kpyun_loss_packet"} 0✅️ 丢包率 0%(网络正常)Grafana 出图:
1)添加变量 --> 查询 --> 名称:target --> 标签(显示名称):目标网络丢包率 --> 变量编辑器 查询类型: Label values(标签值) 标签:instance # 只匹配以 www. 开头的值(value) 正则:/^(?<value>www\..*)$/ # 剩下的不用填值预览: www.baidu.com
2)添加面板 --> 配置可视化# PromQLloss_packet{instance="$target"}
# 自定义 Legend$target丢包率

exporter 实现自定义指标监控
pushgateway 的缺陷是:重启后数据丢失(不配持久化)、需要自己写脚本推,用起来麻烦 生产环境中,让开发写 Golang / Python 程序实现自定义 exporter 才是正解
- exporter = 把业务指标翻译成 Prometheus 文本格式的程序
- 业务程序常驻运行,指标随时可拉,不需要 pushgateway 中转
- 生产环境多用 Go 写 exporter(静态编译、零依赖、性能好),也有用 Python 的(
prometheus_client库)
Go 程序自定义 exporter
1)编写 exporter 代码(本机,/home/jiuzhao/go/src/ 下)jiuzhao@Ubuntu ~$ cd /home/jiuzhao/go/src/jiuzhao@Ubuntu src$ lshello# 创建项目目录jiuzhao@Ubuntu src$ mkdir -p ./kpyun-go-exporter && cd ./kpyun-go-exporter# 再创建项目骨架jiuzhao@Ubuntu kpyun-go-exporter$ go mod init kpyun-go-exporterjiuzhao@Ubuntu kpyun-go-exporter$ lsgo.mod # 生成模块配置文件jiuzhao@Ubuntu ~$ vim main.gopackage main
import ( "encoding/json" "log" "net/http" "time"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp")
var ( requestTime = promauto.NewSummary(prometheus.SummaryOpts{ Name: "request_processing_seconds", Help: "Time spent processing request", }) requestCount = promauto.NewCounter(prometheus.CounterOpts{ Name: "request_count_total", Help: "Total request count of the host", }))
func appsHandler(w http.ResponseWriter, r *http.Request) { start := time.Now() defer requestTime.Observe(time.Since(start).Seconds()) requestCount.Inc()
w.Header().Set("Content-Type", "application/json") resp := []map[string]string{ {"office": "https://www.kpyun.com"}, {"auther": "jiuzhao"}, } b, _ := json.Marshal(resp) w.Write(b)}
func main() { // 指标服务 :8000 go func() { http.Handle("/metrics", promhttp.Handler()) log.Fatal(http.ListenAndServe(":8000", nil)) }()
// 业务服务 :8001 mux := http.NewServeMux() mux.HandleFunc("/apps", appsHandler) log.Println("启动kpyun程序: kpyun-go-exporter, 业务 http://0.0.0.0:8001/apps, 指标 http://0.0.0.0:8000") log.Fatal(http.ListenAndServe(":8001", mux))}
2)拉取依赖并静态编译jiuzhao@Ubuntu kpyun-go-exporter$ go mod tidy# 拉取 github.com/prometheus/client_golang(GOPROXY 已配 goproxy.cn)jiuzhao@Ubuntu kpyun-go-exporter$ CGO_ENABLED=0 go build -ldflags="-s -w" -o /home/jiuzhao/下载/kpyun-go-exporter $(pwd)# CGO_ENABLED=0 静态编译,-ldflags="-s -w" 去掉调试信息减小体积jiuzhao@Ubuntu ~$ ls -lh /home/jiuzhao/下载/kpyun-go-exporter-rwxrwxr-x 1 jiuzhao jiuzhao 9.9M Aug 16 20:13 /home/jiuzhao/下载/kpyun-go-exporter# 可执行文件
3)拷贝二进制到 node3 并启动(单个静态二进制,目标机零依赖)jiuzhao@Ubuntu ~$ scp /home/jiuzhao/下载/kpyun-go-exporter node3:/usr/local/bin/[root@node3 ~]# nohup /usr/local/bin/kpyun-go-exporter > /var/log/kpyun-go-exporter.log 2>&1 &
4)验证两个端口[root@node3 ~]# ss -ntl | egrep ":8000|:8001"LISTEN 0 4096 *:8000 *:*LISTEN 0 4096 *:8001 *:*# 8000 = 指标端口(client_golang 的 promhttp)# 8001 = 业务端口(net/http 的 /apps)
5)客户端测试[root@node3 ~]# curl -s http://10.0.0.12:8001/apps[{"office":"https://www.kpyun.com"},{"auther":"jiuzhao"}]# 实际业务,对外提供的服务[root@node3 ~]# curl -s http://10.0.0.12:8000/metrics | grep -E "request_count|request_processing_seconds"# HELP request_count_total Total request count of the host# TYPE request_count_total counterrequest_count_total 1# HELP request_processing_seconds Time spent processing request# TYPE request_processing_seconds summaryrequest_processing_seconds_sum 6.52e-07request_processing_seconds_count 1📌 代码结构拆解(两个端口各司其职)
| 端口 | 提供者 | 作用 |
|---|---|---|
| 8000 | client_golang 的 promhttp.Handler() | 指标 /metrics,给 Prometheus 拉 |
| 8001 | net/http 的 /apps 处理器 | 业务 /apps,给用户访问 |
requestCount.Inc():每次请求/apps,计数器 +1(counter 类型,单调递增)defer requestTime.Observe(time.Since(start).Seconds()):统计请求处理耗时(summary 类型)- 两个 HTTP 服务同时运行、互不干扰(业务走主 goroutine,指标走
go子 goroutine)
💡 Go 版 vs Python 版:Go 编译成单个静态二进制,目标机零依赖(Python 要 pip install flask prometheus_client);性能也更好,生产环境主流选 Go
# 模拟压测(制造指标变化)1)在 node2 上写个压测脚本,随机频率请求 /apps[root@node2 ~]# cat > /root/kpyun_curl_metrics.sh <<"EOF"#!/bin/bash
URL=http://10.0.0.12:8001/apps
while true;do curl_num=$(( $RANDOM%50+1 )) sleep_num=$(( $RANDOM%5+1 )) for c_num in `seq $curl_num`;do curl -s $URL &> /dev/null done sleep $sleep_numdoneEOF[root@node2 ~]# nohup bash /root/kpyun_curl_metrics.sh > /dev/null 2>&1 &# 每轮随机打 1~50 次请求,然后睡 1~5 秒,模拟不规律的访问流量监控自定义 exporter
1)修改 prometheus.yml 追加 Go exporter 的 job(Prom)root@Prom ~# vim /etc/prometheus/prometheus.yml... - job_name: "kpyun-go-exporter" metrics_path: "/metrics" scheme: "http" static_configs: - targets: - 10.0.0.12:8000
2)检查语法 + 热加载root@Prom ~# promtool check config /etc/prometheus/prometheus.ymlChecking /etc/prometheus/prometheus.yml SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntaxroot@Prom ~# curl -X POST http://localhost:9090/-/reload
3)验证采集(等 15s 抓取一轮后)root@Prom ~# curl -s "http://localhost:9090/api/v1/query" --data-urlencode "query=request_count_total" | jq -r ".data.result[] | \"\(.metric.job) | \(.value[1])\""kpyun-go-exporter | 160✅️ 请求总数已达 160(压测生效)
4)Grafana 出图# 请求总数(累计值,单调递增)request_count_total
# 每分钟请求增量(次数)increase(request_count_total[1m])
# 每秒请求速率 QPS(平均)rate(request_count_total[1m])`面板右侧 ---> 标准选项 ---> 单位 ---> 吞吐量 ---> 请求/秒钟 (rps)`
# 每秒瞬时速率(敏感,看突发尖峰)irate(request_count_total[1m])`吞吐量 ---> 操作/秒钟 (ops) 或 ✅ 正确`
# 累计平均耗时(总耗时/总次数)request_processing_seconds_sum / request_processing_seconds_count`右侧 Standard options(标准选项)→ Unit(单位)→ Time(时间)→ microseconds (微秒μs)`
Prometheus 的服务发现实战

官方文档(动态配置):https://prometheus.io/docs/prometheus/3.5/configuration/configuration/
Prometheus 支持静态配置和动态配置两种方式指定监控目标
| 方式 | 配置 | 特点 |
|---|---|---|
| 静态配置 | static_configs | 改配置需热加载或重启服务 |
| 动态配置 | *_sd_config | 无需重启,自动监听文件/注册中心变化 |
常见的服务发现:
- file_sd_config:基于文件(json/yaml)
- consul_sd_config:基于 consul 注册中心
- kubernetes_sd_config:基于 K8s
基于文件的服务发现(file_sd)

📌 file_sd 的本质:目标清单(主机清单),不是数据
file_sd 文件里没有任何指标数据,只有两样东西:要监控的地址(targets) + 附加的标签(labels) 它解决的是”监控谁”的问题,不是”监控到什么”的问题
(1)file_sd 文件写”去监控 10.0.0.2:9100”
(2)Prometheus 读到后,去这个地址抓 /metrics
(3)真正的数据(CPU/内存/磁盘)是 node-exporter 实时生成的,被 Prometheus 抓回来
(4)数据存在 Prometheus 自己的 TSDB,跟 file_sd 文件无关
🌰 类比:file_sd 文件 = 通讯录(记着”打给谁”),真正的数据 = 打通电话后听到的内容 有了这些文件,Prometheus 就知道该监控哪些主机了
1)修改 prometheus.yml 追加 file_sd job(Prom)root@Prom ~# vim /etc/prometheus/prometheus.yml... - job_name: "kpyun-file-sd" metrics_path: "/metrics" scheme: "http" file_sd_configs: - files: - /tmp/xixi.json - /tmp/haha.yaml
2)检查语法(此时文件还没创建,会有 WARNING,属正常)root@Prom ~# promtool check config /etc/prometheus/prometheus.ymlChecking /etc/prometheus/prometheus.yml WARNING: file "/tmp/xixi.json" for file_sd in scrape job "kpyun-file-sd" does not exist WARNING: file "/tmp/haha.yaml" for file_sd in scrape job "kpyun-file-sd" does not exist SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntax
3)热加载配置root@Prom ~# curl -X POST http://localhost:9090/-/reload
4)编写 json 格式的 target 文件root@Prom ~# cat > /tmp/xixi.json <<"EOF"[ { "targets": [ "10.0.0.2:9100" ], "labels": { "school": "kpyun", "class": "C413" } }]EOF
5)验证自动发现(无需 reload!)root@Prom ~# curl -s "http://localhost:9090/api/v1/targets" | jq -r ".data.activeTargets[] | select(.labels.job==\"kpyun-file-sd\") | \"\(.labels.instance) | school=\(.labels.school // \"-\")\""10.0.0.2:9100 | school=kpyun✅️ 自动发现 json 里的目标
6)再写一个 yaml 格式的文件root@Prom ~# cat > /tmp/haha.yaml <<"EOF"- targets: - "10.0.0.11:9100" - "10.0.0.12:9100" labels: address: Tianjin class: C413EOF
7)验证(无需 reload,自动发现 2 个新目标)root@Prom ~# curl -s "http://localhost:9090/api/v1/targets" | jq -r ".data.activeTargets[] | select(.labels.job==\"kpyun-file-sd\") | \"\(.labels.instance) | \(.health)\""10.0.0.2:9100 | up10.0.0.11:9100 | up10.0.0.12:9100 | up✅️ 三个目标全部自动发现
# WebUIhttp://10.0.0.10:9090/targets?search=kpyun-file-sd
📌 一个 job 下可以有多个 targets
file_sd 文件里写的 targets 虽然地址不同(10.0.0.2<9100>9100> / 10.0.0.11<9100>9100> / 10.0.0.12<9100>9100>),但它们都挂在同一个 job_name: “kpyun-file-sd” 下
- job_name = 分组标签:Prometheus 会给这些 target 统一打上 job=“kpyun-file-sd” 标签,把它们归为一类监控任务
- targets 不同 → instance 标签不同:每台的 instance 是各自的地址,用来区分”是哪一台”
- job 相同 → 属于同一组:一个 job 就是”一组要监控的节点”,file_sd 文件里罗列的 targets 就是这组的成员
- 文件里的 labels(如 school、class)是额外附加的自定义标签,方便后续按维度筛选/分组
8)删除文件验证自动下线(无需 reload)root@Prom ~# rm -f /tmp/xixi.jsonroot@Prom ~# sleep 5root@Prom ~# curl -s "http://localhost:9090/api/v1/targets" | jq -r ".data.activeTargets[] | select(.labels.job==\"kpyun-file-sd\") | \"\(.labels.instance)\""10.0.0.11:910010.0.0.12:9100✅️ 10.0.0.2 自动下线,无需任何 reload📌 file_sd 的核心价值:改文件 = 改目标,全程零 reload
- Prometheus 实时 watch 这些文件,文件一变(增删改)目标列表立即更新
- 配合 CMDB/脚本自动生成 json/yaml,就能实现批量、自动化的监控目标管理
- json 和 yaml 两种格式都支持,字段名略有不同(json 用
targets/labels,yaml 也一样)
部署 consul 集群

consul 是 HashiCorp 出品的服务注册与发现工具,Prometheus 可以通过它动态发现监控目标
1)下载并解压 consul(三台都装,官方源)jiuzhao@Ubuntu ~$ wget -P /home/jiuzhao/下载 https://releases.hashicorp.com/consul/2.0.3/consul_2.0.3_linux_amd64.zipjiuzhao@Ubuntu ~$ scp /home/jiuzhao/下载/consul_2.0.3_linux_amd64.zip node1:/tmp/# node2/node3 同样拷贝[root@node1 ~]# unzip -l /tmp/consul_2.0.3_linux_amd64.zipArchive: /tmp/consul_2.0.3_linux_amd64.zip Length Date Time Name--------- ---------- ----- ---- 4943 08-07-2026 22:52 LICENSE.txt ❌189911975 08-07-2026 22:52 consul ✅--------- -------189916918 2 files[root@node1 ~]# unzip /tmp/consul_2.0.3_linux_amd64.zip consul -d /usr/local/bin/Archive: /tmp/consul_2.0.3_linux_amd64.zip inflating: /usr/local/bin/consul `只解压 consul 文件`[root@node1 ~]# consul version | head -1Consul v2.0.3# 三台都解压
2)创建数据目录(三台,FHS 规范放 /var/lib/)[root@node1 ~]# mkdir -p /var/lib/consul[root@node2 ~]# mkdir -p /var/lib/consul[root@node3 ~]# mkdir -p /var/lib/consul
3)编写 systemd 服务文件(三台,用 systemd 托管替代 nohup 裸跑)# node1 是 bootstrap 引导节点,参数里多一个 -bootstrap;node2/node3 用 -retry-join 加入集群[root@node1 ~]# cat > /etc/systemd/system/consul.service <<"EOF"[Unit]# 服务描述Description=HashiCorp Consul Agent# 官方文档地址Documentation=https://developer.hashicorp.com/consul/docs# 等待网络就绪后再启动After=network-online.target# 主动声明依赖网络在线目标Wants=network-online.target
[Service]# 前台常驻进程:consul agent 不 fork,拉起即算成功Type=simple# 异常退出时自动重启Restart=on-failure# 重启间隔 3 秒RestartSec=3# 文件描述符上限LimitNOFILE=65536# 启动命令(bootstrap 引导,首个节点)ExecStart=/usr/local/bin/consul agent -server -bootstrap -bind=10.0.0.2 -data-dir=/var/lib/consul -client=10.0.0.2 -ui
[Install]# 开机自启WantedBy=multi-user.targetEOF
# node2 / node3 的服务文件与 node1 区别:去掉 -bootstrap,加上 -retry-join=10.0.0.2[root@node2 ~]# cat > /etc/systemd/system/consul.service <<"EOF"[Unit]Description=HashiCorp Consul AgentDocumentation=https://developer.hashicorp.com/consul/docsAfter=network-online.targetWants=network-online.target
[Service]Type=simpleRestart=on-failureRestartSec=3LimitNOFILE=65536# 启动命令(客户端,retry-join 加入 node1 集群)ExecStart=/usr/local/bin/consul agent -server -bind=10.0.0.11 -data-dir=/var/lib/consul -client=10.0.0.11 -ui -retry-join=10.0.0.2
[Install]WantedBy=multi-user.targetEOF
# node3 同理(bind/client 换成 10.0.0.12)[root@node3 ~]# sed -i 's/10.0.0.11/10.0.0.12/g' /etc/systemd/system/consul.service[root@node3 ~]# grep ExecStart /etc/systemd/system/consul.serviceExecStart=/usr/local/bin/consul agent -server -bind=10.0.0.12 -data-dir=/var/lib/consul -client=10.0.0.12 -ui -retry-join=10.0.0.2
4)启动 consul 集群(三台)[root@node1 ~]# systemctl daemon-reload && systemctl enable --now consul[root@node2 ~]# systemctl daemon-reload && systemctl enable --now consul[root@node3 ~]# systemctl daemon-reload && systemctl enable --now consul
5)验证端口[root@node1 ~]# ss -lnt | grep -E ":8300|:8500"LISTEN 0 4096 10.0.0.2:8300 0.0.0.0:*LISTEN 0 4096 10.0.0.2:8500 0.0.0.0:*`8300 = RPC,8500 = HTTP/WebUI`# 三台都验证
6)查看集群成员(注意:consul 默认连 localhost:8500,client 绑了具体 IP 需指定)[root@node1 ~]# export CONSUL_HTTP_ADDR=http://10.0.0.2:8500[root@node1 ~]# consul membersNode Address Status Type Build Protocol DC Partition Segmentnode1 10.0.0.2:8301 alive server 2.0.3 2 dc1 default <all>node2 10.0.0.11:8301 alive server 2.0.3 2 dc1 default <all>node3 10.0.0.12:8301 alive server 2.0.3 2 dc1 default <all>✅️ 3 节点 consul 集群就绪
7)访问 WebUIhttp://10.0.0.2:8500/ui/dc1/nodes
📌 consul 为什么用 systemd 托管而不是 nohup?
consul 和 prometheus/pushgateway 一样是 Go 写的前台进程,systemd 托管才是生产级做法:
- nohup 裸跑的缺点:进程挂了不会自动拉起、重启机器后不会自启、状态没法用
systemctl status查 - systemd 托管:
Restart=on-failure自动重启、WantedBy=multi-user.target开机自启、systemctl统一管理 - 三个节点服务文件只有 ExecStart 一行不同:node1 带
-bootstrap,node2/node3 带-retry-join - node3 直接
sed替换 IP 复制 node2 的配置,省去手写
基于 consul 的服务发现(consul_sd)

1)修改 prometheus.yml 追加 consul_sd job(Prom)root@Prom ~# vim /etc/prometheus/prometheus.yml... - job_name: "kpyun-consul-service-discovery" consul_sd_configs: - server: 10.0.0.2:8500 - server: 10.0.0.11:8500 - server: 10.0.0.12:8500 # 重写/重处理标签 relabel_configs: # 匹配 consul 的源标签字段(服务名) - source_labels: [__meta_consul_service] # 正则匹配 consul 自身服务 regex: consul # 执行 drop 动作,把 consul 自己从监控目标里剔除 action: drop
2)检查语法 + 热加载root@Prom ~# promtool check config /etc/prometheus/prometheus.yml SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntaxroot@Prom ~# curl -X POST http://localhost:9090/-/reload📌 relabel_configs 的 drop 规则解读
__meta_consul_service是 consul_sd 自动加的元标签,值是服务名regex: consul+action: drop:把名为consul的服务(consul 集群自身)从监控目标里剔除- 为什么不监控 consul 自己?consul 的自身指标有专门的 consul_exporter(下一节),避免重复采集
注册服务到 consul
1)注册 node2 的 node-exporter 服务到 consul[root@node1 ~]# curl -X PUT -d '{"id":"prometheus-node2","name":"kpyun-prometheus-node2","address":"10.0.0.11","port":9100,"tags":["node-exporter"],"checks":[{"http":"http://10.0.0.11:9100","interval":"5s"}]}' http://10.0.0.2:8500/v1/agent/service/register'⚠️ 健康检查中的 interval 改小(5s)是为了实验时快点变绿、方便观察'📌 注册服务的 curl 命令里,两个 IP 各是什么?
curl -X PUT -d ’{“id”:“prometheus-node2”,“name”:”…”,“address”:“10.0.0.11”,“port”<9100>9100>,…}’ http://10.0.0.2:8500/v1/agent/service/register
| 部分 | IP | 含义 |
|---|---|---|
| 数据体(JSON) | 10.0.0.11<9100>9100> | 要注册的服务在哪(被监控的 node-exporter 地址) |
| URL | 10.0.0.2<8500>8500> | 告诉哪个 consul 节点(consul agent 地址) |
- 数据体描述 —> “服务的位置”;URL 指明 —> “告诉谁”
- curl 命令在任何一台机器上执行都行(只要同网段、网络能通),不需要在服务端、也不需要 consul 节点上执行
🌰 curl 是”报信员”——数据体说”服务在哪”,URL 说”报给哪个 consul”,报信员自己在哪都无所谓,网络通就行
2)注册 node3 的 node-exporter 服务[root@node1 ~]# curl -X PUT -d '{"id":"prometheus-node3","name":"kpyun-prometheus-node3","address":"10.0.0.12","port":9100,"tags":["node-exporter"],"checks":[{"http":"http://10.0.0.12:9100","interval":"5s"}]}' http://10.0.0.2:8500/v1/agent/service/register
3)查看已注册服务[root@node1 ~]# curl -s http://10.0.0.2:8500/v1/catalog/services{"consul":[],"kpyun-prometheus-node2":["node-exporter"],"kpyun-prometheus-node3":["node-exporter"]}
# WebUIhttp://10.0.0.2:8500/ui/dc1/services`等一会,才能变绿`1)检查是 周期性 的(JSON 里配的 `interval`)2)等到第一个 interval 周期:agent 才去访问 `http://IP:9100` 做第一次检查3)生产环境用 5m~30s 视情况而定(interval 越大,变绿越慢,但检查压力越小)
4)验证 Prometheus 自动发现(无需改配置、无需 reload)root@Prom ~# curl -s "http://localhost:9090/api/v1/targets" | jq -r ".data.activeTargets[] | select(.scrapePool==\"kpyun-consul-service-discovery\") | \"\(.labels.instance) | \(.health)\""10.0.0.12:9100 | up10.0.0.11:9100 | up✅️ 注册到 consul 的服务被 Prometheus 自动发现并监控📌 consul_sd 的核心价值:服务注册即监控
- 新服务只要
curl注册到 consul,Prometheus 自动纳入监控,无需改 prometheus.yml、无需 reload - 注销服务同理,自动从监控目标移除
- 这正是”动态配置”相比”静态配置”的优势——目标增减全自动
`注销服务(彩蛋)`# 注册:告诉 node1 的 agentcurl -X PUT -d '{...}' http://10.0.0.2:8500/v1/agent/service/register
# 注销:也要找 node1 的 agent(deregister 按服务 id)curl -X PUT http://10.0.0.2:8500/v1/agent/service/deregister/prometheus-node2注册是 agent 级别 API(/v1/agent/service/register),注册信息先存在你连的那个 consul agent 本地,再通过 Raft 同步到整个集群
- 注册时找哪个 consul 节点,注销时也找同一个节点(因为注册信息存在那个 agent 本地)
- 注销的 URL 和注册相同,只是 API 变成 /deregister/<服务id>
- 注销 curl 也一样,在任何一台机器上执行都行
-
“在哪执行”(node1 上跑还是宿主机上跑)无所谓,只要网络能到 consul 的 8500
-
“发给谁”(URL 里的 8500 地址)才决定注册信息落在哪个 agent 上
-
🌰 注册走 /v1/agent/service/register,注销走 /v1/agent/service/deregister/
监控 consul 应用(consul_exporter)

1)下载并解压 consul_exporterjiuzhao@Ubuntu ~$ wget -P /home/jiuzhao/下载 https://github.com/prometheus/consul_exporter/releases/download/v0.13.0/consul_exporter-0.13.0.linux-amd64.tar.gzjiuzhao@Ubuntu ~$ scp /home/jiuzhao/下载/consul_exporter-0.13.0.linux-amd64.tar.gz node2:/tmp/[root@node2 ~]# tar tf /tmp/consul_exporter-0.13.0.linux-amd64.tar.gzconsul_exporter-0.13.0.linux-amd64/consul_exporter-0.13.0.linux-amd64/consul_exporterconsul_exporter-0.13.0.linux-amd64/NOTICEconsul_exporter-0.13.0.linux-amd64/LICENSE[root@node2 ~]# tar xf /tmp/consul_exporter-0.13.0.linux-amd64.tar.gz -C /usr/local/bin/ consul_exporter-0.13.0.linux-amd64/consul_exporter --strip-components=1[root@node2 ~]# consul_exporter --version 2>&1 | head -1consul_exporter, version 0.13.0💡 consul_exporter 只需部署 1 台即可监控整个集群
和 Kafka/ES exporter 同理:—consul.server 连任意一个 consul 节点,集群级 API 返回的是整个集群的状态 所以 node2 一台部署,就能监控 node1/2/3 全部 consul 节点(consul_raft_peers 3 就是证据)
2)编写 systemd 服务文件(systemd 托管)[root@node2 ~]# cat > /etc/systemd/system/consul_exporter.service <<"EOF"[Unit]# 服务描述Description=Consul Exporter# 等待网络就绪后再启动After=network-online.target# 主动声明依赖网络在线目标Wants=network-online.target
[Service]# 前台常驻进程Type=simple# 异常退出时自动重启Restart=on-failure# 重启间隔 3 秒RestartSec=3# 启动命令(--consul.server 指定任意一个 consul 节点即可)ExecStart=/usr/local/bin/consul_exporter --consul.server="http://10.0.0.11:8500" --web.telemetry-path="/metrics" --web.listen-address=:9107
[Install]# 开机自启WantedBy=multi-user.targetEOF[root@node2 ~]# systemctl daemon-reload && systemctl enable --now consul_exporter
3)验证指标[root@node2 ~]# curl -s http://10.0.0.11:9107/metrics | grep -E "^consul_up|^consul_raft_peers"consul_raft_peers 3consul_up 1✅️ consul_exporter 正常,看到 3 个 raft peers# WebUI 验证
4)注册 consul_exporter 服务到 consul(让 Prometheus 自动发现)[root@node2 ~]# curl -X PUT -d '{"id":"consul-server","name":"consul-cluster","address":"10.0.0.11","port":9107,"tags":["consul-cluster"],"checks":[{"http":"http://10.0.0.11:9107","interval":"5s"}]}' http://10.0.0.2:8500/v1/agent/service/register
5)验证 Prometheus 自动发现 consul_exporterroot@Prom ~# curl -s "http://localhost:9090/api/v1/targets" | jq -r ".data.activeTargets[] | select(.scrapePool==\"kpyun-consul-service-discovery\") | \"\(.labels.instance) | \(.health)\""10.0.0.12:9100 | up10.0.0.11:9100 | up10.0.0.11:9107 | up✅️ consul_exporter 被自动发现,无需改配置
6)Grafana 导入模板 ID12049💡 这一步完美演示了 consul_sd 的威力:
部署 consul_exporter → 注册到 consul → Prometheus 零改动自动开始监控 consul 本身
整个链路 部署 → 注册 → 自动发现 → 监控 全程不需要碰 prometheus.yml

Prometheus 的联邦模式

联邦模式主要作用就是减轻单台 Prometheus server 的 I/O 压力,实现分布式存储数据
- 子 server(32/33):负责就近采集原始数据(下沉到不同区域/集群)
- 主 server(31):不直接采原始数据,而是从子 server 的
/federate端点拉取聚合结果 - 好处:单台 server 的采集/存储压力被分摊到多台
📌 老师环境 → 实际环境的 IP 映射
| 老师环境 | 实际环境 | 角色 |
|---|---|---|
| prometheus-server31 (10.0.0.31) | Prom (10.0.0.10) | 主 server(联邦汇总) |
| prometheus-server32 (10.0.0.32) | node2 (10.0.0.11) | 联邦子 server(file_sd 监控 node1) |
| prometheus-server33 (10.0.0.33) | node3 (10.0.0.12) | 联邦子 server(consul_sd 监控 node2/3) |
| node-exporter41 (10.0.0.41) | node1 (10.0.0.2) | node-exporter |
| node-exporter42 (10.0.0.42) | node2 (10.0.0.11) | node-exporter + pushgateway + consul_exporter |
| node-exporter43 (10.0.0.43) | node3 (10.0.0.12) | node-exporter + Go exporter |
⚠️ 老师环境是 6 台机器(31/32/33 三台 server + 41/42/43 三台被监控机) 我们只有 4 台(Prom + node1/2/3),所以让 node2 同时扮演 42 和 32、node3 同时扮演 43 和 33 一台机器既是被监控机(跑 node-exporter),又是联邦子 server(跑 prometheus 二进制),两者互不冲突
部署子 server(32 监控 node1)
1)停用 cockpit 释放 9090 端口(⚠️ Rocky 10 默认自带 cockpit Web 管理界面,占用 9090!)[root@node2 ~]# systemctl disable --now cockpit.socketRemoved '/etc/systemd/system/sockets.target.wants/cockpit.socket'.[root@node2 ~]# ss -lnt | grep 9090 || echo "9090 已释放"9090 已释放
2)一键安装 prometheus server(node2 = 32 节点)root@Prom ~# cd /server/scripts/# 里面有一键安装的脚本root@Prom scripts# tar zcf autoinstall-prometheus.tar.gz autoinstall-prometheus/root@Prom scripts# scp autoinstall-prometheus.tar.gz 10.0.0.11:/tmproot@Prom scripts# scp autoinstall-prometheus.tar.gz 10.0.0.12:/tmp[root@node2 ~]# mkdir -p /server/scripts && cd /server/scripts[root@node2 scripts]# tar xf /tmp/autoinstall-prometheus.tar.gz -C /server/scripts[root@node2 scripts]# cd autoinstall-prometheus/[root@node2 autoinstall-prometheus]# bash install-prometheus-server.sh i... 安装成功!Prometheus v3.13.2 已运行服务状态: active监听端口: LISTEN 0 4096 *:9090✅️ node2 的 prometheus server 装好了
3)配置 file_sd 监控 node1[root@node2 ~]# mkdir -p /etc/prometheus/sd[root@node2 ~]# cat > /etc/prometheus/sd/kpyun-linux.yaml <<"EOF"- targets: - "10.0.0.2:9100" labels: school: kpyun class: RedhatEOF[root@node2 ~]# cat >> /etc/prometheus/prometheus.yml <<"EOF"
- job_name: "kpyun-file-sd-yaml-node_exporter" file_sd_configs: - files: - /etc/prometheus/sd/*.yamlEOF[root@node2 ~]# promtool check config /etc/prometheus/prometheus.yml SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntax[root@node2 ~]# curl -X POST http://localhost:9090/-/reload
4)验证 32 采集 node1[root@node2 ~]# curl -s "http://localhost:9090/api/v1/targets" | jq -r ".data.activeTargets[] | \"\(.scrapePool) | \(.labels.instance) | \(.health)\""kpyun-file-sd-yaml-node_exporter | 10.0.0.2:9100 | upprometheus | localhost:9090 | up✅️ 32 用 file_sd 采集到了 node1部署子 server(33 监控 node2/3)
1)停用 cockpit 释放 9090(node3)[root@node3 ~]# systemctl disable --now cockpit.socket[root@node3 ~]# ss -lnt | grep 9090 || echo "9090 已释放"9090 已释放
2)一键安装 prometheus server(node3 = 33 节点)[root@node3 ~]# tar xf /tmp/autoinstall-prometheus.tar.gz -C /server/scripts[root@node3 ~]# cd /server/scripts/autoinstall-prometheus/[root@node3 autoinstall-prometheus]# bash install-prometheus-server.sh i... 安装成功!Prometheus v3.13.2 已运行
3)配置 consul_sd 监控 node2/node3[root@node3 ~]# cat >> /etc/prometheus/prometheus.yml <<"EOF"
- job_name: "kpyun-consul-sd-node_exporter" consul_sd_configs: - server: 10.0.0.2:8500 - server: 10.0.0.11:8500 - server: 10.0.0.12:8500 relabel_configs: - source_labels: [__meta_consul_service] regex: consul action: dropEOF[root@node3 ~]# promtool check config /etc/prometheus/prometheus.yml SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntax[root@node3 ~]# curl -X POST http://localhost:9090/-/reload
4)验证 33 采集 node2/node3[root@node3 ~]# curl -s "http://localhost:9090/api/v1/targets" | jq -r ".data.activeTargets[] | \"\(.scrapePool) | \(.labels.instance) | \(.health)\""kpyun-consul-sd-node_exporter | 10.0.0.11:9100 | upkpyun-consul-sd-node_exporter | 10.0.0.11:9107 | upkpyun-consul-sd-node_exporter | 10.0.0.12:9100 | upprometheus | localhost:9090 | up✅️ 33 用 consul_sd 采集到了 node2/node3 + consul_exporter主 server 31 配置联邦汇总
1)修改主 server(Prom = 31)配置,用 /federate 端点拉取 32/33root@Prom ~# vim /etc/prometheus/prometheus.yml`注释之前的配置`# 去掉纯注释行和空行,但保留行内注释root@Prom ~# egrep -v '^[[:space:]]*$|^[[:space:]]*#' /etc/prometheus/prometheus.yml`关键点是 ^[[:space:]]*# 匹配行首任意空格后紧跟 # 的行`global: scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.alerting: alertmanagers: - static_configs: - targets:rule_files:scrape_configs: - job_name: "prometheus" static_configs: - targets: ["localhost:9090"] labels: app: "prometheus" - job_name: "kpyun-prometheus-federate-32" metrics_path: "/federate" honor_labels: true params: "match[]": - '{job="prometheus"}' - '{__name__=~"node.*"}' static_configs: - targets: - "10.0.0.11:9090" - job_name: "kpyun-prometheus-federate-33" metrics_path: "/federate" honor_labels: true params: "match[]": - '{job="prometheus"}' - '{__name__=~"node.*"}' - '{__name__=~"consul.*"}' static_configs: - targets: - "10.0.0.12:9090"💡 联邦模式参数详解
| 参数 | 含义 |
|---|---|
metrics_path: "/federate" | 拉取的是子 server 的联邦端点,不是 /metrics |
params.match[] | 过滤要汇总哪些指标,支持 {job="..."}和 {__name__=~"..."} 正则 |
honor_labels: true | 保留子 server 传来的原始标签(否则会被主 server 的 job 标签覆盖) |
📌 match[] 是联邦模式的”筛选器”
联邦不是把子 server 的所有指标都拉过来(那样等于没分担压力),而是只拉需要的
'{job="prometheus"}':只拉 job 名为 prometheus 的指标(精确匹配)'{__name__=~"node.*"}':只拉node_开头的指标(Linux 主机指标)(正则匹配)'{__name__=~"consul.*"}':只拉consul_开头的指标(consul 监控)(正则匹配)- 在 Prometheus 联邦中,多个 match[ ] 参数之间是 OR(并集) 关系
- 没有匹配到的指标不会被汇总,这就是**“分布式采集 + 按需汇总”** ✅
2)删除 Prometheus TSDB '旧数据'# 停止主 serverroot@Prom ~# systemctl stop prometheus# 删除 TSDB 数据目录root@Prom ~# rm -rf /var/lib/prometheus/data/*
3)检查语法 + 重新启动root@Prom ~# promtool check config /etc/prometheus/prometheus.yml SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntaxroot@Prom ~# systemctl start prometheus
4)验证联邦 targetsroot@Prom ~# curl -s "http://localhost:9090/api/v1/targets" | jq -r ".data.activeTargets[] | select(.scrapePool | startswith(\"kpyun-prometheus-federate\")) | \"\(.scrapePool) | \(.labels.instance) | \(.health)\""`稍微等一会,刚重启`kpyun-prometheus-federate-32 | 10.0.0.11:9090 | upkpyun-prometheus-federate-33 | 10.0.0.12:9090 | up✅️ 联邦端点全部连通
5)验证汇总到的指标(主 server 上能查到全部 node 指标)root@Prom ~# curl -s "http://localhost:9090/api/v1/query" --data-urlencode "query=node_boot_time_seconds" | jq -r ".data.result[] | \"\(.metric.instance) | job=\(.metric.job)\""10.0.0.2:9100 | job=kpyun-file-sd-yaml-node_exporter # 来自 32 联邦10.0.0.11:9100 | job=kpyun-consul-sd-node_exporter # 来自 33 联邦10.0.0.12:9100 | job=kpyun-consul-sd-node_exporter # 来自 33 联邦✅️ 主 server 汇总了子 server 采集的全部 node 指标
6)验证 consul 指标也从 33 联邦过来root@Prom ~# curl -s "http://localhost:9090/api/v1/query" --data-urlencode "query=consul_raft_peers" | jq -r ".data.result[] | \"\(.metric.instance) | value=\(.value[1])\""10.0.0.11:9107 | value=3✅️ consul 指标通过联邦汇总到了主 server
7)Grafana 导入模板 ID1860
Grafana 出图故障排查技巧

当 Grafana 查不到数据时,按`数据流向`逐段排查:` 用户自定义指标 → pushgateway → Prometheus → Grafana `
排查顺序(从源头到末端):1)数据是否推送到 pushgateway?curl -s http://10.0.0.11:9091/metrics | grep <指标名>
2)Prometheus 是否采集到数据?curl -s "http://localhost:9090/api/v1/query" --data-urlencode "query=<指标名>" | jq
3)Grafana 数据源配置是否正确?
4)变量引用问题(PromQL 里的变量是否定义正确)
5)时间选择范围是否有问题文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!















