监控、日志与告警体系

上线之后最危险的状态不是「出故障」,而是出故障两小时后才由用户告诉你。监控要解决的就是这件事:让你比用户早一步知道。它不需要一开始就上 Prometheus + Grafana + 全链路追踪那一整套,一台服务器加一个免费拨测、一个机器人告警,就已经能覆盖绝大多数事故场景。本章按四层讲清最小可用方案。

要解决的问题

  • 监控到底要盯什么,四层各看什么指标?
  • 最省钱又能救命的最小组合是什么?
  • 没有监控系统,怎么用一条 curl 加定时任务做出报警?
  • 日志怎么轮转,磁盘怎么不被写满?
  • 告警太多没人看怎么办?
  • 个人站该做到什么程度才算「不裸奔」?

监控的四个层次

层次看什么常用手段缺了这一层会怎样
可用性站点能不能打开、接口是否 200外部拨测、curl 定时任务、免费监控服务用户先发现问题,你最后知道
资源CPU、内存、磁盘、inode、连接数、句柄node_exporter + Prometheus + Grafana、云监控磁盘满了整机不可用,却毫无征兆
应用错误率、响应延迟、异常栈、队列积压应用日志关键字告警、APM、journalctl表现为「站点能开但功能坏了」
业务登录数、下单数、支付成功率业务看板与同比阈值技术指标全绿,但业务已经出事

排障顺序是自上而下:用户报障 → 看可用性 → 看应用错误率 → 看资源水位 → 看业务指标。监控建设的顺序则相反:先把可用性和资源做上,再补应用与业务

最小可用方案

手段实现成本覆盖范围
外部拨测免费监控服务(UptimeRobot 一类)或自建脚本0DNS、证书、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

资源阈值与日志告警

指标查看命令告警阈值说明
负载uptime15 分钟负载 > 核数 × 1.5要看持续值,不看瞬时
磁盘df -h使用率 > 85%提前告警,别等写满
inodedf -i使用率 > 85%小文件多时 inode 先满,报错仍是「磁盘满」
内存free -m可用 < 10%,或 swap 持续换入换出available 而不是 free
连接数ss -sESTAB 较基线突增 3 倍可能是攻击,也可能是连接泄漏
服务存活systemctl is-active nginxactive立即告警
# 应用日志关键字告警:最近 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 看趋势;日志必须配轮转并对容器日志限大小;告警的关键不是数量而是「每条都有人动」,用持续时长、聚合、分级和明确归属来降噪——监控的目的只有一个:比用户更早知道。

笔记加载中…