# 第 2 章 部署与点亮:让 Prometheus 真正跑起来
本章导读
第 1 章我们讲了架构,但架构是 "纸上的图"。本章开始动手,把图上的每一个方框都变成一台机器上的一个进程。
这一章信息密度很高,涉及五个软件的安装、一份配置文件的逐行讲解、以及六个真实踩过的坑。请务必不要跳过 "故障现场" 这类小节 —— 面试官不问你装成功过什么,只问你出过错之后怎么定位的。
一句话总结本章目标:把 9090(Prometheus)、9100(Node Exporter)、3000(Grafana)、9091(Pushgateway)这四个端口全部点亮,并且能在 Grafana 上看到四台主机的 CPU 曲线。
学习目标
| 序号 | 学完本章,你应当能够 |
|---|---|
| 1 | 独立完成 Prometheus Server 的二进制安装,并写出规范的 systemd 服务单元 |
| 2 | 解释 --web.enable-lifecycle 的作用与安全风险,说明生产环境为何要禁用 |
| 3 | 逐行读懂 prometheus.yml 的四段式结构,独立新增一个采集 job |
| 4 | 用 promtool 做配置语法检查,并说清 "不重启生效" 的三种方式 |
| 5 | 用 Docker / Docker Compose 跑起 Prometheus,并正确处理配置与数据两个持久化问题 |
| 6 | 在任意节点上部署 Node Exporter,并让 Prometheus 采集到它 |
| 7 | 用 "五步法" 排查 "目标抓不到数据" 的故障 |
| 8 | 完成 Grafana 的数据源配置与社区 Dashboard 导入,并解释 "监控 Grafana 自己为什么绕一圈" |
| 9 | 说清 Pushgateway 的适用场景、工作原理与四个固有缺陷 |
# 2.1 开工之前:先把拼图摆好
# 2.1.1 五个软件,各管一段
第 1 章我们列过组件职责表,现在把它落成 "要装什么":
| 软件 | 默认端口 | 一句话职责 | 类比 Zabbix |
|---|---|---|---|
| Prometheus Server | 9090 | 拉取指标 + 内置 TSDB 存储 + 规则计算 | zabbix-server + 数据库 |
| Node Exporter | 9100 | 把本机的 CPU / 内存 / 磁盘 / 网络指标用 HTTP 暴露出来 | zabbix-agent |
| Grafana | 3000 | 把数据画成漂亮的图 | zabbix-web (但强太多) |
| Alertmanager | 9093 | 接收告警、去重、分组、路由、发送 | 内置告警动作(本章不展开) |
| Pushgateway | 9091 | 给 "抓不到" 的瞬时指标当中转代理 | zabbix-proxy (角色相近) |
📌 一个重要认知:Prometheus Server 只做两件事 —— 采集、存储。它自带的那个 Web 界面(9090)只用于调试查询,从来没打算让你拿它当监控大屏。所以 "要装 Grafana" 不是可选项,而是必选项。
# 2.1.2 部署拓扑:谁装在哪
flowchart TB | |
subgraph MON["监控服务器(可合设,也可分离部署)"] | |
direction LR | |
P["Prometheus Server<br/>:9090"] | |
G["Grafana<br/>:3000"] | |
PG["Pushgateway<br/>:9091"] | |
P <-->|PromQL 查询| G | |
PG -->|被 Pull| P | |
end | |
subgraph NODES["被监控节点"] | |
direction LR | |
N1["node101<br/>Node Exporter :9100"] | |
N2["node102<br/>Node Exporter :9100"] | |
N3["node103<br/>Node Exporter :9100"] | |
N4["监控服务器自己<br/>Node Exporter :9100"] | |
end | |
N1 -->|Pull /metrics| P | |
N2 -->|Pull /metrics| P | |
N3 -->|Pull /metrics| P | |
N4 -->|Pull /metrics| P |
三条部署原则(经验,不是规定):
- Prometheus / Grafana / Pushgateway 可以装在同一台(实验环境、小规模环境),也可以分开装。分开装的好处是性能互不干扰、故障不连带。
- Node Exporter 必须装在被监控的每一台机器上,一台不能少 —— 包括 Prometheus 自己那台,因为监控系统本身也需要被监控。
- 生产环境请把
--storage.tsdb.path(数据目录)独立挂一个分区,避免数据把根分区写满导致整机不可用。
# 2.1.3 版本怎么选:先看兼容性
监控栈是 "多组件协同",版本乱配最容易出问题。下面是一套经过验证、兼容性良好的企业级组合:
| 组件 | 推荐版本 | 示例文件 |
|---|---|---|
| Prometheus | 2.53.x | prometheus-2.53.2.linux-amd64.tar.gz |
| Alertmanager | 0.27.x | alertmanager-0.27.0.linux-amd64.tar.gz |
| Node Exporter | 1.8.x | node_exporter-1.8.2.linux-amd64.tar.gz |
| Grafana | 11.x | grafana-enterprise_11.2.0_amd64.deb |
| Pushgateway | 1.10.x | pushgateway-1.10.0.linux-amd64.tar.gz |
⚠️ 关于 2.x 还是 3.x
Prometheus 已经发布了 3.x,并且提供了 LTS(长期支持) 版本。课程的演示环境用的是 3.5.0 LTS,功能上比 2.x 多了些改进。
但要注意:大量企业存量环境仍然是 2.53.x,生态里的部分 Exporter、Dashboard、规则文件是按 2.x 写的。所以:
- 新环境可以上 3.x LTS;
- 接手老环境时不要手贱升级主版本;
- 预发布版(rc /beta/ 带 -rc 后缀)一律不要装到生产,它不是给生产用的。
# 2.1.4 六种部署方式
官方文档:https://prometheus.io/docs/prometheus/latest/installation/
| 方式 | 适用场景 | 一句评价 |
|---|---|---|
| 包安装(apt /yum) | 快速验证、小规模 | 最省事,但版本由仓库决定,往往偏旧 |
| 二进制安装 | 生产主力(学完再看第 4 行) | 版本可控、零依赖,必须掌握 |
| Ansible | 批量部署几十上百台 | 有现成 Role,改改变量就行 |
| Docker | 快速起、CI 环境 | 一条命令,注意挂载 |
| Docker Compose | 一键起整套监控栈 | 适合实验与中小规模 |
| Kubernetes | 云原生环境 | 用 kube-prometheus / Operator / Helm |
💡 学习路径建议:先二进制,再容器。
你只有先在裸机上手动装过一遍,才知道容器里那个-v到底在挂什么、那个--config.file到底指向哪。反过来学,你会永远停留在 "复制粘贴能跑,坏了不会修" 的层次。
# 2.2 方式一:包安装(5 分钟上手)
# 2.2.1 Ubuntu / Debian
Ubuntu 的官方仓库里直接带 Prometheus 包:
# 先看看仓库里有哪个版本 | |
apt list prometheus | |
# ubuntu 22.04 | |
# prometheus/jammy 2.31.2+ds1-1ubuntu1 amd64 | |
# ubuntu 24.04 | |
# prometheus/noble-security 2.45.3+ds-2ubuntu0.2 amd64 | |
apt -y install prometheus |
装完服务自动启动,并且 —— 这是很多人不知道的一点 —— 包里自带了一个 Node Exporter:
systemctl status prometheus | |
systemctl status prometheus-node-exporter.service | |
ss -ntlp | grep prometheus | |
# LISTEN 0 4096 *:9090 users:(("prometheus",pid=2969,fd=4)) | |
# LISTEN 0 4096 *:9100 users:(("prometheus-node",pid=2195,fd=3)) |
所以你会看到 9090 和 9100 同时被监听,进程名分别是 /usr/bin/prometheus 和 /usr/bin/prometheus-node-exporter 。
💡 包安装的价值:适合 "我今天就想看一眼 Prometheus 长啥样"。它的配置文件在
/etc/prometheus/prometheus.yml,数据目录在/var/lib/prometheus,都由打包者安排好了,你不用操心。
# 2.2.2 RHEL / Rocky / CentOS
RHEL 系默认没有 Prometheus 仓库,需要自己配 yum repo(packagecloud 提供):
[prometheus] | |
name=prometheus | |
baseurl=https://packagecloud.io/prometheus-rpm/release/el/$releasever/$basearch | |
repo_gpgcheck=1 | |
enabled=1 | |
gpgkey=https://packagecloud.io/prometheus-rpm/release/gpgkey | |
https://raw.githubusercontent.com/lest/prometheus-rpm/master/RPM-GPG-KEY-prometheus-rpm | |
gpgcheck=1 | |
metadata_expire=300 |
⚠️ 包安装的两大局限(也正是生产偏爱二进制的原因):
- 版本被仓库锁死,仓库里是什么版本你就用什么版本,通常落后官方好几个小版本,甚至大版本;
- 目录布局固定,想自定义数据目录、想和别的实例并存,就很别扭。
# 2.3 方式二:二进制安装(生产标准做法)
# 2.3.1 为什么 Go 程序装起来这么简单
Prometheus 全套组件都是 Go 写的,编译产物是静态链接的单一二进制文件。这意味着:
- 零运行时依赖 —— 不需要 Python、不需要 JDK、不需要 libc 的某个版本;
- 拷到任何 Linux 上都能跑 ——Ubuntu、Rocky、Alpine 都一样;
- 镜像可以做得很小 —— 甚至可以
FROM scratch。
验证一下就明白了:
ldd prometheus promtool | |
# prometheus: | |
# 不是动态可执行文件 | |
# promtool: | |
# 不是动态可执行文件 |
"不是动态可执行文件",就是静态编译的意思。
# 2.3.2 下载与解压
官方下载:https://prometheus.io/download/
历史版本:https://github.com/prometheus/prometheus/releases
cd /usr/local | |
wget https://github.com/prometheus/prometheus/releases/download/v2.53.2/prometheus-2.53.2.linux-amd64.tar.gz | |
tar xf prometheus-2.53.2.linux-amd64.tar.gz | |
# 建软链接,去掉版本号,将来升级只改软链 | |
ln -s prometheus-2.53.2.linux-amd64 prometheus |
💡 软链接的价值:以后从 2.53.2 升级到 2.53.3,只需要解压新目录、把软链指过去、重启服务。所有配置和 systemd 单元里的路径一个字都不用改。这是运维的标准习惯。
⚠️ 国内下载慢的老问题:GitHub 直连经常断。稳妥做法是一次性把安装包下载好,随安装脚本一起分发,做离线安装 —— 生产环境装东西,永远不要指望 "现场 wget 能成功"。
解压后目录内容:
/usr/local/prometheus/ | |
├── console_libraries/ # 内置控制台模板的库 | |
├── consoles/ # 内置控制台模板 | |
├── LICENSE # 可以删 | |
├── NOTICE # 可以删 | |
├── prometheus # ★ 主程序 | |
├── prometheus.yml # ★ 默认配置 | |
└── promtool # ★ 运维神器(语法检查/查询调试/TSDB 工具) |
# 2.3.3 目录规划与权限
原始包里的文件平铺在一起,不够规整。按 "程序 / 配置 / 数据" 分离:
cd /usr/local/prometheus | |
mkdir bin conf data | |
mv prometheus promtool bin/ | |
mv prometheus.yml conf/ | |
# 创建专用的服务账号:不要用 root,也不要让它能登录 shell | |
useradd -r -s /sbin/nologin prometheus | |
# 统一属主属组 | |
chown -R prometheus:prometheus /usr/local/prometheus/ |
最终结构:
/usr/local/prometheus/ | |
├── bin/ prometheus, promtool | |
├── conf/ prometheus.yml | |
└── data/ ← TSDB 数据(不建也行,首次启动会自动创建) |
📌
-r表示系统账号(UID 较小、默认不建家目录),-s /sbin/nologin表示禁止交互登录。服务账号就该是这种 "只存在、不登录" 的形态,安全上更干净。
再把 bin 加到 PATH,省得每次写全路径:
cat > /etc/profile.d/prometheus.sh <<'EOF' | |
export PROMETHEUS_HOME=/usr/local/prometheus | |
export PATH=${PROMETHEUS_HOME}/bin:$PATH | |
EOF | |
source /etc/profile |
# 2.3.4 第一次启动:先让它 "裸跑" 一下
主程序参数很多,但初始化阶段只需要关心两个:
prometheus --help | head -20 |
| 参数 | 含义 | 默认值 |
|---|---|---|
--config.file | 配置文件路径 | prometheus.yml (相对于当前工作目录) |
--web.listen-address | UI / API 监听地址 | 0.0.0.0:9090 |
--storage.tsdb.path | TSDB 数据目录 | data/ |
先什么都不带,直接跑一次,你会看到报错:
cd /usr/local/prometheus/bin | |
./prometheus | |
# level=error msg="Error loading config" err="open prometheus.yml: no such file or directory" |
🧠 为什么? 因为默认配置文件名是相对路径
prometheus.yml,它在当前工作目录下找。而我们刚把配置文件挪到了conf/。
那就在上层的工作目录里启动,把配置路径指过去:
cd /usr/local/prometheus | |
./bin/prometheus --config.file=conf/prometheus.yml |
起来了。此时浏览器访问 http://<IP>:9090 就能看到那个 "简陋但够用" 的 Web 界面。
# 2.3.5 ⭐ 故障现场一:以 root 启动过一次,之后再也起不来
这是初学者 90% 会踩、且完全不知道原因的坑。完整重现一遍:
现象:手动用 root 跑通了,然后删掉进程、改成 systemd 以 prometheus 用户启动,服务起不来。
systemctl start prometheus | |
systemctl status prometheus | |
# 一直 failed |
排查过程(这才是重点):
- 先看端口有没有起来 ——
ss -ntlp | grep 9090,没有。 - 再看服务状态 ——
systemctl status prometheus,只有一行 "启动失败",信息不够。 - 看日志 ——
journalctl -u prometheus -n 50 --no-pager(或看/usr/local/prometheus/data同级目录下有没有生成日志文件),发现关键的一句:
msg="Error opening storage" err="open /usr/local/prometheus/data/queries.active: permission denied" |
- 验证猜想 ——
ls -l /usr/local/prometheus/data/,发现里面的文件属主是root:root。
根因:
我第一次是用 root 身份手动启动的,Prometheus 在
data/目录下自动创建了queries.active、lock、wal/等文件,属主全部是 root。
现在 systemd 以prometheus普通用户启动,去写这些 root 拥有的文件 —— 权限不足。
解决:
chown -R prometheus:prometheus /usr/local/prometheus/ | |
# 特别是 data 目录,重新设一遍 | |
chown -R prometheus:prometheus /usr/local/prometheus/data | |
systemctl restart prometheus |
⭐ 这条经验请写进笔记:
调试阶段用哪个身份启动,生产就用哪个身份。
不要图省事用 root 试跑 —— 它会污染数据目录、日志文件、以及你自己都没意识到的 PID 文件。"先 root 试跑,再改普通用户" 是典型的错误顺序。
# 2.3.6 写 systemd 服务单元
标准单元文件(Ubuntu 在 /lib/systemd/system/ ,Rocky 在 /usr/lib/systemd/system/ ,两者都兼容 /etc/systemd/system/ ):
[Unit] | |
Description=Prometheus Server | |
Documentation=https://prometheus.io/docs/introduction/overview/ | |
After=network.target | |
[Service] | |
Restart=on-failure | |
User=prometheus | |
Group=prometheus | |
WorkingDirectory=/usr/local/prometheus/ | |
ExecStart=/usr/local/prometheus/bin/prometheus \ | |
--config.file=/usr/local/prometheus/conf/prometheus.yml | |
ExecReload=/bin/kill -HUP $MAINPID | |
LimitNOFILE=65535 | |
[Install] | |
WantedBy=multi-user.target |
逐行解释,这几行每一行都有用:
| 配置 | 作用 | 为什么必须有 |
|---|---|---|
After=network.target | 在网络就绪后再启动 | 否则开机可能先于网络起来,抓取全部失败 |
User / Group | 以专用账号运行 | 最小权限原则,避免被拿下后拿整机 |
WorkingDirectory | 工作目录 | 关键:决定了相对路径( data/ )解析到哪里 |
ExecStart ... --config.file | 启动命令与配置路径 | 用绝对路径,不依赖 PATH |
ExecReload=/bin/kill -HUP $MAINPID | systemctl reload 发 SIGHUP | 让不重启进程重载配置成为可能 |
LimitNOFILE=65535 | 打开文件数上限 | TSDB 会打开大量 chunk 文件,默认 1024 不够。这是性能优化项 |
Restart=on-failure | 异常退出自动拉起 | 提高可用性 |
WantedBy=multi-user.target | 允许开机自启 | systemctl enable 才有意义 |
启用:
systemctl daemon-reload | |
systemctl enable --now prometheus.service | |
systemctl is-active prometheus | |
ss -ntlp | grep 9090 |
⚠️
LimitNOFILE到底影响什么?
它的效果你能亲眼看到 —— 访问http://<IP>:9090/metrics,搜索process_max_fds:
process_max_fds 6.5535e+04改一下 service 文件里的值(比如改成 66666),重启服务后再看,这个指标就跟着变了。"配置文件的每一个数字,都会在某个指标里体现"—— 这是理解监控系统的一个非常好的思维方式。
# 2.3.7 验证:Web UI 的三个菜单
浏览器访问 http://<prometheus-ip>:9090/ ,顶部菜单四项:
| 菜单 | 作用 | 你需要知道的 |
|---|---|---|
| Alerts | 当前处于活动状态的告警 | 显示由规则计算出的 pending /firing 告警 |
| Graph | 查询与画图界面 | 默认落地页,PromQL 就在这里写 |
| Status | 各种运行状态 | 排障时最常用,见下 |
| Help | 帮助与文档链接 | 很少用 |
Status 下的子菜单(排障必去):
| 子菜单 | 内容 | 典型用途 |
|---|---|---|
Runtime & Build Information | 版本、启动时间、构建信息、内存等 | 确认版本、确认服务是否刚重启 |
Command-Line Flags | 实际生效的启动参数 | 确认 service 文件里写的参数真的生效了 |
Configuration | 当前生效的配置(YAML 原文) | 确认 reload 之后配置真的变了 |
Rules | 规则文件的加载与计算情况 | 告警不触发时第一个该看的地方 |
Targets | 所有采集目标的健康状态 | ⭐ 抓不到数据时第一个该看的地方 |
Service Discovery | 服务发现得到的动态目标列表 | 排查 K8s /file_sd 不生效 |
⚠️ 两个 "只存在于内存" 的认知:
- Graph 页面里写的表达式,刷新就没了。 这就是为什么必须要有 Grafana—— 它负责把表达式 "存下来" 变成面板。
- Targets 页面上的
Last Scrape时间和State颜色,是你判断 "数据到底有没有进来" 最快的方式。绿色 UP = 抓到了;红色 DOWN = 抓不到。
# 2.4 管理 API 与 --web.enable-lifecycle 的安全陷阱
# 2.4.1 四个 API 端点
Prometheus 提供了一组管理 API 用于自动化运维:
| 端点 | 方法 | 作用 | 是否需要 --web.enable-lifecycle |
|---|---|---|---|
/-/healthy | GET | 健康检查,通常恒返回 200 | ❌ 不需要 |
/-/ready | GET | 就绪检查,能响应查询后返回 200 | ❌ 不需要 |
/-/reload | POST / PUT | 重载配置文件 | ✅ 需要 |
/-/quit | POST / PUT | 关闭 Prometheus 进程 | ✅ 需要 |
# 健康 / 就绪:永远可用 | |
curl http://prom.example.com:9090/-/healthy | |
# Prometheus Server is Healthy. | |
curl http://prom.example.com:9090/-/ready | |
# Prometheus Server is Ready. | |
# 重载 / 关闭:需要开启 lifecycle | |
curl -X POST http://prom.example.com:9090/-/reload | |
curl -X POST http://prom.example.com:9090/-/quit | |
# Requesting termination... Goodbye! |
未开启该选项时:
curl -X POST http://prom.example.com:9090/-/quit | |
# Lifecycle API is not enabled. |
# 2.4.2 ⭐ 故障现场二:我在另一台机器上,一行命令关掉了你的监控
开 --web.enable-lifecycle 之后,会发生这样的事:
# 注意:这是在【另一台无关的远程主机】上执行 | |
curl -X POST http://10.0.0.100:9090/-/quit | |
# 服务直接退出 | |
ss -ntlp | grep 9090 # 端口没了 | |
systemctl is-active prometheus # inactive |
不需要任何认证,不需要登录服务器,只要能访问 9090 端口。 整个监控系统被一行 curl 干掉了。
🚨 结论必须记住:
--web.enable-lifecycle生产环境不要开。它的收益是什么?—— 能通过 API 触发 reload。
但问题是:我们本来就有systemctl reload prometheus(靠ExecReload发 SIGHUP),效果完全一样,还不需要暴露任何危险接口。我在很多人的 service 文件里见过这一行,他们大多不知道这行意味着 "谁都能远程关掉你的监控"。别做那个人。
# 2.4.3 重载配置的四种方式对比
| 方式 | 命令 | 是否重启进程 | 前提 | 建议 |
|---|---|---|---|---|
| systemd reload | systemctl reload prometheus | ❌ 不重启 | ExecReload 配了 kill -HUP | ⭐ 生产首选 |
| 信号 | kill -HUP <pid> | ❌ 不重启 | 无 | 手工排障可用 |
| API reload | curl -X POST :9090/-/reload | ❌ 不重启 | 必须开 lifecycle(不建议) | 不推荐 |
| 重启服务 | systemctl restart prometheus | ✅ 重启 | 无 | 改了启动参数时才需要 |
📌 分清两类配置:
- 配置文件(
prometheus.yml) 里的东西 → 可以热加载;- 命令行启动参数(如
--storage.tsdb.path、--web.listen-address)→ 必须重启才能生效。改了什么,就用对应的方式生效,不要图省事一律 restart。
# 2.4.4 顺带一提:谁来监控 Prometheus 自己
/-/healthy 和 /-/ready 的价值在于:它们让外部系统可以监控 Prometheus 本身。
Prometheus 监控万物 —— 但 Prometheus 挂了,谁来发现?
三种常见做法:
- 双机互监控:A 的 Prometheus 抓 B 的指标,B 的抓 A 的,任一方挂了另一方告警;
- 外部探活:另一个系统(Zabbix / 云监控 / 负载均衡健康检查)定时 curl
/-/healthy; up指标自监控:用另一个 Prometheus 实例抓本实例的/metrics,看up{job="prometheus"}。
💡 回到第 1 章的比喻:警察管普通人,那谁管警察?—— 这既是技术问题,也是一个非常好的面试发散点。
# 2.5 常用启动参数与容量规划
完整参数表:https://prometheus.io/docs/prometheus/latest/command-line/prometheus/
不过真正需要你关心的是下面这几个:
| 参数 | 默认值 | 作用 | 调优建议 |
|---|---|---|---|
--config.file | prometheus.yml | 配置文件路径 | 用绝对路径 |
--web.listen-address | 0.0.0.0:9090 | 监听地址 | 对外暴露时建议配合反向代理 + 认证 |
--web.read-timeout | 5m | 请求读超时,关闭空闲连接 | 防止空闲连接堆满 |
--web.max-connections | 512 | 最大并发连接数 | 大屏轮询多时适当调大 |
--storage.tsdb.path | data/ | TSDB 数据目录 | ⭐ 独立分区,别和根分区放一起 |
--storage.tsdb.retention.time | 15d | 样本保留时长 | 企业通常 15d;太长吃磁盘和内存,太短查不到历史 |
--query.timeout | 2m | 单次查询最长执行时间 | 复杂 PromQL 多时可调大 |
--query.max-concurrency | 20 | 最大并发查询数 | 与 Grafana 面板数量相关 |
--web.enable-lifecycle | false | 允许 API reload/quit | 🚨 生产保持 false |
保留期的取舍(面试常问):
举例:10 万条活跃序列、每 15 秒采一次、保留 15 天、压缩后按 1.5 字节 / 点估算:
100000 \times \frac{86400}{15} \times 15 \times 1.5 \approx 1.3\ \text注意:这个公式只用于量级估算。真实占用还要看标签基数、chunk 对齐、索引大小等。但把 "序列数" 和 "采样频率" 作为两个乘法因子放在分子上,这个直觉是对的 —— 序列爆炸和采集频率过高,是磁盘被打满的两大主因。
# 2.6 prometheus.yml :逐行读懂配置文件
官方参考:https://prometheus.io/docs/prometheus/latest/configuration/configuration/
# 2.6.1 两类配置的分工
| 配置方式 | 存什么 | 例子 |
|---|---|---|
| 命令行参数 | 不可变的系统级参数 | 数据目录、保留期、监听地址 |
| 配置文件(YAML) | 与 "数据动态获取" 相关的配置 | 抓谁、怎么抓、告警规则、告警发往哪 |
# 2.6.2 默认配置的四段式结构
global: | |
scrape_interval: 15s # 抓取间隔,默认 1m | |
evaluation_interval: 15s # 规则计算间隔,默认 1m | |
# scrape_timeout 默认取 global 默认值 10s,且不应超过 scrape_interval | |
alerting: | |
alertmanagers: | |
- static_configs: | |
- targets: # 这里留空,等第 6 章再填 | |
rule_files: # 告警规则文件列表,先空着 | |
# - "rules/*.yml" | |
scrape_configs: # ★ 最核心:监控谁 | |
- job_name: "prometheus" | |
static_configs: | |
- targets: ["localhost:9090"] |
四大段各自的作用:
| 段 | 作用 | 本章是否要改 |
|---|---|---|
global | 全局默认值,被各 job 继承 | 可能微调 scrape_interval |
alerting | 告警发给哪个 Alertmanager | 留空 |
rule_files | 外置规则文件(告警规则 / 记录规则) | 留空 |
scrape_configs | 采集目标列表(一组 job) | ⭐ 本章重点 |
除此之外,还有扩展配置 8 项( tls_config 、 relabel_config 、 metric_relabel_configs 、 remote_write 、 remote_read …)和平台集成配置 12 项( file_sd_config 、 dns_sd_config 、 consul_sd_config 、 kubernetes_sd_config …),后面章节逐个展开。
# 2.6.3 scrape_configs 的结构
scrape_configs: | |
- job_name: "<job_name>" | |
metrics_path: /metrics # 默认值,可省略 | |
scheme: http # 默认值,可省略 | |
static_configs: | |
- targets: [ "<host_ip>:<host_port>", ... ] | |
labels: { <labelname>: <labelvalue>, ... } |
三个概念对应第 1 章的知识点:
- 一个
scrape_configs下可以有多个 job,job 就是分类(同一类指标的机器放一个 job); - 一个 job 下的
static_configs.targets里每一项,就是一个 instance(实例),格式ip:port; labels是给这个 job 下所有目标附加的标签,可用于后续 PromQL 过滤。
💡 给 job 命名的建议:起一个 " 能看出这是什么东西 " 的名字,比如
node_exporter、mysql、redis、nginx。这个名字会原封不动地出现在up{job="..."}、Grafana 的变量下拉框、以及告警模板里。名字起得烂,后面到处难受。
# 2.6.4 ⭐ 故障现场三:缩进错了,targets 永远不生效
YAML 是缩进敏感的语言,而 scrape_configs 的结构是 "列表套列表套字典",嵌套很深。典型错误:
# ❌ 错误:targets 和 static_configs 不对齐 | |
- job_name: 'node_exporter' | |
static_configs: | |
- targets: ['10.0.0.101:9100'] |
或者:
# ❌ 错误:把 targets 写成了 static_configs 的兄弟 | |
- job_name: 'node_exporter' | |
static_configs: | |
- targets: ['10.0.0.101:9100'] | |
- targets: ['10.0.0.102:9100'] # ← 这一行变成了一个新的 job(且没有 job_name) |
后果: promtool check config 可能报错;也可能不报错但语义完全变了 ——targets 变成了另一个匿名 job,或者根本没被解析。
⭐ 所以:改完配置,先检查语法,再 reload。
promtool check config /usr/local/prometheus/conf/prometheus.yml# Checking /usr/local/prometheus/conf/prometheus.yml# SUCCESS: ... is valid prometheus config file syntax故意少写一个冒号试试,它会准确告诉你第几行出错 —— 这个反馈比 "reload 之后没数据,然后你猜了半天" 高效一百倍。
# 2.6.5 promtool :被严重低估的运维工具
promtool --help |
| 子命令 | 用途 |
|---|---|
promtool check config <file> | ⭐ 检查配置语法与语义 |
promtool check rules <file> | 检查告警 / 记录规则文件 |
promtool query instant <url> <expr> | 命令行直接跑 PromQL,便于脚本化 |
promtool tsdb ... | TSDB 分析(如 analyze 看块与序列统计) |
💡 养成习惯:任何配置变更 → 先
check config→ 再 reload → 再去看 Targets 页面。 三步走,能省掉 80% 的 "改完没生效" 的困惑。
# 2.7 方式三:容器化部署
# 2.7.1 官方 Dockerfile 全解读
官方镜像: prom/prometheus (注意是 prom 组织,不是 prometheus ,直接搜全名经常搜不到)
来源:https://github.com/prometheus/prometheus/blob/main/Dockerfile
ARG ARCH="amd64" | |
ARG OS="linux" | |
FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest | |
COPY .build/${OS}-${ARCH}/prometheus /bin/prometheus | |
COPY .build/${OS}-${ARCH}/promtool /bin/promtool | |
COPY documentation/examples/prometheus.yml /etc/prometheus/prometheus.yml | |
WORKDIR /prometheus | |
RUN chown -R nobody:nobody /etc/prometheus /prometheus && chmod g+w /prometheus | |
USER nobody | |
EXPOSE 9090 | |
VOLUME [ "/prometheus" ] | |
ENTRYPOINT [ "/bin/prometheus" ] | |
CMD [ "--config.file=/etc/prometheus/prometheus.yml", "--storage.tsdb.path=/prometheus" ] |
看懂了这 10 行,你就看懂了所有 Go 程序的官方镜像:
| 行 | 含义 | 对我们的启示 |
|---|---|---|
FROM busybox | 基础镜像只是提供 shell 和根证书 | 镜像可以做到很小(甚至 scratch ) |
COPY prometheus /bin/ | 就是把我们手动装的那个二进制拷进去 | 二进制会了,镜像就是 "打包" |
配置文件在 /etc/prometheus/ | 默认配置路径 | 修改配置 = 覆盖这个文件 |
USER nobody | 以 nobody 运行 | 非 root,安全 |
VOLUME ["/prometheus"] | 数据目录 | 不挂载就随容器删掉而丢失 |
CMD [...] | 默认启动参数 | 传入自己的参数会完全覆盖 CMD |
💡 这就是为什么 "二进制学扎实" 如此重要:容器里跑的就是那个二进制,参数一模一样,只是配置文件位置和数据目录换成了镜像里约定的路径。没有任何魔法。
# 2.7.2 三种启动姿势
# ① 最简:默认配置 + 默认数据目录(不持久化) | |
docker run -d --name prometheus -p 9090:9090 prom/prometheus | |
# ② 生产可用:挂载配置 + 数据卷 | |
docker run -d --name prometheus -p 9090:9090 \ | |
-v $PWD/prometheus.yml:/etc/prometheus/prometheus.yml \ | |
-v prometheus_data:/prometheus \ | |
prom/prometheus:v2.53.0 | |
# ③ 自定义配置路径(不覆盖镜像内文件,而是改变启动参数) | |
docker run -d --name prometheus -p 9090:9090 \ | |
-v $PWD/prometheus.yml:/prometheus-config/prometheus.yml \ | |
prom/prometheus \ | |
--config.file=/prometheus-config/prometheus.yml |
⚠️ 注意第 ③ 种:一旦你在镜像名后面跟了参数,镜像里的
CMD会被整体替换。所以必须把--storage.tsdb.path之类的必要参数一起写全,否则会回落到默认值(data/,相对于容器内工作目录)。
# 2.7.3 ⭐ 故障现场四:容器里的配置文件改不动
用 docker run 起完之后,你会发现一个尴尬事:
配置文件在镜像里。想改?得
docker exec进去用vi改 —— 而大多数精简镜像里根本没有vi。
正确做法(三步):
# 1. 把镜像内的默认配置拷出来 | |
docker cp prometheus:/etc/prometheus/prometheus.yml ./prometheus.yml | |
# 2. 在宿主机上修改它(想改采集间隔就改) | |
vi ./prometheus.yml | |
# 3. 删掉旧容器,用 -v 挂载覆盖镜像内的配置 | |
docker rm -f prometheus | |
docker run -d --name prometheus -p 9090:9090 \ | |
-v $PWD/prometheus.yml:/etc/prometheus/prometheus.yml \ | |
-v prometheus_data:/prometheus \ | |
prom/prometheus |
验证是否生效:访问 9090 → Status → Configuration,看里面的 scrape_interval 是不是你改的值。
📌 容器化不是 "更好的方式",而是 "另一种打包方式"。
它把 "改配置文件" 这件简单的事,变成了 "覆盖镜像内文件" 这件事。
所以:实验环境用容器图快,生产环境很多人仍然用二进制 + systemd—— 改配置、看日志、排障都更直接。两种方式都要会,别只会一种。
# 2.7.4 Docker Compose:一键起整套监控栈
中小规模环境可以直接用 Compose 拉起完整栈:
version: '3.6' | |
volumes: | |
prometheus_data: {} | |
grafana_data: {} | |
networks: | |
monitoring: | |
driver: bridge | |
services: | |
prometheus: | |
image: prom/prometheus:v2.53.0 | |
volumes: | |
- ./prometheus/:/etc/prometheus/ | |
- prometheus_data:/prometheus | |
command: | |
- '--config.file=/etc/prometheus/prometheus.yml' | |
- '--storage.tsdb.path=/prometheus' | |
- '--storage.tsdb.retention.time=15d' | |
ports: | |
- "9090:9090" | |
networks: [monitoring] | |
restart: unless-stopped | |
alertmanager: | |
image: prom/alertmanager:v0.27.0 | |
command: | |
- '--config.file=/etc/alertmanager/config.yml' | |
- '--storage.path=/alertmanager' | |
ports: | |
- "9093:9093" | |
networks: [monitoring] | |
restart: unless-stopped | |
node-exporter: | |
image: prom/node-exporter:v1.8.2 | |
volumes: | |
- /proc:/host/proc:ro | |
- /sys:/host/sys:ro | |
- /:/rootfs:ro | |
command: | |
- '--path.procfs=/host/proc' | |
- '--path.sysfs=/host/sys' | |
- '--path.rootfs=/rootfs' | |
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)' | |
ports: | |
- "9100:9100" | |
networks: [monitoring] | |
restart: unless-stopped | |
pushgateway: | |
image: prom/pushgateway:v1.10.0 | |
ports: | |
- "9091:9091" | |
networks: [monitoring] | |
restart: unless-stopped | |
grafana: | |
image: grafana/grafana:11.2.0 | |
volumes: | |
- grafana_data:/var/lib/grafana | |
environment: | |
- GF_SECURITY_ADMIN_PASSWORD=admin | |
- GF_USERS_ALLOW_SIGN_UP=false | |
ports: | |
- "3000:3000" | |
networks: [monitoring] | |
restart: unless-stopped |
有两点必须提醒:
node-exporter的三个挂载不是可选项 —— 它要读宿主机的/proc、/sys和根文件系统,否则采集到的是容器自己的(几乎为空的)指标。下一节会详细解释。- 容器内的
prometheus抓容器内的node-exporter时,target 要写node-exporter:9100(服务名),而不是localhost:9100。这是 Compose 网络里的 DNS 解析规则,也是新手最常见的 "抓不到" 原因之一。
# 2.8 Node Exporter:把主机指标 "出口" 出去
# 2.8.1 为什么必须有它
回到第 1 章的结论:Prometheus 的所有指标都必须以 HTTP 形式暴露。
而 Linux 操作系统本身不提供 HTTP 指标端点。你不可能让内核主动开个端口给你读 /proc/stat 。
所以中间必须有个 "转换器":
flowchart LR | |
K["Linux 内核<br/>/proc /sys"] -->|读取| NE["Node Exporter<br/>:9100"] | |
NE -->|"HTTP GET /metrics<br/>纯文本格式"| P["Prometheus Server"] |
Node Exporter 干的事:
- 读取本机的
/proc、/sys、系统调用,拿到 CPU、内存、磁盘、网络、文件系统等原始数据; - 转换成 Prometheus 文本格式(
# HELP/# TYPE/指标 值); - 在本机 9100 端口起一个 HTTP 服务,把结果暴露在
/metrics。
🧠 名字的由来:它把本节点的指标 " 出口(export)"出去,让 Prometheus 来" 进货 "。你不" 出口 ",人家就采不到 —— 这个命名非常形象。
和 Zabbix 的对照:
| Zabbix | Prometheus | |
|---|---|---|
| 采集代理 | zabbix-agent | node_exporter |
| 通信协议 | Zabbix 专有协议 | HTTP/HTTPS |
| 数据方向 | Server 拉(被动)或 Agent 推(主动) | 只有 Server 拉 |
| 谁的客户端 / 服务端 | Server 是服务端,Agent 是客户端 | ⭐ 反过来了:Prometheus 是 HTTP 客户端,Node Exporter 是 HTTP 服务端 |
最后一行请特别注意:角色是反的。Prometheus 主动发起 HTTP 请求(像浏览器),Node Exporter 被动响应(像 Web 服务器)。
# 2.8.2 安装(与 Prometheus 二进制安装几乎同构)
官方下载:https://prometheus.io/download/
GitHub:https://github.com/prometheus/node_exporter
在每一台需要监控的节点上执行:
cd /usr/local | |
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz | |
tar xf node_exporter-1.8.2.linux-amd64.tar.gz | |
ln -s node_exporter-1.8.2.linux-amd64 node_exporter | |
cd node_exporter | |
mkdir bin | |
mv node_exporter bin/ | |
useradd -r -s /sbin/nologin prometheus | |
chown -R prometheus:prometheus /usr/local/node_exporter/ |
💡 看出来了吗?解压 → 软链 → 分目录 → 建账号 → 改属主,和装 Prometheus 的步骤一模一样。这就是 Go 生态的统一 "手感"。学会一个,就会一串。
# 2.8.3 service 文件(含两个坑)
[Unit] | |
Description=Prometheus Node Exporter | |
After=network.target | |
[Service] | |
Type=simple | |
ExecStart=/usr/local/node_exporter/bin/node_exporter --collector.cgroups | |
ExecReload=/bin/kill -HUP $MAINPID | |
Restart=on-failure | |
User=prometheus | |
Group=prometheus | |
[Install] | |
WantedBy=multi-user.target |
systemctl daemon-reload | |
systemctl enable --now node_exporter.service | |
systemctl is-active node_exporter | |
ss -ntlp | grep 9100 |
两个参数坑,来自实战:
| 参数 | 作用 | 坑 |
|---|---|---|
--collector.cgroups | 额外启用 cgroups 采集器 | 想采容器资源占用时需要 |
--no-collector.uname | 禁用 uname 采集器 | ❌ 千万别加! 禁用后 Grafana 里的 Dashboard 拿不到主机名和内核版本,面板上会显示 N/A |
⚠️ 第二个坑非常隐蔽:有人为了 "减少指标数量" 加了
--no-collector.uname,结果所有社区 Dashboard 的主机信息栏全是空的,还以为是模板不兼容。优化指标量请用 relabel 丢弃,不要随手关采集器。
# 2.8.4 验证暴露结果
curl http://localhost:9100/metrics | head -40 |
浏览器访问 http://<node-ip>:9100/ ,会看到一个只有一个 metrics 链接的极简页面 —— 点进去就是全部指标。
💡 这个页面本身就是最好的学习材料。随便挑几个指标,对照
# HELP的说明,你就能明白它代表什么。比看文档快得多。
# 2.8.5 常见指标速查(背诵级)
| 前缀 / 指标 | 含义 |
|---|---|
node_cpu_seconds_total | 各 CPU、各模式(user/system/idle/iowait/softirq…)的累计秒数(Counter) |
node_memory_* | 内存: MemTotal 、 MemFree 、 MemAvailable 、 Buffers 、 Cached 、 Swap* |
node_filesystem_* | 文件系统的容量与可用空间(按挂载点、设备、fstype 分标签) |
node_disk_* | 磁盘 IO:读写字节、IOPS、耗时、队列长度 |
node_network_* | 网卡收发字节、包数、错误数、丢包(按 device 分标签) |
node_load1 / 5 / 15 | 1/5/15 分钟平均负载(Gauge) |
node_boot_time_seconds | 系统启动时间(1970 起的秒数) |
node_time_seconds | 当前系统时间 |
go_* | Exporter 自身的 Go 运行时指标(GC、goroutine、堆) |
process_* | Exporter 自身进程指标( process_max_fds 就属于这类) |
node_uname_info | 主机名、内核版本、架构等信息 |
🔗 回忆第 1 章:
top里的 user /system/nice /idle/iowait /irq/softirq /steal,在node_cpu_seconds_total里就是mode="user"|"system"|"nice"|"idle"|"iowait"|"irq"|"softirq"|"steal"这几个标签值。命令行的每一列,在 Prometheus 里都是一个标签维度。
# 2.8.6 为什么容器里的 Node Exporter 必须 "三件套"
这是本章最值得深入理解的一处。官方推荐命令:
docker run -d \ | |
--net="host" \ | |
--pid="host" \ | |
-v "/:/host:ro,rslave" \ | |
quay.io/prometheus/node-exporter:latest \ | |
--path.rootfs=/host |
三个参数,各有其不可替代的原因:
| 参数 | 作用 | 不加会怎样 |
|---|---|---|
--net="host" | 与宿主机共享网络命名空间 | 采集到的网卡( node_network_* )是容器虚拟网卡的,看不到 eth0 /bond/ 物理网卡 |
--pid="host" | 与宿主机共享 PID 命名空间 | 进程类指标(进程数、 node_processes_* )只看到容器内的 1 号进程 |
-v "/:/host:ro,rslave" | 把宿主机根挂进来 + --path.rootfs=/host | 文件系统指标全是容器 overlay 的,磁盘使用率完全失真; /proc 、 /sys 读到的也是容器自己的 |
⭐ 一句话记忆:
Node Exporter 不是来监控 "自己" 的,是来监控 "宿主机" 的 —— 所以它必须能看见宿主机。
同理,K8s 里部署 node-exporter 一定要用 DaemonSet +hostNetwork: true+hostPID: true+hostPath挂载/proc/sys/。
# 2.8.7 批量部署:脚本 → Ansible → DaemonSet
| 规模 | 方案 | 说明 |
|---|---|---|
| 1~5 台 | 手工 / Shell 脚本 | 课程提供的 install_node_exporter.sh 支持在线与离线安装 |
| 10~100 台 | Ansible | 官方 Role:https://github.com/prometheus-community/ansible/tree/main/roles/node_exporter,改改变量即可 |
| K8s 集群 | DaemonSet | 保证每个节点恰好一个 Pod |
Shell 脚本的核心结构(值得学习它的工程化写法):
NODE_EXPORTER_VERSION=1.8.2 | |
NODE_EXPORTER_FILE="node_exporter-${NODE_EXPORTER_VERSION}.linux-amd64.tar.gz" | |
GITHUB_PROXY=https://mirror.ghproxy.com/ # 加速代理,会失效,注意维护 | |
install_node_exporter() { | |
[ -f "${NODE_EXPORTER_FILE}" ] || wget "${GITHUB_PROXY}${NODE_EXPORTER_URL}" || { color "下载失败!" 1; exit; } | |
tar xf "${NODE_EXPORTER_FILE}" -C "${INSTALL_DIR}" | |
ln -s node_exporter-${NODE_EXPORTER_VERSION}.linux-amd64 node_exporter | |
mkdir -p node_exporter/bin && mv node_exporter bin/ | |
id prometheus &>/dev/null || useradd -r -s /sbin/nologin prometheus | |
chown -R prometheus:prometheus node_exporter/ | |
} | |
node_exporter_service() { ... 写 service 文件、daemon-reload、enable --now ... } |
💡 脚本里的三个好习惯:
- 版本号抽成变量 —— 升级只改一行;
- 先判断本地包是否存在 —— 存在就不下载,天然支持离线安装;
- 幂等 ——
id prometheus &>/dev/null || useradd,重复执行不报错。
Ansible(官方已现成):
- hosts: all | |
become: true | |
roles: | |
- prometheus.prometheus.node_exporter |
K8s DaemonSet(关键字段):
apiVersion: apps/v1 | |
kind: DaemonSet | |
metadata: | |
name: prometheus-node-exporter | |
namespace: prom | |
spec: | |
selector: | |
matchLabels: { app: prometheus, component: node-exporter } | |
template: | |
metadata: | |
labels: { app: prometheus, component: node-exporter } | |
spec: | |
tolerations: | |
- effect: NoSchedule | |
key: node-role.kubernetes.io/master # 主节点也要装,需要容忍污点 | |
containers: | |
- image: prom/node-exporter:v1.8.2 | |
name: prometheus-node-exporter | |
args: | |
- --path.procfs=/host/proc | |
- --path.sysfs=/host/sys | |
- --path.rootfs=/host | |
ports: | |
- containerPort: 9100 | |
hostPort: 9100 | |
volumeMounts: | |
- { name: proc, mountPath: /host/proc } | |
- { name: sys, mountPath: /host/sys } | |
- { name: rootfs, mountPath: /host } | |
hostNetwork: true # ⭐ 与宿主机共享网络 | |
hostPID: true # ⭐ 与宿主机共享 PID | |
volumes: | |
- { name: proc, hostPath: { path: /proc } } | |
- { name: sys, hostPath: { path: /sys } } | |
- { name: rootfs, hostPath: { path: / } } |
📌
DaemonSet= "每个节点跑一个"。这正是 Node Exporter 需要的语义 —— 它不是 "一个服务",而是 "每台机器上的一个探针"。
# 2.9 让 Prometheus 采集 Node Exporter
到这里,Node Exporter 已经在 9100 上 "出口" 了指标,但 Prometheus 还不知道它的存在 —— 所以你在 Prometheus 里搜 node_memory_* 会一无所获。
这是新手最常见的困惑:"我明明在浏览器里能看到指标,为什么 Prometheus 里查不到?"
记住这句话:"暴露" 是 Node Exporter 的事,"采集" 是 Prometheus 的事。两件事必须都做。
# 2.9.1 修改配置
global: | |
scrape_interval: 15s | |
evaluation_interval: 15s | |
alerting: | |
alertmanagers: | |
- static_configs: | |
- targets: | |
rule_files: | |
scrape_configs: | |
- job_name: "prometheus" | |
static_configs: | |
- targets: ["localhost:9090"] | |
# ★ 新增:采集 Node Exporter | |
- job_name: 'node_exporter' | |
metrics_path: /metrics # 默认值,可省略 | |
scheme: http # 默认值,可省略 | |
static_configs: | |
- targets: | |
- '10.0.0.101:9100' | |
- '10.0.0.102:9100' | |
- '10.0.0.103:9100' | |
- '10.0.0.100:9100' # 别忘了 Prometheus 自己这台 | |
labels: | |
env: prod | |
region: bj |
targets 的两种写法(都合法,选顺眼的):
# 写法 A:写在一行(机器少时更紧凑) | |
- targets: ['10.0.0.101:9100','10.0.0.102:9100'] | |
# 写法 B:一行一个(机器多时更清晰,推荐) | |
- targets: | |
- '10.0.0.101:9100' | |
- '10.0.0.102:9100' |
⚠️ 无论哪种写法,
targets都是列表,列表项以-开头。这是 YAML 缩进错误最高发的位置。
# 2.9.2 检查 → 生效 → 验证
# 1. 语法检查 | |
promtool check config /usr/local/prometheus/conf/prometheus.yml | |
# 2. 热加载(不重启) | |
systemctl reload prometheus | |
# 3. 验证:三种方式 | |
curl -s http://localhost:9090/-/ready | |
# 浏览器 → Status → Targets:应看到 node_exporter 这个 job,状态 UP | |
# 4. 用 PromQL 确认 | |
up{job="node_exporter"} |
up 指标的返回值:
| 值 | 含义 |
|---|---|
1 | 上一次抓取成功 |
0 | 上一次抓取失败(连不上、超时、非 2xx) |
四个 target 全部 1 ,说明采集链路通了。此时再查 node_memory_MemTotal_bytes ,四台机器的数据就都出来了。
# 2.9.3 ⭐ 故障现场五:端口写错,一个字符排查半小时
真实过程,值得完整复盘:
- 配好了 job,
reload之后浏览器刷新 Graph 页面,搜memory—— 什么都没有。 - 第一反应:"是不是要等一会?"—— 对,采集是周期性的。当前
scrape_interval是 20s,那就等 20s 再看。 - 等了 20s,还是没数据。
- 第二反应:"是不是配置没生效?"—— 去 Status → Configuration 确认新 job 在不在。在。
- 第三反应:"那到底抓没抓到?"—— 去 Status → Targets,看到
node_exporter这个 job 是红色 DOWN,报错类似:
Get "http://10.0.0.101:9010/metrics": dial tcp 10.0.0.101:9010: connect: connection refused |
- 一眼看出问题:
9010,不是9100。打错了一个数字。
改完 reload,颜色变绿,数据立刻就有了。
⭐ 这就是 "看 Targets 页面" 的价值:它把 "为什么没数据" 这个问题,直接翻译成了一个具体的 URL 和一个具体的错误。
不要一上来就怀疑 Prometheus 有 bug、怀疑版本不兼容、怀疑网络 —— 先看 Targets。
# 2.9.4 "抓不到数据" 五步排查法
把上面的经验固化成流程,以后照着走:
| 步骤 | 动作 | 判断依据 | 结论 |
|---|---|---|---|
| ① 目标端自测 | 在目标机上 curl -s localhost:9100/metrics \| head | 有输出? | 无 → exporter 没起 / 端口不对 |
| ② 跨机自测 | 在 Prometheus 机上 curl -s <target>:9100/metrics | 通了? | 不通 → 防火墙 / 监听地址是 127.0.0.1 / 网络不通 |
| ③ 看 Targets | Web UI → Status → Targets | UP / DOWN + 错误信息 | DOWN → 错误信息直接指出原因 |
| ④ 看配置 | Status → Configuration | 新 job 在不在、targets 对不对 | 不在 → reload 没成功 / 配置语法被拒 |
| ⑤ 看日志 | journalctl -u prometheus -n 50 | 有无 Error reloading config | 有 → 配置有问题 |
四个高频根因(按出现频率排序):
- 端口写错(
9100→9010/9091/910); - 服务监听在
127.0.0.1而不是0.0.0.0—— 本机 curl 通、跨机不通,这是最迷惑人的一种; - 防火墙 / 安全组没放行(
firewalld、ufw、云安全组); - 改完没 reload /reload 失败但没人看日志。
💡 还有一个 "假故障":指标有延迟。刚配好就查,必然查不到 —— 必须等至少一个
scrape_interval。学习时把间隔调成10s,能少等很多;但生产不要低于 10s,否则序列数与磁盘写入会成倍增长。
# 2.10 Grafana:让数据变成图
# 2.10.1 它是什么
Grafana 是一个开源的度量分析与可视化套件,同样用 Go 开发。
核心能力:
| 能力 | 说明 |
|---|---|
| 多数据源 | Prometheus、Zabbix、Graphite、InfluxDB、Elasticsearch、MySQL… 一个界面对接所有 |
| 统一查询编辑器 | 每种数据源有自己的查询语言,Grafana 统一包装并保留各自特性 |
| 同屏混合展示 | 同一个 Dashboard 上可以混用多个数据源 |
| 海量现成模板 | https://grafana.com/grafana/dashboards/ 上有几万个社区面板 |
| 告警 | 内置 Grafana Alerting(本章不展开) |
| 默认端口 | 3000 |
自带 /metrics | 能暴露自身运行指标给 Prometheus 采集 |
💡 和 Zabbix 的关键差异:Zabbix 内置的数据源能力有限(要装插件才支持某些源),而 Grafana 天生一等公民支持 Prometheus—— 数据源列表里 Prometheus 排第一。这也是 "Prometheus + Grafana" 成为事实标准组合的原因。
# 2.10.2 安装(deb 包)
官方下载:https://grafana.com/grafana/download
# 官方源(慢) | |
wget https://dl.grafana.com/enterprise/release/grafana-enterprise_11.2.0_amd64.deb | |
# 或清华镜像(快) | |
wget https://mirrors.tuna.tsinghua.edu.cn/grafana/apt/pool/main/g/grafana-enterprise/grafana-enterprise_11.2.0_amd64.deb |
三种安装方式,强烈推荐第 2 种:
# ① dpkg 装:依赖需要手工解决 | |
dpkg -i grafana-enterprise_11.2.0_amd64.deb | |
apt -y --fix-broken install # 缺依赖时补救 | |
# ② apt 装本地 deb 文件:依赖自动解决 ⭐ 推荐 | |
apt -y install ./grafana-enterprise_11.2.0_amd64.deb | |
# ③ 先装依赖再 dpkg(等价于 ①,但更可控) | |
apt-get install -y adduser libfontconfig1 musl | |
dpkg -i grafana-enterprise_11.2.0_amd64.deb |
📌 为什么
apt install ./xxx.deb比dpkg -i好?dpkg只管解包,不解决依赖;apt会自动把缺的包(如musl、libfontconfig1)一并装上。"先装依赖还是先装包" 这种顺序问题,用 apt 直接消失。
启动与验证:
systemctl daemon-reload | |
systemctl enable --now grafana-server.service | |
systemctl status grafana-server | |
ss -ntulp | grep 3000 |
💡 关于 "企业版":
grafana-enterprise并不是收费版,核心功能完全免费开源(Apache 2.0 部分 + AGPLv3 部分)。企业版只是额外包含了一些企业级特性(如 SSO、报表)。个人和中小团队放心用。
# 2.10.3 首次登录与添加数据源
浏览器访问:http://<grafana-ip>:3000/
默认账号密码:admin / admin
首次登录会要求改密码(可以跳过,生产环境请务必改)。
添加数据源(这是 Grafana 的 "第一课",Zabbix 课程里也练过):
- 左侧菜单 → Connections → Data sources(或首页 "Add your first data source");
- 选择 Prometheus(列表里排第一);
- Connection URL 填
http://<prometheus-ip>:9090- 同一台机器上可以填
http://localhost:9090;
- 同一台机器上可以填
- 认证:Prometheus 默认无认证,留空;
- 拉到最下面点 Save & Test,看到绿色提示 "Data source is working" 即成功。
⚠️ Grafana 服务端为你 "代发请求":Grafana 后端会去访问那个 URL。所以填
localhost:9090只在 Grafana 和 Prometheus 同机时才有效。分机部署时必须填 Prometheus 的真实 IP。
# 2.10.4 ⭐ 认知难点:内置 Dashboard 为什么 "部分没数据"
添加完 Prometheus 数据源后,Grafana 会自动提示导入三个内置面板:
Prometheus StatsPrometheus 2.0 StatsGrafana metrics
导入后你会发现:有的能显示,有的全是 No data 。
原因有两个,性质完全不同:
原因一:指标名变了(版本兼容问题)
内置模板是几年前写的,里面用的 PromQL 表达式引用的指标,在新版本 Prometheus 里可能已经改名或移除。
验证方法(非常值得掌握):
- 打开出问题面板 → 点 Edit(或 "Panel options");
- 看下面的 Query 里的表达式,例如
prometheus_tsdb_...; - 把这段表达式复制出来,粘到 Prometheus 的 Graph 页面执行;
- 如果 Prometheus 里也查不到 → 不是 Grafana 的问题,是这个指标在当前版本里不存在;
- 反过来,在 Graph 页输入
prometheus_前缀,看下拉提示里有哪些指标 —— 就能找到 "改名后的那个"。
💡 这个排查手法适用于所有 "面板没数据" 的场景:永远先在 Prometheus 里验证表达式,再去怀疑 Grafana。
原因二:数据源根本没被采集(逻辑问题)
Grafana metrics 这个面板展示的是 Grafana 自身的指标。但你现在还没有让 Prometheus 去采集 Grafana—— 所以 Prometheus 里压根没有这些数据。详见 2.11 节,那里会完整解释这条 "绕一圈" 的链路。
# 2.10.5 导入社区 Dashboard 展示 Node Exporter 数据
内置模板不适合看主机指标,得用社区模板。
第 1 步:找模板
访问 https://grafana.com/grafana/dashboards/,搜索 node exporter 。
第 2 步:筛选(关键)
| 筛选条件 | 为什么 |
|---|---|
| Data source = Prometheus | 数据源必须匹配 |
| 按下载量 / 评分排序 | 经过大量用户验证,踩坑少 |
| 看更新时间 | ⭐ 太老的模板别用 —— 指标名可能已经变了,会重演 2.10.4 的 "没数据" |
| 看是否中文 | 英文面板不是不能用,但中文面板日常更顺手 |
几个常用 ID(注意:ID 会随社区更新变化,请以官网当前状态为准):
| ID | 说明 |
|---|---|
| 1860 | Node Exporter Full,英文 / 日文,更新较新,指标最全 |
| 8919 | Node Exporter 中文版,日常查看更友好 |
| 16098 | Node Exporter 中文版(另一套风格) |
| 10465 | Zookeeper 监控(2.12 节会用到) |
第 3 步:导入
Grafana → Dashboards → New → Import
→ 输入 Dashboard ID(如 1860)→ Load
→ 选择数据源 Prometheus → Import
💡 两个实用技巧:
- 导入失败(网络拿不到 JSON)时,可以在官网页面直接下载 JSON 文件,再走 "Upload JSON file" 离线导入;
- 给面板改名时带上 ID,例如
Node Exporter Full (1860)—— 半年后你还能知道它是从哪来的。
第 4 步:看不到数据的四个检查点
| 检查点 | 现象 | 处理 |
|---|---|---|
| 节点上没装 Node Exporter | 面板全空 | 装 |
| Prometheus 没采集该节点 | 部分主机空 | 加 target + reload |
| 浏览器时间与服务器时间差 > 3 秒 | 数据 "看起来是空的" | ⭐ 校时(NTP) |
| 选了错误的数据源 / Cluster 变量 | 全空或只有部分 | 检查 Dashboard 顶部的变量下拉框 |
⚠️ 时间误差 > 3 秒会导致查不到数据,这一条很多人不知道,遇到 "明明有数据但图是空的" 时,先
date对比一下。
# 2.10.6 时间显示:UTC 还是本地时间
Prometheus 内部统一使用 UTC 时间戳。Grafana 面板默认可能按 UTC 显示,导致 "我明明下午 4 点起的服务,图上显示 8 点"。
解决:Dashboard 右上角时间选择器 → 切到 Browser Time / Local。
💡 这不是 bug,是时区约定。记住:Prometheus 存 UTC,展示时再转本地时区。 排查告警时间对不上时,先确认时区。
# 2.11 递归之美:监控 Grafana 自己
# 2.11.1 那条 "绕一圈" 的链路
这是本章最反直觉、也最值得理解的一个知识点。
问题:我想在 Grafana 上看到 Grafana 自己的指标,怎么办?
直觉答案:Grafana 自己展示自己就行了嘛。
正确答案:不行。必须绕一圈。
flowchart LR | |
G1["Grafana<br/>:3000/metrics<br/>(自带暴露能力)"] | |
P["Prometheus Server<br/>:9090"] | |
G2["Grafana<br/>Dashboard<br/>(数据源指向 Prometheus)"] | |
G1 -->|"① Prometheus Pull 采集"| P | |
P -->|"② Grafana 用 PromQL 查询"| G2 | |
G2 -.->|"③ 画出来的就是 Grafana 自己的指标"| G1 |
为什么必须这样?
Grafana 是 "展示层",不是 "数据源"。它只能从数据源读数据,而它自己不是数据源。
即使要展示 Grafana 自己的指标,也必须:
- Grafana 把自身指标暴露在
:3000/metrics; - Prometheus 去采集它(在
prometheus.yml里加一个 job); - Grafana 通过 Prometheus 数据源把这些指标查出来画图。
同理可推:Prometheus 自己的指标也是这么来的 ——Prometheus 默认就采集自己( localhost:9090 ),所以 Prometheus Stats 面板有数据;而 Grafana metrics 面板没数据,因为没人去采集 Grafana。
# 2.11.2 配置
先验证 Grafana 确实暴露了指标:
curl -s http://localhost:3000/metrics | head -20 | |
# HELP go_gc_duration_seconds ... | |
# HELP grafana_api_response_status_total ... |
然后在 prometheus.yml 里加一个 job:
- job_name: 'grafana' | |
static_configs: | |
- targets: ['10.0.0.100:3000'] |
promtool check config /usr/local/prometheus/conf/prometheus.yml | |
systemctl reload prometheus |
验证:
up{job="grafana"} # 应为 1 | |
grafana_http_request_duration_seconds_count # 应该有数据了 |
回到 Grafana,刷新 Grafana metrics 面板 —— 数据出来了。
# 2.11.3 由此总结出的一条重要分类
现在我们可以把 "指标从哪来" 分成三类,这是理解 Exporter 生态的钥匙:
| 类型 | 特征 | 例子 | 需要额外装 Exporter 吗 |
|---|---|---|---|
| ① 天生自带 | 软件内部主动暴露 /metrics | Prometheus 自己、Grafana、Zookeeper、Nacos、Etcd、Traefik | ❌ 不需要 |
| ② 需要 Exporter | 软件本身没有 HTTP 指标端点(尤其老软件) | Linux 系统(→ node_exporter)、MySQL(→ mysqld_exporter)、Redis(→ redis_exporter)、Nginx、HAProxy | ✅ 需要 |
| ③ 只能推 | 生命周期太短或无法被访问 | 一次性脚本、定时任务、批处理作业 | ✅ 用 Pushgateway |
🧠 一句话决策法:
先curl http://<软件>:<端口>/metrics试试。
有 → 直接加 job 采;没有 → 找对应 Exporter;连 "常驻" 都做不到 → 上 Pushgateway。
# 2.12 Pushgateway:给 "抓不到" 的指标开一扇门
# 2.12.1 为什么会有这个组件
考虑一个真实需求:统计当前有多少用户登录在这台机器上。
who | wc -l | |
# 1 |
就一个数字。但问题是:
- 它是由一条命令临时算出来的,不是某个常驻服务持续提供;
- 机器上没有 HTTP 服务去暴露这个数字;
- Prometheus 只会
GET /metrics—— 它没法 "执行一条命令"。
Prometheus 抓不到它。 这就是 Pushgateway 存在的理由。
# 2.12.2 工作原理:一个 "代收点"
sequenceDiagram | |
participant J as 脚本或定时任务 | |
participant PG as Pushgateway-9091 | |
participant P as Prometheus-9090 | |
J->>PG: ① HTTP POST 推送指标(主动推) | |
Note over PG: ② 暂存指标(不会自动过期!) | |
P->>PG: ③ GET /metrics(Pull 拉取) | |
PG-->>P: 返回暂存的指标 |
它就是一个 "代理 / 中转站":
你没有 HTTP 服务?我替你有。
你把数据交给我(POST /metrics),我挂在/metrics上等 Prometheus 来拉。
关键结论:Pushgateway 并没有改变 Prometheus"只拉不推" 的本质 —— 它只是给 "无法被拉" 的目标,额外增加了一个 "可以被拉" 的中转点。数据链路的最后一跳,仍然是 Pull。
# 2.12.3 与 Exporter、与 Zabbix Proxy 的对照
这是最容易混淆的一组概念,用表格彻底分清:
| 维度 | Exporter | Pushgateway | Zabbix Proxy |
|---|---|---|---|
| 采集方向 | 被动(等 Prometheus 来拉) | 主动接收(应用推过来) | 主动采集后上报 |
| 装在哪儿 | ⭐ 每个被监控节点都要装一个 | ⭐ 只装一个(任何位置都行,不必在被监控端) | 每个区域 / 网段一个 |
| 是否需要专属 | ⭐ 每种应用一个专用 Exporter(MySQL、Redis 各有各的) | 一个就够,所有应用都能往它推 | 一个通用的即可 |
| 类比 | zabbix-agent | zabbix-proxy | zabbix-proxy |
🧠 记忆锚点:
- Exporter 是 "每个应用配一个专用翻译官"——MySQL 的翻译官不懂 Redis 的话。
- Pushgateway 是 "一个公共收件箱"—— 谁都能往里投信,它不挑。
# 2.12.4 必须知道的四个固有缺陷
Pushgateway 官方文档明确定义了这些局限,面试常问,生产必知:
| 缺陷 | 具体表现 | 后果 |
|---|---|---|
| ① 单点瓶颈 | 只有一份 Pushgateway,多个应用同时推送 | 进程挂了 → 全链路数据中断 |
② up 指标失效 | Prometheus 只能看到 Pushgateway 是否活着,看不到真实应用是否活着 | ⭐ 服务挂了,旧数据还在,图表上一片 "正常"—— 假存活! |
| ③ 永不清理 | 推上去的数据永久保留并持续暴露,除非手动 DELETE | 已下线任务的指标一直 "诈尸" |
| ④ 不做校验 | 脚本算错了、推了脏数据,它照单全收 | 错误数据进入 TSDB,无法追溯 |
🚨 ② 号缺陷最危险。举个真实场景:你的备份任务每天推一次 "备份成功 = 1"。某天任务彻底挂了,不再推送 —— 但 Pushgateway 上那条
backup_success 1还在,Prometheus 一直抓到1,你的大屏一直显示 "备份正常"。你会在真正需要备份的那天发现它已经坏了三个月。应对:在应用侧实现 "心跳 / 时间戳" 指标,用
time() - backup_last_success_timestamp > 86400这类表达式告警。不要指望up。
# 2.12.5 安装
官方下载:https://prometheus.io/download/ 或 https://github.com/prometheus/pushgateway/releases
cd /usr/local | |
wget https://github.com/prometheus/pushgateway/releases/download/v1.10.0/pushgateway-1.10.0.linux-amd64.tar.gz | |
tar xf pushgateway-1.10.0.linux-amd64.tar.gz -C /usr/local/ | |
ln -s pushgateway-1.10.0.linux-amd64 pushgateway | |
mkdir -p /usr/local/pushgateway/bin | |
mv /usr/local/pushgateway/pushgateway /usr/local/pushgateway/bin/ | |
useradd -r -s /sbin/nologin prometheus | |
chown -R prometheus:prometheus /usr/local/pushgateway/ |
service 文件:
[Unit] | |
Description=Prometheus Pushgateway | |
After=network.target | |
[Service] | |
Type=simple | |
ExecStart=/usr/local/pushgateway/bin/pushgateway | |
ExecReload=/bin/kill -HUP $MAINPID | |
Restart=on-failure | |
User=prometheus | |
Group=prometheus | |
[Install] | |
WantedBy=multi-user.target |
systemctl daemon-reload | |
systemctl enable --now pushgateway.service | |
ss -ntlp | grep 9091 |
浏览器访问 http://<ip>:9091/ —— 界面是空的,因为还没人往里推过东西。这正是它的本质:数据不来自它自己,来自 "推给它的应用"。
💡 但它自己也需要被监控 ——
http://<ip>:9091/metrics里能看到它自身的指标(请求数、推送成功数等)。所以按 2.11 节的分类,它属于 "① 天生自带 ",直接加 job 采就行。
# 2.12.6 让 Prometheus 采集 Pushgateway
- job_name: "pushgateway" | |
honor_labels: true # ⭐ 关键 | |
scrape_interval: 10s | |
static_configs: | |
- targets: ["10.0.0.100:9091"] |
honor_labels: true 到底解决什么问题?
不加它时,Prometheus 会把自己配置里的 job="pushgateway" 和 instance="10.0.0.100:9091" 强加到所有指标上 —— 于是所有应用推上来的指标看起来都来自同一台机器,标签全被覆盖了。
加上它之后,Pushgateway 上的 job / instance 标签优先保留,Prometheus 自己的标签会被改名为 exported_job / exported_instance 。这样你才能区分 "这条指标是哪个应用推的"。
⚠️ 所以:采集 Pushgateway,
honor_labels: true几乎是必选项。
也支持文件形式自动发现:
- job_name: pushgateway | |
honor_labels: true | |
file_sd_configs: | |
- files: | |
- targets/pushgateway/*.json | |
refresh_interval: 5m |
// targets/pushgateway/test.json | |
[{"targets": ["pushgateway1.example.com:9091"]}] |
# 2.12.7 推送数据:URL 格式详解
http://<pg-addr>:<pg-port>/metrics/job/<job_name>/[<label_name>/<label_value>]/...
| 路径片段 | 作用 |
|---|---|
/metrics | 固定接收接口 |
/job/<job_name> | 设置 job 标签 |
/instance/<value> | 设置 instance 标签 |
其它 <label>/<value> | 任意附加标签 |
最小示例(推一条: age = 18 ):
echo "age 18" | curl --data-binary @- \ | |
http://pushgateway.example.com:9091/metrics/job/demo/instance/$(hostname -I | awk '{print $1}') |
echo "age 18":生成一行 Prometheus 格式的指标;|:管道,把内容交给 curl 的 stdin;--data-binary @-:从标准输入原样读取并 POST(注意用--data-binary,不要用-d,后者会处理换行符);/job/demo:这条指标的job标签是demo;/instance/<ip>:instance标签是当前主机第一个 IP。
带类型与帮助的完整示例:
cat <<EOF | curl --data-binary @- http://pushgateway.example.com:9091/metrics/job/some_job/instance/some_instance | |
# TYPE some_metric counter | |
some_metric{label="val1"} 42 | |
# TYPE another_metric gauge | |
# HELP another_metric Just an example. | |
another_metric 2398.283 | |
EOF |
三者作用(呼应第 1 章):
| 行 | 作用 |
|---|---|
# TYPE | 声明类型(counter /gauge/histogram /summary/untyped) |
# HELP | 人类可读的说明,Grafana 面板会显示 |
some_metric{...} 42 | 真正的样本: 指标名{标签} 值 |
# 2.12.8 删除数据(重要,别只会推不会删)
# 删除某个组下的全部指标 | |
curl -X DELETE http://pushgateway.example.com:9091/metrics/job/some_job/instance/some_instance | |
# 删除某个 job 下的全部指标 | |
curl -X DELETE http://pushgateway.example.com:9091/metrics/job/some_job |
也可以直接在 Pushgateway 的 Web 界面点删除按钮。
⚠️ 生产纪律:任何上线了又下线的任务,必须清理它在 Pushgateway 上的残留。否则那些 "僵尸指标" 会一直出现在查询结果里,污染图表和告警判断。
# 2.12.9 生产可用脚本
一个通用的自定义指标推送脚本,把 "采集命令" 和 "推送" 解耦:
#!/bin/bash | |
set -uo pipefail | |
# ===== 只改这几行:想监控什么,就换 METRIC_* ===== | |
METRIC_NAME="login_number" | |
METRIC_VALUE_CMD='who | wc -l' | |
METRIC_TYPE="gauge" | |
METRIC_HELP="Current login user number" | |
PUSHGATEWAY_HOST="pushgateway.example.com:9091" | |
JOB_NAME="custom_metrics" | |
INSTANCE=$(hostname -I | awk '{print $1}') | |
INTERVAL=60 | |
PUSH_URL="http://${PUSHGATEWAY_HOST}/metrics/job/${JOB_NAME}/instance/${INSTANCE}" | |
trap 'echo "[$(date "+%F %T")] Exit."; exit 0' INT TERM | |
while true; do | |
VALUE=$(eval "$METRIC_VALUE_CMD") | |
cat <<EOF | curl --silent --show-error --fail --data-binary @- "$PUSH_URL" >/dev/null | |
# HELP ${METRIC_NAME} ${METRIC_HELP} | |
# TYPE ${METRIC_NAME} ${METRIC_TYPE} | |
${METRIC_NAME} ${VALUE} | |
EOF | |
if [ $? -eq 0 ]; then | |
echo "[$(date '+%F %T')] Push OK: ${METRIC_NAME}=${VALUE}" | |
else | |
echo "[$(date '+%F %T')] Push FAILED!" | |
fi | |
sleep "${INTERVAL}" | |
done |
从 "一次性任务" 推数据,交给 cron 定时执行。但不要写超长的单行 cron 命令(引号嵌套会把你逼疯),正确做法是先写一个小脚本,cron 只负责调用它:
# 1. 写脚本 /usr/local/bin/push_login_number.sh | |
#!/bin/bash | |
set -euo pipefail | |
PG="http://pushgateway.example.com:9091" | |
IP=$(hostname -I | awk '{print $1}') | |
N=$(who | wc -l) | |
cat <<EOF | curl -s --fail --data-binary @- "${PG}/metrics/job/custom_metrics/instance/${IP}" >/dev/null | |
# HELP login_number Current login user number | |
# TYPE login_number gauge | |
login_number ${N} | |
EOF |
# 2. 授权 | |
chmod +x /usr/local/bin/push_login_number.sh | |
# 3. cron 只负责调用脚本(/etc/cron.d/push_login_number) | |
*/5 * * * * root /usr/local/bin/push_login_number.sh |
💡 脚本里的三个好习惯:
--fail:HTTP 4xx/5xx 时让 curl 返回非 0,从而能判断推送失败;- 输出成功 / 失败日志:不然任务 "看起来在跑",其实一直在失败;
trap优雅退出:systemd 停止服务时不会留下僵尸循环。
# 2.12.10 验证
- Pushgateway 页面:
http://<ip>:9091/能看到你推上去的指标组; - Prometheus Graph:查
login_number或mem_free,能看到数据。
⚠️ 这里有个小陷阱:如果
honor_labels: true没配,你在 Prometheus 里查到的instance是 Pushgateway 的地址,而不是推送方的地址。查不到特定主机的数据时,先检查这一点。
# 2.13 一个完整例子:监控 Zookeeper
前面所有例子里,被监控对象要么 "本来没有指标"(Linux),要么 "指标要中转"(脚本)。Zookeeper 属于第三类:软件自己就能开指标端口。
# 2.13.1 三种指标来源分类
| 来源 | 例子 | 要点 |
|---|---|---|
| ① 软件内置 | Grafana、Zookeeper、Nacos、Etcd | 改配置打开即可,不用装 Exporter |
| ② 第三方 Exporter | MySQL、Redis、Nginx | 需要单独部署 exporter |
| ③ Pushgateway | 一次性脚本、定时任务 | 主动推 |
| ④ 自行编程 | 业务指标 | 需要开发能力,用客户端库 |
# 2.13.2 打开 Zookeeper 的内置指标端点
Zookeeper 的 zoo_sample.cfg 里已经预置了 Prometheus 相关注释,取消注释即可:
# 在 zoo.cfg 末尾添加(模板里默认就有,只是被注释了) | |
metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider | |
metricsProvider.httpPort=7000 | |
metricsProvider.exportJvmInfo=true |
systemctl restart zookeeper.service | |
curl http://localhost:7000/metrics | head |
💡
metricsProvider.httpPort=7000就是这个软件给自己开的 "出口"。凡是能在某个端口curl到/metrics的软件,都不需要额外装 Exporter。
# 2.13.3 加 job,看数据
- job_name: 'zookeeper' | |
static_configs: | |
- targets: ['10.0.0.101:7000'] |
promtool check config /usr/local/prometheus/conf/prometheus.yml | |
systemctl reload prometheus |
- Prometheus → Status → Targets,确认
zookeeper是 UP; - Grafana → 导入社区模板(如 ID 10465)→ 注意在面板顶部选对 Cluster 变量,否则显示为空。
# 2.14 本章小结
mindmap | |
root(("第2章<br/>部署与点亮")) | |
五个软件 | |
Prometheus 9090 采集+存储 | |
Node Exporter 9100 系统指标 | |
Grafana 3000 展示 | |
Alertmanager 9093 告警 | |
Pushgateway 9091 中转 | |
安装方式 | |
包安装 快但版本旧 | |
二进制 生产标准 | |
容器 注意挂载 | |
Ansible 批量 | |
关键参数 | |
config.file | |
storage.tsdb.path | |
storage.tsdb.retention.time | |
web.enable-lifecycle 危险 | |
配置文件 | |
global | |
alerting | |
rule_files | |
scrape_configs | |
踩过的坑 | |
root启动污染权限 | |
YAML缩进 | |
端口写错 | |
容器内配置文件 | |
指标有延迟 | |
uname采集器 | |
Pushgateway | |
up指标失效 | |
永不清理 | |
单点 | |
不校验 |
六条必须记住的运维经验:
| # | 经验 | 出处 |
|---|---|---|
| 1 | 调试用哪个身份启动,生产就用哪个身份 | root 污染 data 目录 |
| 2 | --web.enable-lifecycle 生产禁用 | 一行 curl 就能远程关掉服务 |
| 3 | 改配置:先 promtool check config ,再 reload,再查 Targets | 三步固定流程 |
| 4 | "暴露" 和 "采集" 是两件事 | Node Exporter 有了还得加 job |
| 5 | 抓不到数据先看 Targets,它会告诉你具体 URL 和错误 | 端口写成 9010 的教训 |
| 6 | 容器化 Node Exporter 必须 host 网络 + host PID + 挂载 / | 否则采的是容器自己 |
# 2.15 面试高频问答
Q1:Prometheus 有几种安装方式?生产推荐哪种,为什么?
包安装、二进制、Ansible、Docker、Docker Compose、K8s 六种。
生产推荐二进制 + systemd:版本可控(不被发行版仓库锁死)、零依赖(Go 静态编译)、目录布局自由(配置 / 数据可分离、数据目录可独立分区)、排障直观(日志、进程、路径都在明处)。大规模机器建议 Ansible,云原生环境用 Operator / Helm。
Q2: --web.enable-lifecycle 是干什么的?为什么不建议开?
它允许通过 HTTP API 触发
/-/reload和/-/quit。风险在于无任何认证:任何能访问 9090 的人都能POST /-/quit直接把监控服务关掉。而 reload 这件事用systemctl reload(配合ExecReload=/bin/kill -HUP)完全能实现。所以生产保持默认 false。
Q3:改了配置文件怎么让它生效?
三步:
promtool check config校验语法 →systemctl reload prometheus(SIGHUP 热加载)→ 在 Status → Configuration 确认生效、Status → Targets 确认目标 UP。
注意:改启动参数(如--storage.tsdb.path)必须 restart,热加载不了。
Q4:为什么 Node Exporter 装在容器里要 --net=host --pid=host -v /:/host ?
因为它的职责是采集宿主机的指标,而不是容器自己的。共享网络命名空间才能看到真实网卡;共享 PID 命名空间才能看到宿主机全部进程;挂载宿主机根目录并指定
--path.rootfs=/host才能读到真实的/proc、/sys和文件系统使用率。否则采到的全是容器 overlay 里的数据。
Q5:Pushgateway 有什么缺点?
四个:① 单点故障;②
up指标失效(看不到真实目标存活,服务挂了旧数据还在,造成 "假存活");③ 数据永不自动清理,必须手动 DELETE;④ 不做数据校验,脏数据照单全收。
因此它只适合短周期 / 一次性任务的指标,长期运行的服务应该用 Exporter。
Q6:如何监控 Grafana 自身?
Grafana 自带
/metrics端点(:3000),在prometheus.yml里加一个 job 采集它,然后在 Grafana 里用 Prometheus 数据源查询展示。注意它不能自己展示自己 ——Grafana 是展示层不是数据源,必须经 Prometheus 中转。
Q7: honor_labels: true 有什么作用?
采集 Pushgateway 时必须加。否则 Prometheus 会用自己配置的
job/instance覆盖掉推送方设置的标签,导致所有应用的数据都像是同一个来源。加上之后保留 Pushgateway 上的标签,Prometheus 自己的标签改名为exported_job/exported_instance。
# 2.16 动手实验
# 实验一(必做) 二进制装全套 + 采集四台主机
目标: up{job="node_exporter"} 返回 4 条,值全为 1。
步骤清单(打勾)
□ 1. 三台机器规划:100=监控服务器,101/102/103=被监控节点
□ 2. 100 上二进制安装 Prometheus(2.53.x 或 3.x LTS)
□ 3. 目录规划 bin/conf/data + 建 prometheus 服务账号 + chown
□ 4. 先手动启动一次验证(注意:用 prometheus 身份,别用 root!)
□ 5. 写 systemd 单元(含 LimitNOFILE、ExecReload),enable --now
□ 6. 访问 9090,看 Status → Runtime 确认版本与启动时间
□ 7. 四台机器全装 node_exporter,加 service,9100 监听
□ 8. curl 各自 9100/metrics 确认暴露正常
□ 9. prometheus.yml 新增 node_exporter job,4 个 target
□ 10. promtool check config → systemctl reload
□ 11. Status → Targets 确认 4 个全 UP
□ 12. Graph 里查 node_memory_MemAvailable_bytes,应有 4 条序列
思考题(做完实验再回答):
- 为什么
data/目录第一次启动是空的,第二次启动就有内容了? process_max_fds这个指标的值,是从哪里来的?(提示:service 文件)- 如果把
scrape_interval改成1s,up的变化会更快,但会带来什么代价?
# 实验二 容器化对照实验
用 Docker 起 Prometheus 和 Node Exporter,故意不加 --net=host ,然后:
- 在容器里执行
curl localhost:9100/metrics | grep node_network—— 看有几块网卡? - 加上
--net=host再执行一次 —— 有几块? - 用两行命令理解 "网络命名空间隔离" 这句话是什么意思。
# 实验三 Grafana 全链路
□ 1. 安装 Grafana(apt install ./xxx.deb)
□ 2. 登 3000,admin/admin,改密码
□ 3. 加 Prometheus 数据源,Save & Test 成功
□ 4. 导入内置 Prometheus Stats → 看数据
□ 5. 导入内置 Grafana metrics → 看到 No data,解释原因
□ 6. 在 prometheus.yml 加 grafana job,reload
□ 7. 回 Grafana 刷新 → Grafana metrics 有数据了
□ 8. 导入 Dashboard 1860 / 8919 → 看到四台主机 CPU 曲线
关键问答:第 5 步为什么没数据?第 7 步为什么就有了?用一句话讲清这条链路。
# 实验四 Pushgateway 推一个自定义指标
# 1. 装 Pushgateway,9091 监听 | |
# 2. prometheus.yml 加 job(务必 honor_labels: true),reload | |
# 3. 推一个登录数 | |
echo "login_number $(who | wc -l)" | curl --data-binary @- \ | |
http://<pg-ip>:9091/metrics/job/custom_metrics/instance/$(hostname -I | awk '{print $1}') | |
# 4. Pushgateway 页面看到指标组 | |
# 5. Prometheus 里查 login_number | |
# 6. 故意停掉脚本,等 5 分钟,再查 login_number —— 数据还在吗?为什么? | |
# 7. 用 DELETE 清掉,再查 —— 没了吧 |
第 6 步的答案,就是 Pushgateway 最大的坑。 请务必亲手做一次。
# 2.17 下章预告
本章我们把 "能跑起来" 这件事做到了:四个端口全亮,四台主机的 CPU 曲线出现在 Grafana 上。
但你会发现一个尴尬的事实:图虽然有了,可我看不懂那些指标名,也不知道该怎么算。
node_cpu_seconds_total 为什么是一个只增不减的巨大数字?为什么不能直接画它就要先做 rate() ?
下一章专门解决这个问题:
- 四种指标类型的本质区别:Counter / Gauge / Histogram / Summary
- 为什么
node_cpu_seconds_total必须配rate(),而node_memory_MemAvailable_bytes可以直接画 - 标签基数(cardinality) 为什么会让 Prometheus 内存爆掉
node_exporter的常用指标该怎么读
本章作业:把 "抓不到数据五步排查法" 抄在笔记本第一页。后面所有实验,你都会用到它。
