Docker Compose 搭建服务器监控告警系统:node_exporter + Prometheus + Alertmanager 钉钉推送实战

为什么个人站长也需要一套自动化监控

很多个人站长的日常是这样的:网站什么时候挂的不知道,往往是访客在群里问了一句"网站打不开了",或者自己正好打开首页发现超时,才急急忙忙登录服务器去排查。运气好的时候服务器只是短暂抽风,重启一下就好了;运气不好时,磁盘早在三天前就满了,数据库写入一直在报错,而站长对此一无所知。

手动检查的办法不是没有,比如每天 SSH 上去看一眼,或者写个 Shell 脚本定时探测。但脚本轮询有几个绕不开的问题:判断逻辑简单,只会"通不通";没有历史数据,看不出内存是不是一天天涨上去的;告警发出来之后也没有记录,没法追溯。想要一套真正能"采集指标、留存历史、自动告警"的方案,Prometheus 生态是目前开源社区里最成熟的选择,而且它完全可以用 Docker Compose 在低配 VPS 上跑起来。

这篇文章会从零开始,用 Docker Compose 部署一套由 node_exporter、Prometheus、Alertmanager 和钉钉机器人组成的监控告警系统。部署完成后,服务器 CPU、内存、磁盘任何一项出现异常,钉钉都会在手机端第一时间推送告警,网站宕机也能在几分钟内知道。

监控架构:四个组件各司其职

整套系统由四个组件组成,它们的职责可以这样理解:

  • node_exporter:跑在服务器上的采集器,负责读取 Linux 系统里的 CPU、内存、磁盘、网络等指标,通过 9100 端口对外提供指标接口;
  • Prometheus:核心的时序数据库,每隔一段时间主动去 node_exporter 拉取一次指标并存储下来,同时按照配置好的规则周期性地评估是否触发告警,默认端口 9090;
  • Alertmanager:告警管家,接收 Prometheus 推过来的告警,负责分组、去重、抑制,再按路由转发给不同的接收渠道,默认端口 9093;
  • webhook-dingtalk:一个轻量的转换器,把 Alertmanager 发来的标准 Webhook 消息转换成钉钉自定义机器人的消息格式,再推送到钉钉群里。

用一句话概括数据流向:node_exporter 采集指标,Prometheus 拉取并判断,一旦命中告警规则就交给 Alertmanager,Alertmanager 通过 webhook-dingtalk 把消息送进钉钉群。下面我们一步步把它搭起来。

第一步:在钉钉里创建自定义机器人

先准备告警的接收端。打开钉钉,进入一个你自己创建的群(比如"服务器告警"),点击群设置,找到"智能群助手",选择"添加机器人",类型选"自定义"。添加时需要设置一个安全校验方式,这里建议选择"自定义关键词",并填写关键词"告警"。

创建完成后会得到一个 Webhook 地址,形如:

https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxxxxxxxxxxxxx

这个 access_token 等同于群机器人的钥匙,任何拿到它的人都能往你的群里发消息,所以千万不要把它提交到公开的代码仓库或者写进会被分享的配置文件里。关键词校验的作用是:机器人只接受消息内容里包含"告警"两个字的请求,这样即使 Webhook 地址泄露,别人也没办法随意往群里刷消息。

第二步:编写 docker-compose.yml

在一台已经安装好 Docker 与 Docker Compose 的服务器上,新建一个目录用于存放监控相关的文件:

mkdir -p /opt/monitor/{rules,data}
cd /opt/monitor

然后创建 docker-compose.yml,内容如下:

