☰
Linux服务器自动化巡检:Shell采集+Python输出表格实战
2026/10/14 10:31:11 网站建设 项目流程

你有没有遇到过这种场景:服务器突然磁盘打满、内存飙升、服务宕机,但打电话问你的时候,你只能一台一台 SSH 上去敲命令,手工拼一条状态汇报,运气好半小时能完事,运气差直接机房熬一宿。

前阵子我接手了一批服务器巡检的活,一开始也是纯手工登上去 free、df、top、systemctl status 一个个看。跑了两个周期就扛不住了:服务器一多,光登录就要十几分钟,输出格式乱,漏查项也时有发生。后来我把整个流程拆成了两段——shell 负责采集,Python 负责整理成表格,配一个大循环把结果全部汇总出来。改完之后的体验完全不一样:一条命令扫完全部机器,输出整齐的表格,磁盘、内存、CPU、服务状态一眼扫完,谁有问题直接被标记出来。

这篇文章就把这套巡检方案完整拆开讲。不管你是刚接触 Linux 运维的新手,还是已经在写脚本但还在用 echo 拼字符串的老手,都能从这里拿到一套可以直接用的东西。方案本身不挑发行版,CentOS、Ubuntu、Debian 都能跑,核心思路就两个:采集和展示分离,脚本和表格输出分离。

1. 巡检到底在巡什么:先把指标清单定下来

网上的巡检脚本一搜一大把,但多数脚本的通病是想到什么采集什么,CPU 有了、磁盘有了、内存有了,真正遇到问题时才发现漏了关键项。我在动手写脚本之前,先花了两个小时把巡检指标彻底梳理了一遍,原则只有一个:凡是能通过命令拿到的状态,就不用手工确认。

1.1 硬指标:CPU、内存、磁盘与负载

这四个是服务器状态的基础,也是告警的主要来源。CPU 我重点看使用率和负载均衡情况,内存看总量、已用、可用和 Swap 的使用比例,磁盘则把分区使用率超过 80% 的单独标记,负载重点对比 load average 和 CPU 核数的关系。

关于负载这里有个常见的误区,单看 load average 数值没有意义。一台 2 核机器负载跑到 4,和一台 16 核机器负载跑到 4,是完全不同的状态。前者已经严重过载,后者还在安全范围。脚本里我做了换算,按固定公式判定:负载值超过核数的 70%,就提示关注,超过核数则标记为严重告警。

磁盘检查还有个容易踩的坑:df -h 默认会把 tmpfs、overlay 这类虚拟文件系统也列出来,这些数据存在内存里,磁盘满没满跟它们没关系。如果脚本不做过滤,巡检表格里会出现一堆让人误判的假数据。我处理的方式是直接排除这些虚拟文件系统,只保留真实物理分区。

内存方面,很多新手看 free 命令会把“已用”理解为真正的内存消耗,其实在 Linux 里还有 buff/cache 这部分。缓冲和缓存内存是可以释放的,真要判断内存是否吃紧,关键看 available 这个值。脚本同样做了处理,显示的时候把缓存分开标注,避免误导。

1.2 软状态:服务存活、端口监听与关键进程

硬件指标正常不代表服务没问题。我见过内存、磁盘、CPU 全部正常,但网站就是打不开的情况,原因就是后端服务悄悄挂了,或者端口被占用。

服务检查分为两类:系统级服务和业务级进程。系统级服务直接用 systemctl is-active 判断,输出 active、inactive、failed 这些状态,脚本里统一转成自定义的状态码,方便 Python 侧汇总。业务进程检查则用 pgrep -f 匹配进程关键词,关键词匹配有个需要注意的地方:匹配关键字如果太短,容易误伤别的进程;如果太长,又可能漏掉。我一般建议直接用启动命令里的独特参数,比如 java 服务的 jar 包名称,nginx 的 master process。

端口监听检查用 ss -tlnp 解析,这里有个细节:ss 命令的输出在不同发行版上字段位置略有差异,解析的时候不要用固定列号,直接用正则匹配端口号后面对应的进程信息更稳。

1.3 需要人看的维度:登录审计、时钟同步与定时任务

