监控、日志与告警体系
上线之后最危险的状态不是「出故障」,而是出故障两小时后才由用户告诉你。监控要解决的就是这件事:让你比用户早一步知道。它不需要一开始就上 Prometheus + Grafana + 全链路追踪那一整套,一台服务器加一个免费拨测、一个机器人告警,就已经能覆盖绝大多数事故场景。本章按四层讲清最小可用方案。
要解决的问题
- 监控到底要盯什么,四层各看什么指标?
- 最省钱又能救命的最小组合是什么?
- 没有监控系统,怎么用一条
curl加定时任务做出报警? - 日志怎么轮转,磁盘怎么不被写满?
- 告警太多没人看怎么办?
- 个人站该做到什么程度才算「不裸奔」?
监控的四个层次
| 层次 | 看什么 | 常用手段 | 缺了这一层会怎样 |
|---|---|---|---|
| 可用性 | 站点能不能打开、接口是否 200 | 外部拨测、curl 定时任务、免费监控服务 | 用户先发现问题,你最后知道 |
| 资源 | CPU、内存、磁盘、inode、连接数、句柄 | node_exporter + Prometheus + Grafana、云监控 | 磁盘满了整机不可用,却毫无征兆 |
| 应用 | 错误率、响应延迟、异常栈、队列积压 | 应用日志关键字告警、APM、journalctl | 表现为「站点能开但功能坏了」 |
| 业务 | 登录数、下单数、支付成功率 | 业务看板与同比阈值 | 技术指标全绿,但业务已经出事 |
排障顺序是自上而下:用户报障 → 看可用性 → 看应用错误率 → 看资源水位 → 看业务指标。监控建设的顺序则相反:先把可用性和资源做上,再补应用与业务。
最小可用方案
| 手段 | 实现 | 成本 | 覆盖范围 |
|---|---|---|---|
| 外部拨测 | 免费监控服务(UptimeRobot 一类)或自建脚本 | 0 | DNS、证书、HTTP 状态码、响应时间 |
| 基础资源 | 云监控告警 + df -h/free 定时巡检 | 0 | 磁盘、内存、CPU 突增 |
| 应用日志告警 | grep 关键字 + 机器人推送 | 0 | 异常堆栈、启动失败 |
| 指标系统 | node_exporter + Prometheus + Grafana | 一台小机器 | 趋势、历史对比、阈值告警 |
node_exporter 暴露指标,Prometheus 定时抓取并做阈值规则,Grafana 负责看图——这套组合的好处是能看到趋势(「磁盘每天涨 2%,还够 10 天」),比只看「现在 88%」有价值得多;代价是多一个常驻进程和一台机器。
# 外部拨测:3 分钟一次,失败就推机器人(放到 cron 里,注意加锁避免叠加)
#!/usr/bin/env bash
# /opt/scripts/healthcheck.sh
set -uo pipefail
URL="https://example.com/healthz"
CODE=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$URL")
if [ "$CODE" != "200" ]; then
curl -s -X POST -H 'Content-Type: application/json' \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$URL 异常:$CODE\"}}" "$WEBHOOK_URL"
fi
# /etc/cron.d/healthcheck
*/3 * * * * root flock -n /tmp/hc.lock /opt/scripts/healthcheck.sh
资源阈值与日志告警
| 指标 | 查看命令 | 告警阈值 | 说明 |
|---|---|---|---|
| 负载 | uptime | 15 分钟负载 > 核数 × 1.5 | 要看持续值,不看瞬时 |
| 磁盘 | df -h | 使用率 > 85% | 提前告警,别等写满 |
| inode | df -i | 使用率 > 85% | 小文件多时 inode 先满,报错仍是「磁盘满」 |
| 内存 | free -m | 可用 < 10%,或 swap 持续换入换出 | 看 available 而不是 free |
| 连接数 | ss -s | ESTAB 较基线突增 3 倍 | 可能是攻击,也可能是连接泄漏 |
| 服务存活 | systemctl is-active nginx | 非 active | 立即告警 |
# 应用日志关键字告警:最近 10 分钟出现 ERROR 超过 20 条就推送
COUNT=$(journalctl -u myapp --since '-10 min' --no-pager | grep -c -E 'ERROR|Traceback|panic')
[ "$COUNT" -gt 20 ] && curl -s -X POST -H 'Content-Type: application/json' \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"myapp 10 分钟内 $COUNT 条错误\"}}" "$WEBHOOK_URL"
# Nginx 状态码分布与慢请求(日志格式见第 14 章)
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
awk '$9 ~ /^5/ {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
grep -o 'rt=[0-9.]*' /var/log/nginx/access.log | cut -d= -f2 | sort -rn | head -5
日志规范与轮转
日志要能回答三个问题:什么时候、哪个请求、为什么错。所以应用日志至少带上时间、级别、请求 ID、耗时;Nginx 侧保证 rt= 与 urt= 都在(见第 14 章)。日志落盘后必须配轮转,否则磁盘写满是迟早的事。
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
[ -f /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid)
endscript
}
# systemd 服务的日志交给 journald,用 vacuum 控制总量
journalctl -u myapp --since '1 hour ago' -p err --no-pager
sudo journalctl --vacuum-time=14d
sudo journalctl --vacuum-size=500M
# Docker 容器日志必须在 daemon.json 里限大小,否则默认无限增长
# {"log-driver":"json-file","log-opts":{"max-size":"50m","max-file":"5"}}
告警降噪
| 噪音来源 | 表现 | 降噪做法 |
|---|---|---|
| 阈值太敏感 | 半夜几十条告警,第二天没人看 | 按 P99 设阈值并要求「持续 5 分钟」 |
| 不做聚合 | 一台机器发 20 条,10 台发 200 条 | 按服务聚合,一条告警带实例列表 |
| 不分级别 | 磁盘 60% 也打电话 | 分 P0/P1/P2,只有 P0 才电话 |
| 不通知恢复 | 不知道什么时候好了 | 配恢复通知,形成闭环 |
| 没有归属 | 大家看到了但没人动 | 明确值班人与升级路径 |
告警的价值在于「看到就会动」。一条没人理的告警,比没有告警更糟——它会训练团队忽略所有告警。
常见坑
| 坑 | 现象 | 做法 |
|---|---|---|
| 只监控服务器不监控站点 | CPU 正常但站点 502 | 外部拨测必须独立于本机 |
| 拨测地址用内网或本机 | 本机网络故障查不出来 | 拨测走公网域名 |
| 容器日志无大小限制 | 磁盘被日志写满 | log-opts max-size/max-file |
| 日志不轮转 | 磁盘增速远超预期 | logrotate + journald vacuum |
| 阈值按瞬时值告警 | 抖动就报警,被无视 | 加持续时长条件 |
| 只告警不记录基线 | 无法判断「突增」 | 记录一份正常水位基线 |
| 没有监控就上线 | 出事全靠用户反馈 | 上线清单里必须有拨测与告警 |
没有监控的上线等于裸奔。个人站的最小组合就是三件事:一个免费的第三方拨测(覆盖可用性与证书到期)、一条磁盘/内存巡检、一个机器人告警群。加起来不到一小时就能配完,却能让你在用户投诉之前动手。
小结:监控按可用性、资源、应用、业务四层建设,先做前两层再补后两层;最小可用方案是「外部公网拨测 + 磁盘内存巡检 + 应用日志关键字告警 + 机器人推送」,有预算再加 node_exporter/Prometheus/Grafana 看趋势;日志必须配轮转并对容器日志限大小;告警的关键不是数量而是「每条都有人动」,用持续时长、聚合、分级和明确归属来降噪——监控的目的只有一个:比用户更早知道。