抓取配置与 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常见错误是写成 http://10.0.0.11:9100/metrics。targets 的元素最终会变成 __address__ 标签,它只接受 host:port 形式。协议由 scheme 决定,路径由 metrics_path 决定。写错的表现是目标直接 DOWN,错误信息里能看到一个畸形的 URL。
scrape_interval 与 scrape_timeout
两者都可以在 job 级别覆盖 global。有两条硬性约束:
scrape_timeout不能大于scrape_interval,否则 Prometheus 启动时直接报错scrape timeout greater than scrape interval。- 抓取超时会导致这次抓取的所有样本被丢弃,
up变成 0,错误显示context deadline exceeded。
主机指标(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 不覆盖。
只有当被抓的目标是一个「代理」而非「本体」时才需要——最典型的就是 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/tokenKubernetes 环境里抓 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 # 跳过证书校验(仅调试用)遇到 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']最经典的事故场景:某个业务上线时给指标加了 user_id 或 request_id 标签,一次抓取从 3000 条样本涨到 300 万条,Prometheus 内存瞬间打满、整个监控系统瘫痪,连排查用的看板都打不开。
配了 sample_limit 之后,故障被限制在这一个 job DOWN 了,其他监控照常工作,up 指标还会直接告诉你是谁出的问题。这是典型的「用局部失败换全局可用」。建议给所有第三方 / 业务方自研的目标都配上。
按下列要求写出一个完整的 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)目标宕机时,它暴露的所有指标都会停止更新(在 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_exporter | 9100 | Linux / Unix 主机 |
| blackbox_exporter | 9115 | 黑盒探测(HTTP/TCP/ICMP/DNS) |
| mysqld_exporter | 9104 | MySQL |
| redis_exporter | 9121 | Redis |
| postgres_exporter | 9187 | PostgreSQL |
| kafka_exporter | 9308 | Kafka |
| pushgateway | 9091 | 短生命周期任务的中转站 |
| cAdvisor | 8080 | 容器资源 |
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.targetcollector 的开关
node_exporter 的功能被切分成几十个 collector,每个负责一类数据。开关规则:
# 关掉某个默认开启的 collector
--no-collector.mdadm
# 打开某个默认关闭的 collector
--collector.systemd
--collector.processes
# 极端做法:先全关,再只开需要的
--collector.disable-defaults \
--collector.cpu --collector.meminfo --collector.filesystem --collector.loadavg默认配置下 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_secondsLinux 会把空闲内存拿去做 page cache,所以 node_memory_MemFree_bytes 在健康的机器上也常年很低,用它算使用率会得到「内存永远 95%」的假警报。node_memory_MemAvailable_bytes 是内核给出的「真正还能分配出去多少」,才是正确的分母来源。
你在一台机器上启动了 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/healthblackbox 侧的模块定义(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 路径(含参数)都是禁区。
你要监控三个外部站点: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官方文档专门写了一节劝退,核心是:
- 它不会自动过期数据。指标一旦推上去就永远留着,除非显式 DELETE 或重启(且没开
--persistence.file)。一台机器下线后,它推的指标会永远挂在那里,形成「僵尸指标」,配的告警可能永远不恢复。 - 它破坏了
up的语义。所有指标都从 Pushgateway 这一个目标出来,up只反映 Pushgateway 活没活,反映不了原始任务的健康状态。 - 它是单点,也是瓶颈。把成百上千个实例的指标都往一个 Pushgateway 推,它会变成整套监控的单点故障。
- 它会丢时间语义。Prometheus 抓 Pushgateway 时打的是「抓取时刻」的时间戳,不是任务实际执行的时刻。
判定标准:如果你的进程「活得足够久,能被抓到至少一次」,就绝对不要用 Pushgateway,直接暴露 /metrics 让 Prometheus 来抓。只有「秒级退出的 cron 任务、CI 流水线步骤、一次性迁移脚本」才是它的正确用途。
另外,服务级别的指标(比如 Web 服务的 QPS)绝对不能走 Pushgateway。
为下列四个需求分别选择方案(node_exporter / textfile collector / blackbox_exporter / Pushgateway / 自定义 Exporter),并说明理由与关键配置:
- 监控 200 台 Linux 服务器的 CPU、内存、磁盘。
- 每天凌晨 3 点跑一次的数据库归档脚本,需要知道它是否成功、耗时多久。
- 监控公司内部自研的一个消息队列中间件(它有一个返回 JSON 的
/admin/statsHTTP 接口)。 - 每台机器上有一个
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 配置体系里最强大也最容易写错的部分。