Prometheus接入Pushgateway实战:二进制与Docker部署、指标推送与远程写入

为什么会有 Pushgateway 这个组件

Prometheus 的核心采集模型是 pull:它按 scrape_interval 周期性地去各个 target 的 /metrics 拉数据。这个模型对长期运行的服务很合适,但对另一类任务就不太友好------那些跑几秒、几十秒就结束的进程。

比如:

  • 每天凌晨跑一次的数据库备份脚本,成功与否、耗时多少,你希望有监控;
  • 一个 CI 流水线里执行的数据同步任务,处理了多少条记录;
  • 临时拉起的离线推理任务,跑完就退出。

这些进程在 Prometheus 下一次抓取之前就已经结束了,pull 模型根本抓不到。Pushgateway 就是为这种场景设计的:任务主动把指标 push 给 Pushgateway,Pushgateway 把这些指标缓存起来并长期暴露在 /metrics 上,Prometheus 只需要照常抓取 Pushgateway 一个 target,就能拿到所有短生命周期任务上报的数据。

这里有个关键点要先说清楚,也是很多人第一次用会误解的地方:

【关键结论】Pushgateway 不是"指标中转的时序数据库",它是一个指标的暂存与聚合点。它不会自动过期删除你推送的数据,同一条指标会一直保留到你主动删除或覆盖。这一点和 Prometheus 主库的 retention 机制完全不同,后面会专门讲怎么处理。

部署方式一:二进制直接跑

Pushgateway 是 Go 写的单文件程序,官方在 GitHub Releases 提供各平台二进制。截至我写这篇文章时,较新的稳定版本是 1.x 系列(例如 v1.9.x / v1.11.x 这类版本号,具体以你下载时 Releases 页面显示的为准,这一点我没有逐一验证每个小版本)。下载对应平台的压缩包后:

bash 复制代码
# 以 linux-amd64 为例,版本号请替换成 Releases 页面上实际的最新版
wget https://github.com/prometheus/pushgateway/releases/download/v1.11.1/pushgateway-1.11.1.linux-amd64.tar.gz
tar xvf pushgateway-1.11.1.linux-amd64.tar.gz
cd pushgateway-1.11.1.linux-amd64
./pushgateway --version

默认它监听 :9091,直接启动:

bash 复制代码
./pushgateway

几个在真实环境里会用到的参数:

  • --web.listen-address=:9091:监听地址,默认就是 9091;
  • --persistence.file=/var/lib/pushgateway/data:把指标持久化到磁盘。这个参数很重要,默认情况下 Pushgateway 的数据只在内存里,进程重启后所有 pushed 指标全丢。加上它之后,Pushgateway 会定期把状态写入文件,重启后恢复;
  • --persistence.interval=5m:持久化写入间隔,默认 5 分钟。

生产环境建议至少加上 --persistence.file。否则一次重启,你所有定时任务的历史指标就没了,而 Pushgateway 本身又不像 Prometheus 那样有 WAL 和远程存储兜底。

想开机自启的话,用 systemd 写个 unit 就行,这部分是标准操作,不展开。

部署方式二:Docker

如果是容器化环境,直接用官方镜像 prom/pushgateway:

bash 复制代码
docker run -d \
  --name pushgateway \
  -p 9091:9091 \
  -v /data/pushgateway:/pushgateway \
  prom/pushgateway:v1.11.1 \
  --persistence.file=/pushgateway/data

注意两点:

  1. 镜像 tag 不要用 latest。我见过因为 latest 被更新、行为有变化导致排查困难的案例,生产上固定版本 tag 更稳妥。
  2. 挂载卷的目录权限要对。官方镜像默认以非 root 用户运行(nobody 或类似),如果宿主机目录属主不对,持久化文件会写失败。可以先 chown 一下,或者先用不挂载的方式跑通再处理权限。

两种方式怎么选,我列个对比:

方案 优点 缺点 适用场景
二进制 无依赖,启动快,资源占用低;systemd 集成简单 需要自己管升级、管进程 单机部署、已有 systemd 体系、边缘节点
Docker 环境隔离,升级换 tag 即可;和容器编排天然契合 多一层运行时;卷权限、网络要额外配置 K8s / Docker Compose 环境,团队统一用容器