services:
  node-exporter:
    image: prom/node-exporter:v1.8.2
    container_name: node-exporter
    restart: always
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--path.rootfs=/rootfs'
    ports:
      - "9100:9100"

  prometheus:
    image: prom/prometheus:v2.53.1
    container_name: prometheus
    restart: always
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./rules:/etc/prometheus/rules:ro
      - prom-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'
    ports:
      - "9090:9090"

  alertmanager:
    image: prom/alertmanager:v0.27.0
    container_name: alertmanager
    restart: always
    volumes:
      - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
      - alert-data:/alertmanager
    command:
      - '--config.file=/etc/alertmanager/alertmanager.yml'
    ports:
      - "9093:9093"

  webhook-dingtalk:
    image: timonwong/prometheus-webhook-dingtalk:v2.1.0
    container_name: webhook-dingtalk
    restart: always
    command:
      - '--ding.profile=webhook1=https://oapi.dingtalk.com/robot/send?access_token=你的TOKEN'
    ports:
      - "8060:8060"

volumes:
  prom-data:
  alert-data:

这里有一个新手最容易踩的坑:node_exporter 容器默认读取的是容器自己命名空间里的 /proc 和 /sys,如果不做任何处理,采集到的其实是容器自身的指标,而不是宿主机的真实负载。所以必须把宿主机的 /proc、/sys 和根目录以只读方式挂载进容器,并通过 --path.procfs、--path.sysfs、--path.rootfs 三个参数告诉 node_exporter 去这些挂载点读取数据。

Prometheus 和 Alertmanager 各自使用一个命名数据卷保存数据,这样容器升级、重建都不会丢历史数据。retention.time 参数把指标保留时间设为 30 天,小内存 VPS 上不建议留太久。webhook-dingtalk 命令里的 access_token 换成第一步拿到的真实值即可。

第三步:配置 Prometheus 采集目标

在 /opt/monitor 下创建 prometheus.yml:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

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

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

scrape_configs:
  - job_name: 'node'
    static_configs:
      - targets: ['node-exporter:9100']

scrape_interval 是 Prometheus 拉取指标的周期,evaluation_interval 是评估告警规则的周期,个人服务器用 15 秒完全够用。rule_files 指向存放告警规则的目录。alerting 段告诉 Prometheus 告警应该发给谁,这里直接用 Compose 网络里的服务名 alertmanager 加端口 9093,Compose 会自动做好容器间的 DNS 解析,这也是把四个服务放在同一个 Compose 文件里的好处——服务之间用服务名互相访问,不需要关心 IP 地址。

第四步:编写告警规则

在 rules 目录下创建 instance.yml,写入几条最常用的主机告警规则:

