Learn
Prometheus/09-scrape-configs-and-exporters

抓取配置与 Exporter

前面八章你已经会写查询了,但查询的前提是「库里有数据」。数据从哪来?答案只有一个入口:Prometheus 主动发起 HTTP 请求,去目标的某个 URL 上把文本格式的指标拉回来。这个动作叫「抓取」(scrape),而描述「抓谁、多久抓一次、怎么抓」的那段配置,就是 scrape_configs。

第 3 章我们只用了它最简单的形态:一个 job_name 加一串 targets。真实环境远比这复杂——目标要走 HTTPS、要带认证、指标路径不是 /metrics、还要传查询参数。同时,被抓的那一端也不总是「应用自己暴露指标」,更多时候是一个中间人程序:Exporter。本章把这两件事一次讲透。

读完本章你会掌握:

  • scrape_configs 里每个常用字段的语义:static_configs、scrape_interval、metrics_path、scheme、params
  • 认证与加密:basic_auth、authorization、tls_config 的正确写法与踩坑点
  • 防止单个目标把 Prometheus 拖垮的保护参数:sample_limit、target_limit、body_size_limit
  • relabel_configs 在抓取流程里处于什么位置(细节留到第 10 章)
  • Exporter 的本质,以及 node_exporter 的安装、常用 collector 与关键指标
  • 用 textfile collector 把脚本产出的数据接进监控体系
  • blackbox_exporter 的黑盒探测思路,以及 Pushgateway 到底适合什么场景

一次抓取到底发生了什么

在钻进配置字段之前,先建立完整的心智模型。Prometheus 对一个目标做一次抓取,内部顺序是这样的:

1. 服务发现(static_configs / file_sd / kubernetes_sd ...)
   产出一批「候选目标」,每个目标自带一组标签,
   包括 __address__、__scheme__、__metrics_path__、__param_* 和一堆 __meta_* 元数据
        到
2. relabel_configs 逐条执行
   可以改写 __address__、丢弃目标、把 __meta_* 变成正式标签
        到
3. 目标定型
   所有以双下划线开头的标签被丢弃;
   若此时没有 instance 标签,就用 __address__ 的值填进去
        到
4. 发起 HTTP 请求
   URL = scheme://__address__ + __metrics_path__ + 由 __param_* 拼出的查询串
        到
5. 解析响应体,得到一批样本
        到
6. metric_relabel_configs 逐条执行(作用在「样本」上,不是「目标」上)
        到
7. 写入 TSDB,并额外附加 up、scrape_duration_seconds 等自监控指标

这张图后面几章会反复用到。现在只需要记住一个关键分界:第 2 步操作的是「目标」,第 6 步操作的是「指标」。搞混这两者是新手最常见的错误来源。

scrape_configs 的完整骨架

下面这份配置故意把常用字段全塞进去了,作为字段速查表:

scrape_configs:
  - job_name: 'order-service'        # 必填,且全局唯一;会成为 job 标签的值
 
    # ---- 频率与超时 ----
    scrape_interval: 15s             # 不写则继承 global.scrape_interval
    scrape_timeout: 10s              # 不写则继承 global.scrape_timeout
 
    # ---- 请求怎么拼 ----
    metrics_path: '/actuator/prometheus'   # 默认 /metrics
    scheme: https                          # 默认 http,可选 http / https
    params:                                # 附加查询参数,值必须是「数组」
      module: [http_2xx]
      collect: [cpu, mem]
 
    # ---- 认证 ----
    basic_auth:
      username: 'prom'
      password_file: '/etc/prometheus/secrets/app_pass'
 
    # ---- TLS ----
    tls_config:
      ca_file: '/etc/prometheus/certs/ca.crt'
      server_name: 'order.internal'
      insecure_skip_verify: false
 
    # ---- 保护性上限 ----
    sample_limit: 50000              # 单次抓取样本数上限,超了整次抓取失败
    label_limit: 30                  # 单条序列标签数上限
    body_size_limit: 20MB            # 响应体大小上限
    target_limit: 200                # 本 job 目标数上限
 
    # ---- 标签行为 ----
    honor_labels: false              # 默认 false,见下文
    honor_timestamps: true           # 默认 true
 
    # ---- 目标从哪来 ----
    static_configs:
      - targets:
          - '10.0.1.5:8443'
          - '10.0.1.6:8443'
        labels:
          env: prod
          team: trade
 
    # ---- 抓取前改写目标 ----
    relabel_configs: []
 
    # ---- 抓取后过滤指标 ----
    metric_relabel_configs: []

