一、先想清楚:监控装在哪里决定它有没有用

大部分人搭监控的第一个错误,是把监控探针装在自己要监控的服务器上。这在逻辑上有个无法回避的问题:探针和被监控对象共享同一个故障域。服务器宕机、网络断开、系统卡死、磁盘写满——这些都会让探针跟着一起死,于是告警永远不会发出来。你以为有监控,实际上最需要告警的那几分钟是沉默的。
所以监控的第一原则是:探测端必须在别的地方。
免费方案能满足这个原则的路径有三条:
第一条,外部探测。 用第三方的拨测服务从外部节点定期访问你的网站或接口,看它是否响应。这条线防的是「服务不可用」,而且因为探测点在外部,它天然不受你服务器故障影响。免费额度通常足够个人和小站使用。
第二条,自建自监控,但在另一台机器上。 用一台便宜 VPS 跑 Prometheus + Grafana + Alertmanager,通过 agent 采集目标机器的指标。这条线防的是「资源耗尽」——磁盘满、内存爆、CPU 打满、服务进程挂了。
第三条,脚本 + 告警通道。 不部署任何重型组件,用 cron 跑几个 shell 脚本,检查关键指标,超阈值就发消息。这条线防的是「证书要过期了」「磁盘快满了」这类「可预测的、会慢慢来的」问题——恰恰是最容易被忽略、又最容易造成事故的一类。
三条线覆盖的是三种不同的故障,缺一条就有一类事故是看不见的。 本文标题里的「三项必备」指的就是这三条。
还有一个容易被忽略的前提:监控要能发现「服务变慢」和「服务彻底挂掉」之间的区别。 很多拨测只配置了「HTTP 状态码非 200 就告警」,这样一来,服务器还能返回 200 但响应时间从 200 毫秒涨到 5 秒,监控系统是看不到的。而对用户来说,后者往往比直接打不开更糟——页面能打开,但慢到没人愿意等。
所以合格的探测必须同时记录两个量:状态码和响应时间。响应时间的阈值应该按业务实际设定,而不是照抄别人的数字。一个纯 API 服务 500 毫秒可能完全正常,而一个静态页面 500 毫秒就说明出问题了。设定阈值的方法是先记录一周的正常基线,再把告警线定在基线的中位数加上 2 到 3 倍。
还有一个层次的问题:外部探测只能看到「对外表现」,看不到「内部状态」。用户请求正常返回,但后端某个 worker 已经僵死、数据库连接池耗尽、磁盘缓存正在疯狂换页——这些都不会立刻反映在 HTTP 状态码上。这就是为什么第二条线(资源与进程监控)不可省略:外部探测告诉你「用户感觉不对」,内部监控告诉你「为什么不对」。
两条线要能对上。理想的流程是:外部探测告警后,你先确认「是全站都挂了还是只有部分接口挂了」,然后用内部监控的数据定位到具体资源指标。如果只有外部探测,你会花大量时间在反复 curl 上;只有内部监控,用户投诉时你才知道出事了——那时候已经晚了。
二、第一项:外部可用性探测

