2KB写入因No space left on device失败,df -h却显示仍有GB空闲
2KB写入因No space left on device失败,df -h却显示仍有GB空闲
2KB的写入因No space left on device失败,而df -h显示仍有数GB空闲,我花了48小时反复删除日志才发现inodes早已耗尽。这份现场笔记记录了误导我的命令和现在的检查清单。
文件系统在分配资源时同时管理块和inode。块用于存放实际数据,inode则存放文件的元数据,包括权限、时间戳和数据块指针。当一个文件系统创建时,系统会预先分配固定数量的inode。这个数量通常在格式化时确定,后期难以动态扩容。
df -h只报告块的使用情况。它显示可用空间时,许多人直接判断磁盘不紧张。但如果inode已经用完,即使还有大量空闲块,任何需要创建新文件或目录的操作都会立即失败,报No space left on device。系统无法为新文件分配inode,就无法完成写入。
这个误导在日志密集型场景特别常见。开发者看到df -h还有空间,就本能地去清理大日志文件,却忽略了真正卡住的是inode数量。48小时的反复删除正是因为一直盯着错误指标。
df -h空闲不等于可以创建新文件
df -h命令输出中显示的可用空间指的是数据块剩余量。它不反映inode的使用状态。当一个分区格式化时,系统会根据分区大小按比例分配inode。例如一个1TB分区可能预分配数百万个inode,但具体数量取决于mkfs时的参数。
一旦inode耗尽,哪怕只剩90%的块空间,新文件的创建也会失败。2KB的JSON写入需要一个新的inode来存放元数据,系统找不到空闲inode就直接拒绝操作。这就是为什么df -h显示还有数GB空闲却无法写入。
很多开发者第一次遇到这个问题时都会困惑。他们运行df -h,看到可用空间充足,就开始排查磁盘配额、权限或进程锁。但这些都不是根源。根源是inode池已经枯竭。
这个现象在容器化和微服务环境中更突出。每个Pod或容器可能产生大量临时文件,快速消耗inode却不占用太多实际空间。df -h看起来健康,实际系统已经无法创建新文件。
小文件sidecar直接耗尽inode而非磁盘空间
我当时正在迭代一个Python worker。它在每次运行后都会在目录下生成一个JSON sidecar文件。这些文件很小,通常只有几百字节到2KB,却每个都对应一个独立的inode。
worker持续运行,不断dump新的JSON sidecar。每个sidecar都是一个独立文件,需要占用一个inode。几万次迭代后,inode数量就迅速耗尽,而实际占用的磁盘空间仍然很小。
小文件是inode杀手。相比大日志文件,小文件数量多,每个都需要单独的inode和目录项。Python worker这种“每次运行写一个sidecar”的模式特别容易触发这个问题。它不是把数据追加到已有文件,而是不断创建新文件。
生产环境中类似场景很多。监控agent生成的大量metrics文件、临时缓存的session文件、CI/CD流水线产生的报告,都以小文件形式存在。它们不吃磁盘空间,却能快速吃光inode。
删除文件后inode未释放的真实原因
很多人以为删掉日志文件就能释放inode。但实际情况更复杂。文件删除时,inode只有在硬链接数为0且没有进程打开该文件时才会被真正释放。
日志清理的常见坑在于只删内容不删文件。一些脚本用truncate -s 0 logfile或echo > logfile清空内容,但文件本身还在,inode没有释放。进程如果还打开着这个文件,即使你删除了文件名,inode也依然被占用,直到进程退出或关闭文件句柄。
我花了48小时反复删除日志,正是因为很多日志文件被正在运行的进程持有。rm命令删除了目录项,但inode计数没有下降。lsof命令能看到这些被删除但仍被进程引用的文件,它们以 (deleted) 状态存在,却继续占用inode。
这个机制保护了正在写入的日志不被意外破坏,但也制造了排查陷阱。清理脚本如果不重启相关进程或使用lsof排查,就无法真正释放inode。
必须同时用df -i和ls检查inode使用率
正确的诊断必须同时检查块和inode。df -i命令直接显示每个文件系统的inode使用率。当IUse%接近100%时,即使Use%很低,也会触发No space left on device。
ls命令配合-i参数可以查看具体文件的inode号,帮助定位哪些目录下文件数量最多。find /path -xdev -type f | wc -l能统计某个分区下的文件总数,快速找到inode消耗大户。
我最终发现问题时,正是同时运行了df -i和df -h。对比两个输出才意识到inode已经100%而块空间还有余量。之前只看df -h的习惯直接把我带偏了48小时。
生产系统中应该把这两个命令做成日常检查的一部分。脚本可以这样写:
|
|
这样能第一时间发现是块还是inode出了问题。
日志清理脚本应加入inode阈值判断
生产环境的日志清理脚本不能只看磁盘使用率。必须同时判断inode使用率,并在达到阈值时触发更激进的清理策略。
一个合理的脚本应该先用df -i检查,如果inode使用率超过85%,就不仅删除旧日志,还要查找并清理小文件密集目录。可以使用find命令按文件数量或按最后修改时间清理。
清理时要考虑进程持有问题。脚本可以先发送信号让应用滚动重启,释放被持有的已删除文件句柄,或者使用lsof +L1找出被删除但仍占inode的文件并处理。
经验表明,只删大文件不处理小sidecar的脚本效果很差。Python worker这类应用产生的海量小JSON文件才是主凶。清理策略需要区分文件大小,对小于10KB的文件采用更短的保留周期。
为inode使用率单独建立监控告警
避免重复踩坑的最好办法是为inode使用率建立独立监控。Prometheus可以采集node_filesystem_inode_free和node_filesystem_inode_files指标,Grafana面板上单独画一条inode使用率曲线。
告警规则建议设置两个阈值:85%警告,95%紧急。告警信息里要明确指出是inode问题,而不是笼统地说磁盘满。这样运维人员就不会再花几十小时盯着df -h排查。
除了系统级监控,应用层也应该暴露自己产生的文件数量指标。Python worker可以统计sidecar生成速率,当速率异常升高时提前告警。
监控系统还应该追踪每个挂载点的文件数增长趋势。突然的斜率变化往往预示着某个新部署的服务在疯狂创建小文件。
事后checklist能把排查时间从48小时压到分钟级
现在我每次遇到No space left on device,第一步不再是删日志,而是按固定checklist走。
清单第一条:同时运行df -h和df -i,对比Use%和IUse%。
第二条:如果IUse%高,用find找出文件数最多的目录。
第三条:用lsof +L1列出被删除但仍被占用的文件,确认是否有进程持有。
第四条:检查最近变更的应用是否在产生大量小文件,特别是JSON、临时缓存或日志sidecar。
第五条:清理前先评估是否需要重启相关进程释放inode。
这个checklist把我后续的类似问题排查时间从48小时压到几分钟。它把经验转化成了可重复的步骤,对中文开发者尤其是运维同学特别实用。很多团队还在只看df -h,踩同样的坑。
把这份清单加入团队的故障处理SOP,能显著降低因inode耗尽导致的故障时长。
参考来源
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/ai001/post/20260905/2KB%E5%86%99%E5%85%A5%E5%9B%A0No-space-left-on-device%E5%A4%B1%E8%B4%A5df-h%E5%8D%B4%E6%98%BE%E7%A4%BA%E4%BB%8D%E6%9C%89GB%E7%A9%BA%E9%97%B2/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com