网站突然写不进日志,上传接口开始失败,数据库提示写入错误。登录服务器后,发现根分区已经接近 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 都充足 实际写入路径、配额、容器限制和文件系统状态 回到应用报错及所在运行环境继续查
Linux 空间不足排查流程:确认文件系统后,分别检查容量、inode 和 df 与 du 的统计差异
先确认挂载范围与资源类型,再定位占用来源;处理后同时验证空间与业务。

排查期间先保留会话与必要的错误信息。若已经发现某个任务持续大量写入,按业务影响暂停该任务、降低非必要日志量或限制上传,避免边查边继续填满空间。

下面前几节的命令以只读检查为主,但遍历目录也会产生 I/O。大目录应逐层缩小范围,避免在已经繁忙的磁盘上同时启动多轮全盘扫描。输出可以保存到运维终端,别把大份诊断报告再写回已经满的分区。

第一步:确认文件系统、容量和 inode 使用情况

先看容量和 inode:

1
2
df -hT
df -i

df -hT 中需要一起看的是:

  • Filesystem:设备或文件系统来源。
  • Type:文件系统类型。
  • Avail 和 Use%:可用空间及使用率。
  • Mounted on:挂载点,决定后面应该从哪里查。

例如,下面是为说明判断方式编写的输出:

1
2
3
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/vda1 ext4 40G 38G 300M 100% /
/dev/vdb1 xfs 200G 60G 140G 30% /data

这说明根文件系统紧张,但 /data 仍有空间。直接统计整个 / 并把 /data 算进去,会把两个不同文件系统混在一起。

有时应用实际写入的位置并不在你以为的数据盘上。针对真实路径继续检查,例如:

1
2
df -hT /var/log
findmnt -T /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
2
sudo du -x -h --max-depth=1 /var | sort -h
sudo du -x -h --max-depth=1 /var/log | sort -h

限制显示深度不等于只扫描一层。 du 为了求出目录总量,仍需要遍历下面的内容。不要把这条命令当成低成本的实时监控。

如果满的是 /data,起点就改成 /data。-x 按文件系统边界限制遍历,也不能代替对 bind mount、容器 overlay 等挂载关系的核对。命令含义可参考 GNU 的 du 文档。

此时先保留错误输出:Permission denied 表示有内容没有统计到,文件消失或变化则可能来自正在运行的服务。把 stderr 全部丢弃后得到的“小占用”,不一定是完整结果。

查找大文件并核对实际磁盘占用

例如,需要列出 /var/log 下逻辑大小超过 1 GiB 的普通文件:

1
2
3
4
sudo find /var/log -xdev -type f -size +1G -printf '%s\t%p\0' \
| sort -z -nr \
| head -z -n 20 \
| tr '\0' '\n'

这组命令使用 GNU find 和支持 NUL 分隔的 GNU 工具,输出第一列为字节数。中间用 NUL 分隔,避免排序阶段把文件名中的换行当成新记录;最后转成普通文本仅供人工查看,不要把它直接拼成批量删除命令。

find -size 看的是文件逻辑大小,稀疏文件可能显示很大,实际分配的磁盘块却没有那么多。对选中的单个文件再比较:

1
2
ls -lh -- /var/log/example.log
sudo du -h -- /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 通过目录树统计能够访问的文件占用。两者口径不同,不能要求结果逐字节一致。

先核对这些条件:

  1. 比较的是否为同一个文件系统,有没有把独立挂载点混进来。
  2. du 是否有足够权限,扫描期间文件是否持续变化。
  3. 是否存在已删除但仍被进程打开的文件。
  4. 是否涉及文件系统元数据、保留块、快照或写时复制等额外占用。
  5. 挂载点下面是否遮住了原有文件,导致当前目录视图看不到它们。

前几项可以在线核对;涉及快照、挂载遮蔽或底层存储的检查,应结合实际文件系统另行规划。不要为验证一个猜测,直接卸载线上正在使用的数据盘。

排查已删除但仍被进程打开的文件

一个典型原因是最后一个文件名已经删除,但进程仍持有打开的文件。这里的“被占用”主要是指进程还打开着文件,不一定存在文件锁。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
2
COMMAND   PID USER FD TYPE DEVICE    SIZE/OFF NLINK  NODE NAME
app 12345 app 8w REG 253,1 10737418240 0 78901 /var/log/app.log (deleted)

它表示示例进程 12345 仍以可写文件描述符 8 持有该文件,NLINK 为 0,路径后带 (deleted)。先核对进程身份和文件描述符:

1
2
ps -p 12345 -o pid,ppid,user,comm
sudo readlink /proc/12345/fd/8

这两条是 Linux 上的只读核对,数字必须换成当前查询结果。进程退出、重启或关闭文件后,原编号可能失效或被复用,操作前需要重新确认,不能照抄示例 PID。

这份列表可能包含其他文件系统上的文件,同一个文件也可能被多次打开。不能把 SIZE/OFF 一列直接相加,当成一定能释放的物理空间;还要核对文件所在设备及实际分配情况。

