Prometheus是什么?服务器监控架构与数据采集机制详解

如果你用过 Zabbix,再接触 Prometheus 会有种"画风突变"的感觉:它不装 Agent 到被监控机上主动汇报,而是反其道而行,由监控端主动去各个目标拉数据。这种设计一开始让人不习惯,但它恰恰是云原生时代流行的原因——被监控对象随时生灭,注册发现自动跟上,采集端不用维护一份长长的清单。

这篇文章讲清楚 Prometheus 的核心:为什么是拉取模型、时序数据长什么样、四种指标类型分别用在什么场景、PromQL 怎么写、node_exporter 怎么装、scrape_configs 怎么配,以及它和被动推送模式的本质差异。目标不是让你背概念,而是理解它的数据模型后,看到任何一条指标都能自己写出查询、想清楚该用哪种类型。搞明白了这些,后面配 Grafana 面板、写告警规则都是水到渠成的事。

拉取模型:为什么是 pull 而不是 push

Prometheus 的采集方式叫 pull,也叫 scrape。它周期性地向被监控目标发起 HTTP 请求,目标在一个固定路径上返回当前的指标快照。默认路径是 /metrics,默认间隔十五秒。

Prometheus  ---->  GET http://target:9100/metrics  ---->  目标返回纯文本指标

/metrics 返回的内容长这样:

# HELP node_cpu_seconds_total Seconds the CPUs spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 12345.67
node_cpu_seconds_total{cpu="0",mode="system"} 234.56
node_memory_MemAvailable_bytes 5.2e+09

这是纯文本、每行一条的格式,# HELP 是解释,# TYPE 是指标类型,下面每行是具体数值。这种"目标自己暴露、采集端来取"的约定叫 exporter 模式。

为什么用 pull?几个实际好处:采集端能通过目标是否响应,天然判断对方存活(up 指标);能通过服务发现自动跟上容器和实例的增减;能集中控制采集频率和超时;调试时直接 curl 目标即可,不依赖任何中间组件。缺点也有:不适合一次性任务和短生命周期任务,也不容易穿透防火墙——这些场景要靠 Pushgateway 或服务端代理来补。

时序数据模型:指标名加标签

Prometheus 存的是时序数据,一条时序由指标名和一个标签集合唯一确定,样点是"时间戳 + 数值"的序列。

指标名{标签1="值1", 标签2="值2"} 数值

举个例子:

http_requests_total{method="POST", handler="/api/login", status="200"} 1027

这里 http_requests_total 是指标名,method、handler、status 是三个标签。指标名加这组标签就构成一条独立的时序。换个 status="500" 就是另一条时序。这样设计的好处是,同一族数据可以用标签自由切分,查询时按标签过滤和聚合,非常灵活。

标签的两个原则要记住:标签值尽量是有限、低基数的枚举,比如状态码、方法、实例名;不要把用户 ID、请求 ID、时间戳这种高基数的值当标签,否则会炸出成千上万条时序,把内存吃光。这是新手最容易犯的性能错误。

四种指标类型

Prometheus 客户端库定义了四种指标类型,选错类型会让查询变得别扭。它们的区别值得一提。

类型 特点 适合场景 例子
Counter 只增不减,重启归零 累计次数、累计字节数 请求总数、错误总数
Gauge 可增可减,瞬时值 当前状态快照 CPU 使用率、内存占用、温度
Histogram 分桶统计,可算分位数 请求耗时、响应大小 接口延迟分布
Summary 客户端算分位数 同上但更省查询开销 请求耗时(滑动窗口)

Counter 是只增的计数器,直接看它的值没意义,要用 rate() 或 increase() 函数算出每秒增长率才有意义。http_requests_total 就是典型 Counter。

Gauge 是瞬时值,可以直接看、直接比较,比如 node_memory_MemAvailable_bytes。

Histogram 把观测值分到预设的桶里(比如耗时小于 0.1 秒、小于 0.5 秒、小于 1 秒各有几个),配合 histogram_quantile() 可以算 P95、P99 分位数。它还会额外产生 _sum 和 _count 两个指标。