有些状态脚本能查,但结论需要人来判断,这类我也一并纳入了巡检范围。登录记录看的是当前登录的用户和登录来源 IP,检查有没有异常来源。时钟同步判断用的是 chronyc tracking 或 timedatectl,输出是否同步的状态。定时任务则检查 crontab 和 /etc/cron.d/ 目录下有哪些任务,便于确认“是不是有人悄悄往生产服务器塞了东西”。

这三项不设自动化阈值,只做展示。因为“异常登录”和“异常定时任务”的判定依赖业务背景,机械地设定规则容易出现误报。表格里把原始数据列出来,人一眼扫过去就能判断。

2. 为什么是 shell 采集、Python 出表:双脚本架构的底层逻辑

把巡检拆成两段脚本是我实践后觉得最优的做法。一开始我尝试过只用 shell 一个脚本干完所有事,结果代码极其臃肿:采集逻辑、文本解析、对齐输出全混在一起,调试一次痛苦一次。后来拆分之后,两边各司其职,逻辑清晰了一倍不止。

2.1 shell 的不可替代性:服务器自带、零依赖、执行效率高

出门在外跑巡检,最怕的就是目标服务器上缺这少那。Python 不一定预装,pip 装第三方库又不现实,但 shell 是每一台 Linux 都有的东西。采集阶段用纯 shell 和 Linux 自带的命令,好处极其明显:零额外依赖,脚本拷过去就能跑,兼容性拉满。

Linux 下很多系统状态只存在于 /proc 和系统命令输出里,这些信息用 shell 获取几乎是零成本。比如 CPU 使用率,读取 /proc/stat 再做两次采样计算;内存信息直接解析 /proc/meminfo;磁盘状态用 df 一条命令。Shell 脚本本身就是胶水语言,把这些系统信息和命令拼接到一起,效率非常高。

采集阶段的耗时问题也要考虑。服务器一多,每台机器上执行 df、free、top 这类命令,单次耗时都在毫秒级,但加起来也是一笔开销。Shell 脚本本身不会因为采集合集命令而变得笨重,这是选择它做采集层的关键原因。

2.2 Python 的适配性:字符串处理、对齐、条件高亮都在射程内

到了整理和展示阶段,shell 的短板就暴露出来了。Shell 处理字符串对齐非常痛苦,printf 做固定宽度输出需要精确计算中文字符宽度,否则表格列全部错位。而 Python 处理这类问题天然轻松:split 拆分字段、列表存储行数据、f-string 格式化对齐,再配合条件判断给不同状态标色,几百行数据分钟级就能整理出格式规整的表格。

Python 还有一个 shell 做起来非常麻烦的事情——跨平台统一。如果不依赖第三方库,Python 的字符串格式化在 Windows、macOS、Linux 上表现一致,而 shell 在某些环境下多少会有差异。我们的方案是:采集端必须是 shell,因为要跑在目标机器上;汇总和展示端用 Python,跑在运维机器上,两边优势互补,正好卡在各自的舒适区。

2.3 为什么要“可输出表格”:表格是巡检结果最好的交付形态

巡检结果如果不整理,就是几十行无结构的文本输出。但整理成表格之后,信息的密度提高了好几倍:行是机器列表,列是各项指标,一眼扫下去就能看到哪些指标异常。表格还是最好的交付形态——可以直接贴到周报里,可以转成 HTML 发邮件,可以写进 Confluence 和知识库。

我最终选择了两个输出方向:控制台终端输出带颜色高亮的表格,同时生成一个 Markdown 格式的结果文件。Markdown 的好处是通用,GitLab、GitHub、Confluence 原生支持渲染,不需要额外工具,复制粘贴直接变表格,极大地省去了“整理汇报”的时间成本。

3. shell 采集脚本怎么写:核心代码与关键协议

采集脚本的目标是把“服务器状态”变成“结构化文本”。这里最核心的设计是定义了一套键值对输出协议,所有采集结果按固定格式输出,Python 再按协议解析,两边互不依赖。

3.1 先约定输出格式:键值对协议是脚本协作的基石

协议格式我定义为:

KEY|VALUE|STATUS

三个字段用竖线分隔。KEY 是指标名称,如 CPU_USAGE、MEM_USAGE、DISK_USAGE;VALUE 是具体数值;STATUS 是状态标记,OK、WARN、CRIT 三个值。之所以这么设计,是因为它足够简单,shell 拼字符串不费劲,Python 解析也不费劲。竖线分隔还有一个额外好处:即使 VALUE 里包含空格,split 的时候只要按竖线切就不会出问题。