外部探测要回答一个问题:从外面看,我的服务是不是好的? 注意是「从外面看」——服务器上进程还在跑、Nginx 返回 200,都不代表用户能访问到,因为可能 DNS 解析错了、可能防火墙挡了、可能运营商线路出问题。
最基础的探测是 HTTP 拨测。 用 UptimeRobot、Better Stack、Hetrix Tools 这类提供免费额度的服务,配置方式都类似:填 URL、选监控频率、设置告警联系方式。
自己搭一个更轻量的拨测器,用 cron + curl 就能做到,而且完全免费:
#!/bin/bash
# /opt/monitor/check_http.sh —— 最简可用性探测
URL="https://example.com"
TIMEOUT=10
LOG="/var/log/monitor/http.log"
mkdir -p "$(dirname "$LOG")"
RESULT=$(curl -s -o /dev/null -w "%{http_code} %{time_total}" \
--max-time "$TIMEOUT" "$URL")
CODE=$(echo "$RESULT" | cut -d' ' -f1)
TIME=$(echo "$RESULT" | cut -d' ' -f2)
# 只关心状态码和响应时间两个指标
if [ "$CODE" != "200" ]; then
echo "$(date '+%F %T') FAIL code=$CODE time=${TIME}s url=$URL" >> "$LOG"
# 这里调用你的告警函数
/opt/monitor/notify.sh "网站不可用 HTTP $CODE $URL"
elif awk "BEGIN{exit !($TIME > 3)}"; then
echo "$(date '+%F %T') SLOW time=${TIME}s url=$URL" >> "$LOG"
/opt/monitor/notify.sh "网站响应过慢 ${TIME}s $URL"
else
echo "$(date '+%F %T') OK code=$CODE time=${TIME}s" >> "$LOG"
fi
探测目标不要只挑一个。 单点探测会漏掉一部分故障,合理的探测矩阵是:
| 探测目标 | 检测的故障类型 | 期望结果 |
|---|---|---|
| 首页 | 主站整体不可用 | 200,响应时间 < 3 秒 |
| 一个静态资源(如 CSS) | Nginx 配置或磁盘异常 | 200 |
| 后台登录页 | PHP-FPM 或数据库故障 | 200 或 302 |
| 一个 API 接口 | 应用层故障 | 200,JSON 格式正确 |
| 域名解析(DNS 记录) | DNS 配置错误 | 解析到预期 IP |
| SSL 证书端点 | 证书过期 | 握手成功 |
探测频率的权衡:1 分钟一次是推荐起点。频率太高(比如 10 秒)不会增加有效信息量,只会填满告警渠道、让人对告警脱敏;太低(比如 10 分钟)则可能错过一个持续 5 分钟的故障窗口。
关键一点:探测必须从多个地理位置发起。 只测一个节点的话,你的服务器所在的机房网络抖动会造成假故障,而真正影响用户的其它地区的问题反而看不见。免费服务通常提供多个探测节点,选至少三个不同区域的。
告警阈值要分级,不要一失败就呼叫:
# 连续失败 3 次才告警,避免单次抖动打扰
FAIL_FILE="/tmp/monitor_fail_count"
COUNT=0
[ -f "$FAIL_FILE" ] && COUNT=$(cat "$FAIL_FILE")
if [ "$CODE" != "200" ]; then
COUNT=$((COUNT + 1))
echo "$COUNT" > "$FAIL_FILE"
if [ "$COUNT" -ge 3 ]; then
/opt/monitor/notify.sh "网站连续失败 ${COUNT} 次 $URL"
fi
else
rm -f "$FAIL_FILE"
fi
三、第二项:资源与进程的自监控

外部探测能告诉你「挂了」,但告诉不了你「为什么快挂了」。资源类故障——磁盘快满、内存爆掉、进程僵死——是渐进式的,往往在彻底挂掉之前就有几小时到几天的预警窗口。抓住这个窗口是这一项的价值所在。
不用装 Prometheus 也能拿到关键指标,Linux 自带的工具就够:
# 磁盘使用率(超过 85% 就要处理了)
df -h --output=source,pcent,target | awk 'NR==1 || $2+0 >= 85'
# 只看关键目录,排除 tmpfs
df -h / /var /home | grep -v tmpfs
# 内存实际使用(注意 MemAvailable 比 MemFree 有意义)
free -m
awk '/MemAvailable/ {printf "可用内存 %d MB (%.1f%%)\n", $2/1024, $2/$3*100}' /proc/meminfo
# inode 使用率(文件太多也会把盘写满,哪怕空间还剩)
df -i / | awk 'NR==2 {print "inode 使用率", $5}'
# 负载(1 分钟负载 / CPU 核数 超过 1 说明过载)
cat /proc/loadavg
NPROC=$(nproc)
awk -v n="$NPROC" '{if ($1 > n) print "过载: 1分钟负载", $1, "核数", n}'
# 进程存活检查(服务挂掉但进程还在的情况)
pgrep -x nginx > /dev/null || echo "nginx 进程不存在"
pgrep -x mysqld > /dev/null || echo "mysqld 进程不存在"
写成一个巡检脚本,每 10 分钟跑一次:
#!/bin/bash
# /opt/monitor/check_system.sh
LOG="/var/log/monitor/system.log"
mkdir -p "$(dirname "$LOG")"
ALERT=0
# 1. 磁盘水位
DISK_PCT=$(df -P / | awk 'NR==2 {gsub("%","",$5); print $5}')
if [ "$DISK_PCT" -ge 85 ]; then
echo "$(date '+%F %T') 磁盘告警 / 已用 ${DISK_PCT}%" >> "$LOG"
ALERT=1
fi
# 2. inode 水位
INODE_PCT=$(df -Pi / | awk 'NR==2 {gsub("%","",$5); print $5}')
if [ "$INODE_PCT" -ge 85 ]; then
echo "$(date '+%F %T') inode 告警 / 已用 ${INODE_PCT}%" >> "$LOG"
ALERT=1
fi
# 3. 内存
MEM_PCT=$(awk '/MemAvailable/ {printf "%d", $2/$3*100}' /proc/meminfo)
if [ "$MEM_PCT" -le 15 ]; then
echo "$(date '+%F %T') 内存告警 / 可用仅 ${MEM_PCT}%" >> "$LOG"
ALERT=1
fi
# 4. 负载
LOAD=$(cut -d' ' -f1 /proc/loadavg)
CORES=$(nproc)
if awk "BEGIN{exit !($LOAD > $CORES)}"; then
echo "$(date '+%F %T') 负载告警 / ${LOAD} 超过核数 ${CORES}" >> "$LOG"
ALERT=1
fi
[ "$ALERT" = "1" ] && /opt/monitor/notify.sh "服务器资源告警,详见 $LOG"
内存阈值要设成 15% 而不是 10%。 原因是 Linux 的内存管理策略是「尽量用满」——空闲内存留着不用反而浪费,页缓存还会加速文件访问。所以 MemFree 常年接近 0 是正常的,真正该看的是 MemAvailable(估算在没有交换时还能分配给新进程的内存量)。设成 15% 意味着「只剩不到 15% 可分配」,这个状态下 OOM Killer 的风险已经实质存在了。
进程级监控要注意「进程在但服务不可用」的情况。Nginx 主进程还在,但某个后端 worker 卡死;MySQL 进程在,但连接数打满。检查进程存活只是最粗的粒度:
# 检查 Nginx 是否能正常处理请求(比检查进程存活更准确)
curl -s -o /dev/null -w "%{http_code}\n" --max-time 5 http://127.0.0.1/health
# 返回 200 才算健康
# 检查 MySQL 是否能接受连接
mysqladmin ping 2>&1 | grep -q "alive" && echo "数据库正常"
# 检查 MySQL 慢查询和连接数
mysql -e "SHOW STATUS WHERE Variable_name IN ('Threads_connected','Max_used_connections','Slow_queries');"
更系统的做法是用 node_exporter + Prometheus。 如果你的 VPS 资源够(至少 1 GB 内存),在监控机(不是被监控机)上跑 Prometheus,通过 node_exporter 采集目标机指标:
# 在被监控机上装 node_exporter(它是被采集端,崩了不影响告警)
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
tar xzf node_exporter-*.tar.gz
sudo mv node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/
sudo useradd -r -s /bin/false node_exporter
sudo chown node_exporter:node_exporter /usr/local/bin/node_exporter
# 用 systemd 托管,配置监听地址和只读端口
sudo tee /etc/systemd/system/node_exporter.service > /dev/null <<'EOF'
[Unit]
Description=Node Exporter
After=network.target
[Service]
User=node_exporter
ExecStart=/usr/local/bin/node_exporter --web.listen-address=:9100
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable --now node_exporter
Prometheus 的抓取配置(监控机上的 prometheus.yml):
scrape_configs:
- job_name: 'vps_nodes'
static_configs:
- targets: ['被监控机IP:9100']
labels:
role: 'web'
scrape_interval: 30s
注意 30 秒的抓取间隔对应约 15 秒的告警评估窗口——Prometheus 的 for 字段设成 5m 表示「指标持续异常 5 分钟才告警」。这个设置是为了避免抖动误报,代价是故障响应变慢,两者需要平衡。
四、第三项:证书到期与磁盘水位的提前预警

