一键巡检脚本:CPU / 内存 / 磁盘 / 服务健康
适用场景
- 每日定时巡检,结果推送到群机器人;
- 上线后批量验证多台机器的基线状态;
- 作为监控的补充,做「一次性体检」。
脚本全文
#!/usr/bin/env bash
# health-check.sh —— 系统健康巡检
# 用法: ./health-check.sh [-j] -j 输出 JSON
set -uo pipefail
JSON=0
[[ "${1:-}" == "-j" ]] && JSON=1
# 阈值配置
CPU_THRESHOLD=80 # CPU 使用率 %
MEM_THRESHOLD=85 # 内存使用率 %
DISK_THRESHOLD=85 # 磁盘使用率 %
LOAD_PER_CORE=1.5 # 每核负载上限
status="OK"
declare -a problems=()
# ---------- CPU ----------
read -r _ _ _ _ _ _ _ _ _ idle _ < /proc/stat
sleep 1
read -r _ _ _ _ _ _ _ _ _ idle2 _ < /proc/stat
cpu_usage=$(awk -v i1="$idle" -v i2="$idle2" 'BEGIN{printf "%.1f", 100 - (i2-i1)*100/'"$(nproc)"'/1}')
# 简化计算:直接取两次 idle 差值占比
cpu_usage=$(awk -v i1="$idle" -v i2="$idle2" -v n="$(nproc)" \
'BEGIN{d=i2-i1; if (d<0) d=0; printf "%.1f", 100-d/100/n*100}')
# ---------- 负载 ----------
cores=$(nproc)
load1=$(awk '{print $1}' /proc/loadavg)
load_per_core=$(awk -v l="$load1" -v c="$cores" 'BEGIN{printf "%.2f", l/c}')
# ---------- 内存 ----------
mem_info=$(free -m | awk '/^Mem:/ {printf "%.1f %d %d", $3*100/$2, $3, $2}')
mem_usage=$(echo "$mem_info" | cut -d' ' -f1)
mem_used=$(echo "$mem_info" | cut -d' ' -f2)
mem_total=$(echo "$mem_info" | cut -d' ' -f3)
# ---------- 磁盘 ----------
disk_line=$(df -h --output=pcent,target -x tmpfs -x devtmpfs | awk 'NR>1 && $1+0>0 {print}' \
| sort -rn | head -1)
disk_usage=$(echo "$disk_line" | awk '{gsub(/%/,""); print $1}')
disk_mount=$(echo "$disk_line" | awk '{print $2}')
# ---------- 判定 ----------
awk -v v="$cpu_usage" -v t="$CPU_THRESHOLD" 'BEGIN{exit !(v>t)}' && problems+=("CPU 使用率 ${cpu_usage}% > ${CPU_THRESHOLD}%")
awk -v v="$mem_usage" -v t="$MEM_THRESHOLD" 'BEGIN{exit !(v>t)}' && problems+=("内存使用率 ${mem_usage}% > ${MEM_THRESHOLD}%")
awk -v v="$disk_usage" -v t="$DISK_THRESHOLD" 'BEGIN{exit !(v>t)}' && problems+=("磁盘 ${disk_mount} 使用率 ${disk_usage}% > ${DISK_THRESHOLD}%")
awk -v v="$load_per_core" -v t="$LOAD_PER_CORE" 'BEGIN{exit !(v>t)}' && problems+=("每核负载 ${load_per_core} > ${LOAD_PER_CORE}")
[[ ${#problems[@]} -gt 0 ]] && status="WARN"
# ---------- 输出 ----------
if [[ $JSON -eq 1 ]]; then
printf '{"host":"%s","time":"%s","status":"%s","cpu":%s,"mem":%s,"disk":%s,"disk_mount":"%s","load":"%s","problems":[%s]}\n' \
"$(hostname)" "$(date -Is)" "$status" "$cpu_usage" "$mem_usage" "$disk_usage" "$disk_mount" "$load_per_core" \
"$(printf '"%s",' "${problems[@]:-}" | sed 's/,$//')"
else
echo "===== 巡检报告 $(hostname) $(date '+%F %T') ====="
printf '%-14s %s\n' "CPU:" "${cpu_usage}%"
printf '%-14s %s / %s MB\n' "内存:" "${mem_used}" "${mem_total}"
printf '%-14s %s (%s)\n' "磁盘峰值:" "${disk_usage}%" "${disk_mount}"
printf '%-14s %s (每核 %s)\n' "负载(1m):" "${load1}" "${load_per_core}"
echo
if [[ $status == "OK" ]]; then
echo "结论: 全部指标正常"
else
echo "结论: 发现 ${#problems[@]} 项异常"
for p in "${problems[@]}"; do echo " - $p"; done
fi
fi
# 退出码约定: 0 正常, 1 警告
[[ $status == "OK" ]] && exit 0 || exit 1
设计要点
1. 退出码即接口
脚本用退出码表达结果,可以直接被监控系统消费:
配合 cron:0 9 * * * /opt/ops/health-check.sh -j >> /var/log/health.log 2>&1 || 告警命令
2. 双输出模式
- 人类可读:终端直接看,排版对齐。
-j JSON:便于 jq 解析、写库、推送。
3. 阈值集中在顶部
不要散落在各处。集中定义便于按环境覆盖:
CPU_THRESHOLD=${CPU_THRESHOLD:-80}
这样可以在调用时用环境变量临时覆盖,无需改脚本。
常见坑
set -e 与阈值判断冲突:awk 判定返回非零会直接退出脚本,因此用 set -uo pipefail 而不加 -e。
- CPU 计算:
/proc/stat 的第一次读数是累计值,必须隔一小段时间再读一次求差值。
- 磁盘排序:
df -h 的百分比带 %,排序前要先去掉,否则字符串排序会出错。
小结
- 巡检脚本的核心是「阈值可配 + 输出可解析 + 退出码可用」。
- 用 awk 做浮点比较,比 bash 的
-gt 更稳妥。
- 输出 JSON 是把脚本接入更大自动化体系的关键一步。