groups:
  - name: instance-alerts
    rules:
      - alert: 主机宕机
        expr: up == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "{{ $labels.instance }} 采集失败或主机宕机"

      - alert: CPU使用率过高
        expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.instance }} CPU 使用率超过 90%"

      - alert: 内存使用率过高
        expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.instance }} 内存使用率超过 90%"

      - alert: 磁盘使用率过高
        expr: (1 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"})) * 100 > 85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.instance }} 磁盘使用率超过 85%"

简单解释一下这几条表达式。up 是 Prometheus 内置的指标,目标正常拉取时它等于 1,等于 0 就说明采集失败或者主机挂了。CPU 的表达式稍微复杂:node_cpu_seconds_total 是 CPU 各状态累计秒数,取 mode="idle"(空闲)后用 rate 函数算出 5 分钟内的空闲率,再用 100 减去它得到使用率。内存规则用 MemAvailable 除以 MemTotal 得到可用比例,取反后就是使用率。磁盘规则里用 fstype!~"tmpfs|overlay" 过滤掉临时文件系统和 Docker 的 overlay 层,避免误报。for 子句表示指标需要持续超过阈值指定的时间才会真正触发告警,能有效过滤瞬时抖动。

规则写好后可以先本地校验语法:

docker compose exec prometheus promtool check rules /etc/prometheus/rules/instance.yml

第五步:配置 Alertmanager 转发到钉钉

创建 alertmanager.yml:

global:
  resolve_timeout: 5m

route:
  group_by: ['alertname']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: 'dingtalk'

receivers:
  - name: 'dingtalk'
    webhook_configs:
      - url: 'http://webhook-dingtalk:8060/dingtalk/webhook1/send'
        send_resolved: true

route 段控制告警的聚合策略:group_by 按告警名称分组,group_wait 30 秒内到达的同组告警会合并成一条,避免"刷屏";repeat_interval 4 小时表示同一告警如果一直没有恢复,每隔 4 小时才重复提醒一次。receivers 里把消息发给 webhook-dingtalk 服务的 /dingtalk/webhook1/send 路径,webhook1 对应启动参数里 --ding.profile 定义的名字。send_resolved 设为 true,这样故障恢复后钉钉也会收到一条"已恢复"的通知,形成闭环。

注意:如果钉钉机器人配置了自定义关键词"告警",而告警内容里恰好不包含这个词,钉钉会返回错误码 310000 拒绝推送。webhook-dingtalk 转发消息时会带上告警名称和摘要,通常包含"告警"字样,但为了稳妥,可以把关键词设置成"Prometheus"或者直接不设关键词(不推荐)。

第六步:启动并验证全链路

所有配置文件就位后,启动整套服务:

cd /opt/monitor
docker compose up -d
docker compose ps

看到四个容器都是 Up 状态后,打开浏览器访问 http://你的服务器IP:9090/targets,正常情况下 node 这个 job 的状态应该是 UP。接着访问 9093 端口可以看到 Alertmanager 的界面,里面暂时没有告警是正常的。

验证告警链路最直接的办法是人为制造一个"故障"。先在 rules/instance.yml 里临时把 CPU 阈值改成 1,然后重载配置:

docker compose restart prometheus

等待一两个评估周期(每个周期 15 秒),钉钉群里就会收到 CPU 使用率过高的告警。把阈值改回 90 并再次重启 Prometheus,过一会儿又会收到恢复通知。如果收不到消息,按照这个顺序排查:先看 9090 的 Targets 是否 UP,再看 9090 的 Alerts 页面里规则是否处于 firing 状态,接着看 9093 页面有没有收到告警,最后看 webhook-dingtalk 的容器日志里有没有报错:

docker compose logs webhook-dingtalk --tail 50

如果日志里出现 310000 之类的钉钉错误码,基本可以断定是关键词不匹配或者 access_token 填错。

安全提醒与资源占用

Prometheus 生态的几个端口默认都没有鉴权,9100、9090、9093、8060 如果直接暴露在公网上,任何人都能读到你的服务器指标,甚至远程触发一些管理接口,这等于把服务器的体检报告贴在了大门口。稳妥的做法是让这些端口只监听本机:把 compose 文件里的端口映射改成 "127.0.0.1:9090:9090" 这种形式,平时通过 SSH 隧道访问;或者用云防火墙只放行自己的 IP。node_exporter 的 9100 端口其实根本不需要对宿主机以外开放,Prometheus 容器走的是 Compose 内部网络,把它的 ports 映射整个删掉也不影响采集。

资源占用方面,这套系统非常轻量:node_exporter 几乎可以忽略不计,Prometheus 在单机小规模采集下内存占用通常在两三百兆以内,配合 30 天的保留策略,磁盘占用每天几十兆而已,1G 内存的 VPS 也能跑得动。如果你不想用钉钉,也可以删掉 webhook-dingtalk 服务,在 Alertmanager 里改用 email_config 直接发邮件,比如使用 QQ 邮箱的 SMTP 服务,服务器地址 smtp.qq.com,端口 465,发信账号填 QQ 邮箱地址,密码填邮箱设置里生成的授权码。

监控系统属于"平时用不上、关键时刻救命"的基础设施。把它搭好之后,你只需要在手机里把钉钉群的消息通知打开,剩下的就交给规则去盯。网站半夜宕机、磁盘悄悄写满这些事,从此都会在第一时间变成一条推送出现在你眼前,而不是等用户来告诉你。

Last modification:September 8th, 2026 at 08:00 am

Leave a Comment