深色模式
巡检脚本编写
摘要:巡检脚本的价值在于把「每天要看的十几个指标」压缩成一屏可读的健康快照。本文用 Bash 实现一个单机巡检脚本:采集 CPU/内存/磁盘/服务/时间同步等指标,统一判定 OK/WARN/ALERT,并输出文本与 JSON 两种格式。
适用环境
bash
bash --version | head -1
which vmstat free df ss systemctl ntpq || sudo apt install -y procps iproute2 systemd ntp1
2
2
操作步骤
一、先定「看什么」
一次有效巡检至少覆盖:
| 维度 | 指标 | 告警阈值参考 |
|---|---|---|
| CPU | load5 / CPU 使用率 | load5 > 核数 × 1.5 |
| 内存 | 可用内存百分比、Swap 使用 | 可用 < 10% 或 Swap > 100MB |
| 磁盘 | 各挂载点使用率、inode | > 85% |
| 服务 | 关键服务 active 状态 | 非 active |
| 网络 | 监听端口、ESTABLISHED 数 | 端口未监听 |
| 时间 | NTP 同步状态 | 偏移 > 500ms |
| 安全 | 最近登录失败次数 | 短时激增 |
二、实现采集函数
bash
#!/usr/bin/env bash
# healthcheck.sh - 输出本机健康快照
set -euo pipefail
DISK_WARN="${DISK_WARN:-85}"
LOAD_FACTOR="${LOAD_FACTOR:-1.5}"
MEM_WARN="${MEM_WARN:-10}" # 可用内存百分比下限
SERVICES="${SERVICES:-nginx sshd}"
STATUS="OK" # 全局最差状态
REPORT=""
# 记录一条检查结果:级别 + 描述
add() {
local level="$1" item="$2" detail="$3"
REPORT+="$(printf '%-6s %-14s %s\n' "$level" "$item" "$detail")"$'\n'
# 升级全局状态:ALERT > WARN > OK
case "$level" in
ALERT) STATUS="ALERT" ;;
WARN) [[ $STATUS == "OK" ]] && STATUS="WARN" ;;
esac
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
三、各项检查
bash
check_cpu() {
local cores load5
cores="$(nproc)"
load5="$(awk '{print $2}' /proc/loadavg)"
local limit
limit="$(awk -v c="$cores" -v f="$LOAD_FACTOR" 'BEGIN{printf "%.2f", c*f}')"
if awk -v l="$load5" -v m="$limit" 'BEGIN{exit !(l>m)}'; then
add ALERT "CPU/load5" "${load5} > ${limit} (cores=${cores})"
else
add OK "CPU/load5" "${load5} (limit ${limit}, cores ${cores})"
fi
}
check_mem() {
local avail_pct swap_used
# free -m 的 Mem 行: Mem: total used free shared buff/cache available → 第 7 列是 available
local avail_mb
avail_mb="$(free -m | awk '/^Mem:/{print $7}')"
avail_pct="$(awk -v a="$avail_mb" '/MemTotal/{printf "%d", a*100/$2}' /proc/meminfo)"
swap_used="$(free -m | awk '/^Swap:/{print $3+0}')"
if (( avail_pct < MEM_WARN )); then
add ALERT "MEM/avail" "${avail_pct}% < ${MEM_WARN}%"
else
add OK "MEM/avail" "${avail_pct}% (swap used ${swap_used}MB)"
fi
}
check_disk() {
while read -r fs size used avail pct mount; do
pct="${pct%\%}"
if (( pct >= DISK_WARN )); then
add ALERT "DISK${mount}" "${pct}% used"
elif (( pct >= DISK_WARN - 10 )); then
add WARN "DISK${mount}" "${pct}% used"
else
add OK "DISK${mount}" "${pct}% used"
fi
done < <(df -P -x tmpfs -x devtmpfs | awk 'NR>1{print $1,$2,$3,$4,$5,$6}')
# inode 也要查:磁盘没满但 inode 用完同样写不进文件
while read -r pct mount; do
pct="${pct%\%}"
(( pct >= DISK_WARN )) && add ALERT "INODE${mount}" "${pct}% used"
done < <(df -Pi -x tmpfs -x devtmpfs | awk 'NR>1{print $5,$6}')
}
check_service() {
for s in $SERVICES; do
if systemctl is-active --quiet "$s"; then
add OK "SVC/$s" "active"
else
add ALERT "SVC/$s" "$(systemctl is-active "$s" 2>/dev/null || echo unknown)"
fi
done
}
check_port() {
local port="$1"
if ss -lnt | awk '{print $4}' | grep -q ":${port}$"; then
add OK "PORT/$port" "listening"
else
add ALERT "PORT/$port" "not listening"
fi
}
check_ntp() {
if command -v chronyc >/dev/null 2>&1; then
chronyc tracking 2>/dev/null | awk -F'[: ]+' '/Last offset/{printf "offset_ms=%s\n", $4}'
elif command -v ntpq >/dev/null 2>&1; then
ntpq -p | tail -1
else
add WARN "NTP" "未安装 chrony/ntp"
fi
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
注意
只查磁盘空间不查 inode 是巡检脚本的经典疏漏:小文件极多的场景(如日志、缓存)会先耗尽 inode,表现为「磁盘还有空间但报 No space left on device」。
四、输出报告
bash
main() {
echo "===== 主机健康快照 $(date '+%F %T') $(hostname) ====="
check_cpu; check_mem; check_disk; check_service
check_port 80; check_ntp
printf '%s' "$REPORT" | sort
echo "----------------------------------------"
echo "总体状态: $STATUS"
[[ $STATUS == "ALERT" ]] && exit 1
exit 0
}
main "$@"1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
五、接入定时与告警
bash
# 每 10 分钟巡检一次,非 OK 时输出留痕
*/10 * * * * /usr/local/bin/healthcheck.sh >> /var/log/ops/health.log 2>&1
# 只看告警项
/usr/local/bin/healthcheck.sh | grep -E "^(ALERT|WARN)" || echo "全部正常"1
2
3
4
5
2
3
4
5
六、输出 JSON 便于采集
若需要接入监控系统,可用 Python 版本采集后输出 JSON,再由 agent 上报。Bash 版本适合人工查看和 SSH 批量执行,两者可并存。
验证
- [ ] 脚本输出包含 CPU、内存、磁盘、服务四类结果
- [ ] 人为把
DISK_WARN调低到 1,能触发 ALERT 且退出码为 1 - [ ]
systemctl stop一个被检查的服务后,巡检能报 ALERT - [ ] 脚本在 cron 下执行结果正确(注意 cron 的 PATH 较窄,命令用绝对路径)
bash
DISK_WARN=1 /usr/local/bin/healthcheck.sh >/dev/null; echo "退出码=$?"1
常见坑
- cron 环境 PATH 太窄:
systemctl、ss找不到,脚本应使用绝对路径或在顶部显式export PATH。 - 阈值写死在脚本里:不同机器配置不同,应用环境变量或配置文件覆盖。
- 只看磁盘不看 inode:见上文注意事项。
- 没有退出码语义:监控系统无法判断成败,应让 ALERT 时退出非零。
- 巡检本身太重:
du、全盘扫描会让巡检变成新的负载来源,应使用df等轻量命令。