服务器如何搭建监控系统?Prometheus与Grafana部署思路

没有监控的服务器就像没装仪表盘的车:能开,但你不知道油量、水温、转速,只能等它冒烟了才发现问题。更现实的问题是——故障发生之后,没有历史数据,你连"当时到底怎么了"都回答不了,复盘只能靠猜。

这篇文章讲怎么从零搭一套可用的监控体系:监控要分哪几层、Prometheus 的拉取模型和数据模型是什么、node_exporter 部署与常用指标、prometheus.yml 的关键配置、记录规则与告警规则怎么写、Alertmanager 的路由与抑制思路、Grafana 怎么接数据源和导面板、存储与保留期怎么算、单机小规模有什么轻量替代、以及最容易被忽视的"告警风暴治理"。

覆盖面偏向"中小企业和个人站长能落地"的方案,不需要 Kubernetes,一台 2 核 4G 的小机器就够跑起来监控几十台服务器。

一、监控要分四层

很多人的监控只有"CPU、内存、磁盘"这一层,结果业务已经挂了监控还是绿的——因为机器没事,有事的是应用。完整的监控应该覆盖四层,每层回答不同的问题。

层级 关注对象 典型指标 回答的问题
基础设施层 服务器、网络设备 CPU、内存、磁盘、网络、内核事件 机器还活着吗?资源够吗?
中间件层 Nginx、数据库、缓存、消息队列 连接数、慢查询、命中率、队列积压 依赖组件健康吗?
应用层 自己写的程序 QPS、响应时间、错误率、JVM GC、线程池 我的程序运行得怎么样?
业务层 业务结果 注册数、订单量、支付成功率、库存 用户能正常完成他要的事吗?

四层之间越往上越接近用户感受。基础设施层全绿而业务层告急的情况非常常见(比如 DNS 解析失败、第三方接口挂了、某个业务分支逻辑错误),这也是为什么监控不能只做最底层。

分层的另一个价值是告警定级:基础设施告警通常可以等几分钟,业务层告警(支付成功率掉零)必须立刻响应。把四层混在一起同等对待,必然导致告警疲劳。

部署顺序建议:先做基础设施层,因为它最标准化、工具最成熟;然后补业务层,因为它最能直接反映损失;中间件和应用层穿插进行,按遇到的痛点逐步加。不要一开始就追求大而全,一套跑不通的完美方案不如一套跑着的最小方案。

二、Prometheus 的两个核心设计

Prometheus 之所以成为云原生监控的事实标准(CNCF 毕业项目),主要因为它有两个克制而清晰的设计:拉取模型和多维数据模型。

拉取模型(Pull vs Push)

传统监控(如 Zabbix agent 主动上报、StatsD)是推模型:被监控端主动把数据发给服务端。Prometheus 反过来:服务端主动去拉取被监控端暴露的 HTTP 接口。

Prometheus Server  --HTTP GET /metrics-->  node_exporter:9100
                   --HTTP GET /metrics-->  app:8080/metrics

拉模型的好处很实际:

  • 调试极其方便:任何指标都能用 curl http://target:9100/metrics 直接看,不需要专门的客户端。
  • 目标状态由服务端掌握:Prometheus 知道每个 target 是否在 scrape 成功,并自动生成 up 指标。up == 0 本身就是一条最基础的告警。
  • 不会因为被监控端乱推而打爆服务端:采集节奏由服务端统一管理。
  • 天然适配动态环境:结合服务发现(file_sd、consul、k8s SD),target 列表自动更新。

拉模型的代价是需要 target 可达。对于短生命周期任务(比如批处理 Job,可能几秒就结束了,来不及被拉),Prometheus 提供了 Pushgateway 作为补充——但这属于例外机制,滥用 Pushgateway 会破坏拉模型的简洁性(比如多个实例推同一个 job 时的指标冲突和数据不过期问题)。

数据模型:时间序列 + 标签

Prometheus 存储的每条数据是一个时间序列(time series),由两部分唯一确定:指标名(metric name)+ 一组标签(labels,键值对)。

node_cpu_seconds_total{cpu="0",mode="user",instance="web01:9100",job="node"}  12345.67

