Python 磁盘写满报错速查:No space left on device 六因定位与清理
更新时间:2026-10-24。FAQ 速查页,面向「程序跑得好好的,半夜突然写文件炸了」的同学。Python 磁盘写满是长跑服务最经典的资源类事故:第一天上线一切正常,第三十天凌晨
logging一写就抛OSError: [Errno 28] No space left on device,应用接着连环崩。这类报错的共同特征是——报错措辞永远一样,但满的对象有三种:磁盘块满了、inode 满了、或者配额/临时目录满了,三者处置完全不同。本篇把六类高频病因、df -h与df -i的分辨口径、删文件不释放空间的经典坑一次讲清,核心方法只有一个:先df两个参数都看一眼,再决定清什么。
本文要回答的问题
- 明明
df -h显示还有空间,为什么还是报 No space left on device? - 日志文件已经
rm删掉了,磁盘占用为什么一点没降? /tmp满了会让哪些看似无关的操作一起炸?- Docker 容器里磁盘写满,宿主机的锅还是容器自己的锅?
- 怎么让日志「自动滚动」而不是无限增长把盘吃光?
一、报错原文与典型现象
真实报错长这样(工程路径以 /proj/demo 占位):
$ python3 /proj/demo/app.py
Traceback (most recent call last):
File "/proj/demo/app.py", line 12, in <module>
handler.emit(record)
OSError: [Errno 28] No space left on device
# logging 写日志时盘满;常见于跑了几十天的服务$ python3 /proj/demo/build_data.py
Traceback (most recent call last):
File "/proj/demo/build_data.py", line 30, in <module>
pickle.dump(cache, open("/proj/demo/cache.pkl", "wb"))
OSError: [Errno 28] No space left on device
# 但 df -h 明明还有 40% 剩余——这是 inode 耗尽,不是块耗尽认清机制一句话:报错的 Errno 28 是内核返回的,「space」既指数据块也指 inode(文件元数据槽位),还可能是用户配额(quota)或 /tmp 所在的小分区。不先分清满的是哪一种,清理就是瞎清。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| 日志写着写着抛 Errno 28 | 日志文件无限增长吃满块 | df -h 看 Use% 是否 100 | 删/截断旧日志,配 logrotate |
df -h 有剩余但仍报错 | inode 被海量小文件耗尽 | df -i 看 IUse% 是否 100 | 清小文件(缓存、空会话文件) |
rm 删了大文件空间不降 | 进程仍持有已删文件句柄 | lsof +L1 找 deleted 文件 | 重启进程或用 : > file 截断 |
写 /tmp 报错、主盘却空 | /tmp 是独立小分区或 tmpfs | df -h /tmp 单独看 | 清 /tmp 或改 TMPDIR 指大分区 |
| 只有某个用户/目录写不进 | 触达磁盘配额(quota)上限 | quota -s 查看配额 | 申请提额或清该用户数据 |
| 容器内 Errno 28、宿主机正常 | 容器层/卷写满或镜像膨胀 | docker system df 看占用 | 清悬空镜像与容器可写层 |
三、排查决策图
这张图是磁盘写满的标准动线:红是报错入口与定罪根因,蓝是排查动作,绿是验证通过,橙是修复动作,紫是防复发沉淀。
读这张图抓住主线:先分辨「块满还是 inode 满」再动手。df -h 看字节、df -i 看文件个数,两个指标任何一个到 100% 都会报同一个 Errno 28;定位用 du -sh /* 2>/dev/null | sort -h 从根往下钻,再用 lsof +L1 把「删了但没释放」的文件揪出来。
四、逐条排查展开
第一病因:日志无限增长吃满磁盘块。 最经典。Python 用 logging.FileHandler 直接写文件,没有滚动机制,一天几百 MB,一个月就把盘写穿。处置分两步:先用 du -sh /proj/demo/logs/* 确认凶手,旧日志直接删或截断(: > app.log 不中断进程就能清零);再上滚动——Python 侧换 RotatingFileHandler(按大小)或 TimedRotatingFileHandler(按天),系统侧配 logrotate 兜底。
第二病因:inode 耗尽——海量小文件的隐形杀手。 df -h 显示剩余 40%,写文件却报满,这是 inode(每个文件一个的元数据槽位)用完了。典型凶手:__pycache__ 堆积、逐文件的缓存目录(一个 key 一个文件)、空会话文件。验证 df -i 的 IUse% 到 100 即定罪。处置:找到小文件密集目录(find /proj/demo -type f | awk -F/ '{print $3}' | sort | uniq -c | sort -rn | head 粗查),批量清理;缓存改单文件数据库(sqlite/shelve)替代「一 key 一文件」。
第三病因:rm 删了文件但空间不释放。 这是 Unix 文件系统语义:进程还持有文件句柄时,文件名删了、数据块不回收,df 依旧满。典型现场:你删了 20GB 日志,磁盘占用纹丝不动。验证:
$ lsof +L1
# 列出所有「已删除但仍被进程持有」的文件(deleted 标记)处置:重启持有该文件的进程让内核真正回收;不想重启就别 rm,改用 : > /proj/demo/logs/app.log 原地截断,空间立即释放且进程无感。
第四病因:/tmp 独立分区或 tmpfs 写满。 很多发行版把 /tmp 挂成几 GB 的小 tmpfs,主盘 500GB 空着也没用。tempfile.mkstemp()、pip 解压、多进程 mmap 都默认往 /tmp 写,一满集体报错。验证 df -h /tmp 单独看。处置:清 /tmp 老文件;临时文件多的程序改环境变量 TMPDIR=/proj/demo/tmp 指到大分区,或代码里 tempfile.mkstemp(dir=...) 显式指定目录。
第五病因:磁盘配额(quota)触顶。 多用户服务器常见:整盘没满,但你这个用户的配额到了,同样返回 Errno 28(配额场景也可能返回 EDQUOT,措辞是 Disk quota exceeded)。验证 quota -s 看自己的用量与上限。处置:清自己家目录大头(du -sh ~/* | sort -h | tail),或找管理员提额;别往别人目录里塞——写别人目录算别人的配额。
第六病因:容器内写满。 Docker 场景三层都可能是凶手:容器可写层写满(容器内 df 满、宿主机不满)、挂载卷写满、镜像膨胀把宿主机盘吃光连带容器报错。验证 docker system df 看镜像与容器占用,docker system df -v 看逐容器可写层大小。处置:docker system prune 清悬空镜像与停止的容器;日志走 json-file 驱动的要配 max-size/max-file 滚动,否则容器日志文件本身就能把盘写穿。
顺带提一个容易误判的现象:Errno 28 有时出现在「看起来和磁盘无关」的操作上——multiprocessing 创建共享内存、mmap 映射大文件、甚至 SQLite 建索引都会向文件系统要空间,它们走的往往是 /dev/shm 或临时目录这类小分区。遇到「这个操作怎么会报盘满」的困惑时,先想它背后真正写的是哪个路径,再用 df -h <路径> 对号入座,比在主盘上大海捞针式地翻大目录快得多,也更不容易误判。
五、规范做法与预防
- 日志必配滚动:代码里
RotatingFileHandler(maxBytes=100_000_000, backupCount=5),系统侧 logrotate 双保险,永远不存在「无限增长的日志文件」。 - 磁盘双指标告警:监控同时盯块用量与 inode 用量(
df -h/df -i),80% 预警而不是 100% 炸了之后才发现。 - 删除前先想句柄:清服务日志用截断(
: > file)代替rm;确需rm就约好重启窗口,避免「删了不释放」的假象耽误判断。 - 临时文件指大分区:部署模板里统一
TMPDIR指向数据盘,别让小 tmpfs 成为单点。 - 定期清理任务:cron 每周清
__pycache__、旧缓存、过期会话文件;Docker 主机加docker system prune周期任务。
这一篇在知识体系里的位置
本篇是 Python 排错速查簇的资源篇,与系统资源类报错互补:内存耗尽被杀看 Python 资源超限速查;文件路径类报错看 Python 路径不存在速查;权限不够写不进看 Python 权限拒绝速查;程序卡死不动看 Python 超时挂死速查;缓存「改了不生效」看 pycache 与 pip 缓存陈旧速查;同批姊妹篇看 assert 被优化失效速查 与 datetime 时区坑速查。「df 双指标 + lsof 找已删文件」这套心法对任何语言的磁盘事故通用。
一句话总结
磁盘写满的本质是内核在块、inode、配额、tmp 分区四个维度任一触顶都返回同一个 Errno 28;六因处置——日志无界增长配滚动并截断旧文件、inode 耗尽清海量小文件、删了不释放用 lsof +L1 找占用进程、tmp 小分区改 TMPDIR、配额触顶清家目录或提额、容器场景 docker system prune 加日志滚动;定位两件套是「df -h 与 df -i 双看 + du 逐层钻」,防复发主线是日志滚动、双指标 80% 告警、定期清理任务,把半夜炸盘变成白天收预警。