job_name 与 static_configs

job_name 不只是个注释,它会自动变成每条序列上的 job 标签。所以命名要按「服务」而不是「机房」或「机器」来划分,否则查询时会很难聚合。

static_configs 是最简单的服务发现方式:目标写死在配置里。它是一个数组,每一项包含 targets(地址列表)和可选的 labels(对该项下所有目标统一附加的标签)。因为是数组,你可以按标签分组:

- job_name: 'node'
  static_configs:
    - targets: ['10.0.0.11:9100', '10.0.0.12:9100']
      labels:
        env: prod
        region: bj
    - targets: ['10.0.9.31:9100']
      labels:
        env: staging
        region: sh
⚠️targets 里只写「主机:端口」,不要带路径和协议

常见错误是写成 http://10.0.0.11:9100/metrics。targets 的元素最终会变成 __address__ 标签,它只接受 host:port 形式。协议由 scheme 决定,路径由 metrics_path 决定。写错的表现是目标直接 DOWN,错误信息里能看到一个畸形的 URL。

scrape_interval 与 scrape_timeout

两者都可以在 job 级别覆盖 global。有两条硬性约束:

  1. scrape_timeout 不能大于 scrape_interval,否则 Prometheus 启动时直接报错 scrape timeout greater than scrape interval。
  2. 抓取超时会导致这次抓取的所有样本被丢弃,up 变成 0,错误显示 context deadline exceeded。
💡不同 job 用不同频率是很常见的

主机指标(CPU、内存)变化平缓,30s 甚至 1m 足够;核心业务的错误率和延迟建议 15s,因为它们直接决定告警的响应速度。而某些昂贵的目标(比如一个要跑 SQL 才能出指标的 exporter)可能要设成 5m。按 job 分别设置,而不是一刀切调 global。

metrics_path 与 scheme

metrics_path 默认 /metrics。常见的例外:

应用类型路径
标准 Exporter、Go/Java/Python 客户端库/metrics
Spring Boot Actuator/actuator/prometheus
blackbox_exporter 探测/probe
挂在反代子路径下的应用/myapp/metrics

scheme 只能是 http 或 https,默认 http。注意它是 job 级别的——同一个 job 下的所有目标必须使用同一协议。如果一批目标混用 HTTP 和 HTTPS,要么拆成两个 job,要么用 relabel_configs 改写 __scheme__(第 10 章会讲)。

params:往 URL 上拼查询参数

params 的值必须是字符串数组,不能是裸字符串——这是 YAML 层面最容易写错的地方:

# 正确
params:
  module: [http_2xx]
  target: ['https://example.com']
 
# 错误:会报 "cannot unmarshal !!str into []string"
params:
  module: http_2xx

配置里写的 params 会被转换成 __param_<名字> 标签。上面的例子等价于设置了 __param_module 标签,最终拼出的 URL 是 http://主机:端口/probe?module=http_2xx。反过来也成立:在 relabel_configs 里给 __param_target 赋值,就等于给 URL 加了一个 target= 参数。blackbox_exporter 的经典配置正是靠这个机制工作的。

honor_labels:谁的标签说了算

假设目标暴露的指标里自带一个 job="batch" 标签,而抓它的 job 名叫 pushgateway,冲突了怎么办?

  • honor_labels: false(默认):Prometheus 赢。目标自带的标签被重命名为 exported_job,Prometheus 自己的 job="pushgateway" 保留。
  • honor_labels: true:目标赢。保留目标暴露的 job="batch",Prometheus 不覆盖。
