第 18 章 · Prometheus、OpenTelemetry 与 APM
本章目标:使用 django-prometheus 或 prometheus_client 在 shop-demo 暴露 /metrics(请求计数、延迟直方图);理解 RED 指标与 Grafana 告警概念;入门 OpenTelemetry Django instrumentation,让 trace_id 贯穿 ORM、Redis 与 Celery;将结构化日志与 metrics 通过 request_id 关联(衔接 ch09);了解 APM(Datadog / New Relic 类工具)概念;建立生产可观测性 checklist。
学时建议:5~6 小时(含 2 小时 Prometheus + Grafana 跟练)
前置:本模块 ch09 请求 ID 与结构化日志;ch10 Redis 与 Celery;ch11 Gunicorn;ch17 ORM 慢查询与连接池。监控概念可对照 ops-deploy 与 xiaozi-cloud 告警章节。
18.1 可观测性三大支柱
Metrics Logs Traces
Prometheus/Grafana JSON → ELK/Loki OpenTelemetry → Jaeger
「系统是否异常?」 「发生了什么?」 「慢在哪里?」
| 支柱 | shop-demo 示例 |
|---|---|
| Metrics | /metrics 暴露 django_http_requests_total_by_view_transport_method |
| Logs | request_id=abc123 关联访问与审计日志 |
| Traces | trace_id 串联 ORM 查询、Redis 缓存与 Celery 任务 |
统一使用虚构 shop-demo、api.example.com;严禁写入真实监控地址、API Key 或租户 ID。
RED 方法(请求型服务):Rate 每秒请求、Errors 错误率、Duration 延迟分布。Django 后台与 DRF API 均适用。
18.2 django-prometheus 集成
方案 A:django-prometheus(推荐,开箱即用)
pip install django-prometheus
shopdemo/settings.py:
INSTALLED_APPS = [
"django_prometheus",
# ... 其他应用
]
MIDDLEWARE = [
"django_prometheus.middleware.PrometheusBeforeMiddleware",
"django.middleware.security.SecurityMiddleware",
# ... 原有中间件(含 ch09 RequestLogMiddleware)
"django_prometheus.middleware.PrometheusAfterMiddleware",
]
# 数据库、缓存也可被采集
DATABASES = {
"default": {
"ENGINE": "django_prometheus.db.backends.postgresql",
# ...
}
}
CACHES = {
"default": {
"BACKEND": "django_prometheus.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/0",
}
}
shopdemo/urls.py:
urlpatterns = [
path("", include("django_prometheus.urls")), # 暴露 /metrics
# ... 业务路由
]
默认指标含 HTTP 请求计数、延迟直方图、数据库连接、缓存命中等。
方案 B:prometheus_client(自定义指标更灵活):在 core/middleware.py 用 Counter/Histogram 记录 method、view、status,通过 generate_latest() 暴露 /metrics(仅内网)。适合需要精细控制 label 或不想改 DB ENGINE 的场景。
| 方案 | 适用 |
|---|---|
| django-prometheus | 快速接入、DB/Cache 指标齐全 |
| prometheus_client | 自定义业务 Counter、细粒度控制 |
自定义业务 Counter:
from prometheus_client import Counter
order_created_total = Counter(
"shopdemo_order_created_total", "订单创建次数", ["source"]
)
# 视图中:order_created_total.labels(source="api").inc()
保护 /metrics(生产必做):
# Nginx 限制内网 IP,或 Bearer Token 校验 METRICS_TOKEN
# urls.py 中不要将 /metrics 暴露给公网
18.3 本地 Prometheus + Grafana 跟练
shop-demo/deploy/prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: shop-demo
metrics_path: /metrics
static_configs:
- targets: [host.docker.internal:8000]
labels: {env: staging, service: shop-demo}
shop-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(django-prometheus 指标名):
| Panel | 表达式 |
|---|---|
| QPS | rate(django_http_requests_total_by_view_transport_method[1m]) |
| P95 | histogram_quantile(0.95, rate(django_http_requests_latency_seconds_by_view_method_bucket[5m])) |
| 5xx 比 | sum(rate(django_http_responses_total_by_status{status=~"5.."}[5m])) / sum(rate(django_http_responses_total_by_status[5m])) |
| DB 连接 | django_db_new_connections_total |
若使用自定义 shopdemo_* 指标,将表达式中的前缀替换即可。
18.4 Grafana 告警规则(虚构示例)
shop-demo/deploy/alerts/shop-demo.yml:
groups:
- name: shop-demo-staging
rules:
- alert: ShopDemoHighErrorRate
expr: |
sum(rate(django_http_responses_total_by_status{status=~"5.."}[5m]))
/ sum(rate(django_http_responses_total_by_status[5m])) > 0.05
for: 5m
labels: {severity: critical, service: shop-demo}
annotations:
summary: "shop-demo 5xx 错误率超过 5%"
- alert: ShopDemoHighLatencyP99
expr: |
histogram_quantile(0.99,
sum(rate(django_http_requests_latency_seconds_by_view_method_bucket[5m])) by (le)
) > 2
for: 10m
labels: {severity: warning}
annotations:
summary: "P99 延迟超过 2 秒"
- alert: ShopDemoMetricsTargetDown
expr: up{job="shop-demo"} == 0
for: 2m
labels: {severity: critical}
| 字段 | 说明 |
|---|---|
expr | PromQL 触发条件 |
for | 持续时长,防抖动 |
annotations | Runbook 链接(用占位 URL) |
通知渠道用占位 Webhook,不写真实密钥。
18.5 OpenTelemetry Django instrumentation
OpenTelemetry(OTel) 统一 Traces/Metrics/Logs 的 CNCF 标准,展示单次请求完整路径:
trace_id: 7a3f9c2e...
├─ span: GET /api/v1/products/ [120ms]
│ ├─ span: django.db SELECT [85ms]
│ └─ span: redis GET cache:key [3ms]
└─ span: celery send_task [2ms] (异步任务投递)
pip install opentelemetry-api opentelemetry-sdk \
opentelemetry-instrumentation-django \
opentelemetry-instrumentation-psycopg2 \
opentelemetry-instrumentation-redis \
opentelemetry-instrumentation-celery \
opentelemetry-exporter-otlp
shopdemo/telemetry.py:
import os