Linux 服务器空间不足排查:磁盘容量、inode 与文件占用
网站突然写不进日志,上传接口开始失败,数据库提示写入错误。登录服务器后,发现根分区已经接近 100%。
这时最需要弄清楚的是:哪一个文件系统满了、耗尽的是容量还是 inode、空间由谁占用,以及处理后为什么能够恢复。 找到大文件只完成了一半,文件所属服务、数据用途和增长原因,同样决定了下一步能做什么。
尤其要注意:文件名消失,不代表数据块已经回收。已删除的文件仍被进程打开时,空间可能继续占用,甚至继续增长。 所以处理结果要看目标文件系统的可用空间和业务恢复情况,不能只看删除命令有没有报错。
本文按线上排查顺序展开,主要面向使用 GNU 工具的 Linux 服务器。系统日志部分适用于 systemd,容器部分适用于 Docker Engine;Alpine 的 BusyBox、其他容器运行时和托管数据库需要对照各自的工具与权限。
资料核对至 2026 年 10 月 23 日。本文是排查指南,示例路径、输出和容量均用于说明判断方法,不是某台生产服务器的事故实录。
故障现象与初步判断
| 看到的现象 | 优先核对 | 下一步 |
|---|---|---|
df -hT 的 Use% 很高 |
对应设备和挂载点 | 在该文件系统内逐层检查目录占用 |
| 还有容量,却创建不了文件 | df -i 的 IUse% |
查大量小文件所在的目录 |
df 很满,du 找不到相应占用 |
是否同一文件系统、权限及打开的已删除文件 | 检查挂载关系与 lsof +L1 |
| 删了大日志,空间没有回来 | 进程是否仍持有文件 | 按服务支持的方式重新打开日志或释放句柄 |
| Docker 所在分区满 | 数据根目录、镜像、写层、卷与日志 | 对照 Docker 统计与宿主机实际路径 |
| 应用报错,但宿主机的容量和 inode 都充足 | 实际写入路径、配额、容器限制和文件系统状态 | 回到应用报错及所在运行环境继续查 |
排查期间先保留会话与必要的错误信息。若已经发现某个任务持续大量写入,按业务影响暂停该任务、降低非必要日志量或限制上传,避免边查边继续填满空间。
下面前几节的命令以只读检查为主,但遍历目录也会产生 I/O。大目录应逐层缩小范围,避免在已经繁忙的磁盘上同时启动多轮全盘扫描。输出可以保存到运维终端,别把大份诊断报告再写回已经满的分区。
第一步:确认文件系统、容量和 inode 使用情况
先看容量和 inode:
1 | df -hT |
df -hT 中需要一起看的是:
Filesystem:设备或文件系统来源。Type:文件系统类型。Avail和Use%:可用空间及使用率。Mounted on:挂载点,决定后面应该从哪里查。
例如,下面是为说明判断方式编写的输出:
1 | Filesystem Type Size Used Avail Use% Mounted on |
这说明根文件系统紧张,但 /data 仍有空间。直接统计整个 / 并把 /data 算进去,会把两个不同文件系统混在一起。
有时应用实际写入的位置并不在你以为的数据盘上。针对真实路径继续检查,例如:
1 | df -hT /var/log |
findmnt -T 用来查包含该路径的文件系统。把 /var/log 换成应用实际写入的目录,先确认路径确实存在;数据盘挂载失败后,程序也可能把数据写进根文件系统上的同名目录。
df -i 则检查 inode。在 ext4 等文件系统上,大量小文件可能先耗尽 inode,导致仍有剩余容量却无法创建新文件。不同文件系统的 inode 分配方式不同,不能只凭某一列显示为零或未知就判断故障,仍要结合文件系统类型和实际报错。GNU 的 df 文档说明了容量、inode 和挂载点的统计含义。
第二步:定位目录空间占用
假设确认满的是根文件系统,先看第一层目录:
1 | sudo du -x -h --max-depth=1 / | sort -h |
这里的参数各有作用:
-x:不跨到不同文件系统,避免把独立数据盘算进来。-h:用便于阅读的单位显示容量。--max-depth=1:只展示第一层汇总,便于判断下一步。sort -h:按带单位的数值排序。
最后的 / 汇总行是总量,不是一个需要删除的额外目录。发现 /var 占用明显较大,再查下一层:
1 | sudo du -x -h --max-depth=1 /var | sort -h |
限制显示深度不等于只扫描一层。 du 为了求出目录总量,仍需要遍历下面的内容。不要把这条命令当成低成本的实时监控。
如果满的是 /data,起点就改成 /data。-x 按文件系统边界限制遍历,也不能代替对 bind mount、容器 overlay 等挂载关系的核对。命令含义可参考 GNU 的 du 文档。
此时先保留错误输出:Permission denied 表示有内容没有统计到,文件消失或变化则可能来自正在运行的服务。把 stderr 全部丢弃后得到的“小占用”,不一定是完整结果。
查找大文件并核对实际磁盘占用
例如,需要列出 /var/log 下逻辑大小超过 1 GiB 的普通文件:
1 | sudo find /var/log -xdev -type f -size +1G -printf '%s\t%p\0' \ |
这组命令使用 GNU find 和支持 NUL 分隔的 GNU 工具,输出第一列为字节数。中间用 NUL 分隔,避免排序阶段把文件名中的换行当成新记录;最后转成普通文本仅供人工查看,不要把它直接拼成批量删除命令。
find -size 看的是文件逻辑大小,稀疏文件可能显示很大,实际分配的磁盘块却没有那么多。对选中的单个文件再比较:
1 | ls -lh -- /var/log/example.log |
这里的 example.log 是占位路径,执行时换成已经确认的真实文件。逻辑大小和实际占用的区别,也意味着“最大的文件”不一定能释放最多空间。
第三步:排查 inode 耗尽与小文件积累
假设 df -hT 仍有较多空间,df -i 却显示某个文件系统的 inode 接近耗尽,可以按目录统计 inode 使用:
1 | sudo du -x --inodes --max-depth=1 /var | sort -n |
找到数量突出的目录后,继续往下缩小范围。这里不加 -h,方便按数值比较。命令需要 GNU du 支持 --inodes;不支持时先查本机帮助,避免套用不兼容选项。
常见来源包括没有过期策略的缓存、会话文件、失败任务的临时文件和按请求持续生成的小文件。确定目录属于谁以后,再核对保留时间、正在使用的文件,以及应用是否有自带的清理机制。
删除一个巨大的备份文件通常只能解决容量问题。若真正耗尽的是 inode,应该检查文件数量;反过来,清理很多几字节的小文件,也未必能显著缓解容量不足。
第四步:核对 df 与 du 的统计差异
df 看文件系统的空间账,du 通过目录树统计能够访问的文件占用。两者口径不同,不能要求结果逐字节一致。
先核对这些条件:
- 比较的是否为同一个文件系统,有没有把独立挂载点混进来。
du是否有足够权限,扫描期间文件是否持续变化。- 是否存在已删除但仍被进程打开的文件。
- 是否涉及文件系统元数据、保留块、快照或写时复制等额外占用。
- 挂载点下面是否遮住了原有文件,导致当前目录视图看不到它们。
前几项可以在线核对;涉及快照、挂载遮蔽或底层存储的检查,应结合实际文件系统另行规划。不要为验证一个猜测,直接卸载线上正在使用的数据盘。
排查已删除但仍被进程打开的文件
一个典型原因是最后一个文件名已经删除,但进程仍持有打开的文件。这里的“被占用”主要是指进程还打开着文件,不一定存在文件锁。Linux 允许删除打开的普通文件名,进程却仍可以通过原来的文件描述符继续访问它。
此时 ls 看不到原文件,du 也不能再沿原路径找到它,但文件系统仍需保留它占用的块。删除正在写入的日志以后,进程甚至可能继续向这个已删除文件写数据,df 的使用量仍然上升。 重新创建一个同名文件,也不会自动把进程原来的文件描述符切换过去。
对于普通文件,需要移除剩余硬链接并释放进程持有的引用,空间才具备回收条件。文件名与打开引用的关系可参考 Linux unlink(2) 手册。
有 lsof 且具备相应权限时,可以检查:
1 | sudo lsof -nP +L1 |
+L1 选择链接数小于 1 的打开文件,用来查找已经 unlink 的文件。结合 COMMAND、PID、FD、DEVICE、SIZE/OFF 和 NAME 判断哪个进程持有目标文件。lsof 官方手册列出了该选项的含义。
下面是教学用的简化输出,进程、编号和大小均为示例:
1 | COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME |
它表示示例进程 12345 仍以可写文件描述符 8 持有该文件,NLINK 为 0,路径后带 (deleted)。先核对进程身份和文件描述符:
1 | ps -p 12345 -o pid,ppid,user,comm |
这两条是 Linux 上的只读核对,数字必须换成当前查询结果。进程退出、重启或关闭文件后,原编号可能失效或被复用,操作前需要重新确认,不能照抄示例 PID。
这份列表可能包含其他文件系统上的文件,同一个文件也可能被多次打开。不能把 SIZE/OFF 一列直接相加,当成一定能释放的物理空间;还要核对文件所在设备及实际分配情况。
处理时按这个顺序进行:
- 确认文件属于哪个服务、是否仍在增长,以及释放它会影响哪些业务。
- 优先使用该服务明确支持的日志 reopen 或轮转方式,让进程重新打开日志。普通 reload 是否能做到这一点,取决于服务实现。
- 没有合适的在线释放方式时,再安排受控重启对应服务或容器,并验证业务恢复。如果多个进程持有同一文件,只处理其中一个未必能够释放空间。
- 重新运行
lsof -nP +L1,结合设备和 inode 判断目标文件是否仍被引用,再用df -hT检查目标文件系统,而不是只检查原路径是否存在。
不要把强杀未知进程或截断 /proc/PID/fd/FD 当成默认步骤。文件描述符可能指向数据库或其他关键数据,绕过服务直接处理会扩大故障。也不要为释放一个应用持有的日志,直接重启整台服务器。
目标引用消失后,继续观察可用空间和新日志是否正常写入;并发写入、稀疏文件等因素会影响回收数值,不能要求 df 增加量精确等于示例里的逻辑大小。
第五步:分类检查日志、Docker 和数据库占用
检查 journal 占用与归档保留策略
使用 systemd 的服务器可以先查看 journal 占用:
1 | sudo journalctl --disk-usage |
它统计的是 journal 文件,不能代表整个 /var/log。应用自己写出的文本日志、压缩包和备份仍需分别核对。
确认旧 journal 可以按保留要求清理后,才执行下面的操作。这一步会删除旧日志,先确认故障取证、审计和留存需求;1G 只是示例上限。
1 | sudo journalctl --rotate --vacuum-size=1G |
vacuum 针对已归档文件;配合 rotate 可以先轮转当前日志。即便如此,也不要把结果理解成整个 /var/log 必须精确降到 1G。具体行为见 journalctl 官方文档。
长期控制可按实际空间预算配置 journald 的保留上限,例如持久日志的 SystemMaxUse 和 SystemKeepFree;配置位置、优先级及生效方式需对照本机版本。journald.conf 官方文档说明了这些选项。
检查应用日志与备份的用途及保留条件
发现某个日志或备份很大后,先查它属于哪个服务、是否还在写入、是否已有轮转规则,以及是否存在可用的恢复副本。
同一满分区内的重命名不会腾出容量;把大日志在原分区压缩,也可能因临时空间不足而失败。需要转存时,应确认目标确实在另一文件系统或远端,传输完成并验证后再决定如何处置原文件。
已经确认过期、没有活跃引用且可删除的文件,可以按明确清单处理;正在写入的日志应通过应用或日志工具支持的方式管理。恢复少量空间以后,还需要追查为什么会持续增长,例如异常重试、调试日志未关闭或备份任务重复执行。
检查 Docker 存储占用与日志配置
在有相应 Docker 权限的宿主机上检查:
1 | docker info --format '{{.DockerRootDir}}' |
docker system df -v 可查看镜像、容器、卷及构建缓存等统计,镜像层还存在共享部分,不能把展示的大小简单相加。docker ps --size 展示容器写层等信息,也不能完整代表容器日志和挂载数据的宿主机占用。参考 Docker 空间统计和容器存储说明。
不要把 /var/lib/docker/overlay2 当成固定起点。实际配置、rootless 模式以及使用 containerd image store 的安装都可能有不同存储位置;先确认引擎版本、存储方式和实际目录,再对相关路径运行 df、findmnt 与逐层统计。
对已经定位到的容器,可以查看日志配置和挂载:
1 | docker inspect --format '{{json .HostConfig.LogConfig}}' app-container |
app-container 换成真实名称。LogPath 的结果取决于日志驱动,可能为空;挂载列表则能帮助确认数据是否写到了独立卷或宿主机目录。
如果使用 json-file 且没有轮转限制,持续输出的日志可能不断增长。长期修复可以在对应 Compose 服务中显式配置日志轮转,例如:
1 | services: |
这段是合并片段,不是可直接替代生产配置的完整 Compose 文件。参数值按日志量和保留需求调整;现有容器通常需要按计划重建才能使用新配置,单纯重启容器不会更新创建时的日志选项。重建前确认持久化、服务依赖和中断窗口,完成后再次 inspect 核对。Docker 日志配置文档明确说明了新旧容器的生效边界。
Docker 管理的日志文件不适合用外部工具直接编辑或截断,这可能干扰日志系统。JSON File 驱动文档给出了相应说明。
同样,RECLAIMABLE 或“未使用”不等于业务允许删除。按对象确认回滚镜像、停止容器、卷和缓存的用途,再选择清理范围;system prune 的删除对象也不应被当作线上默认清理清单。
确认数据库数据与日志的清理条件
如果主要占用来自数据库目录,需要继续区分业务数据、临时文件、错误日志、慢日志,以及复制或恢复所需的日志。
例如,MySQL 的 binlog 涉及复制和按时间点恢复。判断能否清理时,要同时核对副本进度、备份恢复链和保留策略,使用对应版本支持的数据库管理机制。不能仅根据文件年龄就从文件系统删除,也不能因为文件名中带 log 就当作普通应用日志。MySQL 的 PURGE BINARY LOGS 文档说明了相关条件。
本文不提供跨版本通用的数据库删除命令。确认空间属于正常保留的业务数据时,应转向容量规划或存储扩容。
第六步:验证空间回收与业务恢复
操作完成后,先回到最初确认的文件系统检查:
1 | df -hT /var/log |
路径换成这次实际处理的目标。如果起初问题涉及打开的已删除文件,再看 lsof +L1 是否还有目标文件的引用;如果涉及 journal 或 Docker,也要复查对应统计及配置。
然后验证业务是否恢复:写日志、上传、任务落盘或数据库写入,应使用服务自身的健康检查或可控测试。一次命令返回成功,不能代表所有写入链路都已经正常。
再间隔一段时间观察使用量。如果空间很快重新被填满,说明持续增长的来源仍未解决。把这次处理记录下来:涉及哪个文件系统、占用来源、采取了什么操作、释放多少空间,以及业务验证结果。
容量确实不足时,可以继续看本站的云服务器 Debian 12 根分区扩容记录。那篇记录包含特定环境下的迁移和扩容过程,执行前仍需核对自己的分区、LVM、文件系统及引导方式。
容量管理与防复发措施
把这次找到的来源转成对应的长期措施:
| 本次原因 | 后续措施 |
|---|---|
| 日志持续增长 | 配置轮转、保留上限,修复异常输出来源 |
| 缓存或临时小文件堆积 | 设置过期策略,观察文件数量和 inode 使用 |
| 备份不断累积 | 明确保留周期、异地副本和恢复验证 |
| Docker 日志或构建数据增长 | 限制日志轮转,按用途管理镜像和缓存生命周期 |
| 正常业务数据增长 | 评估增长速度,提前安排归档、迁移或扩容 |
监控除了使用率,还应包含剩余绝对容量、inode 和增长速度。相同的使用率,在小系统盘和大数据盘上的剩余空间不同;即使当前比例不高,异常写入也可能在短时间内耗尽空间。
告警触发后,把文件系统、容量或 inode、占用来源和增长速度一并带给处理人员;故障解除后,将保留策略和负责人写进维护记录,避免下一次又从全盘搜索开始。
事后复盘
服务恢复后,整理一次复盘,记录影响、证据、原因和后续行动。故障期间拿不准的地方标为待确认;没有监控或日志支撑的数据,不补写成确定结论。
记录业务影响、时间线与恢复证据
下面是可填写模板,时间和影响范围需要根据真实记录补齐:
| 记录项 | 需要填写的内容 |
|---|---|
| 故障对象 | 主机或服务标识、文件系统、挂载点,是否涉及容量或 inode |
| 业务影响 | 哪些接口、任务或写入失败;影响多少请求或用户,统计依据是什么 |
| 起止时间 | 首次业务受影响、告警、接手处理、开始缓解、业务恢复、验证结束;注明时区 |
| 发现方式 | 监控、用户反馈或人工检查,附图表、日志或工单证据 |
| 处理经过 | 按时间列出观察、判断、操作和结果,保留未生效的尝试及其解释 |
| 恢复依据 | 目标分区可用空间、inode、写入测试、错误率与后续观察结果 |
告警时间不一定是故障开始时间,进程重启成功也不一定是业务恢复时间。若只能确定某个时间区间,就记录区间和证据缺口,不把处理人员接到通知的时间当成全部影响时长。
证据需要保留到可复查的位置。用于公开分享时,再去除凭据、真实服务器地址和用户数据;不要为了腾空间,把仅有的故障证据也一起清掉。
分析触发因素与防护缺口
复盘至少区分四件事:什么变化触发了增长、什么资源实际耗尽、为什么没有提前发现或限制,以及什么操作真正恢复了业务。
例如,只有实际证据支持时,才可以把下面的链条写进结论:某应用异常重试导致日志持续增长;该日志没有有效轮转;根文件系统可用空间耗尽;处理时删除了文件名,但进程仍持有文件,所以容量没有回升;随后通过服务支持的方式释放引用,空间才被回收。
这条链里,日志异常增长是需要追查的触发因素,缺少轮转和告警是防护缺口,删除后仍被占用解释了处理为何没有立即生效。不能只把“磁盘满”或“某人删错了”作为最终原因。
如果无法证明是哪一个进程、哪一份文件或哪次变更造成增长,就保留假设并列出下一步取证方法。复盘应说明当时有哪些信息和可用工具,以及流程哪里需要改进。
明确行动项、负责人和验收标准
下面是候选行动模板,按本次真实原因选用,不代表已经实施:
| 行动项 | 优先级 | 负责人 / 截止日期 | 验收标准 |
|---|---|---|---|
| 为确认持续增长的日志配置轮转 | 待定 | 待填写 | 核对运行实例的实际配置,在受控测试中确认轮转生效与占用边界 |
| 补齐容量、inode 和增长速度告警 | 待定 | 待填写 | 阈值有业务依据,测试告警能到达值班人,并附对应挂载点与排查入口 |
| 把已删除文件句柄检查加入处理手册 | 待定 | 待填写 | 演练能定位持有者,通过服务支持的方式释放后,用 lsof、df 和业务检查验证 |
| 修复异常重试、重复备份或缓存保留问题 | 待定 | 待填写 | 原触发条件下不会持续无界增长,观察窗口和测试证据有记录 |
每项再关联可追踪的工单或记录,完成时填写验证证据。尚未执行的配置属于计划,不能因为写进复盘就标记为已修复;“加强监控”“以后注意”也需要落实到具体改动和验收条件。
关于复盘的事实依据、改进方向和可追踪行动,可以参考 Google SRE 的事后复盘实践。对这类磁盘故障,复盘完成后应能回答:下一次增长会怎样被限制,怎样更早被发现,处理人员又能怎样确认空间真的释放。