这里 node_cpu_seconds_total 是指标名,花括号里是四个标签。查询时按标签做过滤、聚合和分组,这是 Prometheus 表达力的来源。

四种指标类型:

类型 特点 典型例子 查询注意事项
Counter 只增不减的累计值,重启归零 node_cpu_seconds_total、HTTP 请求总数 必须用 rate()/increase() 算速率
Gauge 可增可减的瞬时值 node_memory_MemAvailable_bytes、当前连接数 可直接用,也可做 avg/max
Histogram 采样到预置 bucket 的分布 请求耗时分布 用 histogram_quantile() 算分位数
Summary 客户端直接算好的分位数 部分老 exporter 不能跨实例聚合,不如 Histogram 灵活

Counter 必须用 rate() 处理是新手最常见的坎。直接看 node_cpu_seconds_total 的原始值毫无意义(它是开机以来累计的秒数),正确算法:

# 每秒 user 态 CPU 时间增长量,除以总 CPU 时间再乘 100 得百分比
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

rate(metric[5m]) 计算过去 5 分钟的平均每秒增长率,它会自动处理 Counter 归零(重启)的情况。区间选择有讲究:至少要大于采集间隔的 4 倍(比如 15 秒采集一次,至少用 [1m]),太短会因为样本太少而抖动。

PromQL 的几个常用查询(这些是运维日常必会的):

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

# 内存可用率
100 * node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes

# 磁盘剩余空间百分比
100 * node_filesystem_avail_bytes{mountpoint="/",fstype!="rootfs"} / node_filesystem_size_bytes

# 磁盘空间多久后耗尽(基于 6 小时增长率的线性预测,单位秒)
predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|squashfs"}[6h], 4*3600)

# 网络入带宽
rate(node_network_receive_bytes_total{device!~"lo|veth.*|docker.*"}[5m])

# 目标存活探测
up{job="node"} == 0

最后一条 预测磁盘多久耗尽 是特别值得加的一条告警,它比"磁盘使用率 > 85%"更有用——因为它给出的是明确的时间窗口,能提前安排扩容而不是半夜被叫起来。

三、node_exporter 部署

node_exporter 采集主机的硬件和内核指标,是基础设施层的主力采集器。它刻意不做进程级监控——那个职责属于 process-exporter 或 cAdvisor,这是 Unix 哲学的体现。

# 下载(版本号请在官方 Releases 页面确认,这里是示例格式)
cd /tmp
curl -LO https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
tar xzf node_exporter-1.8.2.linux-amd64.tar.gz
sudo cp node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/

创建 systemd 单元:

# /etc/systemd/system/node_exporter.service
[Unit]
Description=Prometheus Node Exporter
Documentation=https://github.com/prometheus/node_exporter
After=network-online.target

[Service]
Type=simple
User=nodeexp
Group=nodeexp
ExecStart=/usr/local/bin/node_exporter \
  --web.listen-address=:9100 \
  --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|var/lib/docker)($|/) \
  --collector.netclass.ignored-devices=^(veth|br-|docker|lo)$ \
  --collector.ethtool --collector.tcpstat
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadOnlyPaths=/
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo useradd -rs /bin/false nodeexp
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
sudo systemctl status node_exporter

# 验证:直接拉取指标
curl -s http://localhost:9100/metrics | head -20
curl -s http://localhost:9100/metrics | grep -c '^node_'

从发行版仓库安装也可以,但版本通常较旧:

sudo dnf install -y node_exporter          # RHEL、Rocky(部分仓库提供)
sudo apt install -y prometheus-node-exporter   # Debian、Ubuntu

防火墙(风险提示:9100 端口不应暴露到公网,它包含了大量主机信息,应该只允许 Prometheus 服务器访问):

# RHEL、Rocky、CentOS(firewalld 富规则,限定来源 IP)
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.1.100/32" port protocol="tcp" port="9100" accept'
sudo firewall-cmd --reload