输出里需要展示说明的只读项,统一把 STATUS 置为空字符串,表格里对应显示为 “-”,保持协议一致。

3.2 核心采集模块:CPU、内存、磁盘、负载、服务状态

下面这段 shell 代码是我采集脚本的核心片段,涵盖了 CPU、内存、磁盘、负载、服务状态五个最常用的模块。

#!/bin/bash # collect.sh - 服务器巡检采集脚本,输出 KEY|VALUE|STATUS # CPU 使用率:两次采样 /proc/stat CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1) CPU_STATUS="OK" if awk "BEGIN{exit !($CPU_USAGE > 80)}"; then CPU_STATUS="WARN" fi echo "CPU_USAGE|${CPU_USAGE}%|${CPU_STATUS}" # 内存使用率:通过 available 和 total 计算 MEM_TOTAL=$(free -m | awk '/^Mem:/ {print $2}') MEM_AVAIL=$(free -m | awk '/^Mem:/ {print $7}') MEM_USAGE=$(( (MEM_TOTAL - MEM_AVAIL) * 100 / MEM_TOTAL )) MEM_STATUS="OK" if [ "$MEM_USAGE" -gt 80 ]; then MEM_STATUS="WARN" elif [ "$MEM_USAGE" -gt 90 ]; then MEM_STATUS="CRIT" fi echo "MEM_USAGE|${MEM_USAGE}%|${MEM_STATUS}" # 磁盘使用率:排除虚拟文件系统,取真实分区最大值 DF_OUTPUT=$(df -h | grep -Ev 'tmpfs|overlay|udev|shm|loop' | awk '{print $1, $5}' | tail -n +2) MAX_DISK_USAGE=0 DISK_DETAIL="" while read -r FS USAGE_PERCENT; do USAGE_NUM=$(echo "$USAGE_PERCENT" | tr -d '%') DISK_DETAIL="${DISK_DETAIL} ${FS}:${USAGE_PERCENT}" if [ "$USAGE_NUM" -gt "$MAX_DISK_USAGE" ]; then MAX_DISK_USAGE=$USAGE_NUM fi done <<< "$DF_OUTPUT" DISK_STATUS="OK" if [ "$MAX_DISK_USAGE" -gt 80 ]; then DISK_STATUS="WARN" elif [ "$MAX_DISK_USAGE" -gt 90 ]; then DISK_STATUS="CRIT" fi echo "DISK_USAGE|${MAX_DISK_USAGE}%|${DISK_STATUS}" echo "DISK_DETAIL|${DISK_DETAIL}|" # 负载:load average 与 CPU 核数对比 CPU_CORES=$(nproc) LOAD_1=$(uptime | awk -F'load average:' '{print $2}' | cut -d',' -f1 | tr -d ' ') LOAD_STATUS="OK" if awk "BEGIN{exit !($LOAD_1 > $CPU_CORES * 0.7)}"; then LOAD_STATUS="WARN" fi if awk "BEGIN{exit !($LOAD_1 > $CPU_CORES)}"; then LOAD_STATUS="CRIT" fi echo "LOAD|${LOAD_1} (${CPU_CORES} cores)|${LOAD_STATUS}" # 服务状态:systemctl 检测 for SERVICE in nginx mysql redis; do ACTIVE=$(systemctl is-active "$SERVICE" 2>/dev/null) if [ "$ACTIVE" = "active" ]; then STATUS="OK" else STATUS="CRIT" fi echo "SERVICE_${SERVICE}|${ACTIVE}|${STATUS}" done

这段代码有很多细节值得展开说说。

CPU 使用率这里,top -bn1 第一次输出的数据在系统刚启动或 load 极低时可能不准,需要采样两次。上面的命令只做单次采样,是我刻意简化了便于阅读。实际投入生产时,我会先取一次 top 结果丢弃,再取第二次做计算,这样 CPU 使用率才稳定。

内存使用率用的是 available 而不是 used。从运维角度,available 更能真实反映“还有多少能用”,因为它已经把 buff/cache 中可以回收的部分计算进去了。如果直接用 used 计算,经常出现内存显示用了 90% 但系统一点也不卡的现象,会误导告警判断。