这一项是最被低估的。服务器不会突然坏,但证书会到期、磁盘会慢慢满、日志会慢慢涨——这些问题的特点是「完全可以避免」,但因为不会立刻造成故障,往往被拖到变成事故。
证书到期监控:
#!/bin/bash
# /opt/monitor/check_cert.sh
DOMAIN="example.com"
WARN_DAYS=30
DAYS_LEFT=$(( ($(date -d "30 days" +%s) - $(date +%s)) / 86400 ))
# 取证书剩余有效期(天)
CERT_DAYS=$(echo | openssl s_client -servername "$DOMAIN" -connect "$DOMAIN":443 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null \
| cut -d= -f2)
EXPIRY=$(date -d "$CERT_DAYS" +%s 2>/dev/null)
LEFT=$(( (EXPIRY - $(date +%s)) / 86400 ))
if [ "$LEFT" -lt "$WARN_DAYS" ]; then
/opt/monitor/notify.sh "SSL 证书 ${DOMAIN} 将在 ${LEFT} 天后到期"
fi
更实用的做法是直接监控证书文件本身,避免依赖网络连通性:
# 监控本机证书文件(Let's Encrypt 路径)
CERT_FILE="/etc/letsencrypt/live/example.com/fullchain.pem"
EXPIRY=$(openssl x509 -enddate -noout -in "$CERT_FILE" | cut -d= -f2)
DAYS=$(( ( $(date -d "$EXPIRY" +%s) - $(date +%s) ) / 86400 ))
[ "$DAYS" -lt 30 ] && /opt/monitor/notify.sh "证书 ${DAYS} 天后到期"
两种方式要一起用。监控本地文件能发现「续期没跑」,监控外部端点能发现「证书链不全导致浏览器报错」这类本地文件看起来正常的问题。
磁盘水位监控要监控「趋势」而不是只看「当前值」。 突然的占用(比如日志暴涨)靠当前值能抓到,但缓慢增长(比如每天涨 1 GB)靠当前值只会等你到了 90% 才告警,那时候已经没时间处理了。趋势告警的做法是记录历史值:
#!/bin/bash
# /opt/monitor/check_disk_trend.sh
LOG="/var/log/monitor/disk_usage.log"
CURRENT=$(df -P / | awk 'NR==2 {gsub("%","",$5); print $5}')
echo "$(date '+%F') $CURRENT" >> "$LOG"
# 取 7 天前的值做对比
OLD=$(tail -n +1 "$LOG" | head -n -1 | sed -n '1p' | awk '{print $2}')
if [ -n "$OLD" ] && [ "$CURRENT" -gt "$OLD" ]; then
GROWTH=$((CURRENT - OLD))
if [ "$GROWTH" -ge 5 ]; then
/opt/monitor/notify.sh "磁盘一周增长 ${GROWTH}%,当前 ${CURRENT}%"
fi
fi
日志轮转是防止磁盘被日志吃满的根本手段,比监控更重要:
# logrotate 配置示例:/etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
su root root
}
别忘了监控「日志自己占多少空间」。很多服务器的根分区是被一份几 GB 的日志文件撑满的:
# 找出占用最大的 10 个文件
sudo du -ah /var/log 2>/dev/null | sort -rh | head -10
# 找出最大的 10 个目录
sudo du -h --max-depth=1 / 2>/dev/null | sort -rh | head -10
第四项补充:流量与带宽监控。 这一项特别值得单列,因为月度流量超额是 VPS 最常见的「账单意外」来源,而超额往往在你毫无察觉的时候发生——直到账单出来。
# 用 vnstat 监控按天/按月的流量(需要先安装 vnstat)
sudo apt-get install -y vnstat
vnstat --oneline b
vnstat -d -m
# 从网卡累计计数看实时速率(单位换算成 Mbps)
awk 'NR==2 {printf "入 %.1f Mbps, 出 %.1f Mbps\n", $2*8/1000000, $10*8/1000000}' /proc/net/dev
# 找出出站流量最大的前 10 个远端 IP(定位是谁在消耗额度)
sudo ss -tn state established | awk 'NR>1 {print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -10
流量告警的阈值应该按「额度百分比」而不是「绝对值」设定。 因为不同套餐的额度差了几个数量级——库里最小的 RosCloud-01 是 1024 GB,最大的 sixtynet LAX-LARGE 和云途 HK.DS.S 都是 40000 GB。用统一的绝对阈值必然要么误报要么漏报。
按额度比例告警的标准档位:50%(正常)、75%(注意)、90%(即将超额)、100%(已超额或即将)。关键的一点是告警要能区分「已经用掉的」和「已经卖掉的」——很多面板显示的是「已售出流量」,那是服务商的数据,不是你的。
#!/bin/bash
# /opt/monitor/check_traffic.sh
# 参数:已用流量 GB、额度 GB
USED=${1:-0}
QUOTA=${2:-1024}
PCT=$(( USED * 100 / QUOTA ))
case $PCT in
9[0-9]|100) LEVEL="P0 紧急"; MSG="流量已用 ${PCT}%";;
7[0-9]|8[0-9]) LEVEL="P1 重要"; MSG="流量已用 ${PCT}%";;
5[0-9]|6[0-9]) LEVEL="P2 提示"; MSG="流量已用 ${PCT}%";;
*) exit 0;;
esac
/opt/monitor/notify.sh "[$LEVEL] $MSG(已用 ${USED}GB / 额度 ${QUOTA}GB)"
这个脚本的输入数据从哪来? 两个来源:一是面板的流量统计(手动查或定期抓取),二是服务器本机的网卡累计计数。后者的准确度取决于「统计起点」——如果你在周期中途重置了计数器,算出来的值就不准。vnstat 之所以要用,就是它按自然月维护历史数据库,能给出准确的「本月至今」。
别忘了超额后的成本预警。 库里云途全系都写明了流量重置价(200 元 / 450 元 / 500 元),RosCloud 和 sixtynet 的这个字段是空的。所以对后两家,你更需要提前预警——因为超额的计费方式不明。流量告警的价值不只是「知道超了」,更是「在超额之前就停手」。
五、告警渠道怎么选