ℹ️什么时候需要 honor_labels: true

只有当被抓的目标是一个「代理」而非「本体」时才需要——最典型的就是 Pushgateway 和 Federation(联邦)。它们转发的是别人的指标,那些指标自带的 job / instance 标签才是真身份。其余场景一律保持默认 false。

认证与 TLS

basic_auth

basic_auth:
  username: 'prom'
  password: 'plaintext-not-recommended'
  # 或者(推荐)
  password_file: '/etc/prometheus/secrets/app_pass'

password 和 password_file 二选一。强烈建议用 password_file:密码不进配置文件、不进 Git、可以单独设 chmod 600。注意密码文件的内容包含末尾换行也没关系,Prometheus 会自动去掉尾部空白。

authorization:Bearer Token 等

2.26 之后 bearer_token / bearer_token_file 被统一到 authorization 下:

authorization:
  type: Bearer                                   # 默认就是 Bearer,可省略
  credentials_file: /var/run/secrets/token

Kubernetes 环境里抓 kubelet / apiserver 就是这个写法,配合 ServiceAccount 挂进来的 token 文件。

tls_config

scheme: https
tls_config:
  ca_file: /etc/prometheus/certs/ca.crt      # 用私有 CA 验证服务端证书
  cert_file: /etc/prometheus/certs/prom.crt  # 双向 TLS 时的客户端证书
  key_file: /etc/prometheus/certs/prom.key
  server_name: order.internal                # SNI 与证书域名校验用的名字
  insecure_skip_verify: false                # 跳过证书校验(仅调试用)
⚠️insecure_skip_verify 是临时药,不是解决方案

遇到 x509: certificate signed by unknown authority 时,把 insecure_skip_verify 设成 true 确实能立刻变绿,但你也同时关掉了对中间人攻击的全部防护。正确做法是把签发证书的 CA 证书放进 ca_file。

另一个高频报错是 x509: cannot validate certificate for 10.0.1.5 because it doesn't contain any IP SANs——原因是 targets 里写的是 IP,而证书里只有域名。解法是设置 server_name: order.internal 让校验用域名而非 IP。

保护性上限:别让一个目标压垮整套监控

这几个字段平时不起眼,但在事故里能救命。它们的共同思路是:宁可让一个目标失败,也不要让整个 Prometheus OOM。

字段作用超限后的行为
sample_limit单次抓取返回的样本数上限整次抓取失败,up 变 0,一条样本都不写入
label_limit单条序列的标签数上限整次抓取失败
label_name_length_limit标签名长度上限整次抓取失败
label_value_length_limit标签值长度上限整次抓取失败
body_size_limit响应体字节数上限,如 20MB抓取失败并报 body size limit exceeded
target_limit本 job 允许的目标数上限超出后本 job 的所有目标都不抓
- job_name: 'third-party-app'
  sample_limit: 20000
  body_size_limit: 10MB
  static_configs:
    - targets: ['10.0.3.9:8080']
💡sample_limit 是「基数爆炸」的熔断器

最经典的事故场景:某个业务上线时给指标加了 user_id 或 request_id 标签,一次抓取从 3000 条样本涨到 300 万条,Prometheus 内存瞬间打满、整个监控系统瘫痪,连排查用的看板都打不开。

配了 sample_limit 之后,故障被限制在这一个 job DOWN 了,其他监控照常工作,up 指标还会直接告诉你是谁出的问题。这是典型的「用局部失败换全局可用」。建议给所有第三方 / 业务方自研的目标都配上。

🎯练习 1:把需求翻译成 scrape_configs

按下列要求写出一个完整的 job 配置:

  • job 名为 payment-api
  • 抓 10.2.0.7:8443 和 10.2.0.8:8443 两个实例,统一打标签 env: prod、team: pay
  • 走 HTTPS,指标路径为 /internal/metrics
  • 需要 Basic 认证,用户名 monitor,密码存放在 /etc/prometheus/secrets/pay_pass
  • 服务端用的是公司自签 CA(证书文件在 /etc/prometheus/certs/corp-ca.crt),证书里的域名是 pay.corp.internal
  • 抓取间隔 20 秒,超时 15 秒
  • 单次抓取样本数不允许超过 30000

