作为运维,服务器出了问题才去排查?太被动了。真正的做法是:让问题主动来找你。本文用 Docker Compose 一键部署 Prometheus + Grafana 监控系统,实时掌握服务器的 CPU、内存、磁盘、网络状态,把被动救火变成主动预防。

前言

刚学运维的时候,我对"监控"没什么概念。服务器能用就行,出了问题再排查呗。

直到有一次,线上服务器磁盘满了,服务挂了两个小时才被用户反馈发现。领导问了一句:“你们没有监控吗?”

从那之后我才意识到:监控是运维的基本功,不是可选项

本文记录我用 Docker Compose 部署 Prometheus + Grafana 监控系统的完整过程,包括架构设计、配置详解、踩坑记录,适合和我一样的运维新手入门。

一、为什么需要监控?

传统运维的痛点

场景 没有监控 有监控
CPU 飙高 用户反馈卡顿才发现 提前告警,主动处理
磁盘满了 服务挂了才知道 80% 就提醒,提前清理
内存泄漏 运行几天后崩溃 趋势图一眼看出来
流量突增 不知道,直到服务器扛不住 实时看到流量变化

监控系统的价值

  • 主动发现问题:不用等用户反馈,指标异常自动告警
  • 快速定位原因:CPU 高?内存满?网络拥堵?图表一目了然
  • 数据驱动决策:该扩容了还是该优化代码?看数据说话
  • 事后复盘分析:回溯故障时间段的指标变化,找到根因

二、Prometheus + Grafana 是什么?

一句话解释

  • Prometheus — 采集数据、存储数据(时序数据库)
  • Grafana — 展示数据、生成图表(可视化看板)
  • Node Exporter — 采集服务器指标的"探针"

类比理解

把监控系统想象成医院的体检中心:

组件 医院类比 作用
Node Exporter 检测仪器 采集心跳、血压、体温等数据
Prometheus 检验科 把检测数据存档、分析
Grafana 体检报告 把数据变成直观的图表

架构图

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   服务器     │     │ Prometheus  │     │   Grafana   │
│             │     │             │     │             │
│ Node        │────▶│  采集数据    │────▶│  展示图表    │
│ Exporter    │     │  存储数据    │     │  生成看板    │
│ (采集指标)   │     │             │     │             │
└─────────────┘     └─────────────┘     └─────────────┘
    端口:9100          端口:9090           端口:3000

数据流向

  1. Node Exporter 部署在服务器上,每时每刻采集 CPU、内存、磁盘、网络等指标
  2. Prometheus 每 15 秒主动去 Node Exporter 拉取一次数据,存入自己的时序数据库
  3. Grafana 从 Prometheus 读取数据,生成实时更新的图表

为什么是 Prometheus 主动拉而不是 Node Exporter 主动推?

这叫 Pull 模型。好处是:

  • Prometheus 统一控制采集频率,不用每个采集目标都配置
  • 采集目标挂了,Prometheus 立刻知道(拉不到数据了)
  • 方便扩展,新增服务器只需在 Prometheus 配置里加一行地址

三、什么是时序数据库?

Prometheus 用的是时序数据库(TSDB),专门存储按时间顺序排列的数据。

和 MySQL 的区别

MySQL 存用户信息

| 姓名   | 年龄 | 城市   |
|--------|------|--------|
| 张三   | 20   | 北京   |
| 李四   | 22   | 上海   |

这种数据不怎么变,叫"状态数据"。

时序数据库存监控数据

| 时间           | CPU使用率 |
|----------------|-----------|
| 10:00:00       | 30%       |
| 10:00:15       | 35%       |
| 10:00:30       | 28%       |
| 10:00:45       | 90%       |
| ...每15秒一条...           |

为什么不用 MySQL?

假设 15 秒采一次,1 台服务器 1 天 = 5760 条数据,10 台服务器 1 个月 = 170 万条。

用 MySQL 存也能跑,但:

  • 查询"最近 1 小时 CPU 平均值"要扫大量行,
  • 数据都是追加,不需要 MySQL 的修改、删除、关联查询功能,浪费
  • 存储效率低,同样的数据占更多空间

时序数据库专门为这种场景优化:

  • 写入极快(数据天然按时间排好)
  • 查询某个时间段极快(索引按时间建的)
  • 压缩率高(相邻时间的值差不多,压缩后省空间)

简单理解:MySQL 是通用工具箱,时序数据库是专用工具。监控数据这种"按时间排列、只增不改"的场景,专用工具更快更省。

四、Docker Compose 一键部署

4.1 项目结构

