适用:所有 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(飞书/钉钉/企业微信)中推荐,手机立刻收到
短信要钱关键系统

飞书机器人(推荐)

  1. 飞书群 → 设置 → 群机器人 → 添加 → 自定义机器人
  2. 复制 webhook 地址
  3. 脚本里加:
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":"告警内容"}}'

五、避免"告警疲劳"

最大的问题不是没监控,是告警太多,你就不看了。

三条规矩:

  1. 只对"真需要人干预"的事报警

    • 磁盘 85% → 报(你该清理了)
    • 内存 60% → 不报(正常波动)
  2. 加降噪:同一个问题不要连着报

    # 记录上次报警时间,1 小时内不重复
    COOLDOWN=3600
    STATE="/tmp/monitor.last.$1"
    
  3. 区分"警告"和"严重"

    • 磁盘 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企业级高

推荐顺序:

  1. 先写脚本(本篇)—— 理解原理,零依赖
  2. Uptime Kuma(Docker 一键) —— 有仪表盘,能监控多个站点
  3. netdata(一条命令) —— 看实时性能曲线
  4. 真需要了再上 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|安全加固:把门锁好