relabel_configs:先建立印象

relabel_configs 是 scrape_configs 里威力最大的字段,它在抓取之前对目标的标签集做一系列改写。因为「目标 = 一组标签」,改标签就等于改一切:改 __address__ 等于改抓谁,改 __metrics_path__ 等于改抓哪个路径,把某个目标的标签集整个丢弃就等于不抓它。

先看一个最常见的形态,感受一下:

relabel_configs:
  # 只保留 env 标签为 prod 的目标,其余全部丢弃
  - source_labels: [env]
    regex: 'prod'
    action: keep
 
  # 把 __address__ 里的端口去掉,写入一个新标签 host
  - source_labels: [__address__]
    regex: '([^:]+):.*'
    target_label: host
    replacement: '$1'

每一条规则的通用套路是:从 source_labels 取值 → 用 regex 匹配 → 按 action 决定做什么。第 10 章会把八种 action、separator、modulus、正则锚定规则等细节全部讲清楚,这里只要先建立「它在抓取之前、作用于目标标签」这个定位就够了。

每次抓取自动附加的自监控指标

不管目标暴露了什么,Prometheus 都会为每次抓取额外写入几条指标,它们不来自目标,是 Prometheus 自己生成的:

up{job="node", instance="10.0.0.11:9100"}         # 1 = 抓取成功,0 = 失败
scrape_duration_seconds                            # 本次抓取耗时(秒)
scrape_samples_scraped                             # 目标返回了多少条样本
scrape_samples_post_metric_relabeling              # 经过 metric_relabel 过滤后剩多少条
scrape_series_added                                # 本次新增了多少条「新序列」

这几条是排障利器:

# 谁在拖慢抓取?
topk(10, scrape_duration_seconds)
 
# 谁的指标量最大?(找基数大户)
topk(10, scrape_samples_scraped)
 
# metric_relabel 到底过滤掉了多少?
scrape_samples_scraped - scrape_samples_post_metric_relabeling
 
# 哪个目标在不停「造」新序列?(基数爆炸的早期信号)
topk(10, scrape_series_added)
ℹ️up 是唯一「目标挂了也还有」的指标

目标宕机时,它暴露的所有指标都会停止更新(在 PromQL 里表现为查不到),但 up 依然存在,只是值变成 0。所以「实例宕机」的告警永远写成 up == 0,而不能靠业务指标消失来判断——序列消失了,表达式自然也就没有结果,告警反而不会触发。

Exporter:把「别人的数据」翻译成 Prometheus 格式

Prometheus 只会做一件事:HTTP GET 一个 URL,解析文本格式的指标。可是 Linux 内核不会主动暴露 /metrics,MySQL 也不会,Redis 更不会。这中间缺的那一层翻译,就叫 Exporter。

一个 Exporter 的工作流程极其朴素:

HTTP GET /metrics 打到 Exporter
        到
Exporter 现场去读数据源
  (读 /proc 文件、执行 SHOW GLOBAL STATUS、调用 INFO 命令、请求某个 HTTP API……)
        到
把结果转换成 Prometheus 文本格式返回
        到
Prometheus 解析并写库

注意 Exporter 通常不自己存数据。它是无状态的,每次被抓时才现场采集。这意味着:没人抓它的时候,它几乎不消耗资源;它挂了,Prometheus 立刻能通过 up == 0 发现。

常见 Exporter 及其约定端口(社区有一份端口分配表,避免撞车):

Exporter端口采集对象
node_exporter9100Linux / Unix 主机
blackbox_exporter9115黑盒探测(HTTP/TCP/ICMP/DNS)
mysqld_exporter9104MySQL
redis_exporter9121Redis
postgres_exporter9187PostgreSQL
kafka_exporter9308Kafka
pushgateway9091短生命周期任务的中转站
cAdvisor8080容器资源

node_exporter 实战