# 或者使用 zone 方式
sudo firewall-cmd --permanent --new-zone=prom --set-target=ACCEPT
sudo firewall-cmd --permanent --zone=prom --add-source=10.0.1.100/32
sudo firewall-cmd --permanent --zone=prom --add-port=9100/tcp
sudo firewall-cmd --reload

# Debian、Ubuntu(ufw)
sudo ufw allow from 10.0.1.100 to any port 9100 proto tcp comment 'prometheus scrape'
sudo ufw reload

更稳妥的做法是只允许内网网段,并且 Prometheus 与被监控机之间走内网或 VPN。

node_exporter 提供几十个 collector,默认开启的是通用集合。常用的有:cpu、meminfo、diskstats、filesystem、netdev、loadavg、vmstat、systemd、uname。按需启停:--collector. 和 --no-collector.。

一个实用建议:启用 --collector.systemd(systemd collector)可以监控每个服务的状态,但要注意它会拉取所有 unit,数量多时有性能开销,用 --collector.systemd.unit-include 限定范围。

四、Prometheus 配置

# /etc/prometheus/prometheus.yml
global:
  scrape_interval: 15s        # 全局默认采集间隔
  scrape_timeout: 10s         # 单次采集超时,必须小于 interval
  evaluation_interval: 15s    # 告警规则评估间隔
  external_labels:
    env: production
    region: cn-beijing

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - 127.0.0.1:9093

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

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node'
    scrape_interval: 15s
    file_sd_configs:
      - files:
          - /etc/prometheus/targets/nodes.yml
        refresh_interval: 30s
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance

  - job_name: 'node_dc2'
    scrape_interval: 30s
    static_configs:
      - targets:
          - '10.0.2.11:9100'
          - '10.0.2.12:9100'
        labels:
          dc: dc2

配合文件服务发现(推荐,比把所有目标写死在主配置里更灵活):

# /etc/prometheus/targets/nodes.yml
- targets:
    - '10.0.1.11:9100'
    - '10.0.1.12:9100'
    - '10.0.1.13:9100'
  labels:
    role: web
    dc: dc1

- targets:
    - '10.0.1.21:9100'
    - '10.0.1.22:9100'
  labels:
    role: db
    dc: dc1

几个配置要点:

  • scrape_timeout 必须小于 scrape_interval,否则采集会堆积。
  • external_labels 用于在联邦或远程写到外部存储时区分数据来源,多集群场景必配。
  • file_sd_configs 优于 static_configs:修改目标列表不需要重启 Prometheus,它会按 refresh_interval 自动重载。
  • 使用 relabel_configs 可以在采集阶段丢弃不需要的目标、改写标签。比如用 __meta_kubernetes_* 这类内部标签做过滤。

启动服务:

