# 第 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 Server9090拉取指标 + 内置 TSDB 存储 + 规则计算zabbix-server + 数据库
Node Exporter9100把本机的 CPU / 内存 / 磁盘 / 网络指标用 HTTP 暴露出来zabbix-agent
Grafana3000把数据画成漂亮的图zabbix-web (但强太多)
Alertmanager9093接收告警、去重、分组、路由、发送内置告警动作(本章不展开)
Pushgateway9091给 "抓不到" 的瞬时指标当中转代理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

三条部署原则(经验,不是规定):

  1. Prometheus / Grafana / Pushgateway 可以装在同一台(实验环境、小规模环境),也可以分开装。分开装的好处是性能互不干扰、故障不连带。
  2. Node Exporter 必须装在被监控的每一台机器上,一台不能少 —— 包括 Prometheus 自己那台,因为监控系统本身也需要被监控。
  3. 生产环境请把 --storage.tsdb.path (数据目录)独立挂一个分区,避免数据把根分区写满导致整机不可用。

# 2.1.3 版本怎么选:先看兼容性

监控栈是 "多组件协同",版本乱配最容易出问题。下面是一套经过验证、兼容性良好的企业级组合:

组件推荐版本示例文件
Prometheus2.53.xprometheus-2.53.2.linux-amd64.tar.gz
Alertmanager0.27.xalertmanager-0.27.0.linux-amd64.tar.gz
Node Exporter1.8.xnode_exporter-1.8.2.linux-amd64.tar.gz
Grafana11.xgrafana-enterprise_11.2.0_amd64.deb
Pushgateway1.10.xpushgateway-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

⚠️ 包安装的两大局限(也正是生产偏爱二进制的原因):

  1. 版本被仓库锁死,仓库里是什么版本你就用什么版本,通常落后官方好几个小版本,甚至大版本;
  2. 目录布局固定,想自定义数据目录、想和别的实例并存,就很别扭。

# 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-addressUI / API 监听地址0.0.0.0:9090
--storage.tsdb.pathTSDB 数据目录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

排查过程(这才是重点):

  1. 先看端口有没有起来 —— ss -ntlp | grep 9090 ,没有。
  2. 再看服务状态 —— systemctl status prometheus ,只有一行 "启动失败",信息不够。
  3. 看日志 —— 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"
  1. 验证猜想 —— 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 $MAINPIDsystemctl 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 不生效

⚠️ 两个 "只存在于内存" 的认知:

  1. Graph 页面里写的表达式,刷新就没了。 这就是为什么必须要有 Grafana—— 它负责把表达式 "存下来" 变成面板。
  2. Targets 页面上的 Last Scrape 时间和 State 颜色,是你判断 "数据到底有没有进来" 最快的方式。绿色 UP = 抓到了;红色 DOWN = 抓不到。

# 2.4 管理 API 与 --web.enable-lifecycle 的安全陷阱

# 2.4.1 四个 API 端点

Prometheus 提供了一组管理 API 用于自动化运维:

端点方法作用是否需要 --web.enable-lifecycle
/-/healthyGET健康检查,通常恒返回 200❌ 不需要
/-/readyGET就绪检查,能响应查询后返回 200❌ 不需要
/-/reloadPOST / PUT重载配置文件✅ 需要
/-/quitPOST / 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 reloadsystemctl reload prometheus❌ 不重启ExecReload 配了 kill -HUP⭐ 生产首选
信号kill -HUP <pid>❌ 不重启无手工排障可用
API reloadcurl -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 挂了,谁来发现?

三种常见做法:

  1. 双机互监控:A 的 Prometheus 抓 B 的指标,B 的抓 A 的,任一方挂了另一方告警;
  2. 外部探活:另一个系统(Zabbix / 云监控 / 负载均衡健康检查)定时 curl /-/healthy ;
  3. up 指标自监控:用另一个 Prometheus 实例抓本实例的 /metrics ,看 up{job="prometheus"} 。

💡 回到第 1 章的比喻:警察管普通人,那谁管警察?—— 这既是技术问题,也是一个非常好的面试发散点。


# 2.5 常用启动参数与容量规划

完整参数表:https://prometheus.io/docs/prometheus/latest/command-line/prometheus/

不过真正需要你关心的是下面这几个:

