Prometheus + Grafana 监控面板实战

想给服务器、容器和数据库装上一双「看得见运行状态」的眼睛?Prometheus + Grafana 是目前最主流的开源监控可观测性组合:Prometheus 负责指标采集与存储,Grafana 负责把数据画成漂亮的 dashboard。本文从零带你用 Docker 拉起整套监控栈,配置抓取目标、写 PromQL 查询、做告警,并给出生产环境落地建议。

一、为什么选 Prometheus + Grafana

在众多监控方案里,Prometheus 胜在「拉模型(pull)」+ 时间序列数据库 + 强大的 PromQL 查询语言;Grafana 则把任意数据源都变成可视化面板。两者都是 CNCF 毕业项目,社区生态成熟,对云原生(Kubernetes)天然友好。对中小团队来说,它几乎是「零授权费就能上生产」的标杆组合。

和传统 Zabbix 相比,Prometheus 的配置即代码、标签(label)多维模型,让「按服务、按实例、按环境」灵活切片成为可能;和纯商业 APM 相比,它又能把成本压到极低。如果你已经在用我们讲过的Docker 网络模式K8s 入门,这套监控栈能直接复用同一套容器基础设施。

1.1 拉模型(pull)意味着什么

理解 pull 是理解 Prometheus 一切设计的钥匙。被监控方只需暴露一个返回纯文本指标的 HTTP 接口(通常是 /metrics),由 Prometheus 主动定时来取,而不是像 StatsD 那样由应用把数据推出去。这带来三个直接好处:一是被监控端逻辑极简,不需要维护上报队列和失败重试;二是「目标是否可达」本身就是一个天然的健康信号,Prometheus 会为每个目标自动生成 up 指标;三是你随时能用浏览器手动打开 /metrics 看原始数据排查,调试成本极低。

代价是 Prometheus 必须网络可达每个目标。对于跑在防火墙后、或生命周期极短的批处理任务(Job / CronJob),拉不到就是拉不到,此时要用 Pushgateway 让任务主动把结果推到中转站再被拉取。但要记住 Pushgateway 只适合短任务,绝不要拿它替代常规 exporter,否则会丢掉实例级别的存活语义。

二、架构总览:采集、存储、可视化、告警

2.1 四个角色各司其职

一套最小可用的监控栈由四个组件构成,职责边界清晰:

组件职责默认端口
Prometheus定时拉取指标、存 TSDB、执行 PromQL、触发告警9090
Grafana查询数据源、绘制 dashboard、管理告警面板3000
Node Exporter暴露主机 CPU/内存/磁盘等指标9100
Alertmanager接收告警、去重、分组、路由到邮件/钉钉/Webhook9093

2.2 数据流向

Prometheus 按 scrape_interval 周期性去各 exporter 拉指标 → 写入本地 TSDB;Grafana 通过 Prometheus 数据源反向查询并渲染;当 PromQL 告警规则命中,Prometheus 把告警推给 Alertmanager,再由它分发通知。整条链路是「采集—存储—可视化—告警」的闭环。

三、用 Docker Compose 一键拉起

先把整套组件用 docker-compose 编排起来,网络部分可参考Docker 网络模式详解里的 bridge 配置。下面这份 compose 已把端口与数据卷固定,避免容器重建丢数据。

version: "3.8"
services:
  prometheus:
    image: prom/prometheus:v2.53.0
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prom_data:/prometheus
    command:
      - --config.file=/etc/prometheus/prometheus.yml
  grafana:
    image: grafana/grafana:11.1.0
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      GF_SECURITY_ADMIN_PASSWORD: "ChangeMe!"
  node-exporter:
    image: prom/node-exporter:v1.8.1
    ports:
      - "9100:9100"
  alertmanager:
    image: prom/alertmanager:v0.27.0
    ports:
      - "9093:9093"
    volumes:
      - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
volumes:
  prom_data:
  grafana_data:

