
1. Pushgateway到底解决什么问题1.1 Pull模型与Push模型先搞明白你面对的是哪种任务Prometheus的核心设计是pull模型也就是由Prometheus服务器主动去各个exporter抓取指标。这个设计让Prometheus能自主控制采集节奏目标宕机了可以立刻感知也天然避开了agent往中心疯狂上报导致中心被打爆的问题。但pull模型有一个天然的盲区它要求监控目标必须能被Prometheus访问到而且目标得活得够久等到Prometheus来抓。现实运维里这两条经常不成立。我举个最常见的例子——凌晨三点跑的财务管理批处理脚本生命周期就几十秒Prometheus默认15秒拉一次这个任务启动、干活、退出、删除临时文件整个过程可能一次抓取窗口都没有碰到。另外还有一类场景监控目标在NAT后面或者在客户内网里面Prometheus根本连不过去pull模式直接失效。Pushgateway就是为这种场景设计的。它本身是一个常驻服务专门用来接收各类短命任务主动推上来的指标然后暂存在内存里等Prometheus按自己的节奏来拉。打个不恰当的比方Pull模型像你去菜市场买菜但有个厨师凌晨3点把菜放在你家门口的临时寄存柜等你起床再取走这个寄存柜就是Pushgateway。它把“任务结束就消失”的数据变成了“Prometheus随时能拿到”的持久化数据。1.2 适合用Pushgateway的场景与不该用的场景我见过不少团队把Pushgateway当成万能推流工具什么指标都往里面塞最后把自己坑得很惨。先说说真正适合用的场景批处理与定时任务cron、airflow、dolphinscheduler这类任务执行完就退出无法让Prometheus边运行边抓取。任务内部把结果推到Pushgateway是最合理的方案。短生命周期容器比如在K8s里跑的Job类型Pod处理完就CompletedPrometheus还没抓就被清掉了。Pushgateway可以为这类Pod保留最终状态。网络隔离或跨网段监控比如数据库所在网段安全策略极严格不允许Prometheus主动连接但允许应用服务器主动外联特定端口那就可以通过Pushgateway转发。需要上报“最后一次成功时间”的一类特殊状态比如备份系统你只需要知道“最后一次备份是几点、是否成功”这类最终状态型指标用Pushgateway非常合适。反过来下面这些场景绝对不要用Pushgateway常规Web服务或数据库指标这类服务存活时间长Prometheus直接抓更合适。硬推送到Pushgateway只会增加链路故障点还会造成指标延迟。多实例同一业务指标聚合上报如果20个后端的QPS都推到同一个job下面且不区分instance后推送的会覆盖前面的最终结果完全失真。需要秒级延时的指标Pushgateway本质上引入了“推-暂存-拉”的中间层数据到Prometheus会有额外延迟不适合对实时性敏感的场景。1.3 为什么要单独装它和exporter、textfile collector的取舍Prometheus生态里其实还有其他处理短命任务的办法比如node_exporter的textfile collector可以先让脚本把指标写到一个.prom文件里再由node_exporter读取暴露出来。很多老团队喜欢用textfile方案因为它不用额外部署一个Pushgateway进程。但textfile有一个很麻烦的限制指标更新依赖node_exporter的文件读取节奏而且所有任务都往同一台node_exporter上写文件文件的并发管理、格式校验都得自己处理。更重要的是textfile的指标一旦写入删除同样麻烦跟Pushgateway过期不清理的问题如出一辙。我个人的判断是如果只有一两台机器上的几个自定义脚本指标textfile够用如果是一个平台、多条业务线的批处理任务都在上报状态那独立部署Pushgateway做集中管理更清晰至少你可以通过job和instance分组用HTTP API直接删除过期组不用SSH到每台机器去删文件。2. 安装部署二进制、Docker和systemd三种方式实测2.1 二进制安装最干净也最容易排查Pushgateway是一个纯Go编译的二进制文件没有任何外部依赖单文件扔上去就能跑。安装路径通常放在/usr/local/pushgateway下面我习惯先建目录再下载mkdir -p /usr/local/pushgateway cd /usr/local/pushgateway wget https://github.com/prometheus/pushgateway/releases/download/v1.10.0/pushgateway-1.10.0.linux-amd64.tar.gz tar zxvf pushgateway-1.10.0.linux-amd64.tar.gz cp pushgateway-1.10.0.linux-amd64/pushgateway /usr/local/pushgateway/然后直接启动验证/usr/local/pushgateway/pushgateway --web.listen-address:9091启动后访问http://服务器IP:9091/metrics能看到Pushgateway自身的运行指标比如pushgateway_build_info、pushgateway_http_requests_total说明进程没问题。这里有个版本选择的坑提醒一下Pushgateway的1.x版本比较成熟建议找最新的稳定版不要用太老的0.x版本老版本在标签处理上和honor_labels的配合有些差异。下载前可以先访问GitHub Releases页面确认最新版本号但不用刻意追新稳定能用就行。2.2 Docker方式部署一条命令搞定但编码问题要注意用Docker部署最大的好处是省去了二进制包管理的麻烦适合已经在用容器编排的环境。一条命令就能拉起来docker run -d \ --name pushgateway \ -p 9091:9091 \ prom/pushgateway:latest但直接用latest有个隐患镜像的latest标签对应的是最新稳定版一旦上游更新你重启容器就自动升级了对于监控组件来说这是不可控的。我建议指定版本号docker run -d \ --name pushgateway \ -p 9091:9091 \ --restartalways \ prom/pushgateway:v1.10.0如果你要保留Pushgateway重启后的数据需要挂载持久化文件。Pushgateway默认是把数据存在内存里的容器一停数据就没了这对短命任务监控是个大问题。推荐启动时加上持久化参数docker run -d \ --name pushgateway \ -p 9091:9091 \ --restartalways \ -v /data/pushgateway:/data \ prom/pushgateway:v1.10.0 \ --persistence.file/data/pushgateway-file \ --persistence.interval5m关于这个持久化我多说一句--persistence.file指定的文件会定期把内存中所有指标序列化写入。--persistence.interval控制写盘间隔默认5分钟。如果Pushgateway被kill掉最多丢失interval间隔内的新推送数据。注意interval值不是越小越好写盘频繁会对磁盘产生压力但指标丢失窗口也会缩短建议结合业务容忍度来配置。2.3 用systemd托管生产环境的标准姿势无论用哪种方式装的二进制生产环境都应该交给systemd管理不然一旦进程退出没人把它拉起来。我用的unit文件如下[Unit] DescriptionPrometheus Pushgateway Afternetwork.target [Service] Typesimple Userpushgateway Grouppushgateway ExecStart/usr/local/pushgateway/pushgateway \ --web.listen-address:9091 \ --persistence.file/data/pushgateway/pushgateway-file \ --persistence.interval5m Restartalways RestartSec5 LimitNOFILE65535 [Install] WantedBymulti-user.target注意Userpushgateway需要先创建系统用户useradd -r -s /sbin/nologin pushgateway mkdir -p /data/pushgateway chown -R pushgateway:pushgateway /data/pushgateway然后重载并启动systemctl daemon-reload systemctl enable --now pushgateway systemctl status pushgateway生产环境我还习惯加一个LimitNOFILE65535因为Pushgateway在大量指标推送时句柄占用会升高默认1024很容易撞到上限提前调大可以少踩很多坑。3. 推数据到Pushgateway分组机制是核心3.1 用curl发送指标最直接的体验Pushgateway的HTTP接口非常简单。推送一条指标最基础的方式是echo job_duration_seconds{jobcleanup_task} 42.5 | curl --data-binary - http://localhost:9091/metrics/job/cleanup_task这条命令做了什么--data-binary会把输入原样作为HTTP body发送Pushgateway解析body里的Prometheus文本格式然后把指标归到URL路径/metrics/job/cleanup_task这个分组下。注意这里有个非常容易踩的坑如果用curl -d而不是--data-binarycurl会默认把body里的换行符、加号等做URL转义处理导致指标格式解析失败报text format parsing error。我刚接触这东西时在这个问题上折腾了很久后来养成习惯推指标一律用--data-binary -。推送之后查看Pushgateway页面能看到cleanup_task这个job下有一个指标job_duration_seconds。然后Prometheus配置里抓到这个指标后实际存储的metric是job_duration_seconds{jobcleanup_task, instance}。3.2 job和instance分组URL路径的语义Pushgateway最核心的设计就是通过URL路径来组织指标分组格式是/metrics/job/job_name /metrics/job/job_name/instance/instance_name第二种带instance的路径适合同一组任务里有多台机器分别上报。比如有三台数据库备份机器每台都执行备份任务如果不区分instance后推送的会覆盖先推送的使用/instance/host路径后每台机器有了独立分组互不干扰。这里需要对job和instance标签的语义做个澄清URL中的job和instance会被Pushgateway自动作为标签写入时间序列。如果你的指标文本里也带了job标签会发生冲突而冲突的解决规则取决于Prometheus抓取时的honor_labels配置这个我在第4章详细展开。另外Pushgateway还有一个隐含约定相同URL路径下如果多次推送相同名称的指标后推送的会覆盖先推送的。这个行为既是特性也是坑。特性在于短命任务每次执行后只需要把最新状态推上来即可不需要自己去删除旧值坑在于你如果想保留每一次运行结果就需要把每次运行批次作为标签区分开比如加一个类似run_id2025-06-18-033000的标签。3.3 用Python客户端库推送集成到业务代码里很多项目不是用命令行脚本推指标而是在Python代码里内嵌上报逻辑这就用到了prometheus_client库。pip install prometheus_client然后代码里可以这样推送from prometheus_client import CollectorRegistry, Gauge, push_to_gateway registry CollectorRegistry() g Gauge(backup_last_success, 最后备份成功时间, [host], registryregistry) g.labels(hostdb-01).set_to_current_time() push_to_gateway(localhost:9091, jobdb_backup, registryregistry)注意prometheus_client提供了三种语义不同的推送函数很多人容易用错push_to_gateway推送到指定job如果这个job下已经存在同名的指标会被整体替换成你这次registry里所有的指标。这意味着其他进程往同一个job推的指标会被清掉所以如果多个进程共用同一个job务必用下面两种。pushadd_to_gateway只添加/更新你这次推送中存在的指标不删除该job下其他已有的指标。这是最常用的方式。delete_from_gateway按job名称删除整个分组的数据。如果你是cron任务每天跑一次每次只更新“最后一次执行时间”用pushadd_to_gateway最稳妥它不会动到别的指标。另外还有一种是用Java、Go等其他语言它们的Prometheus客户端库也都实现了对应的push接口基本逻辑跟Python一致就是构建registry、注册指标、然后调用push方法换语言时只要确定好job分组和删除语义逻辑可以照搬。3.4 删除指标手动清理和API调用Pushgateway的指标不会自动过期这个特性很多人会误解。如果同一个job的脚本不产出了旧的指标会一直留在Pushgateway里Prometheus也会一直拉到这个过期的值导致持续告警。删除指标有两种方式第一种通过HTTP DELETE接口删除指定的job或instance分组# 删除job为cleanup_task的所有指标 curl -X DELETE http://localhost:9091/metrics/job/cleanup_task # 删除job下指定instance的指标 curl -X DELETE http://localhost:9091/metrics/job/cleanup_task/instance/10.0.0.1第二种如果你开启了--web.enable-admin-api可以通过管理接口删除curl -X PUT http://localhost:9091/api/v1/admin/wipe这个wipe接口会把所有分组全部清空一般用于彻底重置场景谨慎使用。我建议生产环境默认不开--web.enable-admin-api日常清理走DELETE接口就够了因为admin接口没有认证机制一旦暴露出去任何人都能把你整个监控数据清掉。4. 在Prometheus中配置抓取最重要的几个参数4.1 scrape_config配置示例与honor_labelsPushgateway安装好了只是一半另外一半是在Prometheus配置文件里把它的数据抓进来。下面是一个典型的抓取配置scrape_configs: - job_name: pushgateway honor_labels: true static_configs: - targets: [localhost:9091]注意到honor_labels: true这个参数了吗这是Pushgateway配置里最值得深入理解的一个参数。默认情况下honor_labels: falsePrometheus在抓取时会将自己的job和instance标签作为强制标签覆盖掉Pushgateway传来指标中同名的标签。也就是说你通过URL路径设置的jobcleanup_task如果Prometheus里配置的job_name是pushgateway抓取后实际存储的标签就会变成jobpushgatewayURL里的cleanup_task会被丢弃。这会导致你无法区分不同任务的指标所有指标混成一个job在告警规则里根本无法精准匹配。如果把honor_labels设为truePrometheus就会保留Pushgateway里的标签Prometheus自己的job_name只作为调用段的标识。我推荐的用法是honor_labels: true同时在static_configs的labels里指定一个区别于其他target的job名称比如- job_name: pushgateway honor_labels: true static_configs: - targets: [localhost:9091] labels: product_env: production这样Prometheus会额外添加product_envproduction标签方便不同环境的指标隔离而pushgateway上报的分组信息也能完整保留。4.2 用push_time_seconds过滤过期指标前面反复提到Pushgateway的指标不会自动过期这问题在告警场景下尤其致命。举个例子备份脚本每月月底执行一次如果6月脚本因为依赖库升级失败但5月的指标还留在Pushgateway里你看到的还是“备份成功”的旧状态告警根本不会触发。Pushgateway自动维护了一个时间序列叫做push_time_seconds{jobxxx, instanceyyy}表示该分组最后一次成功推送的时间Unix时间戳。我们可以利用它来识别“这个任务是否还活着”。在PromQL里可以用这个表达式过滤掉超过一定时间没有更新的指标backup_last_success{jobdb_backup} and on(job, instance) (time() - push_time_seconds{jobdb_backup}) 300它的逻辑是先筛选出这个job的指标然后取push_time_seconds判断该分组是否在5分钟内更新过不满足就剔除。用这个方式脚本挂了之后旧的指标很快就从查询结果中消失不会造成误报。告警规则里可以把这个表达式包起来- alert: DatabaseBackupStale expr: (time() - push_time_seconds{jobdb_backup}) 3600 labels: severity: critical annotations: summary: 数据库备份已超过1小时未上报这里不看具体指标值只靠心跳时间来判断业务新鲜度这是处理Pushgateway数据时很有价值的一个思路。很多人只关注推上来的业务指标却忘了push_time_seconds这个内置指标它在排查任务存活问题时非常好用。4.3 与Alertmanager联动的完整姿势Pushgateway本身不直接产生告警它只是指标的搬运工。告警还是要靠Prometheus的rule去触发然后交给Alertmanager分发。联动结构是任务脚本 - Pushgateway - Prometheus (抓取规则评估) - Alertmanager - 通知一个我在生产环境验证过的完整示例假设业务场景是每日数据同步任务。任务脚本里做三件事记录任务开始时间。执行同步逻辑成功后把synchronize_success{resultsuccess}设为1失败设为0。把push_time_seconds自然更新每次推送都会更新这个时间戳。Prometheus侧配置两条告警规则一条判断同步结果是否为0一条判断是否有心跳。两条规则各自覆盖不同的失败模式第一条捕获业务层面的失败第二条捕获脚本本身挂掉或网络不通的情况。这两者配合覆盖基本不会漏告警。4.4 接入Grafana展示时的常用查询最后简单提一下Grafana侧怎么展示Pushgateway的数据。由于Pushgateway里不同job是独立的Grafana中的查询表达式一般带job或instance的过滤条件例如max_over_time(synchronize_success{jobsync_job}[24h])用max_over_time取一天内的最大值能看出当天任务是否成功过。如果想看任务最近一次上报距今多少秒可以查time() - push_time_seconds{job~$job}配合Grafana模板变量可以做一个拉选框按任务名查看心跳。这类图我建议加上阈值线超过300秒标红一眼能看出谁失联了。5. 生产环境折腾中踩过的坑5.1 指标覆盖与标签爆炸Pushgateway的数据模型是靠标签区分的但它不是时序数据库不存储历史数据同一个指标名标签组合只保留最新值。我前面推荐用/metrics/job/xxx/instance/yyy来给不同机器分组但这还不够。如果同一个instance上同一个指标名被多个业务逻辑复用比如task_count既表示数据行数又表示文件个数它们会互相覆盖。解决办法是保证指标名和标签的数学唯一性。比如统一命名规范模块_对象_指标类型像sync_rows_total、sync_files_total这样即使job相同、instance相同也不冲突。还有一类问题是指标名一样但标签值基数超大最典型的是把task_id作为标签每次运行一个任务就多一条时序几天下来几万条序列全堆在Pushgateway内存里Prometheus抓一次也够呛。Pushgateway虽然后台有label cardinality限制默认是8个标签以内但架不住任务量多。我建议对高基数的标签保持克制或者干脆值不用标签记录而是推送给日志系统Pushgateway只保留汇总状态。5.2 Pushgateway单点与性能瓶颈不少团队的Pushgateway就一个实例挂在某个节点上。批处理任务集中在这个时段上报Pushgateway会瞬时扛大量写入。放在内网环境还好但如果机器规格太小比如1核1G可能出现内存飙升、容器OOM的情况。这个我实测很稳的底线是Pushgateway单机扛几千个分组没问题但前提是别让单个job下面挂上百万标签组合。Pushgateway官方有自己的高可用思路多个Pushgateway副本共享同一份--persistence.file比如挂同一个NFS或Ceph文件系统数据先落地共享存储这样任何一个副本宕掉另一个副本从文件恢复数据。这个方案我没有大规模验证过就不展开推荐了。更常见的做法是接受Pushgateway的小概率故障让任务脚本推送失败时能重试同时在Prometheus侧设置合理的抓取超时和重试整体设计上不要把Pushgateway当成强一致性的核心存储。5.3 权限与暴露风险Pushgateway的HTTP接口默认没有任何认证。你一旦把它绑到公网IP的0.0.0.0上任何人都能往你这里推垃圾指标甚至可以推一个jobalertmanager之类的伪造数据干扰告警判断。更严重的是如果开了--web.enable-admin-api任何人都可以直接执行wipe清空所有指标。生产部署我的建议是Pushgateway只监听内网IP不要监听0.0.0.0。比如直接--web.listen-address10.0.1.100:9091。如非必要不开--web.enable-admin-api。如果有跨网络需求在前面挡一层nginx做basic authauth_basic Prometheus Pushgateway; auth_basic_user_file /etc/nginx/.htpasswd;网络策略上只允许任务执行机和Prometheus服务器访问9091端口。5.4 关于Prometheus版本兼容性最近Prometheus 2.53.0已经发了很多人在升级Prometheus后发现Pushgateway抓取的数据显示异常排查半天发现是Prometheus对下来文本格式的解析更严格了特别是一些旧版push_client推上来的指标带了spec类型不合法或者重复标签。遇到这类问题优先看Prometheus的日志抓取目标里会有out of order或者duplicate sample for timestamp之类的报错然后去升级对应的客户端推送库让数据的文本格式符合新规范。Prometheus从2.x早期到现在抓取协议和文本格式整体是向后兼容的但处理异常数据的告警提示越来越严格。Pushgateway本身版本也会迭代所以我的建议是Pushgateway和Prometheus都不必追最新但不要落后超过一个大版本避免出现协议层面的兼容问题。6. 一个完整的落地案例从零到告警通知6.1 业务场景以一个比较典型的场景收尾假设一个电商平台每天凌晨要做一次数据报表生成报表数据生成好后写入数仓。我们要监控四件事任务是否执行、是否成功、耗时多少、生成的数据量是否异常。任务跑在独立的调度机上调度机无法被Prometheus直接访问。6.2 实施步骤调度机的脚本里加一段Python逻辑from prometheus_client import CollectorRegistry, Gauge, Counter, pushadd_to_gateway registry CollectorRegistry() report_success Gauge(report_generate_success, 报表生成是否成功, [task_name], registryregistry) report_duration Gauge(report_generate_duration_seconds, 报表生成耗时, [task_name], registryregistry) report_rows Gauge(report_generate_rows, 报表数据行数, [task_name], registryregistry) result generate_report() # 业务函数 report_success.labels(task_namedaily_report).set(1 if result.ok else 0) report_duration.labels(task_namedaily_report).set(result.duration) report_rows.labels(task_namedaily_report).set(result.rows) pushadd_to_gateway(pushgateway.internal:9091, jobreport_generate, registryregistry)Pushgateway内部路径会生成/metrics/job/report_generate每次任务跑完指标都更新到这个分组下。Prometheus配置加一个最精简的抓取目标scrape_configs: - job_name: pushgateway honor_labels: true static_configs: - targets: [pushgateway.internal:9091]告警规则里定义一条关于“报表任务失败”的规则再定义一条关于“报表心跳丢失”的规则。前者通过report_generate_success 0触发后者通过push_time_seconds判断超过指定阈值触发。这样业务失败、脚本挂掉、Pushgateway本身宕机三类问题分别由不同规则覆盖异常时Alertmanager会按配置发送到钉钉或邮件。6.3 复盘这套方案的几个心得这个方案上线这两年多我最直观的感受是Pushgateway适合“最终状态型”的监控指标不太适合“过程型”的指标。报表任务这种跑完就结束、只看最终结果的场景数据推给PushgatewayPrometheus拉过去规则一匹配告警链路非常清晰。中间踩过坑也有不少比如最开始用push_to_gateway导致同一个job下其他任务的指标被整体替换掉后来改成pushadd_to_gateway才算安稳再比如第一次把指标推上来后在Grafana查不到排查半天是honor_labels默认false把job标签覆盖了。这些坑在官方文档里其实都有提及但不会有哪篇文章把你实际会遇到的前因后果串起来讲。这也是我写这篇文章的原因希望后面接手Pushgateway的运维兄弟能绕过这些弯路少花点时间填坑多花点时间解决真正的业务问题。