node_exporter 是最常用的 Exporter,负责采集主机的 CPU、内存、磁盘、网络、文件系统等指标。

安装与启动

VERSION=1.8.2
wget https://github.com/prometheus/node_exporter/releases/download/v${VERSION}/node_exporter-${VERSION}.linux-amd64.tar.gz
tar xzf node_exporter-${VERSION}.linux-amd64.tar.gz
cd node_exporter-${VERSION}.linux-amd64
 
# 前台跑一下试试
./node_exporter --web.listen-address=:9100
 
# 另开一个终端验证
curl -s http://localhost:9100/metrics | head -20

生产环境同样用 systemd 托管,写入 /etc/systemd/system/node_exporter.service:

[Unit]
Description=Prometheus Node Exporter
After=network-online.target
 
[Service]
User=node_exporter
Group=node_exporter
Type=simple
Restart=on-failure
ExecStart=/usr/local/bin/node_exporter \
  --web.listen-address=127.0.0.1:9100 \
  --collector.textfile.directory=/var/lib/node_exporter/textfile_collector \
  --no-collector.mdadm \
  --no-collector.infiniband
 
[Install]
WantedBy=multi-user.target

collector 的开关

node_exporter 的功能被切分成几十个 collector,每个负责一类数据。开关规则:

# 关掉某个默认开启的 collector
--no-collector.mdadm
 
# 打开某个默认关闭的 collector
--collector.systemd
--collector.processes
 
# 极端做法:先全关,再只开需要的
--collector.disable-defaults \
  --collector.cpu --collector.meminfo --collector.filesystem --collector.loadavg
💡关掉用不上的 collector 是性价比最高的瘦身手段

默认配置下 node_exporter 一台机器大约暴露 500 到 1500 条序列,其中 mdadm(软 RAID)、infiniband、nfsd、wifi 这类指标在绝大多数环境下从来没人看过。200 台机器 × 300 条无用序列 = 6 万条白白占内存和磁盘的序列。

先用 curl -s localhost:9100/metrics | grep -c '^node_' 数一下总量,再用 /tsdb-status 页面看看哪些指标序列最多,然后逐个 --no-collector.X 关掉。

几条必会的 node_exporter 查询

# CPU 使用率(100% 减去空闲占比)
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
 
# 内存使用率(用 MemAvailable,比 MemFree 准确得多)
100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)
 
# 根分区剩余空间百分比
100 * node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}
 
# 磁盘 IO 饱和度(io_time 占比接近 1 说明磁盘打满)
rate(node_disk_io_time_seconds_total[5m])
 
# 网卡入向流量(字节每秒)
rate(node_network_receive_bytes_total{device!~"lo|veth.*"}[5m])
 
# 主机运行时长
node_time_seconds - node_boot_time_seconds
⚠️用 MemAvailable,别用 MemFree

Linux 会把空闲内存拿去做 page cache,所以 node_memory_MemFree_bytes 在健康的机器上也常年很低,用它算使用率会得到「内存永远 95%」的假警报。node_memory_MemAvailable_bytes 是内核给出的「真正还能分配出去多少」,才是正确的分母来源。

🎯练习 2:诊断 node_exporter 抓取问题

你在一台机器上启动了 node_exporter,本机 curl http://localhost:9100/metrics 有输出。但 Prometheus 的 Targets 页面显示这个目标 DOWN,错误是 connection refused。Prometheus 跑在另一台机器上。

请给出:最可能的原因是什么?如何验证?如何修复?另外,如果错误变成 context deadline exceeded,排查思路又该怎么变?

textfile collector:让任何脚本都能产出指标

有些数据没有现成的 Exporter,但你能用脚本拿到——比如「上次备份成功的时间」「证书还有几天过期」「某个业务表的行数」。这时不需要写 Exporter,用 node_exporter 自带的 textfile collector 就够了。

原理简单到有点朴素:你把符合 Prometheus 文本格式的内容写进一个 .prom 文件,node_exporter 每次被抓取时读取指定目录下所有 .prom 文件,把内容原样拼进自己的输出里。

开启