Summary 在客户端直接算出分位数,省去服务端计算,但分位数不能在服务端跨实例聚合,且计算成本落在被监控进程上。选择上,需要跨实例聚合计分位数就用 Histogram,单实例且想减轻服务端负担可以用 Summary;绝大多数场景用 Histogram 更合适。

PromQL 入门

PromQL 是 Prometheus 的查询语言,掌握几条常用表达式就能覆盖八成日常需求。

即时查询某个 Gauge:

node_memory_MemAvailable_bytes
node_memory_MemAvailable_bytes / 1024 / 1024 / 1024          # 转成 GB

用标签过滤:

node_filesystem_avail_bytes{mountpoint="/", fstype!="tmpfs"}
http_requests_total{status=~"5.."}                            # 正则匹配 5xx

Counter 求速率(最常用):

rate(http_requests_total[5m])                                 # 每秒请求数
rate(http_requests_total{status=~"5.."}[5m])                  # 每秒错误数
increase(node_network_receive_bytes_total[1h])                # 一小时增量

算 CPU 使用率(一条经典表达式,值得背下来):

100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

它的逻辑是:node_cpu_seconds_total{mode="idle"} 是各核心空闲时间的 Counter,rate(...[5m]) 算出每秒空闲比例,avg by (instance) 按实例对多核心取平均,最后用 100 减去空闲比例,就是使用率。

按标签聚合:

sum by (instance) (rate(http_requests_total[5m]))             # 按实例汇总 QPS
sum(rate(http_requests_total[5m]))                            # 全量汇总
topk(5, rate(http_requests_total[5m]))                        # 取前五
avg_over_time(node_load1[30m])                                # 三十分钟平均负载

几个高频函数要分清:rate 用于 Counter,算每秒平均增长率,会自动处理重启归零;irate 用最后两个点算瞬时速率,更敏感但易受抖动影响;increase 算一段时间内的增量;avg_over_time、max_over_time 用于对 Gauge 做时间窗口聚合;histogram_quantile(0.95, ...) 算 P95 分位数。记住 rate 只用于 Counter,用在 Gauge 上结果没有意义。

部署 node_exporter

node_exporter 是采集 Linux 主机指标的官方 exporter,它暴露 CPU、内存、磁盘、网络、文件系统、负载等指标。

下载并安装(以最新稳定版思路为例,版本号按官方发布替换):

cd /tmp
wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz
tar xf node_exporter-1.7.0.linux-amd64.tar.gz
sudo mv node_exporter-1.7.0.linux-amd64/node_exporter /usr/local/bin/
/usr/local/bin/node_exporter --version

做成 systemd 服务,开机自启:

# /etc/systemd/system/node_exporter.service
[Unit]
Description=Node Exporter
After=network.target

[Service]
User=nobody
ExecStart=/usr/local/bin/node_exporter
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
curl -s http://localhost:9100/metrics | head

能返回一堆指标就说明装好了。RHEL 系记得放行防火墙:

# RHEL、Rocky、CentOS
sudo firewall-cmd --permanent --add-port=9100/tcp
sudo firewall-cmd --reload

# Debian、Ubuntu
sudo ufw allow 9100/tcp

风险提示: 9100 端口会暴露主机的大量信息,应该只允许监控机访问。生产环境建议用 --web.listen-address=127.0.0.1:9100 绑定本机,再用 Prometheus 通过 SSH 隧道或反向代理访问,或者用防火墙限定来源 IP,不要直接对公网开放。

安装并配置 Prometheus

从官方发布页下载解压:

cd /tmp
wget https://github.com/prometheus/prometheus/releases/download/v2.51.0/prometheus-2.51.0.linux-amd64.tar.gz
tar xf prometheus-2.51.0.linux-amd64.tar.gz
sudo mv prometheus-2.51.0.linux-amd64 /opt/prometheus
sudo mkdir -p /var/lib/prometheus

配置文件 /opt/prometheus/prometheus.yml:

global:
  scrape_interval: 15s          # 默认采集间隔
  evaluation_interval: 15s      # 规则评估间隔