参数默认值作用调优建议
--config.fileprometheus.yml配置文件路径用绝对路径
--web.listen-address0.0.0.0:9090监听地址对外暴露时建议配合反向代理 + 认证
--web.read-timeout5m请求读超时,关闭空闲连接防止空闲连接堆满
--web.max-connections512最大并发连接数大屏轮询多时适当调大
--storage.tsdb.pathdata/TSDB 数据目录⭐ 独立分区,别和根分区放一起
--storage.tsdb.retention.time15d样本保留时长企业通常 15d;太长吃磁盘和内存,太短查不到历史
--query.timeout2m单次查询最长执行时间复杂 PromQL 多时可调大
--query.max-concurrency20最大并发查询数与 Grafana 面板数量相关
--web.enable-lifecyclefalse允许 API reload/quit🚨 生产保持 false

保留期的取舍(面试常问):

磁盘占用≈活跃序列数×采样频率×保留时长×单点字节数\text{磁盘占用} \approx \text{活跃序列数} \times \text{采样频率} \times \text{保留时长} \times \text{单点字节数}

举例: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

有两点必须提醒:

  1. node-exporter 的三个挂载不是可选项 —— 它要读宿主机的 /proc 、 /sys 和根文件系统,否则采集到的是容器自己的(几乎为空的)指标。下一节会详细解释。
  2. 容器内的 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 干的事:

  1. 读取本机的 /proc 、 /sys 、系统调用,拿到 CPU、内存、磁盘、网络、文件系统等原始数据;
  2. 转换成 Prometheus 文本格式( # HELP / # TYPE / 指标 值 );
  3. 在本机 9100 端口起一个 HTTP 服务,把结果暴露在 /metrics 。

🧠 名字的由来:它把本节点的指标 " 出口(export)"出去,让 Prometheus 来" 进货 "。你不" 出口 ",人家就采不到 —— 这个命名非常形象。

和 Zabbix 的对照:

ZabbixPrometheus
采集代理zabbix-agentnode_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 / 151/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 ... }

💡 脚本里的三个好习惯:

  1. 版本号抽成变量 —— 升级只改一行;
  2. 先判断本地包是否存在 —— 存在就不下载,天然支持离线安装;
  3. 幂等 —— 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 ⭐ 故障现场五:端口写错,一个字符排查半小时

真实过程,值得完整复盘:

  1. 配好了 job, reload 之后浏览器刷新 Graph 页面,搜 memory —— 什么都没有。
  2. 第一反应:"是不是要等一会?"—— 对,采集是周期性的。当前 scrape_interval 是 20s,那就等 20s 再看。
  3. 等了 20s,还是没数据。
  4. 第二反应:"是不是配置没生效?"—— 去 Status → Configuration 确认新 job 在不在。在。
  5. 第三反应:"那到底抓没抓到?"—— 去 Status → Targets,看到 node_exporter 这个 job 是红色 DOWN,报错类似:
Get "http://10.0.0.101:9010/metrics": dial tcp 10.0.0.101:9010: connect: connection refused
  1. 一眼看出问题: 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 / 网络不通
③ 看 TargetsWeb UI → Status → TargetsUP / DOWN + 错误信息DOWN → 错误信息直接指出原因
④ 看配置Status → Configuration新 job 在不在、targets 对不对不在 → reload 没成功 / 配置语法被拒
⑤ 看日志journalctl -u prometheus -n 50有无 Error reloading config有 → 配置有问题

四个高频根因(按出现频率排序):

  1. 端口写错( 9100 → 9010 / 9091 / 910 );
  2. 服务监听在 127.0.0.1 而不是 0.0.0.0 —— 本机 curl 通、跨机不通,这是最迷惑人的一种;
  3. 防火墙 / 安全组没放行( firewalld 、 ufw 、云安全组);
  4. 改完没 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 课程里也练过):

  1. 左侧菜单 → Connections → Data sources(或首页 "Add your first data source");
  2. 选择 Prometheus(列表里排第一);
  3. Connection URL 填 http://<prometheus-ip>:9090
    • 同一台机器上可以填 http://localhost:9090 ;
  4. 认证:Prometheus 默认无认证,留空;
  5. 拉到最下面点 Save & Test,看到绿色提示 "Data source is working" 即成功。