我自己的习惯是:如果整个监控栈(Prometheus、Grafana、Alertmanager)都在容器里跑,那 Pushgateway 也放容器里,用同一个 compose 文件管理最省心;如果是裸机部署的 Prometheus,那 Pushgateway 用二进制 + systemd 反而更少出问题。

验证服务是否起来

不管哪种方式,起来之后先确认:

bash 复制代码
curl -s http://localhost:9091/-/healthy
# 正常会返回 Pushgateway is Healthy.

访问 http://localhost:9091/ 能看到一个简单的 Web UI,初始状态下没有任务,页面是空的。push 之后这里会列出所有 job 和对应的指标。

用 Python 推送指标

Pushgateway 的 push 接口是 HTTP 的,理论上你用 requests 手拼也行,但官方维护了 prometheus_client 这个库,封装了 push 逻辑,用起来更规范。安装:

bash 复制代码
pip install prometheus_client

我这边用的是 prometheus_client 0.20.x 系列,下面代码基于这个版本的 API。

最基础的推送长这样:

python 复制代码
from prometheus_client import CollectorRegistry, Gauge, push_to_gateway

# 1. 用独立 registry,避免和进程内默认 registry 混在一起
registry = CollectorRegistry()

# 2. 定义指标
g = Gauge(
    'backup_duration_seconds',
    'Duration of the nightly backup job',
    registry=registry
)
g.set(42.5)

# 3. 推送
push_to_gateway(
    'localhost:9091',
    job='nightly_backup',
    registry=registry
)

几点解释:

为什么要单独建 CollectorRegistry? 默认的 registry 是全局的,如果这个进程里还跑着 HTTP server 之类的、暴露了别的指标,用默认 registry 会把不相关的一堆指标一起推上去。用独立 registry 只推你想推的,干净。

job 参数是什么? 它是 Pushgateway 里分组用的标签。同一个 job 下的指标会归到一组。后面 Prometheus 抓取时,这个 job 会体现在 job 标签上(注意:如果 Prometheus 的 scrape_config 里也定义了 job,会按规则处理,这里容易混淆,见下一节)。

推送的位置 :push_to_gateway 默认走的是 POST /metrics/job/<job> 这个路径。这个路径语义是"替换 该 job 下的所有指标"------每次 push 会覆盖掉这个 job 之前的内容。如果你希望按实例区分,用 grouping_key:

python 复制代码
push_to_gateway(
    'localhost:9091',
    job='nightly_backup',
    registry=registry,
    grouping_key={'instance': 'db-primary-01'}
)

这样路径会变成 /metrics/job/nightly_backup/instance/db-primary-01,不同实例的数据互不覆盖。

push 完,去 Pushgateway 的 Web UI 或 curl http://localhost:9091/metrics 就能看到你推的指标了,格式和 Prometheus 的 exposition format 一致。

一个容易踩的语义坑

push_to_gateway(也就是默认的 POST 到 /metrics/job/...)是全量替换。这意味着:

如果你分两次推送,第一次推了指标 A,第二次只推了指标 B,那 A 会被删掉。因为 POST 的语义是"这个 job 下的指标现在就是这些"。

如果你想要"追加/只更新某一条"的语义,应该用 pushadd_to_gateway,它对应 PUT 方法,只更新你推送的指标,不动其他。这个区别很多人第一次用会踩。

python 复制代码
from prometheus_client import pushadd_to_gateway

# 只更新这一条,不影响该 job 下其他已有指标
pushadd_to_gateway(
    'localhost:9091',
    job='nightly_backup',
    registry=registry
)

配置 Prometheus 抓取 Pushgateway

Pushgateway 本身就是一个普通的 Prometheus target,在 prometheus.yml 里加一段:

yaml 复制代码
scrape_configs:
  - job_name: 'pushgateway'
    honor_labels: true
    static_configs:
      - targets: ['localhost:9091']

这里重点说 honor_labels。

默认情况下 ,Prometheus 抓取时会给它抓到的所有指标加上 job 和 instance 标签,值来自 scrape_config。但 Pushgateway 上暴露的指标里,本身就带了 push 时指定的 job 标签(还有 instance 等 grouping key)。两边的 job 会冲突。

  • 如果 honor_labels: false(默认),Prometheus 会把 Pushgateway 暴露的 job 标签重命名成 exported_job,然后用自己的 job="pushgateway" 覆盖。结果就是你在 Grafana 里查 job="nightly_backup" 查不到,得查 exported_job。
  • 如果 honor_labels: true,Prometheus 保留指标自带的 job,不覆盖。这样你在 Pushgateway 里推的 job="nightly_backup" 在 Prometheus 里就是原样的。