prometheus-grafana/
├── docker-compose.yml                    # 主编排文件
├── prometheus/
│   └── prometheus.yml                    # Prometheus 配置
├── grafana/
│   ├── provisioning/
│   │   ├── datasources/
│   │   │   └── prometheus.yml            # 数据源自动配置
│   │   └── dashboards/
│   │       └── dashboards.yml            # 看板自动配置
│   └── dashboards/
│       └── node-exporter.json            # 看板模板(从 Grafana 社区下载)
└── README.md

4.2 Prometheus 配置

global:
  scrape_interval: 15s    # 每 15 秒采集一次
  scrape_timeout: 10s     # 采集超时时间

scrape_configs:
  # 监控 Prometheus 自身
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  # 监控服务器(通过 Node Exporter)
  - job_name: "node-exporter"
    static_configs:
      - targets: ["node-exporter:9100"]

逐行解释

  • scrape_interval: 15s — Prometheus 每 15 秒去抓一次数据,生产环境一般 15-60 秒
  • job_name — 采集任务的名称,可以理解为一组采集目标
  • targets — 采集目标的地址,用容器名 node-exporter 而不是 localhost,因为它们在同一个 Docker 网络里

4.3 docker-compose.yml

services:
  # Prometheus — 时序数据库,采集和存储监控数据
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.retention.time=15d"
      - "--web.enable-lifecycle"
    networks:
      - monitoring

  # Node Exporter — 采集服务器指标(CPU/内存/磁盘/网络)
  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    restart: unless-stopped
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - "--path.procfs=/host/proc"
      - "--path.sysfs=/host/sys"
      - "--path.rootfs=/rootfs"
      - "--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)"
    networks:
      - monitoring

  # Grafana — 可视化看板,把数据变成图表
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
      - ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
      - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
    depends_on:
      - prometheus
    networks:
      - monitoring

volumes:
  prometheus_data:
  grafana_data:

networks:
  monitoring:
    driver: bridge

关键配置解释

Node Exporter 的 volumes

- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro

Node Exporter 需要读取宿主机的 /proc(进程信息)和 /sys(内核信息)来采集指标。挂载时加 :ro(只读),安全起见只让它读,不让它改。

--collector.filesystem.mount-points-exclude

--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)

排除 /proc/sys 等虚拟文件系统。这些不是真实磁盘,Node Exporter 需要读它们获取数据,但不需要监控它们的"磁盘使用量"(这个问题本身就不存在)。

注意 $$ 是 Docker Compose 的转义写法,因为 $ 在 YAML 里有特殊含义。传到容器里后 $$ 变成普通的 $

Grafana 的 volumes

- ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
- ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro

这三行实现了自动化配置

  • 第一行:自动连接 Prometheus 数据源,不用手动在 Grafana 界面添加
  • 第二行:告诉 Grafana 去哪里找看板 JSON 文件
  • 第三行:把看板 JSON 文件挂载到容器里

数据持久化

volumes:
  prometheus_data:   # Prometheus 的时序数据
  grafana_data:      # Grafana 的配置和看板

不用 Docker Volume 的话,容器删了数据就没了。加了这两个卷,容器重建后历史数据还在。

4.4 Grafana 配置文件

除了 docker-compose.yml,Grafana 还需要两个 provisioning 配置文件,实现数据源和看板的自动配置。

数据源配置grafana/provisioning/datasources/prometheus.yml):

apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: true

这个文件告诉 Grafana:Prometheus 数据源在哪(http://prometheus:9090),并且设为默认数据源。Grafana 启动时自动连接,不需要手动在界面添加。

看板配置grafana/provisioning/dashboards/dashboards.yml):

apiVersion: 1

providers:
  - name: "default"
    orgId: 1
    folder: ""
    type: file
    disableDeletion: false
    editable: true
    options:
      path: /var/lib/grafana/dashboards
      foldersFromFilesStructure: false

这个文件告诉 Grafana:去 /var/lib/grafana/dashboards 目录找 JSON 看板文件,找到就自动加载。

看板模板:从 Grafana 社区下载 Node Exporter 看板(Dashboard ID: 1860),访问 https://grafana.com/grafana/dashboards/1860 获取 JSON 文件,放入 grafana/dashboards/ 目录。

4.5 一键启动

docker-compose up -d

三个容器几秒钟就全部启动完成。

五、效果展示

5.1 容器运行状态

docker-compose ps

在这里插入图片描述

三个容器全部 Up:Prometheus(9090)、Node Exporter(9100)、Grafana(3000)。

5.2 Prometheus 监控目标

