> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Prometheus 与 Grafana 监控
- URL: https://blog.vercanti.com/prometheus-yu-grafana-jian-kong/
- Published: 2026-08-28T14:34:26.000Z
- Updated: 2026-08-28T14:56:32.000Z
- Description: Prometheus 的数据模型由四个部分组成： 完整表示形式： Prometheus 采用主动拉取（pull）模型，区别于大多数监控系统的推送模型： 1. 被监控目标暴露 HTTP /metrics 端点，返回 Prometheus 文本格式数据 2. Prometheus Server 按照配置的 scrape_interval 定期访问该端点 3. 抓取到的数据存入本地 TSDB（时序数据库） 4. 通过 evaluation_interval 周期性执行告警规则 优势：Prometheus 侧控制采集频率，目标无需知道 Prometheus 存在
- Author: yellowdog
- Tags: DevOps

> 官方文档：<https://prometheus.io/docs/> | <https://grafana.com/docs/grafana/latest/>  
> 适用版本：Prometheus 2.x / Grafana 10.x（2026-05-07 核实）

## 核心概念

### 数据模型

Prometheus 的数据模型由四个部分组成：

- **Metric name**：指标名称，描述被测量的事物（如 `http_requests_total`）
- **Labels**：键值对标签，用于区分同一指标的不同维度（如 `{method="GET", status="200"}`）
- **Timestamp**：时间戳，毫秒精度的 Unix 时间戳
- **Value**：浮点数值

完整表示形式：

```
http_requests_total{method="GET", handler="/api/users", status="200"} 1234 1712800000000

```

### Metric 类型

| 类型        | 描述             | 适用场景             | 特点                 |
| --------- | -------------- | ---------------- | ------------------ |
| Counter   | 单调递增计数器，只增不减   | 请求总数、错误总数、已处理字节数 | 重启后归零，配合 rate() 使用 |
| Gauge     | 可任意增减的当前值      | 内存用量、连接数、队列长度、温度 | 反映当前状态             |
| Histogram | 将观测值分桶统计，带累积计数 | 请求延迟分布、响应体大小分布   | 服务端计算分位数，精度固定      |
| Summary   | 客户端计算分位数       | 需要精确分位数但样本量小的场景  | 客户端计算，无法跨实例聚合      |

### Scrape 原理（Pull 模型）

Prometheus 采用主动拉取（pull）模型，区别于大多数监控系统的推送模型：

1. 被监控目标暴露 HTTP `/metrics` 端点，返回 Prometheus 文本格式数据
2. Prometheus Server 按照配置的 `scrape_interval` 定期访问该端点
3. 抓取到的数据存入本地 TSDB（时序数据库）
4. 通过 `evaluation_interval` 周期性执行告警规则

优势：Prometheus 侧控制采集频率，目标无需知道 Prometheus 存在，便于服务发现和状态追踪。

---

## prometheus.yml 配置

### global 配置

```yaml
global:
  # 默认抓取间隔，可被 scrape_configs 中的配置覆盖
  scrape_interval: 15s
  # 告警规则评估间隔
  evaluation_interval: 15s
  # 抓取超时时间，必须小于 scrape_interval
  scrape_timeout: 10s
  # 附加到所有时序数据的外部标签，用于联邦或远程写入时区分来源
  external_labels:
    env: production
    region: cn-beijing

```

| 配置项                  | 类型       | 默认值 | 说明                           |
| -------------------- | -------- | --- | ---------------------------- |
| scrape\_interval     | duration | 1m  | 全局抓取间隔                       |
| evaluation\_interval | duration | 1m  | 规则评估间隔                       |
| scrape\_timeout      | duration | 10s | 单次抓取超时，不能超过 scrape\_interval |
| external\_labels     | map      | \-  | 附加到所有数据的外部标签                 |

### scrape\_configs 结构

