适用:所有 Linux 耗时:约 30 分钟 前置:S5(会定时任务)、E3(会看日志)
服务器的特点是:它挂了不会告诉你。
桌面电脑卡住了你会马上发现。服务器磁盘满了、内存耗尽、服务默默退出——你可能几天后才知道,因为用户先发现了。
这一篇讲最朴素但最有效的监控:自己写脚本,出了问题主动报警。
一、先明确:监控什么
别一上来就上 Prometheus + Grafana。 那是大集群的方案。
单台小服务器,监控这五项就够:
| 项目 | 阈值 | 后果 |
|---|---|---|
| 磁盘使用率 | > 85% | 写不进日志、服务崩溃、数据库损坏 |
| 内存使用率 | > 90% | OOM Killer 杀进程 |
| CPU 负载 | 持续 > 核数 | 服务响应慢 |
| 服务存活 | 挂了 | 直接不可用 |
| 证书到期 | < 15 天 | HTTPS 突然全线报错 |
这五项覆盖 95% 的"服务器突然出问题"。
二、五个基础检查命令
# 1. 磁盘
df -h / | awk 'NR==2 {print $5}'
# 2. 内存
free | awk '/^Mem:/ {printf "%.0f", $3/$2*100}'
# 3. CPU 负载
cat /proc/loadavg | awk '{print $1}'
nproc # 核数
# 4. 服务
systemctl is-active nginx
# 5. 证书剩余天数
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2
三、一个完整的监控脚本
把五项打包,超阈值就报警。
nano ~/bin/monitor.sh
#!/bin/bash
# 系统健康检查:磁盘/内存/负载/服务/证书
set -uo pipefail
# ===== 配置 =====
DISK_THRESHOLD=85
MEM_THRESHOLD=90
LOAD_FACTOR=2 # 负载 > 核数*2 才报警
SERVICES="nginx" # 空格分隔多个
DOMAIN="" # 有域名填上,比如 example.com
CERT_DAYS=15
LOG="$HOME/.monitor.log"
# ===== 报警函数(改成你的方式)=====
alert() {
local msg="$1"
echo "[$(date '+%F %T')] ⚠ $msg" | tee -a "$LOG"
# 方式 1:写进系统日志
# logger -t monitor "$msg"
# 方式 2:发邮件(需要配 MTA)
# echo "$msg" | mail -s "服务器告警" you@example.com
# 方式 3:调 webhook(飞书/钉钉/企业微信)
# curl -s -X POST -H 'Content-Type: application/json' \
# -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$msg\"}}" \
# 'https://open.feishu.cn/open-apis/bot/v2/hook/xxx'
}
ok() { echo "[$(date '+%F %T')] ✓ $1" >> "$LOG"; }
# ===== 1. 磁盘 =====
DISK=$(df / | awk 'NR==2 {gsub(/%/,"",$5); print $5}')
if [ "$DISK" -ge "$DISK_THRESHOLD" ]; then
alert "磁盘使用率 ${DISK}% (阈值 ${DISK_THRESHOLD}%)"
else
ok "磁盘 ${DISK}%"
fi
# ===== 2. 内存 =====
MEM=$(free | awk '/^Mem:/ {printf "%.0f", $3/$2*100}')
if [ "$MEM" -ge "$MEM_THRESHOLD" ]; then
alert "内存使用率 ${MEM}% (阈值 ${MEM_THRESHOLD}%)"
else
ok "内存 ${MEM}%"
fi
# ===== 3. 负载 =====
LOAD1=$(awk '{print $1}' /proc/loadavg)
CORES=$(nproc)
LIMIT=$((CORES * LOAD_FACTOR))
# 用整数比较,避免浮点
LOAD_INT=${LOAD1%.*}
if [ "${LOAD_INT:-0}" -ge "$LIMIT" ]; then
alert "CPU 负载 ${LOAD1} (核数 ${CORES},阈值 ${LIMIT})"
else
ok "负载 ${LOAD1}/${CORES}核"
fi
# ===== 4. 服务 =====
for svc in $SERVICES; do
if systemctl is-active --quiet "$svc"; then
ok "服务 $svc 正常"
else
alert "服务 $svc 未运行!"
fi
done
# ===== 5. 证书 =====
if [ -n "$DOMAIN" ]; then
EXPIRY=$(echo | openssl s_client -connect "$DOMAIN:443" -servername "$DOMAIN" 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -n "$EXPIRY" ]; then
EXP_TS=$(date -d "$EXPIRY" +%s 2>/dev/null || echo 0)
NOW_TS=$(date +%s)
DAYS=$(( (EXP_TS - NOW_TS) / 86400 ))
if [ "$DAYS" -lt "$CERT_DAYS" ]; then
alert "证书 $DOMAIN 还有 ${DAYS} 天到期"
else
ok "证书 $DOMAIN 剩余 ${DAYS} 天"
fi
else
alert "无法检查证书 $DOMAIN"
fi
fi
chmod +x ~/bin/monitor.sh
~/bin/monitor.sh
cat ~/.monitor.log
四、报警怎么送到你手上
脚本能发现问题,但得让你知道。
| 方式 | 难度 | 适合 |
|---|---|---|
| 写日志文件 | 最简单 | 你会主动去看 |
logger + journalctl | 简单 | 配合其他日志工具 |
| 发邮件 | 中(要配 MTA) | 传统做法,容易被当垃圾邮件 |
| Webhook(飞书/钉钉/企业微信) | 中 | 推荐,手机立刻收到 |
| 短信 | 要钱 | 关键系统 |
飞书机器人(推荐)
- 飞书群 → 设置 → 群机器人 → 添加 → 自定义机器人
- 复制 webhook 地址
- 脚本里加:
FEISHU_HOOK="https://open.feishu.cn/open-apis/bot/v2/hook/你的地址"
alert() {
local msg="⚠️ 服务器告警:$1"
echo "[$(date '+%F %T')] $msg" | tee -a "$LOG"
curl -s -X POST "$FEISHU_HOOK" \
-H 'Content-Type: application/json' \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$msg\"}}" > /dev/null
}
测试:
curl -s -X POST "$FEISHU_HOOK" -H 'Content-Type: application/json' \
-d '{"msgtype":"text","text":{"content":"监控测试"}}'
群里立刻能看到消息。
注意:
webhook地址等于一个"发消息权限",别提交到公开仓库。
钉钉 / 企业微信
钉钉:
curl -s -X POST 'https://oapi.dingtalk.com/robot/send?access_token=xxx' \
-H 'Content-Type: application/json' \
-d '{"msgtype":"text","text":{"content":"告警内容"}}'
企业微信:
curl -s -X POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx' \
-H 'Content-Type: application/json' \
-d '{"msgtype":"text","text":{"content":"告警内容"}}'
五、避免"告警疲劳"
最大的问题不是没监控,是告警太多,你就不看了。
三条规矩:
只对"真需要人干预"的事报警
- 磁盘 85% → 报(你该清理了)
- 内存 60% → 不报(正常波动)
加降噪:同一个问题不要连着报
# 记录上次报警时间,1 小时内不重复 COOLDOWN=3600 STATE="/tmp/monitor.last.$1"区分"警告"和"严重"
- 磁盘 85% → 警告(今天处理)
- 磁盘 95% → 严重(立刻处理)
一个简单实现:
alert() {
local level="$1" msg="$2"
local key=$(echo "$msg" | md5sum | cut -c1-8)
local stamp="/tmp/monitor.$key"
local now=$(date +%s)
# 1 小时内同一问题不重复
if [ -f "$stamp" ]; then
local last=$(cat "$stamp")
if [ $((now - last)) -lt 3600 ]; then
echo "[$(date '+%F %T')] (降噪跳过) $msg" >> "$LOG"
return
fi
fi
echo "$now" > "$stamp"
echo "[$(date '+%F %T')] [$level] $msg" | tee -a "$LOG"
curl -s -X POST "$FEISHU_HOOK" -H 'Content-Type: application/json' \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"[$level] $msg\"}}" > /dev/null
}
六、加到定时任务
每 10 分钟检查一次。
crontab -e
*/10 * * * * /home/你的用户名/bin/monitor.sh >> /home/你的用户名/.monitor.cron.log 2>&1
或者用 systemd timer(日志更好查,见 S5)。
七、日志轮转:别让日志自己吃满磁盘
监控脚本自己会写日志,久了也会占空间。
sudo nano /etc/logrotate.d/myscripts
/home/zhangsan/.monitor.log /home/zhangsan/.backup.log {
weekly
rotate 4
compress
missingok
notifempty
copytruncate
}
手动测试:
sudo logrotate -d /etc/logrotate.d/myscripts # -d 演练,不真做
sudo logrotate -f /etc/logrotate.d/myscripts # -f 强制执行
copytruncate很关键:它先复制再清空原文件,这样正在写日志的脚本不会因为文件被换掉而写失败。
八、想更进一步的时候
当单机监控不够了,再考虑这些:
| 工具 | 用途 | 复杂度 |
|---|---|---|
| netdata | 实时仪表盘,一条命令装 | 低 |
| Uptime Kuma | 网站可用性监控 + 好看的面板 | 低(Docker) |
| Prometheus + Grafana | 专业监控,指标存储 + 图表 | 高 |
| Zabbix | 企业级 | 高 |
推荐顺序:
- 先写脚本(本篇)—— 理解原理,零依赖
- Uptime Kuma(Docker 一键) —— 有仪表盘,能监控多个站点
- netdata(一条命令) —— 看实时性能曲线
- 真需要了再上 Prometheus
别一上来就 Prometheus。 配好要几小时,而你的问题可能只是"磁盘满了"。
Uptime Kuma 快速上手
docker run -d --name uptime-kuma \
-p 3001:3001 \
-v uptime-kuma:/app/data \
--restart unless-stopped \
louislam/uptime-kuma:1
浏览器打开 http://IP:3001,加监控项即可。10 分钟搞定,界面比脚本好看一万倍。
九、动手:四个练习
练习 1:跑一遍健康检查
df -h / | awk 'NR==2 {print "磁盘: "$5}'
free | awk '/^Mem:/ {printf "内存: %.0f%%\n", $3/$2*100}'
echo "负载: $(cat /proc/loadavg | awk '{print $1}') / $(nproc) 核"
systemctl is-active nginx 2>/dev/null || echo "nginx 未安装"
练习 2:写完整脚本并跑
把第三节的脚本存成 ~/bin/monitor.sh,跑一遍,看日志。
练习 3:故意触发一次告警
# 临时把阈值调低
DISK_THRESHOLD=1 ~/bin/monitor.sh
应该看到磁盘告警。 这就是"报警能正常工作"的验证。
练习 4:配一个 webhook 并测试
按第四节配飞书/钉钉机器人,用 curl 发一条测试消息,确认手机上收到。
十、验证:你过关了吗
- 知道该监控哪五项
- 会用
df/free//proc/loadavg取指标 - 会写一个带阈值的检查脚本
- 能配一个 webhook 把告警发到手机
- 知道要降噪(同一问题不重复报)
- 会把脚本加到 cron/timer
- 会给日志配 logrotate
- 知道"先脚本,再工具"的顺序
十一、不行怎么办
Q1:脚本报 integer expression expected
比较的变量不是数字(可能是空的或者带 %)。检查:
DISK=$(df / | awk 'NR==2 {gsub(/%/,"",$5); print $5}')
echo "[$DISK]" # 用方括号包着看,能发现隐藏字符
Q2:webhook 没收到消息
# 直接测 curl
curl -v -X POST "$HOOK" -H 'Content-Type: application/json' \
-d '{"msgtype":"text","text":{"content":"test"}}'
看 HTTP 状态码和返回内容。 常见:地址错、机器人被禁、关键词校验不通过。
Q3:openssl s_client 卡住
加超时:
timeout 10 openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null
Q4:cron 跑监控但收不到告警 先手动跑一次,确认脚本本身没问题。然后查 cron 日志:
grep CRON /var/log/syslog | tail -10
Q5:告警太多,被淹没 加降噪(第五节),或者提高阈值。报警的价值在于"少而准",不是"多而全"。
Q6:free 显示的可用内存很少,但系统不卡
Linux 会用空闲内存做缓存(buff/cache),这是正常的。看 available 那一列:
free -h
# total used free shared buff/cache available
# Mem: 3.6Gi 921Mi 108Mi 1.0Mi 2.6Gi 2.4Gi
# ↑ 看这个
available 才是"真正还能用的"。
十二、一句话总结
监控五项:磁盘、内存、负载、服务、证书。
自己写脚本 → 超阈值 → webhook 发到手机。
一定要降噪:同一问题 1 小时内不重复报。
别一上来上 Prometheus,先让"出问题能被你知道"。
← 上一篇 S5|定时任务 下一篇 → S7|安全加固:把门锁好