处理时按这个顺序进行:

  1. 确认文件属于哪个服务、是否仍在增长,以及释放它会影响哪些业务。
  2. 优先使用该服务明确支持的日志 reopen 或轮转方式,让进程重新打开日志。普通 reload 是否能做到这一点,取决于服务实现。
  3. 没有合适的在线释放方式时,再安排受控重启对应服务或容器,并验证业务恢复。如果多个进程持有同一文件,只处理其中一个未必能够释放空间。
  4. 重新运行 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
2
sudo journalctl --rotate --vacuum-size=1G
sudo journalctl --disk-usage

vacuum 针对已归档文件;配合 rotate 可以先轮转当前日志。即便如此,也不要把结果理解成整个 /var/log 必须精确降到 1G。具体行为见 journalctl 官方文档。

长期控制可按实际空间预算配置 journald 的保留上限,例如持久日志的 SystemMaxUse 和 SystemKeepFree;配置位置、优先级及生效方式需对照本机版本。journald.conf 官方文档说明了这些选项。

检查应用日志与备份的用途及保留条件

发现某个日志或备份很大后,先查它属于哪个服务、是否还在写入、是否已有轮转规则,以及是否存在可用的恢复副本。

同一满分区内的重命名不会腾出容量;把大日志在原分区压缩,也可能因临时空间不足而失败。需要转存时,应确认目标确实在另一文件系统或远端,传输完成并验证后再决定如何处置原文件。

已经确认过期、没有活跃引用且可删除的文件,可以按明确清单处理;正在写入的日志应通过应用或日志工具支持的方式管理。恢复少量空间以后,还需要追查为什么会持续增长,例如异常重试、调试日志未关闭或备份任务重复执行。

检查 Docker 存储占用与日志配置

在有相应 Docker 权限的宿主机上检查:

1
2
3
docker info --format '{{.DockerRootDir}}'
docker system df -v
docker ps --size

docker system df -v 可查看镜像、容器、卷及构建缓存等统计,镜像层还存在共享部分,不能把展示的大小简单相加。docker ps --size 展示容器写层等信息,也不能完整代表容器日志和挂载数据的宿主机占用。参考 Docker 空间统计和容器存储说明。

不要把 /var/lib/docker/overlay2 当成固定起点。实际配置、rootless 模式以及使用 containerd image store 的安装都可能有不同存储位置;先确认引擎版本、存储方式和实际目录,再对相关路径运行 df、findmnt 与逐层统计。

对已经定位到的容器,可以查看日志配置和挂载:

1
2
3
docker inspect --format '{{json .HostConfig.LogConfig}}' app-container
docker inspect --format '{{.LogPath}}' app-container
docker inspect --format '{{json .Mounts}}' app-container

app-container 换成真实名称。LogPath 的结果取决于日志驱动,可能为空;挂载列表则能帮助确认数据是否写到了独立卷或宿主机目录。

如果使用 json-file 且没有轮转限制,持续输出的日志可能不断增长。长期修复可以在对应 Compose 服务中显式配置日志轮转,例如:

1
2
3
4
5
6
7
8
services:
app:
# 合并到已有服务定义,保留原有 image、volumes 等配置
logging:
driver: json-file
options:
max-size: "20m"
max-file: "3"

这段是合并片段,不是可直接替代生产配置的完整 Compose 文件。参数值按日志量和保留需求调整;现有容器通常需要按计划重建才能使用新配置,单纯重启容器不会更新创建时的日志选项。重建前确认持久化、服务依赖和中断窗口,完成后再次 inspect 核对。Docker 日志配置文档明确说明了新旧容器的生效边界。

Docker 管理的日志文件不适合用外部工具直接编辑或截断,这可能干扰日志系统。JSON File 驱动文档给出了相应说明。

同样,RECLAIMABLE 或“未使用”不等于业务允许删除。按对象确认回滚镜像、停止容器、卷和缓存的用途,再选择清理范围;system prune 的删除对象也不应被当作线上默认清理清单。

确认数据库数据与日志的清理条件

如果主要占用来自数据库目录,需要继续区分业务数据、临时文件、错误日志、慢日志,以及复制或恢复所需的日志。

例如,MySQL 的 binlog 涉及复制和按时间点恢复。判断能否清理时,要同时核对副本进度、备份恢复链和保留策略,使用对应版本支持的数据库管理机制。不能仅根据文件年龄就从文件系统删除,也不能因为文件名中带 log 就当作普通应用日志。MySQL 的 PURGE BINARY LOGS 文档说明了相关条件。

本文不提供跨版本通用的数据库删除命令。确认空间属于正常保留的业务数据时,应转向容量规划或存储扩容。

第六步:验证空间回收与业务恢复

操作完成后,先回到最初确认的文件系统检查:

1
2
df -hT /var/log
df -i /var/log

路径换成这次实际处理的目标。如果起初问题涉及打开的已删除文件,再看 lsof +L1 是否还有目标文件的引用;如果涉及 journal 或 Docker,也要复查对应统计及配置。