node_exporter --collector.textfile.directory=/var/lib/node_exporter/textfile_collector

写一个采集脚本

#!/bin/bash
set -euo pipefail
 
TEXTFILE_DIR=/var/lib/node_exporter/textfile_collector
TMP="$TEXTFILE_DIR/backup.prom.$$"
 
# 假设备份脚本会写一个成功标记文件
if [ -f /var/backups/.last_success ]; then
  TS=$(stat -c %Y /var/backups/.last_success)
  SIZE=$(du -sb /var/backups | cut -f1)
else
  TS=0
  SIZE=0
fi
 
cat > "$TMP" <<EOF
# HELP backup_last_success_timestamp_seconds 上次备份成功的 Unix 时间戳
# TYPE backup_last_success_timestamp_seconds gauge
backup_last_success_timestamp_seconds $TS
# HELP backup_size_bytes 备份目录总大小
# TYPE backup_size_bytes gauge
backup_size_bytes $SIZE
EOF
 
# 关键:原子替换
mv "$TMP" "$TEXTFILE_DIR/backup.prom"

配上 crontab:

*/5 * * * * /usr/local/bin/collect_backup_metrics.sh

然后就能这样告警了:

# 备份超过 26 小时没成功
time() - backup_last_success_timestamp_seconds > 26 * 3600
⚠️必须原子写入,否则会读到半截文件

如果直接 echo ... > backup.prom,node_exporter 有可能正好在文件写了一半时读取,解析失败导致整次抓取失败(不只是这个文件,是整个 /metrics 都返回错误)。

正确做法是先写临时文件、再 mv 到最终路径——同一文件系统内的 mv 是原子操作,读取方要么看到旧版本要么看到新版本,不会看到半截内容。这是 textfile collector 的头号铁律。

💡两个自带的看门狗指标
  • node_textfile_scrape_error:只要有任何一个 .prom 文件解析失败就变成 1。建议为它配一条告警,否则脚本写坏了你根本不会知道。
  • node_textfile_mtime_seconds:每个文件的最后修改时间。用 time() - node_textfile_mtime_seconds > 900 就能发现「cron 挂了、文件不再更新」的情况——这比指标值本身停留在旧值更容易被察觉。

blackbox_exporter:从外部视角探测

前面所有 Exporter 都是「白盒」:站在被监控对象内部读数据。但有些问题只有从外面才看得见——域名解析对不对、TLS 证书快过期了没、从用户所在网络访问延迟多少、HTTP 返回码是不是 200。这就是 blackbox_exporter 的领域。

它的独特之处在于探测目标是通过 URL 参数传入的,所以配置必须借助 relabel:

- job_name: 'blackbox-http'
  metrics_path: /probe
  params:
    module: [http_2xx]          # 用 blackbox.yml 里定义的哪个模块
  static_configs:
    - targets:
        - https://api.example.com/health
        - https://www.example.com
  relabel_configs:
    # 1) 把要探测的 URL 从 __address__ 挪到查询参数 target
    - source_labels: [__address__]
      target_label: __param_target
    # 2) 让 instance 标签显示被探测的 URL,而不是 exporter 的地址
    - source_labels: [__param_target]
      target_label: instance
    # 3) 真正要请求的地址改成 blackbox_exporter 自己
    - target_label: __address__
      replacement: 'blackbox-exporter:9115'

这三条 relabel 是固定套路,值得背下来。执行完之后,Prometheus 实际请求的是:

http://blackbox-exporter:9115/probe?module=http_2xx&target=https://api.example.com/health

blackbox 侧的模块定义(blackbox.yml):

modules:
  http_2xx:
    prober: http
    timeout: 5s
    http:
      valid_status_codes: [200, 204]
      method: GET
      preferred_ip_protocol: ip4
      fail_if_body_not_matches_regexp:
        - '"status"\s*:\s*"ok"'
  tcp_connect:
    prober: tcp
    timeout: 5s
  icmp_ping:
    prober: icmp

关键返回指标:

