Linux服务器巡检脚本:Shell自动化运维与告警收敛实战

Linux服务器巡检脚本:Shell自动化运维与告警收敛实战 简介这份 Linux 服务器日常巡检脚本资料面向系统管理员与运维初学者用于解决服务器状态难以人工逐台核查、隐患发现滞后的问题。脚本覆盖磁盘、内存、CPU、进程、文件更改、用户登录等巡检项并统计物理与逻辑 CPU、核心数、内存使用率、磁盘与 Inode 占用、僵尸进程、自启动服务、计划任务、NTP、JDK 版本等指标巡检结果汇总成报表通过 mail 发送到指定邮箱同时在 log 目录留下按日期命名的报告文件。资源包共 1 个 doc 文档约 118KB直接提供可参考的脚本源码与注释说明包含环境变量设置、IP 获取、版本判断与各巡检函数的组织方式便于按自身环境修改后放进 cron 定时执行。目前已有 1372 人学习下载适合希望快速搭建巡检体系、减少重复手工检查、提升服务器可靠性与安全性的运维人员参考使用。1. 为什么三台以上的 Linux 服务器就不该再用人工敲命令巡检一台 Linux 服务器上df -h、free -m、uptime、ss -lntp这几条 linux常用命令敲一遍两分钟能看完。问题出在第二台、第三台命令一样结论不一样人脑得记住上一台的数值再去对比到第十台漏看一个挂载点、看错一个 inode 占比几乎是必然。Linux 服务器日常巡检脚本的价值就是把重复动作固化成一条命令让每台机器吐出一份格式统一、阈值统一的体检报告。这也是服务器运维里性价比最高的一段自动化不用上监控系统不用改业务代码一个 shell脚本配 cron就能覆盖 CPU、内存、磁盘、服务端口、登录审计这几类最常出事的地方。云服务器按量计费的场景下一次磁盘写满带来的排查时间通常比写脚本贵得多。下面按“采什么—怎么写—怎么落地—怎么进阶”展开适合手里管着几台到几十台 Linux 服务器、想把巡检先自动化起来的人。代码可以直接抄参数和阈值都标清楚方便按自己的环境改。2. Linux 巡检脚本到底采什么CPU、内存、磁盘与服务的指标取舍2.1 采集口径比采集命令更重要用uptime看平均负载得先对齐 CPU 核数4 核机器 load1 到 4 就是满载32 核机器 load1 到 4 只是轻载。很多巡检脚本把 load 固定当阈值 5结果小机器天天告警大机器出了事也不响。CPU 利用率同理top -bn1的瞬间值抖动大用/proc/stat前后两次采样算差值更稳或者退一步用mpstat拿 1 分钟平均值。内存要区分 used 和 available。/proc/meminfo里的MemAvailable才是“新申请内存还能拿到多少”的估计MemFree在 Linux 上通常被 page cache 占满直接拿 free 列当阈值会误报。swap 也别只看用了多少要看pswpin/pswpout换页速率——swap 占用高但换页速率接近 0说明只是老页面待着不动不一定是故障。# 采样 1 秒计算两次 /proc/stat 的差值得到整体 CPU 使用率 read_cpu_usage() { local line1 line2 line1$(grep ^cpu /proc/stat) sleep 1 line2$(grep ^cpu /proc/stat) awk -v a$line1 -v b$line2 BEGIN{ split(a,x, ); split(b,y, ); for(i2;i8;i){t1x[i]; t2y[i]} idle(y[5]-x[5]); totalt2-t1; printf %.1f\n, (total-idle)*100/total } }x[5] 是 idle 字段顺序是 user nice system idle iowait irq softirq两次采样取差值得到这段时间内非 idle 的占比。参数说明sleep 1就是采样窗口压到 0.5 秒也能用但数值跳动会明显线上巡检一般 1 秒足够。指标采集来源常见阈值备注平均负载/proc/loadavg、uptimeload1 0.7×核数必须除以核数看CPU 使用率/proc/stat两次采样80% 持续 5 分钟单点高不用报内存可用/proc/meminfoMemAvailable 总内存 10%别用 MemFreeswap 换页/proc/vmstatpswpin/pswpout速率持续 0静态占用高不一定有问题磁盘容量df -P85%关键挂载点单列inode 使用率df -Pi85%小文件多的机器早报服务状态systemctl is-active非 active只查白名单里的 unit监听端口ss -lntH关键端口不在和预期清单对比2.2 磁盘与 inode最容易被忽略的巡检项df -h看容量df -i看 inode。日志写不停、邮件队列堆积、临时目录小文件暴涨这些场景容量没满但 inode 先满表现出来还是“磁盘写不进去”。巡检脚本里两个都要看而且都要过滤 tmpfs、devtmpfs、overlay 这类伪文件系统否则每次上报几十行噪音看的人就麻木了。磁盘 IO 用iostat -x 1 2取第二次的%util和await。%util 到 100% 不代表一定是瓶颈NVMe 上这个指标并不准确但持续 100% 加上 await 到几十毫秒基本可以判定这块盘扛不住了。# 遍历真实挂载点容量或 inode 任一超阈值就输出告警行 check_disk() { local warn${1:-85} df -P -x tmpfs -x devtmpfs -x overlay 2/dev/null | awk -v w$warn NR1{ if($50 w) printf DISK_HIGH %s used%s\n, $6, $5 } df -Pi -x tmpfs -x devtmpfs -x overlay 2/dev/null | awk -v w$warn NR1{ if($50 w) printf INODE_HIGH %s used%s\n, $6, $5 } }-P是 POSIX 输出格式字段位置固定方便 awk 切-x排除不关心的文件系统类型overlay 在容器宿主机上很常见。参数说明$5是容量百分比$6是挂载点阈值通过函数第一个参数传入默认 85。2.3 服务、端口与登录审计服务用systemctl is-active批量查端口用ss -lntH对比预期清单。这块最容易做成噪音源机器上跑了几十个 unit逐个查没必要只查你真正关心的那几个——nginx、mysqld、redis、docker 或业务自研进程。把关心清单放进配置文件换服务不用动脚本。登录审计看last -n 20、lastb -n 20、who。重点不是每次巡检都贴一遍登录记录而是发现异常——比如非工作时间登录、同一 IP 大量失败。做法是记录上一次巡检的失败登录计数本次超过增量阈值再报。# NEED_SERVICES 每行一个 unit 名NEED_PORTS 每行一个端口 check_services() { while read -r svc; do [ -z $svc ] continue state$(systemctl is-active $svc 2/dev/null) [ $state active ] || echo SVC_DOWN $svc state$state done $NEED_SERVICES } check_ports() { local open open$(ss -lntH | awk {print $4} | sed s/.*://) while read -r p; do [ -z $p ] continue echo $open | grep -qx $p || echo PORT_MISSING $p done $NEED_PORTS }systemctl is-active在没有 systemd 的容器里会报错脚本开头先判断command -v systemctl。ss -lntH的 H 去掉表头$4是本地地址:端口用 sed 抠出端口和预期清单对比。参数说明NEED_SERVICES、NEED_PORTS是配置文件路径每行一项空行跳过。3. 用 Shell 写出可维护的巡检脚本骨架、函数与阈值传参3.1 脚本骨架时间戳、日志目录与退出码#!/usr/bin/env bash set -euo pipefail TS$(date %Y%m%d-%H%M%S) HOST$(hostname -s) BASE_DIR${INSPECT_DIR:-/var/log/inspect} LOG_DIR$BASE_DIR/$HOST mkdir -p $LOG_DIR TXT$LOG_DIR/inspect-$TS.txt JSON$LOG_DIR/inspect-$TS.json : $TXT; : $JSON DISK_WARN${DISK_WARN:-85} MEM_WARN${MEM_WARN:-90} LOAD_WARN_RATIO${LOAD_WARN_RATIO:-0.7} # 统一输出函数同时写文本行和 JSON 行 emit() { local level$1 key$2 msg$3 printf [%s] %-18s %s\n $level $key $msg | tee -a $TXT printf {host:%s,ts:%s,level:%s,key:%s,msg:%s}\n \ $HOST $TS $level $key $msg $JSON }set -euo pipefail让脚本遇错即停、未定义变量报错、管道任一环失败就算失败避免出现“看起来跑完了其实什么也没查”的情况。TS、HOST拼出日志文件名多台机器日志汇总到同一个目录时不互相覆盖。emit是全文唯一出口文本给人看、JSON 给后续采集器看。参数说明环境变量优先没设就用默认值方便测试时临时压阈值。退出码约定所有项通过返回 0有 warn 返回 1有 crit 返回 2。cron 里可据此触发不同动作比如退出码 2 直接走电话告警。3.2 用函数拆分每类检查项check_mem() { local total avail used_pct total$(awk /^MemTotal:/{print $2} /proc/meminfo) avail$(awk /^MemAvailable:/{print $2} /proc/meminfo) used_pct$(( (total - avail) * 100 / total )) if [ $used_pct -ge $MEM_WARN ]; then emit CRIT mem memory used ${used_pct}% (avail ${avail}kB) return 2 fi emit OK mem memory used ${used_pct}% return 0 } check_load() { local cores load1 ratio cores$(nproc) load1$(awk {print $1} /proc/loadavg) ratio$(awk -v l$load1 -v c$cores BEGIN{printf %.2f, l/c}) if awk -v r$ratio -v t$LOAD_WARN_RATIO BEGIN{exit !(rt)}; then emit WARN load load1$load1 cores$cores ratio$ratio return 1 fi emit OK load load1$load1 cores$cores ratio$ratio return 0 }全部从/proc读取避免依赖 sysstat 之类外部包在没有监控 agent 的云服务器上也能跑。MemAvailable 比 MemFree 更贴近“还能给应用多少内存”。load 用比值判断而不是绝对值核数变化不用改脚本。主流程用 shell脚本for循环遍历函数名比手写一长串更容易加检查项RC0 for fn in check_load check_mem check_disk check_services check_ports; do rc0 $fn || rc$? [ $rc -gt $RC ] RC$rc done exit $RC注意每加一个检查项只要写一个函数并挂到循环里不要在主流程里堆if。函数内部只负责返回码和emit不负责处理退出这样单个检查项出问题时不会打断后面的巡检。3.3 阈值参数的传参方式与配置分离方式适用场景优先级环境变量测试临时压阈值CI 里跑最高配置文件日常固定值多机器差异化中命令行参数人工单次排查最高getopts 解析# 先默认再加载配置文件最后被环境变量覆盖 CONF${CONF:-/etc/inspect.conf} [ -r $CONF ] . $CONF配置文件长这样一行一个键DISK_WARN85 MEM_WARN90 LOAD_WARN_RATIO0.7 NEED_SERVICES/etc/inspect.services NEED_PORTS/etc/inspect.ports配置文件权限建议 600里面一旦放 webhook 地址或令牌就更要注意。用.加载而不是解析好处是配置里可以直接写表达式坏处是它本质上是执行代码只放可信内容。4. 巡检结果的输出与落地JSON、定时任务与告警收敛4.1 人读文本与机器可读 JSON 双输出emit一次同时写两份.txt给人直接cat.json每行一条方便被 filebeat、fluent-bit 这类采集器收走也方便用 jq 现场筛。下面这条命令把最新一次巡检里所有非 OK 的项按时间排出来jq -c select(.level!OK) /var/log/inspect/*/inspect-*.json | tail -20-c是紧凑输出一行一条select把 OK 过滤掉剩下的就是要看的。参数说明路径用通配符可以一次扫多台机器的日志如果机器多最好换成按天分目录避免一次打开太多文件。4.2 用 cron 与 systemd timer 定时执行crontab 写法# 每天 8:00 全量巡检17:30 再跑一次 0 8 * * * /usr/local/sbin/inspect.sh -c /etc/inspect.conf /var/log/inspect/cron.log 21 30 17 * * * /usr/local/sbin/inspect.sh -c /etc/inspect.conf /var/log/inspect/cron.log 21systemd timer 写法适合需要依赖关系、错过补跑的场合# /etc/systemd/system/inspect.service [Unit] DescriptionLinux host inspect [Service] Typeoneshot EnvironmentDISK_WARN85 ExecStart/usr/local/sbin/inspect.sh # /etc/systemd/system/inspect.timer [Unit] DescriptionRun inspect daily [Timer] OnCalendar*-*-* 08:00:00 Persistenttrue [Install] WantedBytimers.targetsystemctl daemon-reload systemctl enable --now inspect.timer systemctl list-timers inspect.timerPersistenttrue表示上一次因为停机错过触发开机后会补跑一次。systemctl list-timers能直接看到下一次触发时间比 crontab 更好排查“为什么没跑”。维度cronsystemd timer排查没跑日志分散靠 mailsystemctl status一目了然错过补跑不支持Persistenttrue依赖其他服务需要手写等待After 声明即可跨平台几乎所有发行版需 systemd4.3 告警收敛与去重脚本每分钟发一封“磁盘 86%”的邮件运维会直接把整个发件人屏蔽掉等于没告警。常见做法是状态文件加冷却时间ALERT_STATE${ALERT_STATE:-/var/lib/inspect/alert.state} COOLDOWN${COOLDOWN:-21600} # 默认 6 小时 maybe_alert() { local key$1 msg$2 local now last tmpfile now$(date %s) last$(grep ^$key $ALERT_STATE 2/dev/null | cut -d -f2 || echo 0) if [ $(( now - last )) -lt $COOLDOWN ]; then return 0 fi send_alert $msg tmpfile$(mktemp) grep -v ^$key $ALERT_STATE 2/dev/null $tmpfile || true echo $key$now $tmpfile install -m 600 $tmpfile $ALERT_STATE rm -f $tmpfile }key用“主机名检查项”拼比如host1:mem、host1:disk:/var同一个问题在冷却时间内只发一次问题恢复后再出现可以主动清掉对应的状态行让它立刻能报。参数说明COOLDOWN默认 6 小时磁盘类可以调短到 1 小时服务宕机类建议直接设 0每次都报。发送渠道不管是邮件、企业微信机器人还是自建 webhook统一封到send_alert里脚本主体不用关心。5. 巡检脚本的进阶并发采集、基线对比与虚拟化场景适配5.1 用后台任务把多机巡检压到秒级单机巡检很快但如果你有几十台要串行 ssh 过去收集整体时间就上来了。本机跑的时候也可以并发特别是有几项会走 sleep 或外部命令时run_parallel() { local fns(check_load check_mem check_disk check_services check_ports) local pids() rc0 p r for fn in ${fns[]}; do $fn pids($!) done for p in ${pids[]}; do r0; wait $p || r$? [ $r -gt $rc ] rc$r done return $rc }注意emit写文件不是原子的多进程并发写同一行可能交错。要么让每个检查项先写独立临时文件、最后合并要么加 flock 串行化写动作。检查项数量多、单项耗时长时才值得并发否则 flock 的开销比收益还大。5.2 基线对比从单点数值变成趋势只做阈值判断磁盘到 85% 才报已经在临门一脚了。把关键数值每天记录一行 CSV一周后看斜率更早发现风险磁盘 60% 但每周涨 8%两周后就会报警。# 从 JSON 里抽出磁盘和内存的关键数值按时间排出 jq -r select(.keydisk or .keymem) | [.ts, .host, .key, .msg] | tsv \ /var/log/inspect/*/inspect-*.json | sort | tail -30-r是原始字符串输出tsv把数组转成 tab 分隔方便直接喂给表格工具或导入监控。参数说明多台机器场景下先按 host 分组再排时间否则混在一起看不出趋势。5.3 虚拟机与容器场景下的巡检差异虚拟机里/proc看到的是虚机视角的数值磁盘 IO 实际落在宿主盘上iostat的 %util 会失真通常要在宿主机侧再看一层。容器里更要小心两点容器本身有 CPU、内存的 cgroup 上限进程读到的/proc/meminfo是宿主机的得读 cgroup 文件才准容器里的df默认看的是宿主机挂载点要针对容器挂载卷单独检查。# cgroup v2容器内存限额和当前用量 if [ -r /sys/fs/cgroup/memory.max ]; then max$(cat /sys/fs/cgroup/memory.max) cur$(cat /sys/fs/cgroup/memory.current) if [ $max ! max ]; then pct$(( cur * 100 / max )) [ $pct -ge $MEM_WARN ] emit WARN cgroup_mem used ${pct}% of limit fi ficgroup v1 对应memory.limit_in_bytes和memory.usage_in_bytes路径在/sys/fs/cgroup/memory/下。判断走哪套用stat -fc %T /sys/fs/cgroup返回cgroup2fs就是 v2。参数说明max为字符串max表示没设置内存限额这时不用继续算百分比跳过即可cur单位是字节和限额单位一致所以可以整数相除。本文还有配套的精品资源点击获取