磁盘统计的 grep 排除用了 -Ev,把 tmpfs、overlay、udev、shm、loop 全部过滤掉。不加这层过滤的后果我上面说过,表格里会混入大量非法检查项。再强调一遍:这些虚拟文件系统也不是完全不占内存,在内存告警时把内存占用归属到缓存上才是合理的,磁盘巡检只看真实盘位。

负载阈值用了两层 awk 判断,一层是 0.7 倍的核数告警,一层是核数告警。这个阈值可以根据机器角色调整,比如数据库机器可以调严到 0.5 就告警。nproc 获取的核数是逻辑核数,也就是超线程也算一核,符合 load average 的计算口径。

3.3 远端批量采集:免密登录与循环调度

巡检不可能只在一台机器上跑,集群场景下需要批量采集。我这边处理方式是:运维机上配置好到所有目标服务器的 SSH 免密登录,然后用一个 for 循环把采集脚本分发到各台机器上执行。

#!/bin/bash # remote_collect_all.sh - 批量远程采集 HOSTS="192.168.10.11 192.168.10.12 192.168.10.13" COLLECT_SCRIPT="./collect.sh" for HOST in $HOSTS; do echo "===== ${HOST} =====" ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no "$HOST" 'bash -s' < "$COLLECT_SCRIPT" done

ssh 的 -o StrictHostKeyChecking=no 是为了批处理时避免首次连接询问 yes/no 而阻塞,内网环境可以接受这个降级。生产公网环境建议提前把目标机的 HostKey 加入 known_hosts,不要裸奔跳过校验。

ConnectTimeout=5 非常关键。不加这个参数,某台机器宕机或网络不通时,SSH 连接会一直卡在原地,整个巡检流程被拖死。加了之后,单台机器 5 秒超时,失败直接跳过,巡检流程继续往下走。

这种方式没有用并行,是串行执行的。如果服务器数量超过 50 台,串行会导致总耗时明显增加,我建议改用自身带的循环调度,或者直接并行执行。后续版本需要我可以用 xargs -P 参数做并行优化,但先跑通脚本逻辑更重要。

4. Python 脚本怎么写:采集输出到表格展示

数据库采集完成后得到的是一堆键值对文本,Python 脚本负责把这些文本按机器分组,解析成结构化数据,再做格式化和高亮。

4.1 解析逻辑:从文本到结构化数据

Python 解析这块非常直接,难点只在于设计一个合理的数据结构。我建议用字典嵌套列表的方式:外层字典的 key 是主机 IP,value 是一个列表,列表里每个元素是 (指标名, 数值, 状态) 的元组。这样按机器遍历和按指标查找都很方便。

#!/usr/bin/env python3 # report.py - 解析采集结果并输出表格 import sys import re from collections import defaultdict def parse_input(raw_data): hosts = defaultdict(list) current_host = None for line in raw_data.strip().splitlines(): line = line.strip() if not line: continue if line.startswith("====="): current_host = line.strip("= ") continue if current_host is None: continue match = re.match(r'^(.+?)\|(.+?)\|(.*)$', line) if match: key, value, status = match.groups() hosts[current_host].append((key, value, status)) return hosts

解析时我用了一个正则,可能有人会觉得可以写成更简单的 split("|", 2)。这里特意用正则的原因是想兼容 VALUE 字段里潜在包含竖线的情况,虽然目前采集项里还没有,但留着扩展位成本不高。实际数据验证过,正则性能开销对几十台机器的巡检数据量完全没有压力。

4.2 表格组件:对齐计算与状态高亮

表格输出用最基础的字符串格式化实现,不引入第三方库,保持零依赖的初衷。控制台表格需要对每一列计算最大宽度,才能做到对齐。代码实现如下:

def format_table(hosts): headers = ["HOST", "CPU%", "MEM%", "DISK%", "LOAD", "NGINX", "MYSQL", "REDIS"] rows = [] for host, items in hosts.items(): values = {"CPU": "-", "MEM": "-", "DISK": "-", "LOAD": "-", "SERVICE_nginx": "-", "SERVICE_mysql": "-", "SERVICE_redis": "-"} for key, val, status in items: if key == "CPU_USAGE": values["CPU"] = val elif key == "MEM_USAGE": values["MEM"] = val elif key == "DISK_USAGE": values["DISK"] = val elif key == "LOAD": values["LOAD"] = val elif key.startswith("SERVICE_"): values[key] = val rows.append([host, values["CPU"], values["MEM"], values["DISK"], values["LOAD"], values["SERVICE_nginx"], values["SERVICE_mysql"], values["SERVICE_redis"]]) return headers, rows def print_aligned_table(headers, rows, color=True): width = max([len(h) for h in headers] + [len(r[col]) for r in rows for col in range(len(headers))]) + 2 for col, header in enumerate(headers): print(header.ljust(width), end="") print() print("-" * width * len(headers)) for row in rows: for col, val in enumerate(row): print(val.ljust(width), end="") print()