rule_files:
  - /opt/prometheus/rules/*.yml

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node'
    static_configs:
      - targets:
          - '192.0.2.20:9100'
          - '192.0.2.21:9100'
        labels:
          env: 'prod'
          role: 'web'

  - job_name: 'node-dev'
    static_configs:
      - targets: ['192.0.2.31:9100']
        labels:
          env: 'dev'

scrape_configs 里每个 job_name 是一组采集任务,targets 是要拉取的目标。给目标加 labels 是很好的习惯,后面写告警和画面板时可以按 env、role 过滤。启动:

cd /opt/prometheus
./prometheus --config.file=prometheus.yml --storage.tsdb.retention.time=30d

--storage.tsdb.retention.time 控制数据保留时长,默认十五天。建议做成 systemd 服务管理。访问 http://监控机IP:9090 进入自带的查询界面,在 Graph 页输入 PromQL 就能看图,Status 里的 Targets 页可以看到每个目标是否 UP。

除了静态配置,Prometheus 还支持多种服务发现:file_sd_configs 读本地 JSON 文件,容器环境用 kubernetes_sd_configs,云上还有 ec2_sd_configs 等。运维中很实用的做法是让 Ansible 或脚本生成 targets JSON 文件,Prometheus 监听文件变化自动重载,避免每次加机器都改主配置。

pull 与 push 的差异和适用场景

维度 pull(Prometheus 原生) push(如 Zabbix 主动、Pushgateway)
发起方 监控端主动拉 被监控端主动推
目标存活判断 通过 up 指标天然获得 需额外做心跳检测
动态扩缩容 服务发现自动跟上 目标需自行注册到推送端
穿透防火墙 需监控端能访问目标 只需目标能访问监控端
短任务 不友好,任务结束就没目标了 友好,Pushgateway 暂存
时钟要求 由采集端统一时间 依赖被监控端时间准确

结论落到场景:长期在线的服务器、容器、中间件,用 pull 最省心;一次性的批处理任务、定时脚本、位于 NAT 后无法被访问的设备,用 Pushgateway 补;跨公网的大规模边缘节点,也可以考虑在侧线部署一个采集代理,用远程写入(remote write)回传。不要把 Pushgateway 当通用方案,它是给短任务兜底的,长期指标硬推会失去 pull 的诸多好处。

常见问题

目标显示 DOWN 但手动 curl 明明能通?

最常见是两个原因:一是 Prometheus 所在机器到目标 IP 的网络不通(curl 是在本机测的,不代表监控机能通);二是 Prometheus 配置文件里路径写错了,或改了配置没重启。另外检查 scrape_timeout 是否太短,目标响应慢时会超时。可以在 Status → Targets 页面点开目标看具体报错。

Counter 数值为什么不是从零开始?

Counter 只在进程重启时归零。如果目标运行了很久,http_requests_total 可能是几百万,这是正常的。不要直接看它的绝对值,用 rate() 看每秒增量。当你看到数值突然变小,通常是进程重启了,rate() 会自动处理这种重置。

rate 的时间窗口该选多长?

一般选采集间隔的四倍以上。十五秒采集就至少用 [1m],常用 [5m]。窗口太短,点数太少,rate 会抖动甚至算不出来(少于两个点);窗口太长,反应迟钝,看不出近期变化。告警用 [5m] 比较稳妥。

Prometheus 单机存得下多少数据?

取决于采集规模、标签基数和保留时间。经验上,几万条活跃时序、保留一个月,单机 SSD 通常能扛住;十几万条以上就要考虑分片、远程存储或换用兼容的时序数据库。真正吃内存的是标签基数,务必避免高基数标签。

总结

  • Prometheus 用 pull 模型,监控端主动去目标的 /metrics 拉数据,天然能判断目标存活,适合动态环境。
  • 数据模型是指标名加标签,标签要低基数,别把用户 ID 之类的高基数值当标签。
  • 四种指标类型各司其职:Counter 配 rate()、Gauge 直接看、Histogram 算分位数、Summary 客户端算分位数。
  • 掌握 rate、increase、avg by、topk、histogram_quantile 几条 PromQL,就能覆盖绝大多数监控与告警查询。
  • node_exporter 负责主机指标,9100 端口要限制来源;Prometheus 通过 scrape_configs 配置目标,加标签便于后续过滤。
  • pull 适合长期在线目标,短任务和 NAT 环境用 Pushgateway 补,二者是搭配而非对立。