# /etc/systemd/system/prometheus.service
[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target

[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
  --config.file=/etc/prometheus/prometheus.yml \
  --storage.tsdb.path=/var/lib/prometheus/data \
  --storage.tsdb.retention.time=30d \
  --web.enable-lifecycle \
  --web.listen-address=0.0.0.0:9090
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

--web.enable-lifecycle 开启后可以用 curl -X POST http://localhost:9090/-/reload 热重载配置,避免频繁重启。

校验配置文件(强烈建议在 reload 前执行):

promtool check config /etc/prometheus/prometheus.yml
promtool check rules /etc/prometheus/rules/*.yml

访问 http://:9090 打开 Web UI,在 Status → Targets 页面确认所有 target 的状态是 UP。

五、告警规则与记录规则

告警规则

# /etc/prometheus/rules/node.yml
groups:
  - name: node.rules
    rules:
      - alert: HostDown
        expr: up{job="node"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "主机失联 {{ $labels.instance }}"
          description: "{{ $labels.instance }} 已连续 2 分钟无法采集,请检查主机状态与 node_exporter。"
          runbook: "检查主机是否宕机 / node_exporter 是否运行 / 网络是否可达"

      - alert: HighCPU
        expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "CPU 持续偏高 {{ $labels.instance }}"
          description: "CPU 使用率 {{ printf \"%.1f\" $value }}% 已持续 10 分钟高于 85%。"

      - alert: LowMemory
        expr: 100 * node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "可用内存不足 {{ $labels.instance }}"

      - alert: DiskWillFillIn4Hours
        expr: |
          predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|squashfs|overlay"}[6h], 4*3600) < 0
        for: 15m
        labels:
          severity: critical
        annotations:
          summary: "磁盘将在 4 小时内写满 {{ $labels.mountpoint }}"

      - alert: HighDiskLatency
        expr: rate(node_disk_io_time_seconds_total[5m]) * 100 > 90
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "磁盘繁忙 {{ $labels.device }}"

for 字段是告警质量的关键。它表示"条件持续满足多久才真正触发",避免了瞬时抖动造成的误报。不同告警应该有不同时长:主机失联可以短(1~2 分钟),资源类问题应该长(5~15 分钟),因为短暂的高负载大多是正常的。

annotations 里除了 summary 和 description,建议加 runbook(处理手册链接)和 dashboard(对应面板链接)。值班的人半夜收到告警,最需要的是"下一步该看哪里"。

记录规则(Recording Rules)

记录规则把常用且开销大的查询预先算好存成新序列,作用是加速面板加载、简化复杂 PromQL:

# /etc/prometheus/rules/recording.yml
groups:
  - name: recording.rules
    interval: 30s
    rules:
      - record: instance:node_cpu_utilisation:rate5m
        expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

      - record: instance:node_memory_utilisation:ratio
        expr: 100 - (100 * node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)

      - record: job:node_load1_per_core:ratio
        expr: node_load1 / count without(cpu, mode) (node_cpu_seconds_total{mode="idle"})

命名约定建议采用 level:metric:operations 格式(如 instance:request_rate:avg1m),社区通用。

六、Alertmanager:路由、抑制与静默

Prometheus 只负责产生告警,Alertmanager 负责把告警变成通知:去重、分组、抑制、静默、路由到不同通道。

# /etc/alertmanager/alertmanager.yml
global:
  resolve_timeout: 5m
  smtp_smarthost: 'smtp.example.com:587'
  smtp_from: 'alert@example.com'
  smtp_auth_username: 'alert@example.com'
  smtp_auth_password: '这里填应用专用口令'          # 不要用真实邮箱密码

route:
  receiver: 'default-webhook'
  group_by: ['alertname', 'instance']
  group_wait: 30s          # 分组等待,收集同一组的其他告警一起发
  group_interval: 5m       # 同一组告警再次发送的最小间隔
  repeat_interval: 4h      # 未解决告警的重复发送间隔
  routes:
    - matchers:
        - severity="critical"
      receiver: 'oncall-phone'
      continue: false
    - matchers:
        - alertname=~"HostDown|DiskWillFillIn4Hours"
      receiver: 'ops-webhook'

receivers:
  - name: 'default-webhook'
    webhook_configs:
      - url: 'http://127.0.0.1:5001/webhook'      # 企业微信/钉钉/Slack 中转

  - name: 'ops-webhook'
    webhook_configs:
      - url: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY'
        send_resolved: true

  - name: 'oncall-phone'
    webhook_configs:
      - url: 'https://your-pager-service/hook'

inhibit_rules:
  # 主机宕机时,抑制该主机上的所有其他告警
  - source_matchers:
      - alertname="HostDown"
    target_matchers:
      - severity="warning"
    equal: ['instance']
  # critical 抑制同一 instance 的 warning
  - source_matchers:
      - severity="critical"
    target_matchers:
      - severity="warning"
    equal: ['instance']

三个时间参数决定了告警的节奏感:

参数 含义 建议值
group_wait 第一个告警到达后等待多久,把同组告警合并发送 10~30s
group_interval 同组已有告警时,多久后再发一批 5m
repeat_interval 未恢复的告警多久重发一次 3h~1d

repeat_interval 设得太短是告警疲劳的主要来源。设成 4 小时意味着同一条未恢复告警每 4 小时打扰一次,配合定期的值班巡检已经足够。真正需要高频打扰的(比如支付成功率掉零)应该有独立的通道和升级机制。

抑制规则(inhibit_rules)的价值极大:一台机器宕机会同时触发"主机失联、CPU 高、内存高、磁盘不上报、所有服务 down"十几条告警。用了抑制规则后,只发一条 "HostDown",其他全部被压掉。这是从"几十条屏,不知道先看哪条"到"一条清晰的根因告警"的关键。

临时静默(maintenance 窗口):在 Alertmanager 的 Web UI 里可以创建 Silence,按标签匹配并设定时间窗口。计划内维护之前先建 Silence,是好习惯。

七、Grafana:数据源与面板

# RHEL、Rocky、CentOS
sudo tee /etc/yum.repos.d/grafana.repo <<'EOF'
[grafana]
name=grafana
baseurl=https://rpm.grafana.com
repo_gpgcheck=1
enabled=1
gpgcheck=1
gpgkey=https://rpm.grafana.com/gpg.key
EOF
sudo dnf install -y grafana

# Debian、Ubuntu
sudo apt install -y apt-transport-https software-properties-common wget
sudo mkdir -p /etc/apt/keyrings/
wget -q -O - https://apt.grafana.com/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/grafana.gpg
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" \
  | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update && sudo apt install -y grafana

sudo systemctl enable --now grafana-server
sudo systemctl status grafana-server
# 默认监听 3000 端口,初始账号 admin / admin,首次登录强制改密码

添加数据源:Configuration → Data sources → Add data source → Prometheus,URL 填 http://localhost:9090(同机)或内网地址,其他保持默认。点 Save & test 验证。

导入现成面板:Dashboards → Import,输入 Grafana 官方面板库的 ID 即可。推荐的几个:

面板用途 说明
Node Exporter Full 主机全面指标,最经典的一个大盘
Node Exporter Quickstart 精简版主机概览,适合总量展示
Prometheus 2.0 Overview 监控 Prometheus 自身
Nginx / MySQL / Redis 官方或社区面板 按使用的组件选用

不要一开始就自己造轮子。 先用社区面板跑起来,再按自己的需求裁剪和改造。导出的 JSON 可以纳入版本管理,实现面板的版本化。

# Grafana 数据源与面板的 Provisioning(推荐做法,配置即代码)
# /etc/grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    url: http://127.0.0.1:9090
    access: proxy
    isDefault: true
    editable: false

provisioning 配置后重启 Grafana 即可自动生效,适合多实例或多环境复制。

八、存储、保留期与规模估算

Prometheus 的 TSDB 采用本地存储,每条时间序列大约占 1~2 字节/样本(压缩后),这是个很有用的估算基准。

粗略估算公式:

磁盘占用 ≈ 时间序列数 × 每秒样本数 × 保留秒数 × 2 字节
简化版:每百万活跃序列按 15 秒采集间隔保留 15 天,约需 170GB 左右

更实际的规模参考:

目标数 序列数(约) 15 天保留所需磁盘
20 台主机 15 万 15~30 GB
100 台主机 75 万 80~150 GB
只保留核心指标 大幅下降 可减一半以上

控制增长的两个手段:保留期和丢弃不必要的序列。

--storage.tsdb.retention.time=30d      # 时间保留(优先级高于 size)
--storage.tsdb.retention.size=50GB     # 按大小保留,两者可同时配

注意:Prometheus 的保留机制是按块(block)整体删除的,默认 2 小时的块。所以磁盘占用会在清理时刻临时超过 retention.size,要给足余量。修改参数后需要重启生效。

减少序列数的实用做法:

  • 在 scrape_config 里用 metric_relabel_configs 丢弃不需要的指标(比 Prometheus 端 drop 更省事)。
  • node_exporter 侧用 --no-collector. 关闭不用的采集器(比如 kube 环境的 --no-collector.wifi,或者不需要的 arp、entropy),部分采集器还支持 --collector..<子项> 精细化控制。
  • 避免高基数(high cardinality)标签:不要把 user_id、URL path、request_id 这类无限增长的取值做成标签。这是 Prometheus 最常见的失控原因——一个 path="/user/12345" 标签能让序列数瞬间暴涨到百万级。

长期存储方案:Prometheus 本地存储不是设计做永久归档的。需要长期留存(数月到数年)时,用 remote_write 接到外部存储(如 VictoriaMetrics、Mimir、Thanos,或者各类时序数据库)。中小企业通常先把本地保留期设到 30~90 天就够了。

九、轻量替代方案的定位

Prometheus + Grafana + Alertmanager 三件套功能强,但也有部署和运维成本。规模小的时候可以考虑替代:

方案 定位 优点 局限
Netdata 单机实时监控 一分钟装好,秒级精度,界面自带,几乎零配置 单机视角强,集群聚合弱,长期存储需额外配置
Zabbix 传统企业监控 推模型+Agent,自带完整告警与可视化,模板丰富,中文资料多 配置较繁琐,大规模时数据库压力大
Prometheus + Grafana 云原生标准 多维数据模型,生态最广,PromQL 表达力强 需自行组装,长期存储与高可用要额外设计
云厂商监控服务 托管方案 开通即用,与云平台资源自动关联 跨云/自建机房覆盖弱,自定义指标可能受限

选型建议:

  • 一两台机器、想立刻看到指标 → Netdata,几分钟就有结果。
  • 已经有多台机器、希望长期做趋势分析和灵活查询 → Prometheus + Grafana。
  • 传统企业 IT、团队熟悉 Zabbix、需要丰富的现成模板和中文生态 → Zabbix 依然合适。
  • 全在云上、没有专职运维 → 先开云厂商的监控服务保底,再考虑是否自建补足。

一个常见的误区是"小的配置不够,先用最全的 Prometheus"。其实反过来:先用最轻的把可观测性建立起来,监控体系的价值在于"有"而不在于"全",随着规模增长再迁移成本并不高。

十、监控系统自身的高可用与告警风暴治理

监控是基础设施,它自己也需要被监控。

最低限度要做三件事:

  1. 监控 Prometheus 自身:up{job="prometheus"}、prometheus_tsdb_head_series(序列数趋势)、prometheus_notifications_errors_total(告警发送失败)、TSDB 压缩与WAL 是否正常。
  2. 跨实例互相监控(dead man's switch 思路):两个 Prometheus 实例互相采集对方的 up,任一方挂了另一方能发出告警。如果只有一个实例,可以用 Alertmanager 的心跳机制加外部探活服务。
  3. Alertmanager 自身要冗余:配置多个实例,Prometheus 的 alertmanagers.static_configs 里写多个地址(新版支持自动发现所有实例并去重)。
alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - '10.0.1.101:9093'
            - '10.0.1.102:9093'

告警风暴治理

这是从"有监控"到"好用的监控"的分水岭。告警风暴的典型场景:一台核心交换机故障 → 50 台主机同时失联 → 每台触发 5 条告警 → 250 条通知同时到达。

治理手段按优先级:

第一,用 for 字段过滤抖动。 绝大多数告警应该是"持续 N 分钟"而不是"瞬时"。这是最有效的一步。

第二,用抑制规则做根因收敛。 如前文所示,用 inhibit_rules 让根因告警压掉衍生告警。这是从"250 条"变成"1 条"的关键。

第三,合理分组。 group_by: ['alertname', 'instance'] 会把同类告警合并成一条消息。可以按 severity 或 cluster 分组以实现不同粒度。

第四,分级。 只有真正需要人立刻处理的才走电话/短信(paging),其余走 IM 群或邮件。severity: critical 才打电话,warning 只记录。

第五,每条告警都要有可执行的处理说明。 收到告警后值班的人应该知道第一步做什么。没有 runbook 的告警等于噪音。

第六,定期清理无效告警。 每季度回顾一次过去三个月的告警:哪些从来没有触发过(可能指标选错了)、哪些触发了但从不需要处理(阈值不对)、哪些重复出现且已自动化处理(应该直接做成自动修复而不是告警)。告警是需要维护的资产,不是配完就完事的。

常见问题

该用多大机器跑 Prometheus?

参考前面估算:每百万活跃序列按 15 秒间隔采集,大约需要 170GB(15 天)。小规模(二三十台主机、十几万序列)用 2 核 4G、100GB SSD 就够,重点是磁盘 IO 要好(SSD),因为 TSDB 的压缩和查询都依赖磁盘。随着规模增长,内存是第二瓶颈(查询时会加载较多的 head block 索引)。

采集间隔设 15 秒还是 30 秒?

15 秒是社区常用默认,足够精度也够省。对故障复盘有帮助。采集间隔要与告警规则里的 rate() 窗口配合:窗口至少是间隔的 4 倍。想要更细的精度可以考虑 5 秒,但存储和查询压力会上升,一般不需要。

为什么 up 是 0 但机器明明是好的?

按顺序查:网络是否可达(从 Prometheus 服务器 curl target:9100/metrics);node_exporter 是否在运行(systemctl status);防火墙是否放行;是否有 relabel_configs 把 target 丢弃了(在 Status → Targets 页面看 Error 列,会有明确原因如 "dropped by relabel")。

告警收不到,但 Prometheus 里能看到告警已经 Firing?

问题在 Alertmanager 链路。依次检查:prometheus.yml 的 alertmanagers 配置; Prometheus Web UI 的 Alerts 页面显示 "FIRING" 说明规则已生效;看 prometheus_notifications_errors_total 指标是否增长;在 Alertmanager UI 里看有没有收到;最后检查接收端的 webhook URL 与密钥是否正确、是否被外部服务限流。

监控本身该怎么备份?

真正重要且无法重建的是配置文件和面板定义,而不是时序数据本身。prometheus.yml、rules/*.yml、alertmanager.yml、Grafana 的 provisioning 目录和导出的面板 JSON,都应纳入 git 版本管理。时序数据丢了可以重新采集,配置丢了整套体系要重建。考虑这个角度看就不会纠结要不要做 TSDB 快照。

容器环境监控用什么?

基础设施层仍用 node_exporter 监控宿主;容器层用 cAdvisor(容器 CPU/内存/网络/文件系统)或 kubelet 自带的 metrics 端点;Kubernetes 集群层还需要 kube-state-metrics 采集资源对象的状态(Deployment 副本数、Pod 状态、节点 Ready 状态)。动态环境下服务发现是关键,用 kubernetes_sd_configs 自动发现 Pod 和 Service。

总结

  • 监控分四层:基础设施、中间件、应用、业务。越往上越接近用户真实感受,四层都覆盖才能避免"机器全绿但业务已挂"。部署顺序建议先基础设施层,再补业务层,中间件和应用逐步加。
  • Prometheus 采用拉取模型(服务端主动 GET target 的 /metrics)而不是推模型,好处是用 curl 就能调试、服务端掌握 target 存活状态(up 指标)、采集节奏统一。短生命周期任务的例外场景才用 Pushgateway。
  • 数据模型是"指标名 + 标签"确定的时间序列,四种类型里 Counter 必须用 rate() / increase() 才有意义,区间窗口至少是采集间隔的 4 倍。推荐加 predict_linear() 做磁盘耗尽预测,它比固定使用率阈值更有行动价值。
  • 告警质量的关键是 for 字段(过滤瞬时抖动,资源类建议 5~15 分钟)、记录规则(预计算加速面板)、以及 Alertmanager 的 inhibit_rules 抑制(用根因告警压掉衍生告警,把 250 条收敛成 1 条)与 repeat_interval(建议 3 小时以上,避免告警疲劳)。
  • Grafana 直接导入社区面板先用起来再改造,用 provisioning 目录实现"配置即代码";不要从零自造。
  • 存储按"每条序列每样本 1~2 字节"估算,通过 retention.time 与 --storage.tsdb.retention.size 控制。严禁用 user_id、URL path 这类高基数值做标签,那是序列数失控的头号原因。长期留存用 remote_write 接外部存储。
  • 选题不必一步到位:一两台机器先用 Netdata,传统企业 IT 用 Zabbix 也完全合适,全托管环境先开云厂商监控。可观测性的价值在于"有"不在于"全"。
  • 最后:监控系统自身也需要高可用(互相采集对方的 up、Alertmanager 多实例),并且要把配置文件和面板定义纳入 git 管理——时序数据丢失可以重采,配置丢失要重建整套体系。告警规则要按季度回顾清理,它是需要持续维护的资产。