几个容易被忽略的细节:镜像统一锁定小版本号而不是 latest,否则某次重建就可能因为 Prometheus 大版本变更导致配置不兼容;prom_datagrafana_data 必须用命名卷,一旦用匿名卷,docker compose down 之后历史指标和你辛苦搭的面板会一起消失;四个服务在同一个 compose 网络里,因此可以直接用服务名互相访问,这就是后面配置里写 node-exporter:9100 而不是 IP 的原因。

启动并做一次基础校验,确认目标全部处于 up 状态再往下走:

# 启动全栈
docker compose up -d

# 看容器状态
docker compose ps

# 校验配置文件语法(改完 yml 强烈建议先跑这个)
docker compose exec prometheus promtool check config /etc/prometheus/prometheus.yml

# 确认所有抓取目标健康,health 应为 "up"
curl -s http://localhost:9090/api/v1/targets | grep -o '"health":"[a-z]*"'

四、配置 Prometheus 抓取目标

prometheus.yml 的核心是 scrape_configs:告诉 Prometheus 去哪些地址、用什么路径拉指标。下面是一个最小可运行配置,含自身、Node Exporter 与 Grafana。

global:
  scrape_interval: 15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
    - static_configs:
        - targets: ["alertmanager:9093"]

rule_files:
  - "/etc/prometheus/rules/*.yml"

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]
  - job_name: "node"
    static_configs:
      - targets: ["node-exporter:9100"]
  - job_name: "grafana"
    static_configs:
      - targets: ["grafana:3000"]

4.1 抓取周期怎么定

scrape_interval 是成本与精度的直接权衡:15 秒是社区默认的甜点值,既能捕捉到大多数抖动,存储压力也可控。调到 5 秒能看清瞬时毛刺,但样本量翻三倍,TSDB 磁盘和内存都会明显吃紧;调到 60 秒则可能完全错过一次持续 30 秒的服务抖动。实践建议是全局保持 15 秒,只对个别高价值 job 单独覆盖,而不是一刀切拉高全局频率。

另一个必调项是 scrape_timeout,它必须小于 scrape_interval,否则上一次抓取还没结束下一轮就已经排队,目标会周期性地假死为 down。当目标数量增长到几十个以上,手工维护 static_configs 就不现实了,应该换成服务发现,让 Prometheus 自动跟随容器与 Pod 的生命周期:

scrape_configs:
  # 基于 Docker 容器标签自动发现
  - job_name: "docker-sd"
    scrape_timeout: 10s
    docker_sd_configs:
      - host: "unix:///var/run/docker.sock"
    relabel_configs:
      # 只抓打了 prometheus.scrape=true 标签的容器
      - source_labels: [__meta_docker_container_label_prometheus_scrape]
        regex: "true"
        action: keep
      # 用容器名覆盖实例名,面板里更可读
      - source_labels: [__meta_docker_container_name]
        target_label: instance

五、Grafana 接入数据源并建面板

启动后访问 http://<服务器IP>:3000,用 admin / 你设的密码登录,添加 Prometheus 数据源(URL 填 http://prometheus:9090)。如果 Grafana 要对外暴露,记得套一层Nginx 反向代理并加 HTTPS,避免裸奔在公网。也可以用 API 一次性灌入数据源,便于 GitOps:

curl -X POST http://admin:ChangeMe!@localhost:3000/api/datasources \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Prometheus",
    "type": "prometheus",
    "url": "http://prometheus:9090",
    "access": "proxy",
    "isDefault": true
  }'

面板方面,新手可直接在 Grafana 官方市场导入 Node Exporter Full(ID 1860)这个经典 dashboard,省去从零画 CPU/内存/磁盘图表的功夫;熟练后再按业务自定。

六、PromQL 实战:从 CPU 到请求延迟

PromQL 是整套监控的「查询语言」,会写才能挖出有价值的信息。下面几个例子覆盖了最常见的观测维度。