```yaml
scrape_configs:
  - job_name: "fastapi-app"
    # 覆盖全局 scrape_interval
    scrape_interval: 10s
    metrics_path: /metrics
    scheme: http

    static_configs:
      - targets:
          - "192.168.1.10:8000"
          - "192.168.1.11:8000"
        labels:
          app: fastapi
          env: production

    # 重打标签：在抓取前修改目标的标签
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: "blackbox-exporter:9115"

  - job_name: "node-exporter"
    static_configs:
      - targets:
          - "192.168.1.10:9100"
          - "192.168.1.11:9100"

```

`scrape_configs` 核心字段：

| 字段                      | 说明                       |
| ----------------------- | ------------------------ |
| job\_name               | 任务名称，自动添加为 job 标签        |
| static\_configs         | 静态目标列表                   |
| file\_sd\_configs       | 基于文件的服务发现                |
| kubernetes\_sd\_configs | Kubernetes 服务发现          |
| relabel\_configs        | 抓取前对目标标签进行转换，可过滤/修改/删除标签 |
| metrics\_path           | 指标路径，默认 /metrics         |
| scrape\_interval        | 覆盖全局间隔                   |

### alerting 配置

```yaml
alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - "alertmanager:9093"
      # 超时设置
      timeout: 10s
      # API 版本
      api_version: v2

```

### rule\_files 告警规则

```yaml
rule_files:
  - "rules/alert_rules.yml"
  - "rules/recording_rules.yml"
  # 支持通配符
  - "rules/*.yml"

```

---

## PromQL 查询语言

### 即时查询 vs 范围查询

| 类型   | 语法                                | 返回值  | 用途        |
| ---- | --------------------------------- | ---- | --------- |
| 即时查询 | metric\_name{label="value"}       | 即时向量 | 当前值、仪表盘数值 |
| 范围查询 | metric\_name{label="value"}\[5m\] | 范围向量 | 配合函数计算速率  |

范围向量不能直接在 Grafana 中绘图，必须用函数（如 `rate()`）将其转换为即时向量。

### 选择器语法

```promql
# 精确匹配
http_requests_total{method="GET", status="200"}

# 正则匹配
http_requests_total{method=~"GET|POST"}

# 排除匹配
http_requests_total{status!="500"}

# 排除正则
http_requests_total{method!~"OPTIONS|HEAD"}

# 范围向量（过去 5 分钟的样本）
http_requests_total{method="GET"}[5m]

# 偏移量（1 小时前的值）
http_requests_total offset 1h

```

### 常用函数

#### rate — 平均速率

```promql
# 过去 5 分钟 HTTP 请求的平均每秒速率（适合 Counter）
rate(http_requests_total[5m])

```

#### irate — 瞬时速率

```promql
# 基于最后两个样本计算瞬时速率，对突刺更敏感
irate(http_requests_total[5m])

```

#### increase — 增量

```promql
# 过去 1 小时内请求总增量
increase(http_requests_total[1h])

```

#### sum by — 聚合

```promql
# 按 job 标签聚合请求总速率
sum by(job) (rate(http_requests_total[5m]))

# 去掉某个标签，保留其余标签
sum without(instance) (rate(http_requests_total[5m]))

```

#### histogram\_quantile — 分位数

```promql
# 计算过去 5 分钟请求延迟的 P99
histogram_quantile(0.99,
  sum by(le) (rate(http_request_duration_seconds_bucket[5m]))
)

# P50 / P95
histogram_quantile(0.50, sum by(le) (rate(http_request_duration_seconds_bucket[5m])))
histogram_quantile(0.95, sum by(le) (rate(http_request_duration_seconds_bucket[5m])))

```

#### topk / bottomk

```promql
# 请求速率最高的 5 个实例
topk(5, rate(http_requests_total[5m]))

# 内存占用最低的 3 个节点
bottomk(3, node_memory_MemAvailable_bytes)

```

#### 其他常用函数