⚠️ Grafana 服务端为你 "代发请求":Grafana 后端会去访问那个 URL。所以填 localhost:9090 只在 Grafana 和 Prometheus 同机时才有效。分机部署时必须填 Prometheus 的真实 IP。

# 2.10.4 ⭐ 认知难点:内置 Dashboard 为什么 "部分没数据"

添加完 Prometheus 数据源后,Grafana 会自动提示导入三个内置面板:

  • Prometheus Stats
  • Prometheus 2.0 Stats
  • Grafana metrics

导入后你会发现:有的能显示,有的全是 No data 。

原因有两个,性质完全不同:

原因一:指标名变了(版本兼容问题)

内置模板是几年前写的,里面用的 PromQL 表达式引用的指标,在新版本 Prometheus 里可能已经改名或移除。

验证方法(非常值得掌握):

  1. 打开出问题面板 → 点 Edit(或 "Panel options");
  2. 看下面的 Query 里的表达式,例如 prometheus_tsdb_... ;
  3. 把这段表达式复制出来,粘到 Prometheus 的 Graph 页面执行;
  4. 如果 Prometheus 里也查不到 → 不是 Grafana 的问题,是这个指标在当前版本里不存在;
  5. 反过来,在 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说明
1860Node Exporter Full,英文 / 日文,更新较新,指标最全
8919Node Exporter 中文版,日常查看更友好
16098Node Exporter 中文版(另一套风格)
10465Zookeeper 监控(2.12 节会用到)

第 3 步:导入

Grafana → Dashboards → New → Import
→ 输入 Dashboard ID(如 1860)→ Load
→ 选择数据源 Prometheus → Import

💡 两个实用技巧:

  1. 导入失败(网络拿不到 JSON)时,可以在官网页面直接下载 JSON 文件,再走 "Upload JSON file" 离线导入;
  2. 给面板改名时带上 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 自己的指标,也必须:

  1. Grafana 把自身指标暴露在 :3000/metrics ;
  2. Prometheus 去采集它(在 prometheus.yml 里加一个 job);
  3. 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 吗
① 天生自带软件内部主动暴露 /metricsPrometheus 自己、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 的对照

这是最容易混淆的一组概念,用表格彻底分清:

维度ExporterPushgatewayZabbix Proxy
采集方向被动(等 Prometheus 来拉)主动接收(应用推过来)主动采集后上报
装在哪儿⭐ 每个被监控节点都要装一个⭐ 只装一个(任何位置都行,不必在被监控端)每个区域 / 网段一个
是否需要专属⭐ 每种应用一个专用 Exporter(MySQL、Redis 各有各的)一个就够,所有应用都能往它推一个通用的即可
类比zabbix-agentzabbix-proxyzabbix-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

💡 脚本里的三个好习惯:

  1. --fail :HTTP 4xx/5xx 时让 curl 返回非 0,从而能判断推送失败;
  2. 输出成功 / 失败日志:不然任务 "看起来在跑",其实一直在失败;
  3. trap 优雅退出:systemd 停止服务时不会留下僵尸循环。

# 2.12.10 验证

  1. Pushgateway 页面: http://<ip>:9091/ 能看到你推上去的指标组;
  2. 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
② 第三方 ExporterMySQL、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 条序列

思考题(做完实验再回答):

  1. 为什么 data/ 目录第一次启动是空的,第二次启动就有内容了?
  2. process_max_fds 这个指标的值,是从哪里来的?(提示:service 文件)
  3. 如果把 scrape_interval 改成 1s , up 的变化会更快,但会带来什么代价?

# 实验二 容器化对照实验

用 Docker 起 Prometheus 和 Node Exporter,故意不加 --net=host ,然后:

  1. 在容器里执行 curl localhost:9100/metrics | grep node_network —— 看有几块网卡?
  2. 加上 --net=host 再执行一次 —— 有几块?
  3. 用两行命令理解 "网络命名空间隔离" 这句话是什么意思。

# 实验三 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 的常用指标该怎么读

本章作业:把 "抓不到数据五步排查法" 抄在笔记本第一页。后面所有实验,你都会用到它。

此文章已被阅读次数:正在加载...更新于

请我喝[茶]~( ̄▽ ̄)~*

Jaxx.Wang 微信支付

微信支付

Jaxx.Wang 支付宝

支付宝