60行Python代码编写磁盘自动监控脚本
作者:IT小本本
这几天的一件小事,让我下决心写这个脚本。
那天早会,测试说环境又挂了。我登上去一看,磁盘使用率 96%,应用日志把根分区写满了。而前一天我其实「巡检过」——5 台服务器挨个敲 df -h,我嫌麻烦,那天只看了 3 台,偏偏漏掉的就是出事那台。
问题不在技术,在重复。**人只要每天做同一件枯燥的事,就一定会漏。**所以我给自己提了个明确需求:写一个 Python 脚本,每天自动巡检磁盘、清理过期日志,一切正常就不吭声,有异常才提醒我。
这篇就是这个脚本的实现过程。60 来行,全程标准库,不用装任何第三方包,拿走就能用。思路不挑语言,你想用 Shell 或 Go 重写一遍也完全可行。
第 1 步:先把「体检项」列清楚
写脚本最容易犯的错,是上来就敲代码。我先花 10 分钟,把要盯的东西写成了三条硬指标:
- 磁盘:关键分区使用率到 80% 记警告,到 90% 记严重;
- 日志:/var/log/app 下 7 天前的 .log 文件直接删;
- 报告:结果写进 /var/log/disk_report.txt,出现严重问题时退出码返回 1。
为什么强调退出码?因为后面要靠它接告警——脚本不弹窗、不发邮件,退出码非 0,服务器上已有的告警链路自然会叫人。分工明确:脚本只负责发现问题,通知是告警系统的事。
第 2 步:磁盘水位,一个函数搞定
查磁盘不需要 psutil 这类第三方库,标准库 shutil 就够了。下面是完整脚本,核心巡检逻辑都在 check_disk 这个函数里,后面几步用到的清日志、出报告、给退出码也都已包含在内:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
disk_inspect.py —— 每天自动巡检磁盘 + 清理过期日志
全程标准库,无需安装任何第三方包。
用法:
python3 disk_inspect.py # 默认 LIST 模式,只打印待删清单,不真删
python3 disk_inspect.py --apply # 确认无误后真删过期日志
python3 disk_inspect.py --help # 查看帮助
"""
import os
import sys
import time
import shutil
import argparse
from pathlib import Path
# ===== 可配置项:改这一块就够了 =====
PARTITIONS = ["/", "/data", "/var"] # 要巡检的挂载点,按机器实际情况增删
WARN_THRESHOLD = 80 # 使用率 >= 80% 记 WARN
CRIT_THRESHOLD = 90 # 使用率 >= 90% 记 CRIT,并让退出码 = 1
LOG_DIR = "/var/log/app" # 要清理的日志目录
LOG_PATTERN = "*.log" # 只清 .log 文件,通配符宁窄勿宽
LOG_RETAIN_DAYS = 7 # 保留最近 7 天,更早的视为过期
REPORT_FILE = "/var/log/disk_report.txt" # 报告输出位置(不推送,想看时 cat)
def check_disk(partitions, warn, crit):
"""查每个分区的使用率,按阈值分级为 OK / WARN / CRIT。
关键细节:用 used / total 算百分比,别拿 free 反推——
部分文件系统的 free 含可回收缓存,和 used 算出来的比例能差三五个点,
报警阈值会对不上。
"""
results = []
for mount in partitions:
try:
usage = shutil.disk_usage(mount)
except OSError:
# 分区不存在或无法访问,记一行 UNKNOWN,不影响其它分区巡检
results.append((mount, None, "UNKNOWN"))
continue
percent = usage.used / usage.total * 100
if percent >= crit:
level = "CRIT"
elif percent >= warn:
level = "WARN"
else:
level = "OK"
results.append((mount, percent, level))
return results
def clean_logs(log_dir, pattern, retain_days, apply):
"""按文件修改时间筛过期日志。
apply=False(默认)只打印待删清单和能腾出的空间,不真删;
apply=True 才真删。第一次跑务必先空跑确认,避免通配符写宽误删归档日志。
"""
cutoff = time.time() - retain_days * 86400 # 过期时间戳,早于此的删
log_path = Path(log_dir)
if not log_path.is_dir():
return [], 0
to_delete = []
freed = 0
for f in log_path.glob(pattern):
try:
st = f.stat()
except OSError:
continue
if st.st_mtime < cutoff:
to_delete.append(f)
freed += st.st_size
if apply:
for f in to_delete:
try:
f.unlink()
except OSError:
pass
return to_delete, freed
def main():
parser = argparse.ArgumentParser(description="磁盘巡检 + 日志清理")
parser.add_argument("--apply", action="store_true",
help="真删过期日志;不加此参数只打印清单(LIST 模式)")
args = parser.parse_args()
# 1) 磁盘水位
disk_results = check_disk(PARTITIONS, WARN_THRESHOLD, CRIT_THRESHOLD)
# 2) 清日志(默认 LIST 模式,--apply 才真删)
deleted_files, freed_bytes = clean_logs(
LOG_DIR, LOG_PATTERN, LOG_RETAIN_DAYS, args.apply)
# 3) 汇总成报告:每分区一行「挂载点:使用率 级别」,方便 grep CRIT
lines = ["Disk Inspect Report - " + time.strftime("%Y-%m-%d %H:%M:%S"), ""]
has_crit = False
for mount, percent, level in disk_results:
if level == "UNKNOWN":
lines.append(f"{mount}: UNKNOWN (无法访问)")
else:
lines.append(f"{mount}: {percent:.1f}% {level}")
if level == "CRIT":
has_crit = True
lines.append("")
action = "已删除" if args.apply else "待删除(LIST 模式)"
lines.append(f"日志清理: {action} {len(deleted_files)} 个文件, "
f"腾出 {freed_bytes / 1024 / 1024:.1f} MB")
if not args.apply and deleted_files:
lines.append("确认无误后加 --apply 真删:")
for f in deleted_files:
lines.append(f" - {f}")
report = "\n".join(lines) + "\n"
# 写报告(不推送,想看时 cat 一下)
try:
with open(REPORT_FILE, "w", encoding="utf-8") as fh:
fh.write(report)
except OSError:
pass # 写不进报告也不能掩盖磁盘问题,退出码照常返回
# 同时打到 stdout,方便 cron 追加到日志文件
print(report, end="")
# 4) 退出码:有 CRIT 就非 0,告警链路据此触发通知
sys.exit(1 if has_crit else 0)
if __name__ == "__main__":
main()
shutil.disk_usage 返回 total、used、free 三个字节数,除一下就是使用率,再按阈值分级。这里有个我踩过的细节:别拿 free 反推。部分文件系统的 free 含可回收缓存,和 used 算出来的百分比能差出三五个点,报警阈值会对不上。所以脚本里写的是 usage.used / usage.total * 100,而不是 1 - free/total。
第 3 步:清日志,先看后删
日志清理的核心,是按文件修改时间筛出过期文件。我写完的第一版没有 dry-run,正要挂上服务器时,组里老同事问了一句:「你确定它不会把正在写的日志删了?」就这一句话,我加了个 LIST 模式:**第一次跑只打印要删的文件和能腾出的空间,人工确认过再切成真删。**后来这个习惯救过我一次——通配符写宽了,把要归档的日志也圈了进去,还好只是打印,没真删。
对应到上面的脚本里就是 clean_logs 函数和 --apply 开关:
python3 disk_inspect.py # LIST 模式:只打印待删清单,不真删 python3 disk_inspect.py --apply # 确认无误后真删过期日志
默认不传 --apply 时,clean_logs 里 apply=False,只把过期文件收集进 to_delete 并累加能腾出的字节数,不会调用 f.unlink()。报告里会逐行列出待删文件路径,外加一行「确认无误后加 --apply 真删」的提示。看一眼没问题了,再带 --apply 跑一次才真正落盘删除。这样通配符写宽、目录配错都有回旋余地。
第 4 步:汇总成报告和退出码
把两步的结果拼成几行文本,写进报告文件,然后按约定给退出码:有任何分区到 90%,sys.exit(1),否则正常退出 0。报告本身不推送,我想看的时候 cat 一下就行;真正的通知,交给退出码触发的告警。
报告格式也有讲究:每个分区一行,「挂载点:使用率 级别」,比如「/data: 83.4% WARN」。这样人看得快,机器也方便处理——真出问题时,我用 grep CRIT 一条命令就能把严重项从报告里捞出来,连告警脚本都不用解析复杂格式。
脚本里这段逻辑在 main() 末尾:
# 写报告(不推送,想看时 cat 一下)
try:
with open(REPORT_FILE, "w", encoding="utf-8") as fh:
fh.write(report)
except OSError:
pass # 写不进报告也不能掩盖磁盘问题,退出码照常返回
# 同时打到 stdout,方便 cron 追加到日志文件
print(report, end="")
# 退出码:有 CRIT 就非 0,告警链路据此触发通知
sys.exit(1 if has_crit else 0)
has_crit 是在汇总分区结果时打上的标记:只要有一个分区命中 CRIT_THRESHOLD,它就为真,最终退出码就是 1。写报告那一段特意套了 try/except——磁盘爆满时报告文件可能也写不进去,但不能因为「写报告失败」就把异常抛出来掩盖真正的磁盘问题,所以静默跳过、退出码照常返回。
第 5 步:挂上 cron,从此不用管
crontab -e 加一行,每天早上 9 点跑一次,输出追加到日志文件:
0 9 * * * /usr/bin/python3 /opt/scripts/disk_inspect.py --apply >> /var/log/disk_inspect.log 2>&1
注意这里 cron 里直接带上了 --apply:上线初期先手动空跑几轮确认清单没问题,稳定之后就交给 cron 用 --apply 真删,不再需要人工干预。>> 把脚本 stdout 追加进 disk_inspect.log,2>&1 把报错也一并收进来,事后排查有据可查。
至此需求闭环:**磁盘到 90%,告警叫我;不叫,就是一切正常。**我不用再每天挨台敲 df -h 了。
三个坑,都是真踩过的
- NFS 挂载目录别依赖 mtime。部分网络文件系统修改时间不可靠,那台 NFS 机器上我改用解析文件名里的日期来筛过期文件。
- 删除范围先 dry-run 确认。通配符宁窄勿宽,打印清单人工过一眼,再放行真删。
- 别在脚本里发邮件、发群消息。通知渠道一变就得改脚本,退出码才是最稳的接口。
可抄清单
- 指标先行:先写清盯什么、阈值多少,再动手
shutil.disk_usage查水位,标准库就够- 删除类操作先 dry-run,人工确认再放行
- 异常用退出码表达,通知交给告警系统
- cron 每天定时跑,输出追加到日志文件
现在这个脚本每天 9 点自己跑,再没在例会上因为磁盘被点名。它总共 60 来行,没有一行是难写的,难的只是下决心把重复的事交出去。
到此这篇关于60行Python代码编写磁盘自动监控脚本的文章就介绍到这了,更多相关Python磁盘监控内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