probe_success                     # 1 成功 0 失败,最重要的一条
probe_duration_seconds            # 整次探测总耗时
probe_http_status_code            # 实际返回的状态码
probe_ssl_earliest_cert_expiry    # 证书最早过期时间(Unix 时间戳)
probe_dns_lookup_time_seconds     # DNS 解析耗时
 
# 证书 15 天内过期告警
(probe_ssl_earliest_cert_expiry - time()) / 86400 < 15

自己写一个 Exporter

当没有现成 Exporter 时,用官方客户端库写一个并不难。Go 版最简形态:

1. 引入 client_golang,定义指标(Gauge / Counter / Histogram)
2. 实现 Collector 接口的 Describe 与 Collect 两个方法,
   在 Collect 里现场去数据源取数、填充指标
3. 用 prometheus.MustRegister 注册
4. 用 promhttp.Handler() 挂在 /metrics 上,ListenAndServe

写自定义 Exporter 有几条社区共识值得遵守:

  • 不要在 Exporter 内部起定时器缓存数据。让它在被抓取时才现场采集,这样数据的时间戳和抓取时间才一致。唯一的例外是采集本身特别慢(比如要跑几秒的 SQL),这时才用后台定时刷新加缓存。
  • 一定要暴露一个 xxx_up 或 xxx_scrape_error 指标,表示「这次去后端取数成功了没」。否则后端挂了时,Exporter 本身还活着(up 是 1),你却拿不到任何异常信号。
  • 指标命名遵守规范:子系统_名词_单位,Counter 以 _total 结尾,单位用基本单位(秒、字节),不要用毫秒和 MB。
  • 绝对不要把高基数的东西做成标签:用户 ID、订单号、请求 ID、完整 URL 路径(含参数)都是禁区。
🎯练习 3:为 blackbox 探测写配置和告警

你要监控三个外部站点:https://shop.example.com、https://api.example.com/ping、https://admin.example.com。blackbox_exporter 部署在 10.0.5.20:9115,模块名 http_2xx。

请写出:(1) Prometheus 的 job 配置;(2) 「站点探测失败持续 2 分钟」的告警表达式;(3) 「TLS 证书 30 天内过期」的告警表达式;(4) 解释为什么必须用 relabel,不能直接把网址写进 targets 就完事。

Pushgateway:给短生命周期任务的中转站

Prometheus 是 Pull 模型:它主动去抓。这对长期运行的服务很合适,但对「跑几秒就退出」的批处理任务完全行不通——等 Prometheus 下一轮来抓的时候,进程早没了。

Pushgateway 就是为这个场景准备的:任务在退出前把指标 Push 给它,它把这些指标缓存住,Prometheus 再从 Pushgateway 上正常 Pull。

用法

# 启动(默认端口 9091,加持久化文件防止重启丢数据)
./pushgateway --persistence.file=/var/lib/pushgateway/data
 
# 推送一条指标:URL 路径就是标签
echo "batch_job_duration_seconds 42.3" | \
  curl --data-binary @- http://pushgateway:9091/metrics/job/nightly_etl/instance/worker-1
 
# 推送多条(注意末尾必须有换行,否则报 400)
cat <<EOF | curl --data-binary @- http://pushgateway:9091/metrics/job/nightly_etl
# TYPE batch_job_last_success_timestamp_seconds gauge
batch_job_last_success_timestamp_seconds 1735689600
# TYPE batch_job_records_processed gauge
batch_job_records_processed 128394
EOF
 
# 删除某个 job 的全部指标(任务下线时务必调用)
curl -X DELETE http://pushgateway:9091/metrics/job/nightly_etl

抓 Pushgateway 的配置有一个必须写的字段:

- job_name: 'pushgateway'
  honor_labels: true          # 必须!否则推上来的 job 标签会被改名成 exported_job
  static_configs:
    - targets: ['pushgateway:9091']

一个完整的批处理任务模式

#!/bin/bash
JOB=nightly_etl
PGW=http://pushgateway:9091/metrics/job/$JOB
 
START=$(date +%s)
if /usr/local/bin/run_etl.sh; then
  SUCCESS=1
