想给服务器、容器和数据库装上一双「看得见运行状态」的眼睛?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 | 接收告警、去重、分组、路由到邮件/钉钉/Webhook | 9093 |
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_data 和 grafana_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 流程,监控就真正成了生产力的护城河。