# 主机 CPU 使用率(5 分钟平均)
100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# 内存可用率
(node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100

# 磁盘根分区使用率
100 - ((node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100)

# 最近 5 分钟 HTTP 请求速率(需业务暴露 http_requests_total)
rate(http_requests_total[5m])

6.1 两个必须分清的概念

初学者最容易踩的坑是混淆瞬时向量和区间向量。node_cpu_seconds_total 返回的是每个时间序列在当前时刻的一个值,属于瞬时向量;写成 node_cpu_seconds_total[5m] 后带上了时间区间,变成区间向量。rate()increase() 这类函数只接受区间向量,而绘图和告警表达式最终又必须收敛回瞬时向量,这就是为什么标准写法总是 rate(xxx_total[5m]) 这种「先取区间再聚合」的嵌套形式。

rate()irate() 的取舍同样常见:前者在整个区间上做线性拟合,曲线平滑,适合做告警和趋势图;后者只取区间内最后两个样本计算,对毛刺极其敏感,适合排障时放大瞬时尖峰,但绝不适合写进告警规则,否则会被一次偶发抖动直接触发。凡是名字以 _total 结尾的计数器,都必须套 rate() 才有意义,直接画原始值只会得到一条单调上升的斜线。

对接口延迟这类核心业务指标,平均值几乎没有参考价值,必须看分位数。如果业务侧暴露的是 Histogram 类型,用 histogram_quantile 算 P95、P99:

# P95 接口延迟(按接口路径分组)
histogram_quantile(0.95,
  sum by(le, path)(rate(http_request_duration_seconds_bucket[5m]))
)

# 错误率:5xx 占全部请求的比例
sum(rate(http_requests_total{status=~"5.."}[5m]))
  / sum(rate(http_requests_total[5m]))

# Top 5 内存占用最高的实例
topk(5, node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)

七、告警规则与 Alertmanager

监控没有告警等于「只是好看」。在 rules/ 下写告警规则,命中后 Prometheus 推给 Alertmanager。

规则里的 for 字段是降噪的关键。一条告警有三种状态:表达式不成立时是 inactive;刚成立但还没满足 for 时长时是 pending,此时不会发通知;持续成立超过 for 才进入 firing 并推给 Alertmanager。所以上面 CPU 告警写 for: 5m,意思是「CPU 连续 5 分钟都超过 85% 才叫我」,一次编译任务带来的短暂满载会在 pending 阶段自动消解。反之 InstanceDown 只给 1 分钟,因为实例掉线是必须立刻知道的事实。

groups:
  - name: node-alerts
    rules:
      - alert: HighCpuUsage
        expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "主机 {{ $labels.instance }} CPU 持续超过 85%"
      - alert: InstanceDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "实例 {{ $labels.instance }} 已下线"
route:
  receiver: "default"
  group_by: ["alertname", "instance"]
  group_wait: 30s
  group_interval: 5m
receivers:
  - name: "default"
    webhook_configs:
      - url: "https://your-webhook.example.com/alert"

八、生产落地建议

  • 持久化与备份:Prometheus TSDB 和 Grafana 配置必须挂卷;Grafana 的 dashboard 建议导出 JSON 进 Git,配合GitHub Actions做版本化与回滚。
  • 保留期与降采样:默认 15 天,生产按磁盘调 --storage.tsdb.retention.time;数据量大的考虑 Thanos / VictoriaMetrics 做长期存储。
  • 不要裸奔公网:Grafana/Prometheus 端口务必走反向代理 + 鉴权 + 防火墙,admin 密码必须改。
  • 标签基数控制:避免用高基数标签(如用户 ID)做 label,否则 TSDB 内存与查询都会爆炸。
  • 告警降噪:用 group_wait / repeat_interval 收敛通知,关键告警才上电话/钉钉,避免「狼来了」。

九、总结

Prometheus + Grafana 用一套开源组合就覆盖了「采集—存储—可视化—告警」全链路,是中小团队上可观测性的最优起点。先用 Docker Compose 跑通最小栈,再按业务补 exporter、写 PromQL、调告警,最后把它纳入 GitOps 与 CI 流程,监控就真正成了生产力的护城河。

上一篇 大模型工具调用:Function Calling 实战
下一篇 MongoDB 文档建模:嵌入式 vs 引用式设计与实战