else
  SUCCESS=0
fi
END=$(date +%s)
 
cat <<EOF | curl --data-binary @- "$PGW"
# TYPE etl_last_run_timestamp_seconds gauge
etl_last_run_timestamp_seconds $END
# TYPE etl_duration_seconds gauge
etl_duration_seconds $((END - START))
# TYPE etl_last_success gauge
etl_last_success $SUCCESS
EOF

对应的告警:

# ETL 昨晚没跑成功
etl_last_success == 0
 
# ETL 超过 25 小时没跑过(比「失败」更隐蔽,也更危险)
time() - etl_last_run_timestamp_seconds > 25 * 3600
⚠️Pushgateway 的四条硬伤,别拿它当万能 Push 网关

官方文档专门写了一节劝退,核心是:

  1. 它不会自动过期数据。指标一旦推上去就永远留着,除非显式 DELETE 或重启(且没开 --persistence.file)。一台机器下线后,它推的指标会永远挂在那里,形成「僵尸指标」,配的告警可能永远不恢复。
  2. 它破坏了 up 的语义。所有指标都从 Pushgateway 这一个目标出来,up 只反映 Pushgateway 活没活,反映不了原始任务的健康状态。
  3. 它是单点,也是瓶颈。把成百上千个实例的指标都往一个 Pushgateway 推,它会变成整套监控的单点故障。
  4. 它会丢时间语义。Prometheus 抓 Pushgateway 时打的是「抓取时刻」的时间戳,不是任务实际执行的时刻。

判定标准:如果你的进程「活得足够久,能被抓到至少一次」,就绝对不要用 Pushgateway,直接暴露 /metrics 让 Prometheus 来抓。只有「秒级退出的 cron 任务、CI 流水线步骤、一次性迁移脚本」才是它的正确用途。

另外,服务级别的指标(比如 Web 服务的 QPS)绝对不能走 Pushgateway。

🎯练习 4:给三个场景选择正确的采集方案

为下列四个需求分别选择方案(node_exporter / textfile collector / blackbox_exporter / Pushgateway / 自定义 Exporter),并说明理由与关键配置:

  1. 监控 200 台 Linux 服务器的 CPU、内存、磁盘。
  2. 每天凌晨 3 点跑一次的数据库归档脚本,需要知道它是否成功、耗时多久。
  3. 监控公司内部自研的一个消息队列中间件(它有一个返回 JSON 的 /admin/stats HTTP 接口)。
  4. 每台机器上有一个 yum check-update 的结果,想知道「有多少个待更新的安全补丁」。

小结

  • scrape_configs 的每个 job 描述「抓谁、多久抓、怎么抓」。job_name 会变成 job 标签,targets 里只写 host:port。
  • URL 由三部分拼成:scheme + __address__ + metrics_path,再加上 params 转成的 __param_* 查询串。
  • 认证用 basic_auth(优先 password_file)或 authorization;HTTPS 场景配 tls_config,IP 直连时记得设 server_name。
  • sample_limit / body_size_limit / target_limit 是基数爆炸的熔断器,给所有非自控目标都配上。
  • 每次抓取会自动附加 up、scrape_duration_seconds、scrape_samples_scraped、scrape_series_added,它们是排障的第一手信息。
  • Exporter 的本质是「无状态翻译层」:被抓时才现场采集。node_exporter 管主机,用 --no-collector.X 瘦身,内存率算分母用 MemAvailable。
  • textfile collector 让任意脚本接入监控,铁律是原子 mv,并为 node_textfile_scrape_error 和文件 mtime 配告警。
  • blackbox_exporter 从外部探测,靠「__address__ 到 __param_target 到 instance 到 __address__ 覆写」三步 relabel 完成配置。
  • Pushgateway 只服务于「秒级退出的批处理」,其余场景一律用 Pull;抓它时必须 honor_labels: true,任务下线要记得 DELETE。
  • 下一章我们把本章一笔带过的 relabel_configs 彻底拆开讲——它是整个 Prometheus 配置体系里最强大也最容易写错的部分。