Prometheus Client多进程模式下make_asgi_app返回空字典问题
GitLab Exporter切换FastAPI+Gunicorn后/metrics返回空的问题解决
问题描述
我用prometheus_client开发了一个GitLab自定义Exporter,通过多进程池实现并行数据收集。之前用start_http_server模块时一切正常,但切换到FastAPI+Gunicorn架构后,/metrics接口返回空字典“{}”。目前服务能正常处理请求,数据已完成收集,PROMETHEUS_MULTIPROC_DIR目录也已被正常填充,但指标无法正常返回。
相关代码
当前FastAPI+Gunicorn版本代码
from fastapi import FastAPI from prometheus_client import make_asgi_app from gitlab_exporter_func import * from gitlab_exporter_var import * import time, os, shutil import uvicorn app = FastAPI(debug=False) @app.get("/metrics") def get_metrics_app(): start_time = time.time() if os.path.exists(prome_stats): shutil.rmtree(prome_stats) os.mkdir(prome_stats) projects_ids=get_projects() get_pipejobs_data(projects_ids) end_time = time.time() execution_time = end_time - start_time print("Execution time:",execution_time) return make_asgi_app(registry=registry) @app.get("/") async def root(): return "Go to /metrics for gitlab metrics" if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=8000)
注册表初始化代码(gitlab_exporter_var.py)
registry = CollectorRegistry() multiprocess.MultiProcessCollector(registry)
之前正常工作的start_http_server版本参考代码
import prometheus_client from prometheus_client import start_http_server from gitlab_exporter_func import * from gitlab_exporter_var import * import time import os import shutil prometheus_client.REGISTRY.unregister(prometheus_client.GC_COLLECTOR) prometheus_client.REGISTRY.unregister(prometheus_client.PLATFORM_COLLECTOR) prometheus_client.REGISTRY.unregister(prometheus_client.PROCESS_COLLECTOR) if __name__ == '__main__': # Start up the server to expose the metrics. start_http_server(8000, registry=registry) while(True): start_time = time.time() if os.path.exists(prome_stats): shutil.rmtree(prome_stats) os.mkdir(prome_stats) projects_ids=get_projects() get_pipejobs_data(projects_ids) end_time = time.time() execution_time = end_time - start_time print("Execution time:",execution_time) time.sleep(max(0, 60-execution_time))
问题分析
- ASGI应用挂载错误:FastAPI路由函数直接返回
make_asgi_app的结果是错误的,make_asgi_app生成的是独立ASGI应用,需要通过挂载方式集成,而非作为路由返回值。 - 请求触发采集的风险:每次请求
/metrics才执行采集逻辑,在Gunicorn多进程模式下会导致进程竞争清理目录,同时重复采集浪费资源。 - 注册表初始化时机:多进程环境下,每个Worker进程都会初始化注册表,若目录清理时机不当,会导致指标文件读取不完整。
解决方案
方案1:正确挂载Prometheus ASGI应用+定时采集
调整代码结构,将Prometheus应用挂载到FastAPI中,同时用定时任务执行指标采集,避免请求触发采集:
from fastapi import FastAPI from prometheus_client import make_asgi_app from gitlab_exporter_func import * from gitlab_exporter_var import * import time, os, shutil import uvicorn from apscheduler.schedulers.asyncio import AsyncIOScheduler app = FastAPI(debug=False) # 挂载Prometheus ASGI应用到/metrics路径 metrics_app = make_asgi_app(registry=registry) app.mount("/metrics", metrics_app) def collect_metrics(): start_time = time.time() # 加锁避免多进程同时清理目录(可选,根据实际情况调整) if os.path.exists(prome_stats): shutil.rmtree(prome_stats) os.mkdir(prome_stats) projects_ids=get_projects() get_pipejobs_data(projects_ids) end_time = time.time() execution_time = end_time - start_time print("Execution time:",execution_time) # 启动时执行一次采集,之后每分钟执行一次 @app.on_event("startup") async def startup_event(): collect_metrics() scheduler = AsyncIOScheduler(timezone="UTC") scheduler.add_job(collect_metrics, 'interval', minutes=1) scheduler.start() @app.get("/") async def root(): return "Go to /metrics for gitlab metrics" if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=8000)
关键注意事项
- 禁止路由返回ASGI应用:必须通过
app.mount挂载Prometheus的ASGI应用,确保指标能被正确序列化返回。 - 定时采集替代请求触发:多进程环境下,请求触发采集易引发资源竞争,定时采集更稳定。
- 目录清理加锁:若多个Worker进程可能同时执行清理,需添加文件锁避免目录操作冲突。
- 环境变量一致性:确保
PROMETHEUS_MULTIPROC_DIR在所有Gunicorn Worker进程中都能正确读取,目录权限为当前用户可读写。
内容的提问来源于stack exchange,提问作者T.Zampetis
相关产品推荐
相关产品推荐