大多数用 Pushgateway 的场景,你都是希望保留 push 时指定的 job 的,所以 honor_labels: true 基本是必配项。

【踩坑提醒】如果你的 Pushgateway 上不同 job 推了同名指标、但标签不同,加上 honor_labels: true 后要确认这些标签组合不会互相冲突,否则会出现 duplicate metric 的抓取错误。

指标清理:Pushgateway 不会自动删

前面提过,Pushgateway 不会自动清理你 push 的数据。一个每天跑一次的备份任务,如果某天不再运行了,它的 backup_duration_seconds 会永远留在那里,Prometheus 也会一直抓到它,告警规则可能因此误判。

所以推送方要负起清理责任。常见做法有两个:

做法一:任务结束前主动删除自己的指标。 prometheus_client 提供了 delete_from_gateway:

python 复制代码
from prometheus_client import delete_from_gateway

delete_from_gateway(
    'localhost:9091',
    job='nightly_backup',
    grouping_key={'instance': 'db-primary-01'}
)

适合"任务跑完就不需要这个指标了"的场景。但注意,如果任务异常退出(比如被 kill),这段清理代码不会执行,指标还是会残留。

做法二:用 Pushgateway 的 API 手动/脚本清理。 比如:

bash 复制代码
# 删除某个 job 下的全部指标
curl -X DELETE http://localhost:9091/metrics/job/nightly_backup

# 删除某个 job 某个 instance 的指标
curl -X DELETE http://localhost:9091/metrics/job/nightly_backup/instance/db-primary-01

可以配合一个定时清理脚本,定期删掉长时间没更新的 job。判断"长时间没更新",可以看 Pushgateway 自动加的一个指标 push_time_seconds,它记录了每条 push 的时间戳。

bash 复制代码
# 该指标能反映每个 job 最后一次 push 的时间
curl -s http://localhost:9091/metrics | grep push_time_seconds

【关键结论】Pushgateway 只适合"状态型"的短任务指标。不要把它当成通用指标通道,更不要用它来做高频率、多实例的常规服务监控------那样会把 push 语义的坑放大,而且所有数据都挤在一个 target 上,抓取压力也集中。

关于远程上报的一点取舍

标题里提到"远程上报",这里想澄清一个容易混淆的概念。

Pushgateway 和 Prometheus 的 remote write(远程写入)是两回事,经常被混为一谈:

  • Pushgateway:面向被监控的短任务,任务是主动方,push 给 Pushgateway,Prometheus 再 pull Pushgateway。它解决的是"pull 抓不到短任务"的问题。
  • remote write :是 Prometheus 自身把采集到的数据转发 到远端存储(比如 Thanos、VictoriaMetrics、Mimir 或云厂商的托管服务)的机制,配置在 Prometheus 的 remote_write 段。它解决的是"本地存储容量有限、需要长期/跨集群存储"的问题。

如果你是"任务在 A 网络,Prometheus 在 B 网络,任务无法直接被 B 的 Prometheus 抓到"这种场景,做法是:任务 push 到本网络内的 Pushgateway,然后让 B 网络的 Prometheus 跨网抓这个 Pushgateway,或者用 Prometheus 的 remote write 把 B 采集的数据转发出去。选择取决于你的网络拓扑和存储需求,不是简单二选一。

yaml 复制代码
# remote_write 的配置形态(示意,具体 endpoint 和认证按你的远端存储文档来)
remote_write:
  - url: "https://your-remote-storage/api/v1/write"
    basic_auth:
      username: "xxx"
      password: "yyy"

这里我要明确一点:remote write 的具体认证方式、endpoint 路径、是否支持某些高级参数,取决于你所用的远端存储后端,各家实现不完全一致。这一点我没法给一个通用答案,请以对应后端的官方文档为准,我不在这里编造。

一个完整的推送示例

把前面的点串起来,写一个"任务开始记录时间 + 结束推送耗时 + 记录处理条数"的脚本:

python 复制代码
import time
from prometheus_client import CollectorRegistry, Gauge, push_to_gateway, delete_from_gateway

