第 18 章 · Prometheus、链路追踪与 APM
本章目标:使用 prometheus_flask_exporter 在 api-demo 暴露 /metrics(请求计数、延迟直方图);理解 Grafana 仪表盘与告警规则(虚构示例);入门 OpenTelemetry,让 trace_id 贯穿 Flask → SQLAlchemy → Redis;将结构化日志与 metrics 通过 request_id 关联(衔接 ch09);了解 APM(Datadog / New Relic 类工具)概念;建立生产可观测性 checklist。
学时建议:5~6 小时(含 2 小时 Prometheus + Grafana 跟练)
前置:本模块 ch09 请求 ID 与结构化日志;ch10 Redis;ch11 Gunicorn;ch17 ORM 慢查询。监控概念可对照 ops-deploy 与 xiaozi-cloud 告警章节。
18.1 可观测性三大支柱
Metrics Logs Traces
Prometheus/Grafana JSON → ELK/Loki OpenTelemetry → Jaeger
「系统是否异常?」 「发生了什么?」 「慢在哪里?」
| 支柱 | api-demo 示例 |
|---|---|
| Metrics | /metrics 暴露 flask_http_request_duration_seconds |
| Logs | request_id=abc123 关联访问与审计日志 |
| Traces | trace_id 串联 ORM 查询与 Redis 调用 |
统一使用虚构 api-demo、api.example.com;严禁写入真实监控地址、API Key 或租户 ID。
RED 方法(请求型服务):Rate 每秒请求、Errors 错误率、Duration 延迟分布。
18.2 prometheus_flask_exporter 集成
pip install prometheus-flask-exporter
api_demo/extensions.py:
from prometheus_flask_exporter import PrometheusMetrics
metrics = PrometheusMetrics.for_app_factory(group_by="endpoint")
def init_extensions(app):
metrics.init_app(app)
# db, cache, login_manager ...
默认暴露 GET /metrics,自动采集 flask_http_request_total、flask_http_request_duration_seconds 等。
自定义业务 Counter:
from prometheus_client import Counter
product_created_total = Counter(
"api_demo_product_created_total", "商品创建次数", ["source"]
)
# 视图中:product_created_total.labels(source="api").inc()
保护 /metrics(生产必做):
# Nginx 限制内网 IP,或 Bearer Token 校验 METRICS_TOKEN
18.3 本地 Prometheus + Grafana 跟练
api-demo/deploy/prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: api-demo
metrics_path: /metrics
static_configs:
- targets: [host.docker.internal:5000]
labels: {env: staging, service: api-demo}
api-demo/deploy/docker-compose.monitoring.yml:
services:
prometheus:
image: prom/prometheus:v2.51.0
volumes: [./prometheus.yml:/etc/prometheus/prometheus.yml:ro]
ports: ["9090:9090"]
grafana:
image: grafana/grafana:10.4.0
ports: ["3000:3000"]
environment:
GF_SECURITY_ADMIN_PASSWORD: changeme-local-only
常用 PromQL:
| Panel | 表达式 |
|---|---|
| QPS | rate(flask_http_request_total[1m]) |
| P95 | histogram_quantile(0.95, rate(flask_http_request_duration_seconds_bucket[5m])) |
| 5xx 比 | sum(rate(...{status=~"5.."}[5m])) / sum(rate(...[5m])) |
18.4 Grafana 告警规则(虚构示例)
api-demo/deploy/alerts/api-demo.yml:
groups:
- name: api-demo-staging
rules:
- alert: ApiDemoHighErrorRate
expr: |
sum(rate(flask_http_request_total{status=~"5.."}[5m]))
/ sum(rate(flask_http_request_total[5m])) > 0.05
for: 5m
labels: {severity: critical, service: api-demo}
annotations:
summary: "api-demo 5xx 错误率超过 5%"
- alert: ApiDemoHighLatencyP99
expr: |
histogram_quantile(0.99,
sum(rate(flask_http_request_duration_seconds_bucket[5m])) by (le)
) > 2
for: 10m
labels: {severity: warning}
annotations:
summary: "P99 延迟超过 2 秒"
- alert: ApiDemoMetricsTargetDown
expr: up{job="api-demo"} == 0
for: 2m
labels: {severity: critical}
| 字段 | 说明 |
|---|---|
expr | PromQL 触发条件 |
for | 持续时长,防抖动 |
annotations | Runbook 链接(用占位 URL) |
通知渠道用占位 Webhook,不写真实密钥。
18.5 OpenTelemetry 简介
OpenTelemetry(OTel) 统一 Traces/Metrics/Logs 的 CNCF 标准,展示单次请求完整路径:
trace_id: 7a3f9c2e...
├─ span: GET /api/v1/products [120ms]
│ ├─ span: sqlalchemy SELECT [85ms]
│ └─ span: redis GET cache:key [3ms]
pip install opentelemetry-api opentelemetry-sdk \
opentelemetry-instrumentation-flask \
opentelemetry-instrumentation-sqlalchemy \
opentelemetry-instrumentation-redis \
opentelemetry-exporter-otlp
api_demo/telemetry.py:
import os
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
from opentelemetry.instrumentation.redis import RedisInstrumentor
def setup_telemetry(app, db_engine=None):