这里每列的宽度都是以所有行内容的最大长度为基准。实际跑起来有个体验问题:如果主机名或 IP 很长,整张表格会被撑得很宽。我后来加了一个折中方案,设置最大宽度 30 个字符,超过多的截断显示。更长的信息可以在下一行“详细信息”里看。

控制台高亮不想调第三方库,用 ANSI 转义序列很简单。状态是 CRIT 的单元格标红,WARN 标黄,OK 不处理。ANSI 代码网上到处都有,但一个细节不能省:转义序列只对终端有效,如果输出重定向到文件,这些字符会变成文件里的乱码。所以 Python 脚本加了一个 --no-color 参数,非交互场景自动关闭颜色。

4.3 联动 shell 采集:一条命令输出完整巡检表格

到这里,把 shell 采集和 Python 展示串联起来,最终调用是这样:

for HOST in $HOSTS; do ssh -o ConnectTimeout=5 "$HOST" 'bash -s' < collect.sh done | python3 report.py

这样直接通过管道把采集结果喂给 Python。实际使用中,我还增加了一个中间层,把采集结果落盘保存,方便留痕。巡检完了除了终端显示,自动归档一份原始数据到当天的目录里,这就是“可输出表格”的完整落地。

4.4 表格最终效果

跑一遍巡检后的控制台效果大致如下(示意,终端带颜色):

HOST CPU% MEM% DISK% LOAD NGINX MYSQL REDIS 192.168.10.11 12% 45% 67% 1.2 (4 cores) active active active 192.168.10.12 89% 92% 88% 6.8 (4 cores) failed active active 192.168.10.13 5% 33% 25% 0.4 (8 cores) active active inactive

第二台机器看起来有明显问题:CPU 89%、内存 92%、磁盘 88%,mysql 状态是 failed,负载 6.8 已经超了 4 核的负载警戒线。巡检表格的价值就在这里,不用逐条看状态,一眼就能定位到问题机器。

5. 踩坑实录:常见问题与排查技巧

脚本写出来不难,难的是让它长期稳定运行。下面这些问题都是我在实际使用中一条一条踩出来的,整理成一个速查表,能帮你在引入这套方案时少走很多弯路。

5.1 中文环境导致的乱码对齐问题

服务器系统语言不统一,有的机器是 en_US,有的机器是 zh_CN。中文字符在表格里占的显示宽度与 ASCII 字符不一致,Python 的 ljust 是按字符数计算的,中文会被顶破对齐。如果输出乱码,现象是表格错位、列宽时大时小。

我的经验是消息里的指标名全部用英文缩写,比如 CPU 不写“中央处理器”,磁盘不写“磁盘使用率”。日志消息里要展示的附加信息,只在需要人工确认的字段放中文,而且只在执行人手动确认时看,不进入表格解析链路。这是一个根治方案:从采集端入手,让所有表格的数据都是 ASCII 字符,彻底避开对齐问题。

5.2 SSH 免密登录与权限问题的隐蔽陷阱

SSH 权限不对是最隐蔽的问题。正常配置了公钥,手摸 SSH 连接成功,但放在 for 循环里运行,可能某些参数导致其他机器登录失败。常见原因:authorized_keys 文件权限是 644,而不是建议的 600;~/.ssh 目录权限是 755,而需要的是 700;还有把公钥放在了家目录之外的地方。

排查技巧是先用 ssh -v 逐台跑一遍,看日志停在哪一步。大部分问题都集中在权限过宽导致被 SSH 拒绝。我整体建议这样自查:

  • ~/.ssh 目录权限必须为 700
  • authorized_keys 文件权限必须为 600
  • 目标机家目录本身不能是组可写状态,否则 SSH 会直接拒绝