监控做得再好,告警发不到人手上就等于零。免费渠道的选择原则和各自的适用场景:
邮件:最基础的渠道,几乎所有服务都支持。缺点是响应慢——邮件可能躺在收件箱里几小时才被看到。适合做非紧急的汇总类告警(比如每日健康报告),不适合做故障告警。
Webhook(自建推送):最灵活也最可靠。用一个 webhook 接收地址,配合脚本调用,国内可以推送到微信/钉钉/企微机器人。这条路的优势是完全免费、无依赖、可自建:
#!/bin/bash
# /opt/monitor/notify.sh —— 统一告警出口
LOG="/var/log/monitor/notify.log"
mkdir -p "$(dirname "$LOG")"
MSG="$1"
TIMESTAMP=$(date '+%F %T')
# 1. 写本地日志(即使推送失败也有记录)
echo "[$TIMESTAMP] $MSG" >> "$LOG"
# 2. 推送到企业微信机器人
WEBHOOK="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=你的KEY"
curl -s -m 10 -X POST "$WEBHOOK" \
-H 'Content-Type: application/json' \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"[VPS告警] $TIMESTAMP $MSG\"}}" \
> /dev/null 2>&1
# 3. 备用通道:邮件(用 msmtp 或 mailx)
echo "[$TIMESTAMP] $MSG" | mail -s "VPS告警: $MSG" [email protected]
# 4. 如果有 Bark / PushPlus 之类的 iOS 推送,也可以并行调用
关键是「本地日志 + 至少两条推送通道」。本地日志保证推送链路自己挂掉时你还能查到告警记录;两条通道保证单条通道故障时不会漏。
告警要分级,不同紧急度用不同通道:
| 级别 | 触发条件 | 通知方式 | 响应预期 |
|---|---|---|---|
| P0 紧急 | 服务不可用、证书即将过期 | 推送 + 短信 | 立即处理 |
| P1 重要 | 磁盘 > 90%、内存 < 10% | 推送 | 1 小时内 |
| P2 提示 | 磁盘 > 80%、备份失败 | 邮件 | 当天处理 |
| P3 记录 | 每日巡检汇总 | 邮件 | 每周看一次 |
告警疲劳是最大的敌人。 同一个问题连续告警 20 次,人就会开始无视整条通道。解决办法是「告警抑制」——同一个问题在未恢复前只发一次,恢复后再发一条「已恢复」:
# 用状态文件实现抑制逻辑:同一 KEY 未恢复前不重复告警
STATE_FILE="/tmp/monitor_alert_state"
KEY="disk_over_90"
if [ -f "$STATE_FILE" ] && grep -q "$KEY" "$STATE_FILE"; then
exit 0 # 已经告过警了,等恢复
fi
echo "$KEY" >> "$STATE_FILE"
/opt/monitor/notify.sh "P1 磁盘超 90%"
# 恢复检查(另一个脚本):状态消失时发恢复通知
if [ -f "$STATE_FILE" ] && grep -q "$KEY" "$STATE_FILE"; then
sed -i "/$KEY/d" "$STATE_FILE"
/opt/monitor/notify.sh "已恢复: 磁盘回到正常水位"
fi
抑制的粒度要选对。 是按「问题类型」抑制,还是按「具体实例」抑制?前者简单(一个磁盘告警 KEY),后者精确(每块盘一个 KEY)。对单台服务器来说按类型就够了;如果监控的是一批机器,务必按实例区分,否则 A 机器的磁盘告警会抑制掉 B 机器的磁盘告警。
抑制的同时要保留「还在持续」的信息。 抑制不是丢弃——被抑制期间的告警应该继续写入本地日志,只是不再推送。做法是往日志里追加一条「该告警仍在持续」的心跳记录,这样你事后翻日志能看到它持续了多久。
# 被抑制时记一条心跳,而不是完全静默
echo "[$(date '+%F %T')] 抑制中: $KEY 仍在异常" >> /var/log/monitor/notify.log
静默窗口(maintenance)也需要。 计划内的重启、升级、备份窗口期间,告警应该被主动静音,否则你会在凌晨被自己的运维操作吵醒。用 cron 在窗口前写入静默标记,窗口后清除:
# 每天 02:00-02:30 静默(假设是备份窗口)
0 2 * * * touch /tmp/monitor_maintenance
30 2 * * * rm -f /tmp/monitor_maintenance
告警脚本开头检查这个文件,存在就只记日志不推送。别忘了给静默窗口设一个上限——如果脚本因为任何原因没跑,标记会一直留着,你的告警就永久静音了。稳妥的做法是让标记文件里带上过期时间戳,超过两小时自动失效。
六、最小可用方案:十分钟搭起来

