markdown
# 实战排坑:Flask 应用接入 Prometheus 监控,从 404 到 UP 的全过程
> 最近给自己的校园失物招领平台接入了 Prometheus 监控,本想优雅地暴露 `/metrics` 端点,结果却踩了一连串的坑。
> 从依赖缺失、pip 超时、代码缩进混乱,到数据库连接失败、容器网络不通......
> 本文详细记录了我排查和解决每一个问题的完整思路,希望能帮你避开同样的弯路。
## 一、背景与架构
- **应用**:基于 Flask 的校园失物招领平台,使用 Docker Compose 部署(Flask + MySQL)。
- **监控目标**:
- **Prometheus** 抓取 Flask 应用业务指标(请求数、响应时间等)
- **node_exporter** 采集服务器 CPU、内存、磁盘等硬件指标
- **Grafana** 可视化展示
- **预期结果**:访问 `http://<服务器IP>:8000/metrics` 返回 Prometheus 格式的指标,Prometheus Targets 中三个 job 均为 **UP**。
现实很骨感:`/metrics` 始终返回 **404**,Prometheus 中 `campus_app` 也是 **DOWN**。
## 二、整体排错思路
1. **先确保应用自身能正常暴露 `/metrics`**(在宿主机 `curl localhost:8000/metrics` 能返回数据)。
2. **再解决 Prometheus 与各 exporter 之间的网络连通问题**(容器网络模式、DNS 解析)。
3. **最后验证 Grafana 可视化**。
下面按排查顺序逐一记录。
## 三、第一轮:Flask 应用的 `/metrics` 一直 404
### 3.1 网站本身能正常访问吗?
在浏览器打开 `www.anxun.xyz`,页面正常,说明应用容器在运行,且端口映射正确。
### 3.2 第一次排错:检查容器日志
```bash
docker logs campus_crowdfunding-campus_app-1
关键错误:
NameError: name 'PrometheusMetrics' is not defined
结论 :代码中使用了 PrometheusMetrics 但没有导入,或者依赖未安装。
验证依赖:
bash
docker exec campus_crowdfunding-campus_app-1 pip list | grep prometheus
# 无输出
说明 prometheus-flask-exporter 未安装。检查 requirements.txt,果然没有这一行。
尝试:添加依赖并重新构建
在 requirements.txt 中添加:
prometheus-flask-exporter
重新构建:
bash
docker compose build --no-cache campus_app
结果 :构建失败,pip 下载超时(Read timed out),因为访问 PyPI 官方源不稳定。
解决:配置国内镜像源
编辑 Dockerfile,在 COPY requirements.txt . 之后,RUN pip install 之前添加一行:
dockerfile
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
重新构建成功,依赖安装完成。
3.3 第二次排错:PrometheusMetrics 初始化顺序错误
构建成功后,容器启动仍报同样的 NameError。
再次查看日志,发现虽然已导入 from prometheus_flask_exporter import PrometheusMetrics,但 metrics = PrometheusMetrics(app) 这行代码放在了 app = Flask(__name__) 之前 ,导致 app 未定义。
为什么会出现这个错误?
因为我最初是在代码开头直接写 metrics = PrometheusMetrics(app),而 app 还没有创建。
正确位置 :在 app = Flask(__name__) 之后立即初始化。
修改后重新构建,NameError 消失,但 /metrics 依然 404。
3.4 第三次排错:手动添加 /metrics 路由
为什么怀疑自动注册失败?
PrometheusMetrics(app) 理论上会自动注册 /metrics 路由,但既然没有,我猜测可能被其他蓝图或错误处理器干扰,于是决定手动添加作为后备方案。
在 app.py 末尾(return app 之前)添加:
python
from prometheus_flask_exporter import generate_latest
@app.route('/metrics')
def metrics_endpoint():
return generate_latest()
结果:容器启动失败,日志显示:
cannot import name 'generate_latest' from 'prometheus_flask_exporter'
原因 :prometheus_flask_exporter 并没有直接提供 generate_latest 函数,它实际上依赖 prometheus_client。
解决 :改用 prometheus_client 的 generate_latest,并传入 metrics.registry(metrics 是 PrometheusMetrics 实例):
python
from prometheus_client import generate_latest
@app.route('/metrics')
def metrics_endpoint():
return generate_latest(metrics.registry)
为什么第一个不行,第二个可以?
prometheus_flask_exporter是一个封装库,它不直接导出generate_latest。prometheus_client是底层的指标库,generate_latest是它的原生函数,需要传入一个注册表(registry)来生成所有指标。PrometheusMetrics(app)内部会维护一个注册表,可以通过metrics.registry访问。
3.5 第四次排错:缩进混乱导致容器不断重启
修改代码后重新构建,容器不断重启,日志显示:
IndentationError: expected an indented block
TabError: inconsistent use of tabs and spaces
原因 :手动添加代码时混用了 Tab 和空格,并且 return app 被意外放在了 except 块内部。
解决:
- 统一使用 4 个空格 缩进。
- 确保
return app在create_app函数末尾且无条件执行(不要缩进到if或except里)。
整理后,应用启动成功,但 /metrics 仍然返回 404。
3.6 第五次排错:根本原因 -- 数据库连接失败
再次查看容器日志,发现大量:
pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on 'localhost'")
应用在 db.create_all() 时崩溃,导致 /metrics 路由根本未被注册。
排查过程:
-
检查容器内环境变量:
bashdocker exec campus_crowdfunding-campus_app-1 env | grep DATABASE_URL输出:
DEV_DATABASE_URL=mysql+pymysql://root:root@localhost/campus_crowdfunding DATABASE_URL=mysql+pymysql://root:root@mysql/campus_crowdfunding -
查看
config.py:DevelopmentConfig使用DEV_DATABASE_URL(默认 localhost)ProductionConfig使用DATABASE_URL(正确指向 MySQL 服务名mysql)
-
当前应用启动时
APP_CONFIG环境变量未设置,默认使用development配置,因此试图连接localhost的 MySQL(容器内没有 MySQL 服务,导致拒绝连接)。
解决方案:
在 docker-compose.yml 中为 campus_app 添加环境变量:
yaml
environment:
- APP_CONFIG=production
- DATABASE_URL=mysql+pymysql://root:root@mysql/campus_crowdfunding
重启容器后,数据库连接成功,/metrics 端点终于返回了 Prometheus 格式的指标!🎉
四、第二轮:Prometheus 抓取问题
4.1 子问题 1:node_exporter 指标抓取失败
现象 :Prometheus Targets 中 node_exporter 状态为 DOWN ,错误 connection refused。但宿主机 curl localhost:9100/metrics 正常。
排查过程:
node_exporter容器使用默认bridge网络,Prometheus 也使用bridge网络(有端口映射),两者不在同一网络,且均非host模式,导致 Prometheus 无法通过localhost:9100访问 node_exporter。
第一次试错 :将 node_exporter 改为 host 网络模式(删除容器并重新创建,加上 --network host)。
结果 :Prometheus 仍然无法抓取,因为 Prometheus 不在 host 网络,容器内的 localhost 指向自身。
第二次试错 :统一所有监控容器为 host 网络模式。
修改 /opt/monitoring/docker-compose.yml,为 prometheus、node_exporter、grafana 都添加 network_mode: host,并删除 ports 映射。
重新创建容器后,node_exporter 变为 UP。
4.2 子问题 2:Prometheus 无法抓取 Flask 应用的 /metrics
现象 :campus_app 状态为 DOWN ,错误 lookup campus_crowdfunding-campus_app-1 on ...: no such host。
原因 :Flask 容器在自定义网络 campus_crowdfunding_default 中,而 Prometheus 现在使用 host 网络,无法通过容器名 DNS 解析。
尝试修复:将 Prometheus 加入 Flask 的网络:
bash
docker network connect campus_crowdfunding_default prometheus
报错:
container sharing network namespace with another container or host cannot be connected to any other network
host 网络的容器不能加入其他网络。
最终解决方案 :利用 Flask 容器的端口映射(0.0.0.0:8000->8000/tcp),让 Prometheus 通过 localhost:8000 抓取。
修改 /opt/monitoring/prometheus.yml:
yaml
- job_name: 'campus_app'
static_configs:
- targets: ['localhost:8000']
重载配置:
bash
docker exec prometheus kill -HUP 1
campus_app 状态变为 UP,抓取成功。
五、额外问题:Grafana 密码忘记
现象 :访问 Grafana 登录页,默认 admin/admin 无法登录。
解决 :使用 grafana-cli 重置密码:
bash
docker exec -it grafana grafana cli admin reset-admin-password <新密码>
docker restart grafana
六、最终状态
| 组件 | 状态 | 访问方式 |
|---|---|---|
| Flask 应用 | 正常 | http://localhost:8000/metrics |
| Prometheus | 正常 | http://公网IP:9090/targets |
| node_exporter | 正常 | 通过 Prometheus 抓取 |
| Grafana | 正常 | http://公网IP:3000 |
所有 Targets 均为 UP,监控大盘完美展示。
七、踩坑总结与避坑指南
- 依赖安装:务必使用国内镜像源,避免 pip 超时。
- 代码缩进:Python 项目统一使用 4 个空格,不要混用 Tab。
- 环境变量 :区分开发/生产配置,容器化部署时显式设置
APP_CONFIG=production。 - 网络模式 :监控组件尽量统一使用
host网络模式,或全部放在同一自定义网络中。 - 手动路由后备 :当自动注册失败时,可以手动暴露
/metrics,注意使用正确的库函数。 - 日志先行 :90% 的问题都能从
docker logs中找到线索。 - 逐步验证 :先保证应用自身
curl通/metrics,再解决 Prometheus 抓取问题。
八、附:完整配置示例
Flask 应用的 app.py 关键片段
python
from prometheus_flask_exporter import PrometheusMetrics
from prometheus_client import generate_latest
def create_app():
app = Flask(__name__)
metrics = PrometheusMetrics(app)
# ... 其他初始化 ...
# 手动添加 /metrics 路由作为保险
@app.route('/metrics')
def metrics_endpoint():
return generate_latest(metrics.registry)
return app
Prometheus 配置文件 prometheus.yml
yaml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node_exporter'
static_configs:
- targets: ['localhost:9100']
- job_name: 'campus_app'
static_configs:
- targets: ['localhost:8000']
Docker Compose 监控服务(host 网络模式)
yaml
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
network_mode: host
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
node_exporter:
image: prom/node-exporter:latest
container_name: node_exporter
network_mode: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
grafana:
image: grafana/grafana:latest
container_name: grafana
network_mode: host
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
volumes:
prometheus_data:
grafana_data:
希望这篇排坑记录能帮你节省时间,早日打通 Flask + Prometheus 监控链路。如果遇到其他问题,欢迎留言交流!