另外,用 root 免密还是用普通用户加 sudo 跑巡检,要考虑清楚。用 root 脚本写法最方便,但安全隐患大。我的实践是巡检只读场景下可以用普通用户 + 脚本内需要的 sudo 命令单独授权,但要注意授的命令白名单别太宽。

5.3 crontab 定时任务环境变量缺失

把巡检脚本挂到 crontab 里跑,出现最多的问题就是找不到命令。crontab 的最小环境变量和交互式 shell 不同,PATH 不是全路径,如果在脚本里直接写 top、free、systemctl 这些命令,会爆出 command not found。

解决方式一是脚本开头显式声明环境变量:

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

方式二是脚本内所有命令都写绝对路径。我的建议是二选一,不要混合。全部命令绝对路径最靠谱,但脚本维护时要多敲几个字符,整体思路是把巡检脚本当作系统守护进程一样对待,环境变量自包含。

5.4 磁盘信息解析中的重复与排序

不同服务器分区情况不同,有的服务器有几个磁盘分区,df 输出行数差异很大。如果脚本只取了第一行磁盘分区,就漏掉了其他分区。数据采集的 DISK_DETAIL 字段我在自定义协议里保留了所有分区详细信息,表格中只展示总览“最大使用率”,如果需要看明细,单独做查看项。

处理磁盘输出的另一个细节是按使用率数字排序。如果班里有多台服务器,每台分区数不同,采集顺序不一致,逐台看明细很痛苦。我在一个版本的脚本里做了排序处理:按使用率从高到低排序输出,方便快速找出快满的分区。

5.5 远程脚本执行中的转义与引号问题

ssh 远程执行 bash -s 把本地脚本内容喂给远端 bash,这个过程有个隐蔽坑:如果脚本里有变量或反引号,在某些执行方式下会被本地 shell 先处理掉。我见过的错误是把脚本文件内容用 ssh $HOST "bash -s" 加上双引号传过去,结果脚本里的 $ 和反引号全部被本地解析了一遍,远端拿到的脚本已经变形。

我推荐的做法是用单引号包裹远端执行命令,或者用 stdin 重定向方式:

ssh -o ConnectTimeout=5 "$HOST" 'bash -s' < "$COLLECT_SCRIPT"

这种方式把本地脚本原样通过标准输入传给远端,不经过本地 shell 解析,能避免绝大多数转义问题。你如果学我用的是文档里的方案,就直接抄这一段,别用双引号。

5.6 高负载情况下 top 命令的无响应问题

服务器负载极高时,手工执行 sleep 1 再去采样 top,往往会出现进程卡死,因为 top 需要等待采样时间窗口结束。这个问题在系统负载超过 50 的时候尤其严重,排查现场最关键的时候脚本反而卡死了。

推荐改进方案是全部用读取 /proc/stat 的方式来计算 CPU 使用率,不依赖 top。前面简化版脚本用的是 top 单次取值,生产环境版本我是用 /proc/stat 两次采样实现的。这段代码采集时延极低,不受系统负载影响,非常稳。建议读者在实际使用时直接把 top 那行替换成这段。

6. 巡检的频率与告警联动:让脚本不止于查,还能防

脚本再快,如果靠人每天手动跑,难以为继。巡检自动接入定时任务之后,要做的事情是让这个方案从“被动查询工具”进化成“主动发现工具”。

6.1 设定合理的巡检频率:不是越快越好

巡检频率取决于业务的重要程度和服务器规模。普通业务一天一次或一周两三次即可;核心数据库和网关机器建议一小时一次;如果业务对稳定性极其敏感,甚至要考虑分钟级采集,这就需要引入完整的监控系统,而不是单靠脚本。

我目前的实践是:基础巡检每天早高峰前跑一次,把结果归档;每周日晚跑一次完整周报,包含所有机器的状态明细。这个频率既不打扰系统,也足以让人及时发现问题。用 crontab 实现非常直接:

0 8 * * * /opt/scripts/run_inspection.sh >> /var/log/inspection.log 2>&1

跑完巡检后,可以再写一个 if 判断:如果存在任一 CRIT 状态,立刻用系统自带工具发告警到通信群。这样凌晨出问题也能第一时间通知到人,无需等到早高峰才发现。

6.2 告警策略与白名单:防止狼来了