GATEWAY = 'localhost:9091'
JOB = 'data_sync'
GROUPING = {'instance': 'sync-worker-01'}

def run_task():
    # 模拟实际业务
    time.sleep(2)
    return 1234  # 处理条数

def main():
    registry = CollectorRegistry()

    duration = Gauge(
        'data_sync_duration_seconds',
        'Duration of the data sync job',
        registry=registry
    )
    processed = Gauge(
        'data_sync_processed_records',
        'Number of records processed',
        registry=registry
    )
    last_success = Gauge(
        'data_sync_last_success_timestamp',
        'Unix timestamp of last successful run',
        registry=registry
    )

    start = time.time()
    try:
        count = run_task()
    except Exception:
        # 失败也推一次,但 success 时间戳不更新,方便告警
        duration.set(time.time() - start)
        push_to_gateway(GATEWAY, job=JOB, registry=registry, grouping_key=GROUPING)
        raise

    duration.set(time.time() - start)
    processed.set(count)
    last_success.set(time.time())

    push_to_gateway(GATEWAY, job=JOB, registry=registry, grouping_key=GROUPING)

if __name__ == '__main__':
    main()

这个脚本的要点:

  • 失败时也 push,但 last_success 不更新。这样你可以基于 time() - data_sync_last_success_timestamp 做"多久没成功"的告警,比单纯看失败次数更直观。
  • 用 grouping_key 区分实例,避免多 worker 互相覆盖。
  • 每次 push 都是全量替换该 job+instance 下的指标,所以三个指标必须一起推,不能只推一部分。

跑完之后,去 Prometheus 里查询 data_sync_duration_seconds,应该能看到数据。如果没有,按这个顺序排查:

  1. curl http://localhost:9091/metrics 确认 Pushgateway 上确实有这条指标;
  2. 确认 Prometheus 的 scrape_configs 里 target 是 Pushgateway 的地址,且 Prometheus 的 Targets 页面显示这个 target 是 UP;
  3. 确认 honor_labels 配置符合预期,查询时用对标签名。

结尾

Pushgateway 解决的问题很具体------pull 模型抓不到短生命周期任务。它的部署不复杂,二进制和 Docker 两种方式按你的环境选即可。真正需要花心思的是语义 :push 是全量替换还是追加、指标谁来清理、honor_labels 怎么配、别把它当成常规服务监控的通道。

如果你只是想让一个定时脚本的结果能被 Prometheus 看到,上面这套流程足够用了。再往上,如果涉及多集群、长期存储、跨网络,那就是 remote write 和远端存储的领域,Pushgateway 只负责它该负责的那一段。

文中涉及具体版本号的地方(如 Pushgateway 的 1.x 小版本、prometheus_client 的 0.20.x),请以你实际下载和安装时看到的版本为准,我在文中没有逐一验证每个小版本的行为差异。

相关推荐
SimonKing1 小时前
SSE、WebSocket 连接丢 Redis 里?那可踩大坑了!
java·后端·程序员
xianyuCcCcCCCcc1 小时前
Docker 容器与网络
网络·docker·容器
YYYing.1 小时前
【设计模式系列 (五) 】原型模式
开发语言·后端·设计模式·原型模式·c/c++
yume_sibai2 小时前
02-Nginx进阶配置完全指南(性能优化 + 安全配置 + 缓存配置 + WebSocket代理)
nginx·安全·性能优化
FYKJ_20102 小时前
springboot鲜花销售系统91056-计算机课程设计、毕业设计
vue.js·spring boot·后端·python·mysql·django·课程设计
苏supper2 小时前
记一次Nacos鉴权报错unknown user,403排查|SecurityProxy源码,fastjson2扩展包缺失
java·后端
A.说学逗唱的Coke2 小时前
【云原生专题】Kubernetes 备份完全实战:用 Velero 搞定集群备份、恢复与跨集群迁移(附完整命令与踩坑记录)
云原生·容器·kubernetes
子非鱼eva2 小时前
ONNX模型导出实战:PyTorch 导出 ResNet18 模型
人工智能·pytorch·python·知识图谱·onnx·昇腾知识图谱
Andreapiki2 小时前
数字化审计校招技术备考指南:SQL、Python、BI、RPA学习路径
python·sql·rpa