Prometheus + Grafana + Alertmanager + 钉钉告警 完整部署
适用:Ubuntu 24.04 | IP:192.168.7.7(示例) | 一键监控 节点+容器,钉钉告警 已用轩辕专属加速通道,镜像秒拉;如果没有的话会拉取失败
一、系统环境准备
# 更新软件源并安装基础工具(含 tree,验证目录用)
apt update && apt install -y curl wget vim tree net-tools二、安装 Docker
# 安装 docker 依赖与 GPG 密钥(阿里云源)
apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 添加 docker 软件源(阿里云镜像)
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装 docker 与 compose 插件
apt update && apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 开机自启
systemctl enable docker && systemctl start docker
# 验证安装
docker --version && docker compose version2.1 配置镜像加速(轩辕专属通道,先备份)
# 备份原配置
cp /etc/docker/daemon.json /etc/docker/daemon.json.bak 2>/dev/null
# 写入加速配置(JSON 不能带注释;dns 用国内,8.8.8.8 容器解析会失败)
cat > /etc/docker/daemon.json << 'EOF'
{
"registry-mirrors": [
"https://ry6uhvsydk0j90kjv4.xuanyuan.dev",
"https://hub.rat.dev",
"https://docker.1panel.live",
"https://dockerpull.org",
"https://docker.m.daocloud.io"
],
"dns": ["223.5.5.5", "119.29.29.29"],
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
EOF
# 加载新配置并重启 docker
systemctl daemon-reload && systemctl restart docker💡 轩辕专属通道(必读)
ry6uhvsydk0j90kjv4.xuanyuan.dev= Docker Hub 专属 → 已配第一位,docker pull nginx这类官方短名自动走它,最快
ry6uhvsydk0j90kjv4-gcr.xuanyuan.dev= gcr.io 专属 → cadvisor 等 gcr 镜像用(见 4.1 image,把官方gcr.io/前缀换成它)daocloud 有白名单,冷门镜像会 denied,故排最后兜底;另两个免费源兜底
三、创建目录
# 创建配置目录(数据目录不用建,compose 命名卷自动管理权限)
mkdir -p /root/prom/yml /root/prom/rules
cd /root/prom四、创建配置文件
4.1 docker-compose.yml
cat > /root/prom/docker-compose.yml << 'EOF'
services:
node-exporter: # 节点监控(CPU/内存/磁盘),告警主要靠它
image: prom/node-exporter:v1.8.2
container_name: node-exporter
restart: always
ports: ["9100:9100"]
# 关键:挂载宿主 /,否则只看到容器自身,磁盘/文件系统指标全失效
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.rootfs=/host'
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
cadvisor: # 容器监控(每容器的 CPU/内存/网络)
image: ry6uhvsydk0j90kjv4-gcr.xuanyuan.dev/cadvisor/cadvisor:v0.47.0 # 轩辕 gcr 专属(官方镜像)
container_name: cadvisor
restart: always
ports: ["8080:8080"]
privileged: true
devices: [/dev/kmsg]
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
prometheus: # 抓取指标+算告警规则
image: prom/prometheus:v2.54.1
container_name: prometheus
restart: always
ports: ["9090:9090"]
volumes:
- "./yml/prometheus.yml:/etc/prometheus/prometheus.yml"
- "prometheus_data:/prometheus" # 数据卷,持久化
- "./rules:/etc/prometheus/rules"
grafana: # 可视化
image: grafana/grafana:11.1.0
container_name: grafana
restart: always
ports: ["3000:3000"]
environment:
- GF_SECURITY_ADMIN_PASSWORD=Admin@2026 # 默认密码,首登后改
volumes:
- "grafana_data:/var/lib/grafana"
alertmanager: # 告警收敛/分发
image: prom/alertmanager:v0.27.0
container_name: alertmanager
restart: always
ports: ["9093:9093"]
volumes:
- "./yml/alertmanager.yml:/etc/alertmanager/alertmanager.yml"
webhook: # 把告警转成钉钉消息
image: timonwong/prometheus-webhook-dingtalk:v2.0.0
container_name: webhook
restart: always
ports: ["8060:8060"]
volumes:
- "./yml/config.yml:/etc/prometheus-webhook-dingtalk/config.yml"
volumes:
prometheus_data:
grafana_data:
EOF⚠️ cadvisor 勿用 bitnami(已停更 denied)、zcube/google 版(免费源回源极慢),就走轩辕 gcr 官方镜像。
4.2 yml/prometheus.yml
cat > /root/prom/yml/prometheus.yml << 'EOF'
global:
scrape_interval: 15s # 抓取频率;测试用 15s 秒级反馈,上线可调 1m
evaluation_interval: 15s # 规则评估频率
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"] # 用 compose 服务名,勿写宿主 IP
rule_files:
- "/etc/prometheus/rules/*.yml"
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
- job_name: "node" # 节点指标(node-exporter)
static_configs:
- targets: ["node-exporter:9100"]
- job_name: "cadvisor" # 容器指标(cadvisor)
static_configs:
- targets: ["cadvisor:8080"]
EOF4.3 yml/alertmanager.yml
cat > /root/prom/yml/alertmanager.yml << 'EOF'
global:
resolve_timeout: 5m
route:
receiver: webhook
group_wait: 10s # 首个告警等 10s 再发(测试秒级看到;生产 30s)
group_interval: 5m
repeat_interval: 2h # 未恢复时重发间隔,生产 2-4h 防轰炸;测试可 1m
group_by: [alertname]
receivers:
- name: webhook
webhook_configs:
- url: http://webhook:8060/dingtalk/webhook1/send # compose 服务名
send_resolved: true # 恢复后也发通知
EOF4.4 yml/config.yml(钉钉机器人)
cat > /root/prom/yml/config.yml << 'EOF'
targets:
webhook1: # 与 alertmanager url 中的 webhook1 对应
url: https://oapi.dingtalk.com/robot/send?access_token=请替换为你的access_token
secret: 请替换为你的加签密钥
EOF⚠️ token/secret 在钉钉群建机器人后获取(见第六步)。安全设置选「加签」即可,不用再勾关键词。
4.5 rules/rules.yml(告警规则)
cat > /root/prom/rules/rules.yml << 'EOF'
groups:
- name: 系统告警
rules:
# CPU: (1-idle比例)*100 = 使用率
- alert: CPU使用率告警
expr: (1 - avg by (instance)(rate(node_cpu_seconds_total{mode="idle"}[2m]))) * 100 > 50
for: 1m # 持续 1m 才触发,防抖动
labels: { severity: warning }
annotations:
summary: "告警 CPU使用率正在飙升"
description: "CPU使用率超过50%(当前 {{ $value }}%)"
# 磁盘:只盯根分区 /,避免 /boot 等小分区误报
- alert: 磁盘使用率告警
expr: (1 - node_filesystem_free_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 > 80
for: 1m
labels: { severity: warning }
annotations:
summary: "告警 硬盘分区使用率过高"
description: "根分区使用率超过80%(当前 {{ $value }}%)"
- alert: 内存使用率告警
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 50
for: 1m
labels: { severity: warning }
annotations:
summary: "告警 服务器可用内存不足"
description: "内存使用率超过50%(当前 {{ $value }}%)"
# up==0 = 抓取目标宕机
- alert: 实例存活告警
expr: up == 0
for: 1m
labels: { severity: warning }
annotations:
summary: "告警 主机宕机"
description: "实例 {{ $labels.instance }} 已宕机"
EOF⚠️ CPU 公式必须乘 100(否则恒>50 永久告警);description 含「告警」兼容只开关键词的机器人。
五、启动
# 进入项目目录
cd /root/prom
# 拉镜像并启动所有服务(轩辕通道,约 1 分钟内完成)
docker compose up -d
# 查看状态:6 个容器全 Up 即正常
docker compose ps⚠️ 报端口占用:
docker compose down清掉旧实例再 up;ss -tlnp | grep -E ':(9100|8080|9090|9093|8060|3000)'查占用者。
六、配置钉钉机器人
钉钉群 → 群设置 → 智能群助手 → 添加机器人 → 自定义
命名「监控告警」;安全设置选「加签」,复制
secret(SEC 开头)与 Webhook 地址(含 access_token)填入
yml/config.yml(4.4),然后:
# 重启 webhook/alertmanager,使新 token 生效
cd /root/prom && docker compose restart webhook alertmanager
# 验证 webhook 日志(告警触发后应看到 resp_status=200)
docker logs webhook --tail=20七、测试告警(端到端)
# 1) 停 node-exporter → 触发"实例存活告警"
docker stop node-exporter
# 2) 看状态:http://192.168.7.7:9090/alerts INACTIVE→PENDING→FIRING(约1-2分钟)
# 3) 钉钉群应收到告警(15s 评估+group_wait 10s,很快)
# 4) 恢复,验证"恢复通知"
docker start node-exporter# 调试(收不到时看这两个日志)
docker logs alertmanager --tail=30
docker logs webhook --tail=30八、访问与 Grafana
Grafana:Configuration → Data Sources → Prometheus → URL http://prometheus:9090 → Save & Test 导入面板:+ → Import → ID 1860(Node Exporter)/14282(cAdvisor) → Load → 选数据源 → Import
九、常用命令
# 进入项目目录
cd /root/prom
# 启动/更新所有服务
docker compose up -d
# 停止所有服务(数据卷保留)
docker compose down
# 重启单个服务
docker compose restart prometheus
# 实时查看日志
docker compose logs -f
# 查看服务状态
docker compose ps十、常见问题排查
十一、架构图
┌────────────────────────────────────────────────────────────┐
│ 虚拟机 192.168.7.7 │
│ ┌───────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐ │
│ │node-exporter││ cAdvisor │ │ Grafana │ │ webhook │ │
│ │ 9100 │ │ 8080 │ │ 3000 │ │ 8060 │ │
│ └─────┬──────┘ └────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ └─────────────┼─────────────┘ │ │
│ ┌───▼───┐ │ │
│ │Prometheus│ (服务名互通) │ │
│ │ 9090 │ │ │
│ └───┬───┘ │ │
│ ┌───▼────┐ │ │
│ │Alertmanager│ 9093 ────────────┘ │
│ └─────────┘ │
└─────────────────────────────┬──────────────────────────────┘
│ HTTPS
┌──────▼──────┐
│ 钉钉机器人 │ (加签)
└─────────────┘附(可选方案 B):自定义钉钉消息样式 —— 自研 Webhook
正文为方案 A:用 timonwong 镜像默认格式(头部 [FIRING]/[RESOLVED] 区分告警/恢复)。 本附录为方案 B(已实测):把 webhook 换成自研 Python 服务,消息样式完全自定(
🚨 告警中/✅ 已恢复)。 照抄可执行:token/secret 会自动从 4.4 的yml/config.yml读取,无需手填;不影响其它配置,可随时切回 A。
B1. 新增脚本 /root/prom/dingtalk_webhook.py
cat > /root/prom/dingtalk_webhook.py << 'EOF'
#!/usr/bin/env python3
# 自研钉钉告警 Webhook:Alertmanager -> 自定义消息 -> 钉钉
import os, json, time, hmac, hashlib, base64, urllib.parse, urllib.request
from http.server import BaseHTTPRequestHandler, HTTPServer
TOKEN = os.environ.get("DING_TOKEN", "")
SECRET = os.environ.get("DING_SECRET", "")
DING_URL = "https://oapi.dingtalk.com/robot/send"
def send_ding(title, text):
ts = str(round(time.time()*1000)) # 钉钉加签(timestamp+secret)
s = urllib.parse.quote_plus(base64.b64encode(
hmac.new(SECRET.encode(), f"{ts}\n{SECRET}".encode(), hashlib.sha256).digest()))
url = f"{DING_URL}?access_token={TOKEN}×tamp={ts}&sign={s}"
body = {"msgtype": "markdown", "markdown": {"title": title, "text": text}}
req = urllib.request.Request(url, data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"})
urllib.request.urlopen(req, timeout=10)
def build(msg):
title, parts = "监控通知", []
for a in msg.get("alerts", []):
st = a.get("status", "firing")
L, A = a.get("labels", {}), a.get("annotations", {})
name, inst, job, sev = L.get("alertname","?"), L.get("instance",""), L.get("job",""), L.get("severity","")
if st == "firing": # 🔴 告警中
title = f"🚨【告警中】{name}"
parts.append(f"🚨 **{name}**\n- 级别:{sev} | 主机:{inst} ({job})\n- 详情:{A.get('description','')}")
else: # 🟢 已恢复
title = f"✅【已恢复】{name}"
parts.append(f"✅ **{name} 已恢复**\n- 主机:{inst} ({job}) 已恢复正常")
body = "\n\n".join(parts) + f"\n\n📋 告警面板:[点此查看]({msg.get('externalURL','')})"
return title, body
class H(BaseHTTPRequestHandler):
def do_POST(self):
try:
msg = json.loads(self.rfile.read(int(self.headers.get("Content-Length",0))) or b"{}")
t, b = build(msg); send_ding(t, b)
self.send_response(200); self.end_headers(); self.wfile.write(b"ok")
except Exception as e:
self.send_response(500); self.end_headers(); self.wfile.write(str(e).encode())
def log_message(self, *a): pass
HTTPServer(("0.0.0.0", 8060), H).serve_forever()
EOFB2. 一键替换 docker-compose.yml 的 webhook 段(自动读 token/secret)
# 在 /root/prom 下执行:自动把 webhook 服务换成自研容器,并从 yml/config.yml 读 token/secret
cd /root/prom && python3 << 'PY'
import re
c = open('docker-compose.yml').read()
cfg = open('yml/config.yml').read()
tok = re.search(r'access_token=([A-Za-z0-9]+)', cfg).group(1)
sec = re.search(r'secret:\s*(\S+)', cfg).group(1)
new = f''' webhook: # 告警 -> 自定义消息 -> 钉钉
image: python:3.11-slim
container_name: webhook
restart: always
ports: ["8060:8060"]
environment:
- DING_TOKEN={tok}
- DING_SECRET={sec}
- TZ=Asia/Shanghai
volumes:
- "./dingtalk_webhook.py:/app/dingtalk_webhook.py:ro"
command: ["python", "/app/dingtalk_webhook.py"]
'''
c2 = re.sub(r' webhook:.*?(?=^volumes:)', new, c, flags=re.S|re.M)
assert c2 != c, '替换未生效,请检查 compose 里 webhook 段'
open('docker-compose.yml','w').write(c2)
print('OK: webhook 段已替换')
PY替换后 config.yml 不再被使用,可留作备份。
B3. 修改 alertmanager.yml 的 url(指向自研服务)
# 把 webhook 接收地址改成自研服务(任何 POST 都收,路径随意)
sed -i 's#http://webhook:8060/dingtalk/webhook1/send#http://webhook:8060/#' /root/prom/yml/alertmanager.yml
grep 'url:' /root/prom/yml/alertmanager.yml # 应显示 http://webhook:8060/B4. 重建并测试
# 1) 重建 webhook(拉取 python:3.11-slim 镜像)+ alertmanager
cd /root/prom && docker compose up -d webhook alertmanager
# 2) 手动发测试,验证自定义格式(返回 ok = 钉钉已收到)
# 已恢复:
curl -s -X POST http://127.0.0.1:8060/ -H 'Content-Type: application/json' -d '{"status":"resolved","externalURL":"http://192.168.7.7:9093","alerts":[{"status":"resolved","labels":{"alertname":"实例存活告警","severity":"warning","instance":"node-exporter:9100","job":"node"},"annotations":{"description":"实例 node-exporter:9100 已宕机"}}]}'
# 告警中:
curl -s -X POST http://127.0.0.1:8060/ -H 'Content-Type: application/json' -d '{"status":"firing","externalURL":"http://192.168.7.7:9093","alerts":[{"status":"firing","labels":{"alertname":"CPU使用率告警","severity":"warning","instance":"node-exporter:9100","job":"node"},"annotations":{"description":"CPU使用率超过50%"}}]}'预期钉钉:第一条 ✅【已恢复】实例存活告警,第二条 🚨【告警中】CPU使用率告警。 格式由脚本
build()决定,想加"宕机时长/时间/emoji"等改脚本后docker compose restart webhook即可。
B5. 回退到方案 A(及验证、再切回 B)
# ① 一键回退 compose 的 webhook 段为 timonwong v2(A 版,恢复 config.yml 挂载)
cd /root/prom && python3 << 'PY'
import re
c = open('docker-compose.yml').read()
new = ''' webhook: # 把告警转成钉钉消息(A 方案)
image: timonwong/prometheus-webhook-dingtalk:v2.0.0
container_name: webhook
restart: always
ports: ["8060:8060"]
environment:
- TZ=Asia/Shanghai
volumes:
- "./yml/config.yml:/etc/prometheus-webhook-dingtalk/config.yml"
'''
c2 = re.sub(r' webhook:.*?(?=^volumes:)', new, c, flags=re.S|re.M)
assert c2 != c, '替换未生效,请检查 compose 里 webhook 段'
open('docker-compose.yml','w').write(c2)
print('OK: 已回退 webhook 段为 A 版')
PY
# ② 还原 alertmanager 接收地址(v2 专用路径)
sed -i 's#http://webhook:8060/#http://webhook:8060/dingtalk/webhook1/send#' /root/prom/yml/alertmanager.yml
# ③ 重建生效
cd /root/prom && docker compose up -d webhook alertmanager回退后如何验证 A 生效:消息必须含「告警」关键词(机器人勾了关键词校验),返回 OK 即钉钉已收到默认格式:
curl -s -X POST http://127.0.0.1:8060/dingtalk/webhook1/send -H 'Content-Type: application/json' -d '{"version":"4","status":"firing","receiver":"webhook","groupLabels":{"alertname":"测试"},"externalURL":"http://192.168.7.7:9093","alerts":[{"status":"firing","labels":{"alertname":"测试","severity":"warning","instance":"node-exporter:9100"},"annotations":{"summary":"告警 回退测试","description":"告警 已回到A方案"}}]}'
# 预期:返回 OK;钉钉消息头部为 [FIRING:1],正文是默认 Description/Graph/Details 格式想再用方案 B:重复执行 B2(替换 compose)→ B3(改 url)→ B4(重建测试) 即可,脚本 B1 已存在无需重建。
💡 无论 A/B:钉钉机器人勾了「关键词:告警」时,测试消息里必须含"告警"两字,否则钉钉返回 400 拒收(报 Unable to talk to DingTalk)。
Prometheus + Grafana + Alertmanager + 钉钉告警 完整部署
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法