告警策略要控制噪音,不然一个月跑下来全是无效告警,看到新告警也会麻木。我踩过的坑是:把阈值调到 80% 后,某台磁盘使用率长期在 80% 到 85% 之间徘徊,每天告警,团队直接无视。后来加了三层机制:

  • 阈值分级,80% 设 WARN,90% 设 CRIT,只在 CRIT 时进行即时通知
  • 同一个告警项一天之内最多推一条,避免重复轰炸
  • 针对已知的“历史遗留问题服务器”单独维护白名单,不在白名单内才触发告警

告警联动的代码逻辑不复杂,核心是在 Python 结果解析后加一个状态汇总判断,如果发现 CRIT 项,就发送一个格式化消息到指定通道。这里注意告警消息里只放必要的字段,别像表格一样把所有数据全推一遍,关键机器和关键指标点到即止。

6.3 归档策略:巡检数据要有历史留痕

巡检数据只展示不归档,等于没有历史。出了问题排查时,需要知道这台机器在故障前三天、前一周是什么状态。我的归档方式是每天跑完巡检后,把所有机器对应的键值对结果按日期组织成文件,存储路径放在专用的巡检目录下,命名规则带日期。这样 90 天的数据可以随时翻出来查询。

每个月另外跑一次汇总脚本,把当月每天的磁盘使用率、内存使用率等关键指标抽出来,形成一条趋势曲线。虽然这个阶段的脚本没有图表能力,但把数据抽出来之后,再粘到表格工具里做趋势图,就能直观看到哪些机器的磁盘持续增长、哪些机器内存逐步吃紧。趋势数据对扩容需求很有说服力,我上次申请加机器资源,就是靠三个月磁盘趋势记录推动的。

7. 扩展思路:从巡检脚本到轻量监控体系的衔接

这套双脚本方案的定位很清晰——它是日常巡检和人工盘点时的效率工具,不是要替代专业监控系统。业务规模变大后,工具自然会向更完善的方向演进。但即使将来上了监控系统,这套巡检仍然有存在价值。

7.1 与监控系统并存的定位差异

专业监控系统做的是持续的指标采集、告警和可视化,覆盖秒级、分钟级的数据。巡检脚本则是定时跑一次的扫描,更关注“当前这批机器的整体状态是否正常”。两者的定位差异在于:监控系统回答“现在发生了什么”,巡检脚本回答“这一批机器现在有没有异常”。监控系统出现时间线断裂的空窗期,巡检脚本的归档数据就是最有力的补充,两者并存并不冲突。

7.2 Python 输出模块的扩展方向

如果后续想把巡检结果输出得更完善,Python 侧已经是现成的接口。加一个函数,把当前表格格式转换成 HTML 表格格式,就能配合邮件发送周报。Markdown 已经有了,HTML 也是十几行的事。甚至可以画简单的条形图,不需要外部库,用字符拼一个简单的可视化效果。

我在实践中的做法是保留今天的双脚本结构,把 Python 侧的输出函数做成插件式:业绩对比用哪种格式,就在调用时传哪种格式。这个设计让“可输出表格”变成“可输出任意展示”,不需要重写底层采集脚本。

7.3 Bash 环境多样性兼容

发行版之间命令细节差异比想象中更多。比如 systemctl 在 CentOS 7 以上都有,但在老旧版本里可能没有;nproc 在部分精简系统里可能不存在。脚本上线前,先准备一个小工具函数做命令存在性检查,缺失的命令单独提示,不要在生产机器上运行时才报错。

我维护脚本时有一个原则:兼容性优先,不追求极简。所有能用保守写法的地方都保守些,先用 command -v 检测命令是否存在,再执行。这套巡检脚本在 Ubuntu 和 CentOS 都跑过小半年,除了发行版间密文有细微差异外,没有出现明显的兼容性问题。

这些经验都是在我自己踩了几次坑之后总结出来的。巡检工作看着简单,真正要做得扎实,很多细节都得较真。如果你也想搭一套属于自己团队的巡检方案,希望这篇内容能帮你省下那些不必要的试错时间。个人最核心的建议就两条:一是务必把采集和展示分成两个独立脚本,后续的维护成本会低很多;二是务必给输出定义一个严谨的协议格式,不给解析留模糊空间。把这两件事做扎实了,剩下的就是按需求往采集脚本里加指标的问题了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询