如果上面三节让你觉得太复杂,这里给一个十分钟能完成的起步版本,它覆盖了 80% 的实际价值。
第一步:准备告警出口。 申请一个企业微信机器人或 Bark 推送地址,拿到 webhook URL。
第二步:在服务器上建目录和脚本。
sudo mkdir -p /opt/monitor /var/log/monitor
sudo tee /opt/monitor/notify.sh > /dev/null <<'EOF'
#!/bin/bash
MSG="$1"
echo "[$(date '+%F %T')] $MSG" >> /var/log/monitor/notify.log
curl -s -m 10 -X POST "$WEBHOOK_URL" -H 'Content-Type: application/json' \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$MSG\"}}" > /dev/null 2>&1
EOF
sudo chmod +x /opt/monitor/notify.sh
第三步:写一个综合巡检脚本。
sudo tee /opt/monitor/check_all.sh > /dev/null <<'EOF'
#!/bin/bash
LOG="/var/log/monitor/check.log"
ALERTS=()
# 服务可用性
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 http://127.0.0.1/)
[ "$CODE" != "200" ] && ALERTS+=("服务异常 HTTP $CODE")
# 磁盘
D=$(df -P / | awk 'NR==2 {gsub("%","",$5)}')
[ "$D" -ge 85 ] && ALERTS+=("磁盘 ${D}%")
# 内存
M=$(awk '/MemAvailable/ {printf "%d", $2/$3*100}' /proc/meminfo)
[ "$M" -le 15 ] && ALERTS+=("内存仅剩 ${M}%")
# 证书
E=$(openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem 2>/dev/null | cut -d= -f2)
L=$(( ( $(date -d "$E" +%s 2>/dev/null) - $(date +%s) ) / 86400 ))
[ "$L" -lt 30 ] && ALERTS+=("证书 ${L} 天后到期")
if [ ${#ALERTS[@]} -gt 0 ]; then
echo "[$(date '+%F %T')] ${ALERTS[*]}" >> "$LOG"
/opt/monitor/notify.sh "[VPS告警] ${ALERTS[*]}"
else
echo "[$(date '+%F %T')] 全部正常" >> "$LOG"
fi
EOF
sudo chmod +x /opt/monitor/check_all.sh
第四步:挂到 cron。
sudo crontab -e
# 每 10 分钟巡检一次
*/10 * * * * /opt/monitor/check_all.sh > /dev/null 2>&1
# 每天 9 点做一次完整巡检并把结果发邮件
0 9 * * * /opt/monitor/check_all.sh && mail -s "每日巡检" [email protected] < /var/log/monitor/check.log
第五步:验证。 手动制造一次故障,确认告警真的会到:
# 临时把磁盘告警阈值改成 0,测试链路
sed -i 's/-ge 85/-ge 0/' /opt/monitor/check_all.sh
sudo /opt/monitor/check_all.sh
# 确认收到告警后改回来
sed -i 's/-ge 0/-ge 85/' /opt/monitor/check_all.sh
第六步:加一个外部探测。 到 UptimeRobot 或同类服务免费注册,填上你的网站地址,选 5 分钟频率,告警方式填邮件。
这套方案的成本是零,覆盖了:服务可用性、资源水位、证书到期这三类最常见的事故原因。
七、选机器时把监控需求算进去

监控是要花资源的,而且资源开销不是固定的。选 VPS 配置时如果把监控的开销漏算,实际用起来会手忙脚乱。
监控 agent 的资源开销大致是:node_exporter 常驻内存约 20 到 40 MB;Prometheus 本身约 500 MB 到 1 GB 内存(取决于抓取目标数量和时间序列基数);Grafana 约 100 到 200 MB;Alertmanager 约 50 MB。
用库里的真实价格算这些开销的边际成本:
| 套餐 | 折月价 | 内存 | 内存单位成本 | 磁盘单位成本 | 跑监控的可行性 |
|---|---|---|---|---|---|
| RosCloud-01 | 19.99 美元 | 1 GB | 19.99 美元/GB | 0.999 美元/GB | 内存不够跑 Prometheus,只适合跑脚本巡检 |
| RosCloud-03 | 39.99 美元 | 4 GB | 10.0 美元/GB | 0.5 美元/GB | 可跑 node_exporter + 轻量采集 |
| RosCloud-05 | 59.99 美元 | 16 GB | 3.75 美元/GB | 0.25 美元/GB | 可跑完整 Prometheus + Grafana |
| sixtynet LAX-STARTER | 16.67 美元 | 2 GB | 8.33 美元/GB | 0.417 美元/GB | 只能跑脚本巡检和轻量 exporter |
| sixtynet LAX-MICRO | 33.33 美元 | 4 GB | 8.33 美元/GB | 0.417 美元/GB | 可跑 Prometheus 单机版 |
| sixtynet LAX-MEDIUM | 66.67 美元 | 8 GB | 8.33 美元/GB | 0.417 美元/GB | 可跑完整栈并保留冗余 |
| sixtynet LAX-Storage-500G | 33.33 美元 | 4 GB | 8.33 美元/GB | 0.067 美元/GB | 内存一般但存储极便宜,适合存监控数据 |
| 云途 HK.DS.M | 350 元 | 32 GB | 10.94 美元/GB | 0.729 美元/GB | 内存充足,单位成本中等 |
| 云途 HK.DS.S | 800 元 | 128 GB | 6.25 美元/GB | 0.133 美元/GB | 内存最充裕,适合跑多台目标采集 |
关键观察:内存单位成本的排序和「跑不跑得动监控」高度相关。 RosCloud-01 的内存单位成本 19.99 美元/GB 是全库最高——但它只有 1 GB,跑 Prometheus 是勉强的(Prometheus 官方建议至少 2 GB,实际 1 GB 会在采集几百个指标时 OOM)。这不是说它不好,而是说它的定位不是监控机。
如果要做自建监控,选型标准很清楚:内存至少 4 GB(Prometheus + Grafana + Alertmanager + node_exporter 的最低可用组合),磁盘至少 20 GB(Prometheus 的 TSDB 数据增长快,保留 30 天大概要 10 到 20 GB)。按这个标准:
- 预算优先选 sixtynet LAX-MICRO(折月 33.33 美元,4 核 4 GB,80 GB NVMe,4000 Mbps,10000 GB 流量)——内存单位成本 8.33 美元/GB,NVMe 保证 TSDB 写入性能。
- 需要存长期监控数据选 sixtynet LAX-Storage-500G(折月 33.33 美元,4 核 4 GB,500 GB NVMe,磁盘单位成本 0.067 美元/GB 是全库最低)——同样价格多出 420 GB 空间。
- 内存需求无上限(比如要监控几十台机器、时间序列基数大)选云途 HK.DS.S(800 元/月,128 GB 内存,6000 GB 硬盘)——但注意它是 SAS HDD 而不是 SSD,Prometheus 的 TSDB 对随机写敏感,SAS HDD 上查询和压缩会明显慢于 NVMe,这种情况下更该选内存大且磁盘是 SSD 的方案(比如云途 HK.DS.P,750 元/月,64 GB 内存,1024 GB SSD)。
监控机还要考虑带宽。 采集 Prometheus 每 15 秒从目标机拉一次指标,流量不大;但如果你要做日志采集或者远程日志分析,流量就会显著上升。sixtynet LAX-MICRO 的 4000 Mbps 端口和 10000 GB 月流量绰绰有余。
还有个隐含成本:监控机自己也需要被监控。 这是递归问题的终点——实践中的解法是监控机的监控用第三方免费探测(因为它只有一台,外部拨测就够了),或者直接接受这个递归。
流量与带宽监控的隐性开销也要算进去。 如果你在监控机上做流量采集(比如从目标机拉 vnstat 数据、或者跑 node_exporter 的 network collector),这部分流量本身也消耗监控机的额度。按 node_exporter 每次采集返回约 10 KB 网络指标、每 30 秒一次计算,一天约 28.8 MB——对额度来说微不足道。
真正吃流量的是日志集中化。 如果你用 Fluentd、Vector 或 rsyslog 把目标机的日志实时推到监控机,每台机器每天的日志量从几十 MB 到几 GB 不等。按 1 GB/天算,一个月 30 GB,十台机器就是 300 GB——这个量级已经足以吃掉一台小配额的机器的月度额度。
用真实价格算这个成本:sixtynet LAX-STARTER 折月 16.67 美元带 5000 GB 流量,单位成本 3.41 美元/TB;云途 HK.DS.M 折月 350 元带 10000 GB 流量,单位成本 35.84 元/TB。如果日志集中化会长期消耗几百 GB,那日志采集本身就该算进选型,而不是当成附属功能。
三个应对方案:
方案一,采集前过滤。 只采集错误级别以上的日志,在 agent 端配置过滤规则。这通常能把日志量砍掉 90% 以上——正常的访问日志占了日志总量的大头,而它们对排障的价值远低于错误日志。
方案二,传输时压缩。 启用 gzip 或 zstd 压缩,文本日志通常能压到原体积的十分之一左右:
# Vector 的压缩配置示例
sources:
app_logs:
type: file
include: ["/var/log/app/*.log"]
transforms:
compress:
type: remap
inputs: [app_logs]
sinks:
central:
type: elasticsearch
inputs: [compress]
compression: gzip
encoding:
codec: json
方案三,按需采集而非全量常驻。 只在告警触发时才去拉取完整日志,平时只采集聚合指标。代价是排查时要多等几十秒到几分钟,但流量占用从「持续」降到「偶发」。
对个人站来说,方案一 + 方案二通常就够了:在 agent 端过滤掉访问日志,保留错误日志,开启压缩。这样十台机器的日志集中化流量可以从每月 300 GB 降到 30 GB 以内,几乎不占额度。
八、常见的监控误报

误报一:把本地探测当成外部探测。 在服务器上 curl 127.0.0.1 永远很快,这验证不了任何用户侧的问题——DNS 解析、路由、运营商链路、CDN 节点,全部没有覆盖。外部探测的价值就在于它在外部。
误报二:阈值定得太敏感。 内存低于 20% 就告警、CPU 超过 60% 就告警,结果是服务器一忙就告警,几周之后你开始无视这条通道。告警阈值应该定在「不处理会造成实际损失」的位置,而不是「偏离正常值」的位置。
误报三:只看 CPU 负载。 现代服务器上 CPU 常常不是瓶颈——瓶颈在内存(换页)、在磁盘 IO(随机写)、在网络(带宽打满)。Linux 的 load average 包含了不可中断睡眠(D 状态,典型是磁盘 IO 等待),所以它比单纯的 CPU 使用率更全面。但磁盘 IO 等待(%iowait)需要单独看:
# 看 IO 等待占比
vmstat 1 5
# si/so 是换入换出,r 是运行队列,wa 是 IO 等待
误报四:网络探测用 ICMP 判定。 有些网络会屏蔽 ICMP 但正常转发 TCP,ping 不通不代表网站挂了。探测必须基于真实协议——HTTP 拨测或 TCP 端口探测。
误报五:告警了但没人知道。 监控跑在服务器上,推送通道也依赖那台服务器能访问外网。如果服务器被墙了、或者 DNS 挂了,推送也发不出去。推送通道的可用性必须独立于被监控对象。
误报六:证书检查只看本机文件。 本机证书文件正常,但中间证书链不完整时浏览器仍然报错,而 openssl x509 读本机文件完全看不出问题。必须有一个从外部发起的 TLS 握手检查。
误报七:把「监控没数据」当成「服务正常」。 Prometheus 抓不到目标时,目标是 up 而不是告警。如果你只配了资源阈值而没配 up == 0 的告警,目标彻底失联时你收不到任何通知。这是最隐蔽的一种漏报:
# Prometheus 规则:必须加这一条
groups:
- name: node_alerts
rules:
- alert: NodeDown
expr: up{job="vps_nodes"} == 0
for: 3m
labels:
severity: critical
annotations:
summary: "监控目标失联"
按你的情况,下一步该看哪家
上面讲的是判断标准,但多数人最后还是想问一句「那我该买谁」。按你的实际情况对照下面几条,直接去看对应的机型页比继续比价更快:
- 跑游戏服或实时业务:看上行抖动与防御能力,普通套餐往往不达标。看 iwebfusion 当前机型与价格
- 只是开发测试、临时用:按小时或月付,用完就删,别买年付。看 justg 当前机型与价格
价格与库存以商家页面当日显示为准,下单前建议先确认退款窗口与是否支持按月付费。
九、常见问题

免费监控够用吗? 对个人站和小规模业务够用。UptimeRobot 这类免费档通常给 50 个监控项、5 分钟检查间隔;自建 Prometheus + Grafana + node_exporter 的软件本身全部免费(Prometheus 是 Apache 2.0,Grafana 社区版是 AGPL 3.0),只有机器要花钱。规模到几百台机器时,存储和内存成本才会显现,那时才需要考虑商业方案。
监控会不会拖慢服务器? 脚本巡检的开销可以忽略不计——每 10 分钟跑一次的 df 和 free 几乎不消耗任何资源。node_exporter 的开销是常驻约 20 到 40 MB 内存,加上每 30 秒一次的采集,占用同样很小。真正需要警惕的是日志采集类监控(Fluentd、Vector),它们会持续读日志文件并转发,磁盘 IO 开销明显,这种场景要考虑采集范围和采样率。
自建监控还是用第三方? 判断标准是你要监控几台机器、要不要自定义指标、能不能接受自己维护。只有一台机器 → 用第三方免费探测,最省事。有多台机器且需要看历史趋势 → 自建 Prometheus。看到「Prometheus 面板」好看而自建,是常见的过度设计。
告警发到微信/钉钉好还是邮件好? 两者都要,但角色不同。推送类渠道用于需要立即处理的故障,邮件用于每日汇总和非紧急提示。只用邮件的问题是响应慢——邮件可能在收件箱里躺几小时;只用推送的问题是信息过载——一天几十条推送会让所有告警都变得不重要。
监控数据要保留多久? 按排障需求定。资源指标(CPU、内存、磁盘、带宽)保留 30 天足够看出周期性;更长期的价值不大,因为机器换了指标形态也变了。日志类的保留周期取决于合规和排障需求,个人站通常 7 到 14 天。
怎么确认监控本身是有效的? 做故障注入测试:手动停掉服务、手动占满磁盘、手动把证书换成快过期的,主动验证告警真的会触发并送达。这件事应该每季度做一次——监控系统静默失效的情况,比没有监控更危险,因为你会一直以为有人在看着。
监控项应该看哪些指标? 按优先级:服务可用性(最重要)、磁盘水位(最常见的静默杀手)、证书有效期(最容易预防的故障)、内存可用量、负载与 IO 等待、网络流量。前三项覆盖了绝大多数实际发生过的 VPS 事故。剩下的按业务重要性加。
自建监控的机器要不要和被监控的机器用同一家? 不必要求同一家,但要意识到如果两家在同一个上游网络,机房级故障时你的告警通道也可能挂。告警推送走第三条路径(不同的网络服务商)能显著降低这个风险——比如监控机在一家,推送走另一家的 API。

喜欢
高兴
鬼脸
呵呵
无聊
伤心