用 Codex 写 Linux 巡检脚本:默认只读是边界,--self-test 才是验收
目标很具体:一个默认只读、可配置、能同时输出文本和 JSON 报告、有明确退出码和自测模式的 Linux Bash 主机健康巡检脚本。
需求先列清楚:
- 支持多种信息源:
/proc、df、ps、systemctl、journalctl、ss; - 兼容 Ubuntu、Debian、Rocky Linux、AlmaLinux、CentOS Stream;
- 处理命令缺失、权限不足、systemd 不可用等异常分支;
- 阈值、退出码和机器可读报告作为自动化接口;
- 最重要的安全边界:巡检脚本默认不应该修改系统——不修改配置、不重启服务、不清理文件、不调用
sudo。
第 5 条决定了整个脚本的形态。采集只能读,任何修复动作都不在脚本职责内。
采集项与对应命令
| 采集项 | 命令 / 来源 |
|---|---|
| 1 分钟归一化负载 | /proc/loadavg |
| 内存使用率 | /proc/meminfo 的 MemTotal / MemAvailable |
| 磁盘空间 | df -P |
| inode | df -Pi |
| 失败的 systemd 单元 | systemctl --failed |
| 僵尸进程 | ps |
| 最近 1 小时高优先级日志 | journalctl |
| 最近 24 小时 OOM | journalctl |
| TCP 监听端口数量 | ss -lntH |
内存看的是 MemAvailable 占比,不是 free 值,这点直接影响告警准确度。
三层架构
脚本拆成采集、判断、报告三层。采集层只负责拿到原始数据并标准化,判断层只吃标准化结果,报告层只消费判断结果。这样文本和 JSON 两条输出路径的结论必然一致,不会出现文本显示告警、JSON 里却写着正常的尴尬。
退出码约定
退出码固定,不随参数变化:
0正常1告警2严重3脚本错误
参数必须校验,告警阈值必须小于严重阈值,否则以 3 退出。JSON 必须正确转义,能被标准解析器读取。
set -euo pipefail 不是万能药
严格模式用 set -euo pipefail:-u 帮忙发现未定义变量,pipefail 让管道中间命令的失败也能传递出来。
但严格模式不是魔法。if、!、|| 和命令替换这几类场景里,返回码仍要显式处理。所以对 systemctl 和 journalctl 单独判断:读取失败时显示 INFO / N/A,而不是错误地显示 OK / 0。把这两个命令的失败当成"一切正常",是这类脚本最容易踩的坑。
「无法检查」和「检查正常」必须分开
关键设计就一句话:把"无法检查"和"检查正常"严格分开。
- 日志权限不足,返回
N/A; - systemd 不可用时,不能误报正常。
一个没有 systemd 的容器里跑巡检,如果输出"0 个失败单元、一切正常",这份报告比没有报告更危险。
--demo 与 --self-test
提供 --demo 匿名演示模式,用内置的假数据生成报告,方便在没数据的环境里先看报告格式长什么样。
提供 --self-test 自测模式,脚本自己验证阈值比较、退出码映射、JSON 转义这些内部逻辑,不依赖宿主机当前状态。
验收方法
在目标机器上按顺序跑:
# 1. 语法检查
bash -n healthcheck.sh
# 2. 内置自测
./healthcheck.sh --self-test
# 3. 非法参数:应退出 3
./healthcheck.sh --warn 95 --crit 90
# 4. 演示报告,肉眼核对文本格式
./healthcheck.sh --demo
# 5. JSON 必须能被标准解析器读取
./healthcheck.sh --json | jq .
第 3 步和第 5 步是硬门槛:参数校验没做,接了 cron 之后行为和预期不一致;JSON 转义没做,监控系统直接解析失败。
静态检查通过不等于能用
bash -n 加自测,不等于覆盖了所有发行版和生产环境。Ubuntu 和 Rocky 上 df、ss 的行为细节有差异,systemd 版本也各不相同。正式使用前仍要在目标发行版测试机上验证一遍。
验证通过后再接入 cron 和监控系统,用退出码做告警分级。