```promql
# 过去 5 分钟的最大值
max_over_time(process_resident_memory_bytes[5m])

# 预测 4 小时后磁盘是否耗尽（线性预测）
predict_linear(node_filesystem_avail_bytes[1h], 4 * 3600)

# 时序存在时间（秒）
time() - process_start_time_seconds

# 绝对值、向上取整、向下取整
abs(delta(cpu_temp_celsius[10m]))
ceil(rate(http_requests_total[5m]))
floor(rate(http_requests_total[5m]))

```

### 运算符

#### 算术运算符

```promql
# 将字节转换为 GB
node_memory_MemTotal_bytes / 1024 / 1024 / 1024

# 计算内存使用率（百分比）
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
  / node_memory_MemTotal_bytes * 100

```

#### 比较运算符

```promql
# 过滤出错误率超过 1% 的服务（返回匹配的时序）
rate(http_requests_total{status=~"5.."}[5m])
  / rate(http_requests_total[5m]) > 0.01

# bool 修饰符：返回 0/1 而非过滤
rate(http_requests_total[5m]) > bool 100

```

| 运算符 | 说明   |
| --- | ---- |
| \== | 等于   |
| !=  | 不等于  |
| \>  | 大于   |
| <   | 小于   |
| \>= | 大于等于 |
| <=  | 小于等于 |

#### 逻辑运算符

```promql
# and：两侧都有匹配才保留
vector_a and vector_b

# or：两侧任意一个有值即保留
vector_a or vector_b

# unless：保留左侧在右侧中没有匹配的部分
vector_a unless vector_b

```

#### 向量匹配

```promql
# 一对一匹配（标签完全相同）
method_code:http_errors:rate5m / ignoring(code) method:http_requests:rate5m

# 多对一匹配
sum by(app, env) (cpu_usage) / on(app) group_left(env) app_info

```

---

## Python 应用接入

### 安装

```bash
pip install prometheus-client

```

### Counter

```python
from prometheus_client import Counter

# 创建 Counter
REQUEST_COUNT = Counter(
    name="http_requests_total",           # 指标名
    documentation="Total HTTP requests",  # 描述
    labelnames=["method", "endpoint", "status_code"],  # 标签维度
)

# 使用
REQUEST_COUNT.labels(method="GET", endpoint="/api/users", status_code="200").inc()
REQUEST_COUNT.labels(method="POST", endpoint="/api/login", status_code="401").inc(1)

```

Counter 参数：

| 参数            | 类型          | 说明                       |
| ------------- | ----------- | ------------------------ |
| name          | str         | 指标名称，建议以 \_total 结尾      |
| documentation | str         | 指标描述，出现在 /metrics 页面     |
| labelnames    | list\[str\] | 标签名列表                    |
| namespace     | str         | 前缀，最终名称为 namespace\_name |
| subsystem     | str         | 子系统前缀                    |
| registry      | Registry    | 注册表，默认 REGISTRY          |

### Gauge

```python
from prometheus_client import Gauge

ACTIVE_CONNECTIONS = Gauge(
    "active_connections",
    "Current active database connections",
    ["db_host"],
)

# 设置绝对值
ACTIVE_CONNECTIONS.labels(db_host="db-01").set(42)

# 增减
ACTIVE_CONNECTIONS.labels(db_host="db-01").inc()
ACTIVE_CONNECTIONS.labels(db_host="db-01").dec(5)

# 追踪进行中的任务数
IN_PROGRESS = Gauge("in_progress_requests", "In-progress requests")

with IN_PROGRESS.track_inprogress():
    # 进入时 +1，退出时 -1
    process_request()

```

### Histogram

```python
from prometheus_client import Histogram

REQUEST_LATENCY = Histogram(
    "http_request_duration_seconds",
    "HTTP request duration in seconds",
    ["method", "endpoint"],
    # 自定义 bucket 边界（秒）
    buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0],
)

# 手动记录
REQUEST_LATENCY.labels(method="GET", endpoint="/api").observe(0.032)

# 使用计时器上下文管理器
with REQUEST_LATENCY.labels(method="GET", endpoint="/api").time():
    result = expensive_operation()

```