访问 http://localhost:9090/targets

在这里插入图片描述

两个 Target 状态都是 UP,说明 Prometheus 已经在正常采集数据。

5.3 Grafana 监控看板

访问 http://localhost:3000,默认账号密码都是 admin

在这里插入图片描述

看板实时展示服务器核心指标:

  • CPU Busy — CPU 使用率
  • RAM Used — 内存使用率
  • Root FS Used — 磁盘使用率
  • Uptime — 服务器运行时长

数据每 15 秒自动刷新,不需要手动操作。

5.4 图表详情

点击某个图表可以放大查看详细数据:

在这里插入图片描述

CPU 图表展示了 System(系统态)、User(用户态)、Iowait(IO等待)等各项细分指标。生产环境中,这些线条会随着服务器负载实时波动,一眼就能看出哪里有问题。

六、踩坑记录

踩坑 1:Prometheus 一直 Restarting

现象docker-compose up -d 后,Prometheus 容器状态一直是 Restarting,无法正常运行。

排查

docker-compose logs prometheus

日志报错:

field scarpe_timeout not found in type config.plain

原因prometheus.ymlscrape_timeout 拼写成了 scarpe_timeout,少了个 rap 的顺序也反了。

解决:修正拼写后重启:

docker-compose restart prometheus

教训:YAML 配置文件的字段名拼错,程序不会自动纠正。容器出问题第一反应是看日志Restarting 状态只说明容器在循环重启,日志里才有真正的错误原因。

踩坑 2:Grafana 看板不显示

现象:Grafana 启动正常,登录后 Dashboards 页面是空的。

排查:检查 grafana/provisioning/dashboards/dashboards.yml,发现第 4 行:

provides:    # ← 错的,少了字母 r

应该是:

providers:   # ← 正确的

解决:修正拼写后重启 Grafana:

docker-compose restart grafana

教训:和踩坑 1 一样,拼写错误是最容易犯也最难发现的错误。肉眼检查很容易漏掉,所以看日志是第一优先级

踩坑 3:$$ 在 docker-compose.yml 中是什么意思?

现象:Node Exporter 配置里看到 ($$|/),不理解为什么是两个 $

解释$ 在 docker-compose.yml 里是变量引用的前缀(如 ${HOME})。要表示一个普通的 $ 字符,需要用 $$ 转义。

传到容器里后,$$ 变成 $,正则表达式变成 ($|/),意思是"字符串结尾或斜杠"。

踩坑 4:容器出问题怎么排查?

这次实验让我总结出一个排查流程:

容器状态异常
    ↓
docker-compose logs 容器名    ← 第一步永远是看日志
    ↓
找到报错信息
    ↓
配置文件拼写错误 / 端口冲突 / 依赖服务未启动
    ↓
修正后 docker-compose restart

90% 的容器问题都能靠看日志解决。剩下 10% 可能需要进容器里面排查:

docker exec -it 容器名 /bin/sh

七、常用运维命令

# 查看所有服务状态
docker-compose ps

# 实时跟踪日志
docker-compose logs -f

# 查看某个服务的日志
docker-compose logs prometheus

# 停止服务(保留数据)
docker-compose down

# 停止并删除数据
docker-compose down -v

# 重启服务
docker-compose restart

# Prometheus 热重载配置(不用重启)
curl -X POST http://localhost:9090/-/reload

八、PromQL 基础查询

PromQL 是 Prometheus 的查询语言,在 http://localhost:9090/graph 可以直接执行:

# CPU 使用率(排除空闲)
100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# 可用内存(GB)
node_memory_MemAvailable_bytes / 1024 / 1024 / 1024

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

# 网络接收速率(bytes/s)
rate(node_network_receive_bytes_total[5m])

这些查询在 Grafana 的图表里也会用到,理解 PromQL 能让你自定义更精确的监控图表。

九、总结

通过这次实验,我学到了:

  1. 监控的重要性 — 运维不是出了问题才去修,而是要提前发现问题
  2. Prometheus 的 Pull 模型 — 主动拉取比被动推送更容易管理
  3. 时序数据库的原理 — 专用工具比通用工具更适合监控场景
  4. Docker 网络 — 同一网络内的容器可以用容器名互相访问
  5. 数据持久化 — Docker Volume 让容器删了数据不丢
  6. 日志排查 — 容器出问题第一反应是看日志

Prometheus + Grafana 是目前最主流的开源监控方案,企业里广泛使用。对于运维新手来说,这是一个非常值得掌握的技能。

项目已开源,欢迎 Star:https://gitee.com/pengjia12345/prometheus-grafana

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