然后验证业务是否恢复:写日志、上传、任务落盘或数据库写入,应使用服务自身的健康检查或可控测试。一次命令返回成功,不能代表所有写入链路都已经正常。

再间隔一段时间观察使用量。如果空间很快重新被填满,说明持续增长的来源仍未解决。把这次处理记录下来:涉及哪个文件系统、占用来源、采取了什么操作、释放多少空间,以及业务验证结果。

容量确实不足时,可以继续看本站的云服务器 Debian 12 根分区扩容记录。那篇记录包含特定环境下的迁移和扩容过程,执行前仍需核对自己的分区、LVM、文件系统及引导方式。

容量管理与防复发措施

把这次找到的来源转成对应的长期措施:

本次原因 后续措施
日志持续增长 配置轮转、保留上限,修复异常输出来源
缓存或临时小文件堆积 设置过期策略,观察文件数量和 inode 使用
备份不断累积 明确保留周期、异地副本和恢复验证
Docker 日志或构建数据增长 限制日志轮转,按用途管理镜像和缓存生命周期
正常业务数据增长 评估增长速度,提前安排归档、迁移或扩容

监控除了使用率,还应包含剩余绝对容量、inode 和增长速度。相同的使用率,在小系统盘和大数据盘上的剩余空间不同;即使当前比例不高,异常写入也可能在短时间内耗尽空间。

告警触发后,把文件系统、容量或 inode、占用来源和增长速度一并带给处理人员;故障解除后,将保留策略和负责人写进维护记录,避免下一次又从全盘搜索开始。

事后复盘

服务恢复后,整理一次复盘,记录影响、证据、原因和后续行动。故障期间拿不准的地方标为待确认;没有监控或日志支撑的数据,不补写成确定结论。

记录业务影响、时间线与恢复证据

下面是可填写模板,时间和影响范围需要根据真实记录补齐:

记录项 需要填写的内容
故障对象 主机或服务标识、文件系统、挂载点,是否涉及容量或 inode
业务影响 哪些接口、任务或写入失败;影响多少请求或用户,统计依据是什么
起止时间 首次业务受影响、告警、接手处理、开始缓解、业务恢复、验证结束;注明时区
发现方式 监控、用户反馈或人工检查,附图表、日志或工单证据
处理经过 按时间列出观察、判断、操作和结果,保留未生效的尝试及其解释
恢复依据 目标分区可用空间、inode、写入测试、错误率与后续观察结果

告警时间不一定是故障开始时间,进程重启成功也不一定是业务恢复时间。若只能确定某个时间区间,就记录区间和证据缺口,不把处理人员接到通知的时间当成全部影响时长。

证据需要保留到可复查的位置。用于公开分享时,再去除凭据、真实服务器地址和用户数据;不要为了腾空间,把仅有的故障证据也一起清掉。

分析触发因素与防护缺口

复盘至少区分四件事:什么变化触发了增长、什么资源实际耗尽、为什么没有提前发现或限制,以及什么操作真正恢复了业务。

例如,只有实际证据支持时,才可以把下面的链条写进结论:某应用异常重试导致日志持续增长;该日志没有有效轮转;根文件系统可用空间耗尽;处理时删除了文件名,但进程仍持有文件,所以容量没有回升;随后通过服务支持的方式释放引用,空间才被回收。

这条链里,日志异常增长是需要追查的触发因素,缺少轮转和告警是防护缺口,删除后仍被占用解释了处理为何没有立即生效。不能只把“磁盘满”或“某人删错了”作为最终原因。

如果无法证明是哪一个进程、哪一份文件或哪次变更造成增长,就保留假设并列出下一步取证方法。复盘应说明当时有哪些信息和可用工具,以及流程哪里需要改进。

明确行动项、负责人和验收标准

下面是候选行动模板,按本次真实原因选用,不代表已经实施:

行动项 优先级 负责人 / 截止日期 验收标准
为确认持续增长的日志配置轮转 待定 待填写 核对运行实例的实际配置,在受控测试中确认轮转生效与占用边界
补齐容量、inode 和增长速度告警 待定 待填写 阈值有业务依据,测试告警能到达值班人,并附对应挂载点与排查入口
把已删除文件句柄检查加入处理手册 待定 待填写 演练能定位持有者,通过服务支持的方式释放后,用 lsof、df 和业务检查验证
修复异常重试、重复备份或缓存保留问题 待定 待填写 原触发条件下不会持续无界增长,观察窗口和测试证据有记录

每项再关联可追踪的工单或记录,完成时填写验证证据。尚未执行的配置属于计划,不能因为写进复盘就标记为已修复;“加强监控”“以后注意”也需要落实到具体改动和验收条件。

关于复盘的事实依据、改进方向和可追踪行动,可以参考 Google SRE 的事后复盘实践。对这类磁盘故障,复盘完成后应能回答:下一次增长会怎样被限制,怎样更早被发现,处理人员又能怎样确认空间真的释放。

参考资料