Histogram 参数：

| 参数      | 类型            | 默认值                          | 说明                        |
| ------- | ------------- | ---------------------------- | ------------------------- |
| buckets | list\[float\] | DEFAULT\_BUCKETS（0.005 到 10） | 分桶边界，Prometheus 自动添加 +Inf |

### Summary

```python
from prometheus_client import Summary

PROCESSING_TIME = Summary(
    "request_processing_seconds",
    "Time spent processing request",
)

@PROCESSING_TIME.time()
def process_request():
    pass

```

Summary 与 Histogram 对比：

| 对比项     | Histogram       | Summary       |
| ------- | --------------- | ------------- |
| 分位数计算位置 | 查询时服务端计算        | 采集时客户端计算      |
| 跨实例聚合   | 支持              | 不支持（分位数无法聚合）  |
| 分位数配置   | 灵活，查询时指定        | 需提前配置         |
| 内存开销    | 固定（bucket 数量决定） | 较高（维护滑动窗口）    |
| 推荐场景    | 分布式、需跨实例聚合      | 单实例、需高精度固定分位数 |

### FastAPI 集成

详细的 FastAPI 路由和中间件用法见 [FastAPI完全指南](https://blog.vercanti.com/fastapi-wan-quan-zhi-nan/)。

```python
import time
from fastapi import FastAPI, Request, Response
from prometheus_client import (
    Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
)

app = FastAPI()

# 定义指标
REQUEST_COUNT = Counter(
    "http_requests_total",
    "Total HTTP requests",
    ["method", "endpoint", "status_code"],
)
REQUEST_LATENCY = Histogram(
    "http_request_duration_seconds",
    "HTTP request latency",
    ["method", "endpoint"],
    buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5],
)

@app.middleware("http")
async def prometheus_middleware(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    duration = time.time() - start

    endpoint = request.url.path
    method = request.method
    status = str(response.status_code)

    REQUEST_COUNT.labels(method=method, endpoint=endpoint, status_code=status).inc()
    REQUEST_LATENCY.labels(method=method, endpoint=endpoint).observe(duration)

    return response

@app.get("/metrics")
def metrics():
    # 返回 Prometheus 文本格式
    return Response(
        content=generate_latest(),
        media_type=CONTENT_TYPE_LATEST,
    )

```

### 推送模式（PushGateway）

适用于批处理任务、短生命周期进程等无法被 pull 的场景：

```python
from prometheus_client import CollectorRegistry, Counter, push_to_gateway

# 使用独立 registry 避免污染全局
registry = CollectorRegistry()

job_duration = Counter(
    "batch_job_duration_seconds_total",
    "Total duration of batch jobs",
    registry=registry,
)

job_duration.inc(42.5)

# 推送到 PushGateway
push_to_gateway(
    gateway="pushgateway:9091",
    job="batch_import",          # job 标签值
    registry=registry,
    grouping_key={"instance": "worker-01"},  # 额外分组键
)

```

`push_to_gateway` 参数：

| 参数            | 说明                      |
| ------------- | ----------------------- |
| gateway       | PushGateway 地址          |
| job           | 任务名称，作为标签附加             |
| registry      | 使用的 Registry            |
| grouping\_key | 额外的分组键，用于区分同一 job 的不同实例 |

---

## Grafana

### 数据源配置

1. 进入 Configuration -> Data Sources -> Add data source
2. 选择 Prometheus
3. 填写 URL（如 `http://prometheus:9090`）
4. 根据需要配置认证信息

核心配置项：

| 配置项             | 说明                                         |
| --------------- | ------------------------------------------ |
| URL             | Prometheus 服务地址                            |
| Scrape interval | 告知 Grafana 数据点的最小间隔，影响 $\_\_rate\_interval |
| Query timeout   | 查询超时                                       |
| HTTP Method     | GET 或 POST，大查询建议用 POST                     |

### Dashboard 基础操作

- 创建 Dashboard：点击左侧 + 图标 -> New Dashboard
- 添加 Panel：Add panel -> Add new panel
- 编辑查询：在 Panel 编辑页输入 PromQL，选择可视化类型
- 保存：Ctrl+S 或点击 Save dashboard
- 导入已有 Dashboard：Dashboards -> Import，粘贴 JSON 或输入 Grafana.com 的 Dashboard ID

### 常用面板类型

| 面板类型        | 适用场景                       | 特点          |
| ----------- | -------------------------- | ----------- |
| Time Series | 随时间变化的趋势（请求速率、延迟）          | 最常用，支持多条折线  |
| Gauge       | 展示当前值占最大值的比例（CPU、内存）       | 直观的仪表盘样式    |
| Stat        | 单一数值展示，支持颜色阈值              | 适合展示总数、当前状态 |
| Table       | 多维度对比数据                    | 支持列排序、着色    |
| Bar Chart   | 分类对比                       | 适合 topk 类查询 |
| Heatmap     | 分布可视化（Histogram bucket 数据） | 直观展示延迟热力图   |

### 变量（Variables）

变量用于在 Dashboard 顶部创建下拉筛选，动态过滤数据。

配置路径：Dashboard Settings -> Variables -> New variable

| 变量类型       | 说明                 | 示例                               |
| ---------- | ------------------ | -------------------------------- |
| Query      | 从 Prometheus 查询标签值 | label\_values(up, job)           |
| Custom     | 手动指定选项列表           | production, staging, development |
| Interval   | 时间间隔选项             | 1m, 5m, 10m, 30m, 1h             |
| Datasource | 动态切换数据源            | \-                               |

在查询中使用变量：

```promql
# $job 和 $instance 为变量名
rate(http_requests_total{job="$job", instance="$instance"}[5m])

# 多值变量（开启 Multi-value 后）
http_requests_total{job=~"$job"}

```

### 告警规则配置

1. 进入 Alerting -> Alert rules -> New alert rule
2. 填写规则名称和 PromQL 表达式
3. 设置触发条件（Threshold：`IS ABOVE 0.05`）
4. 设置 Pending period（持续多久触发，防抖）
5. 配置 Labels 和 Annotations（通知内容模板）
6. 关联 Notification policy 和 Contact point

告警状态说明：

| 状态      | 说明                      |
| ------- | ----------------------- |
| Normal  | 条件未满足                   |
| Pending | 条件满足但未超过 Pending period |
| Firing  | 条件持续满足，告警已触发            |
| NoData  | 查询无返回数据                 |
| Error   | 查询执行出错                  |

### 实用 Dashboard 推荐

| Dashboard 名称            | Grafana ID | 说明                  |
| ----------------------- | ---------- | ------------------- |
| Node Exporter Full      | 1860       | 节点 CPU/内存/磁盘/网络全面监控 |
| Prometheus 2.0 Overview | 3662       | Prometheus 自身性能监控   |
| FastAPI Observability   | 17175      | FastAPI 应用请求、延迟、错误率 |
| MySQL Overview          | 7362       | MySQL 性能监控          |
| Redis Dashboard         | 11835      | Redis 内存、命令、连接数监控   |

---

## AlertManager 告警

### 告警规则 YAML 语法

```yaml
# rules/alert_rules.yml
groups:
  - name: application_alerts
    # 规则评估间隔（覆盖全局）
    interval: 30s
    rules:
      - alert: HighErrorRate
        # PromQL 表达式，结果非空时触发告警
        expr: |
          sum by(job) (rate(http_requests_total{status=~"5.."}[5m]))
          / sum by(job) (rate(http_requests_total[5m])) > 0.05
        # 持续时间：条件满足持续 5m 后才真正触发
        for: 5m
        labels:
          severity: critical
          team: backend
        annotations:
          summary: "服务 {{ $labels.job }} 错误率过高"
          description: "错误率为 {{ $value | humanizePercentage }}，超过 5% 阈值"
          runbook_url: "https://wiki.example.com/runbooks/high-error-rate"

      - alert: HighMemoryUsage
        expr: |
          (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
          / node_memory_MemTotal_bytes > 0.9
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "节点 {{ $labels.instance }} 内存使用率超过 90%"

      - alert: DiskWillFillIn4Hours
        expr: predict_linear(node_filesystem_avail_bytes[1h], 4 * 3600) < 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "节点 {{ $labels.instance }} 磁盘预计 4 小时内耗尽"

```

告警规则字段说明：

| 字段          | 说明                                        |
| ----------- | ----------------------------------------- |
| alert       | 告警规则名称                                    |
| expr        | PromQL 表达式，非空时进入 Pending 或 Firing 状态      |
| for         | 持续时间，防止瞬时抖动触发误告警                          |
| labels      | 附加标签，用于路由和分组                              |
| annotations | 告警描述，支持 Go template 语法（{{ $labels.xxx }}） |

### AlertManager 配置

```yaml
# alertmanager.yml
global:
  # 默认重复发送间隔
  repeat_interval: 4h
  # SMTP 全局配置
  smtp_smarthost: "smtp.example.com:587"
  smtp_from: "alertmanager@example.com"
  smtp_auth_username: "alertmanager@example.com"
  smtp_auth_password: "password"

# 通知路由树
route:
  # 默认路由分组（所有未匹配的告警走这里）
  receiver: "default-receiver"
  # 分组等待时间（积攒同组告警一起发送）
  group_wait: 30s
  # 同组后续通知间隔
  group_interval: 5m
  # 重复通知间隔
  repeat_interval: 4h
  # 按哪些标签分组
  group_by: [alertname, job]

  # 子路由
  routes:
    - match:
        severity: critical
      receiver: "pagerduty-critical"
      continue: false

    - match_re:
        team: "backend|frontend"
      receiver: "dingtalk-webhook"
      group_by: [alertname, team]

# 接收器定义
receivers:
  - name: "default-receiver"
    email_configs:
      - to: "ops-team@example.com"
        send_resolved: true
        headers:
          Subject: "[{{ .Status | toUpper }}] {{ .GroupLabels.alertname }}"

  - name: "dingtalk-webhook"
    webhook_configs:
      - url: "http://dingtalk-webhook:8060/dingtalk/webhook1/send"
        send_resolved: true
        http_config:
          bearer_token: "your-token"

  - name: "pagerduty-critical"
    pagerduty_configs:
      - routing_key: "your-pagerduty-routing-key"
        severity: critical

# 静默规则（运维窗口期使用）
inhibit_rules:
  - source_match:
      severity: critical
    target_match:
      severity: warning
    # 同一 job 和 instance 的 warning 会被 critical 抑制
    equal: [alertname, job, instance]

```

`route` 字段说明：

| 字段               | 说明                    |
| ---------------- | --------------------- |
| receiver         | 默认接收器名称               |
| group\_by        | 分组标签，相同标签组合的告警聚合为一条通知 |
| group\_wait      | 初次发送前等待时间，收集同组告警      |
| group\_interval  | 同一组有新告警时等待多久再发送       |
| repeat\_interval | 告警持续未解决时重复通知间隔        |
| continue         | 匹配此路由后是否继续匹配后续路由      |

---

## 最佳实践

### 指标命名规范

| 规则                   | 正确示例                             | 错误示例                        |
| -------------------- | -------------------------------- | --------------------------- |
| 使用基础单位（秒、字节）         | http\_request\_duration\_seconds | http\_request\_duration\_ms |
| Counter 以 \_total 结尾 | http\_requests\_total            | http\_requests\_count       |
| Gauge 不加 \_total     | active\_connections              | active\_connections\_total  |
| 应用名作前缀               | myapp\_cache\_hits\_total        | cache\_hits\_total          |
| 使用小写和下划线             | node\_memory\_bytes              | nodeMemoryBytes             |
| 比例用 ratio（0–1）       | error\_ratio                     | error\_percent              |

命名格式：`[namespace_][subsystem_]name[_unit][_total]`

### 标签（Label）设计原则

- **低基数原则**：单个 Label 的唯一值不超过 100，全部标签组合不超过 10,000 个时序
- **不在 Label 中放**：用户 ID、订单 ID、请求 ID、IP 地址、邮箱等高基数值
- **可以放的 Label**：`method`、`status`、`endpoint`（固定路由，非含参 URL）、`region`、`env`
- URL 参数路由（如 `/users/123`）需在接入层归一化为 `/users/{id}` 再作 Label

### Recording Rules：预计算昂贵查询

频繁使用的复杂 PromQL 应转换为 Recording Rule，避免每次 Dashboard 刷新都重新计算：

```yaml
# rules/recording_rules.yml
groups:
  - name: http_metrics
    interval: 1m
    rules:
      # 预计算：按 job 聚合的 5 分钟请求速率
      - record: job:http_requests:rate5m
        expr: sum by(job) (rate(http_requests_total[5m]))

      # 预计算：P99 延迟
      - record: job:http_request_duration_p99:rate5m
        expr: |
          histogram_quantile(0.99,
            sum by(job, le) (rate(http_request_duration_seconds_bucket[5m]))
          )

```

在 Dashboard 中直接查询 `job:http_requests:rate5m`，响应速度提升数倍。

### 黄金信号：监控什么

参考 Google SRE 四个黄金信号：

| 信号              | 指标示例        | PromQL 示例                                                                                       |
| --------------- | ----------- | ----------------------------------------------------------------------------------------------- |
| 延迟（Latency）     | P99 请求时间    | histogram\_quantile(0.99, sum by(le) (rate(http\_request\_duration\_seconds\_bucket\[5m\])))    |
| 流量（Traffic）     | 每秒请求数       | sum(rate(http\_requests\_total\[5m\]))                                                          |
| 错误（Errors）      | 5xx 错误率     | sum(rate(http\_requests\_total{status=\~"5.."}\[5m\])) / sum(rate(http\_requests\_total\[5m\])) |
| 饱和度（Saturation） | CPU / 内存使用率 | 1 - avg(rate(node\_cpu\_seconds\_total{mode="idle"}\[5m\]))                                     |

### 告警规则设计

- **告警应对症状，而非原因**：告警"错误率 > 5%"（用户能感知），而非"数据库连接池耗尽"（内部状态）
- **避免无法执行的告警**：每条告警都应有对应的 Runbook，注明处置步骤
- **善用 `for` 防抖**：基础设施告警用 5–10 分钟，避免闪烁
- **分级路由**：critical → 立即呼叫值班，warning → 工作时间处理，info → 仅记录

### Grafana Dashboard 组织

- 每个服务建一个 Dashboard，通过顶部变量（`$job`、`$instance`）切换实例
- Row 按层次组织：Overview → 请求层 → 业务层 → 基础设施层
- 优先使用 Grafana.com 社区 Dashboard（见上方推荐 ID），在其基础上定制
- 使用 `$__rate_interval` 替代硬编码 `[5m]`，自动适配 scrape interval 变化
- Dashboard 用 JSON 文件纳入版本控制（`grafana-dashboard-exporter` 或 Terraform）

---

## 踩坑与注意事项

### 高基数 Label 导致性能问题

**问题**：将用户 ID、URL 参数、请求 ID 等高基数（大量唯一值）字段作为 Label，导致时序数量爆炸式增长，Prometheus 内存和存储急剧膨胀。

```python
# 错误示例：user_id 可能有数百万个唯一值
REQUEST_COUNT = Counter("requests", "...", ["endpoint", "user_id"])
REQUEST_COUNT.labels(endpoint="/api", user_id=user.id).inc()

# 正确示例：只用低基数标签
REQUEST_COUNT = Counter("requests", "...", ["endpoint", "status"])
REQUEST_COUNT.labels(endpoint="/api", status="200").inc()

```

原则：Label 的唯一值组合（cardinality）不应超过 10,000，单个 Label 的唯一值不应超过 100。

### Histogram bucket 设置不合理

**问题**：使用默认 bucket（最大 10 秒）监控数据库查询（通常 < 100ms），导致大量数据集中在第一个 bucket，分位数计算失真。

```python
# 错误：使用默认 bucket，最大值为 10s，不适合毫秒级延迟
DB_LATENCY = Histogram("db_query_seconds", "Database query duration")

# 正确：根据实际延迟范围设置 bucket
DB_LATENCY = Histogram(
    "db_query_seconds",
    "Database query duration",
    buckets=[0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0],
)

```

原则：bucket 应覆盖正常延迟的 90% 以上，在关注的延迟区间内密集分布。

### rate() 函数使用注意

- `rate()` 内的时间窗口至少要包含 **4 个采样点**，否则结果不稳定。公式：窗口 >= 4 × scrape\_interval
- `rate()` 会自动处理 Counter 重置，不要对 Gauge 使用 `rate()`
- 长时间窗口（如 `[1h]`）会平滑短期波动，短时间窗口响应更敏感

### 告警规则 for 字段

`for` 设置过短会导致告警抖动频繁发送通知，设置过长会延迟发现问题。建议：

- 基础设施告警（CPU、内存）：5-10 分钟
- 业务指标告警（错误率）：2-5 分钟
- 依赖不可用（端点 down）：1-2 分钟

---

## 常见陷阱

### 陷阱：`rate()` 应用于 Gauge 指标得到无意义结果

**现象：** 对 CPU 使用率（Gauge）用 `rate(cpu_usage[5m])` 计算，结果忽大忽小毫无规律。  
**原因：** `rate()` 是为单调递增的 Counter 设计的（自动处理重置），Gauge 可以任意上下波动，`rate()` 计算的是区间内的增长速率，对 Gauge 没有意义。  
**解决：** Gauge 指标直接查询或用 `avg_over_time(metric[5m])` 计算时间窗口内的平均值；只对 Counter（名称通常以 `_total` 结尾）使用 `rate()`。

### 陷阱：Alert 频繁抖动（Flapping）

**现象：** 同一告警在短时间内反复触发和恢复，发送大量通知骚扰值班人员。  
**原因：** 指标在阈值附近波动，`for` 持续时间设置过短，每次短暂超阈值就触发告警。  
**解决：** 适当延长 `for` 字段（CPU 告警 ≥ 5 分钟），在 AlertManager 配置 `repeat_interval` 控制重复通知间隔，并启用 `resolve_timeout` 防止过早恢复。

### 陷阱：Grafana Dashboard 时区与数据时区不一致

**现象：** Grafana 图表显示的时间与实际事件时间相差 8 小时（或其他偏移）。  
**原因：** Prometheus 存储时间戳为 UTC，Grafana Dashboard 的时区设置为本地时区，若服务器时区与 Grafana 配置不一致则产生偏移。  
**解决：** 在 Dashboard 设置中将时区改为 `Browser`（跟随用户浏览器）或明确设置 `Asia/Shanghai`；确保 Prometheus 服务器和 Grafana 时区配置一致。

---

## 参见

- [FastAPI完全指南](https://blog.vercanti.com/fastapi-wan-quan-zhi-nan/) — FastAPI 应用接入 Prometheus 的完整示例
- [Docker Compose完全指南](https://blog.vercanti.com/docker-compose-wan-quan-zhi-nan/) — 用 Docker Compose 部署 Prometheus + Grafana + AlertManager 全栈
- [Kubernetes基础](https://blog.vercanti.com/kubernetes-ji-chu/) — Kubernetes 服务发现（`kubernetes_sd_configs`）配置
- [Kubernetes进阶](https://blog.vercanti.com/kubernetes-jin-jie/) — kube-prometheus-stack Helm Chart 部署监控全栈