---
title: "Linux 服务器空间不足排查：磁盘容量、inode 与文件占用"
source: https://www.iowill.com/blog/2026/10/linux-server-disk-full-troubleshooting/
date: 2026-10-23
updated: 2026-10-23
tags: [Docker, Linux, 故障复盘, 故障排查, 日志管理, 磁盘空间]
categories: [系统与运维]
---

# 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 都充足 | 实际写入路径、配额、容器限制和文件系统状态 | 回到应用报错及所在运行环境继续查 |

![Linux 空间不足排查流程：确认文件系统后，分别检查容量、inode 和 df 与 du 的统计差异](https://img.iowill.com/file/系统与运维/Linux服务器空间不足排查/body1.png)

先确认挂载范围与资源类型，再定位占用来源；处理后同时验证空间与业务。

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

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

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

检查文件系统容量和 inode 使用情况：

```bash
df -hT
df -i
```

`df -hT` 中需要一起看的是：

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

用于说明判断方式的示例输出：

```text
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` 算进去，会把两个不同文件系统混在一起。

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

```bash
df -hT /var/log
findmnt -T /var/log
```

`findmnt -T` 用来查包含该路径的文件系统。把 `/var/log` 换成应用实际写入的目录，先确认路径确实存在；数据盘挂载失败后，程序也可能把数据写进根文件系统上的同名目录。

`df -i` 则检查 inode。在 ext4 等文件系统上，大量小文件可能先耗尽 inode，导致仍有剩余容量却无法创建新文件。不同文件系统的 inode 分配方式不同，不能只凭某一列显示为零或未知就判断故障，仍要结合文件系统类型和实际报错。[GNU 的 `df` 文档](https://www.gnu.org/software/coreutils/manual/html_node/df-invocation.html)说明了容量、inode 和挂载点的统计含义。

## 第二步：定位目录空间占用

确认满的是根文件系统后，检查第一层目录的占用：

```bash
sudo du -x -h --max-depth=1 / | sort -h
```

这里的参数各有作用：

-   `-x`：不跨到不同文件系统，避免把独立数据盘算进来。
-   `-h`：用便于阅读的单位显示容量。
-   `--max-depth=1`：只展示第一层汇总，便于判断下一步。
-   `sort -h`：按带单位的数值排序。

最后的 `/` 汇总行是总量，不是一个需要删除的额外目录。发现 `/var` 占用明显较大，再查下一层：

```bash
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` 文档](https://www.gnu.org/software/coreutils/manual/html_node/du-invocation.html)。

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

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

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

```bash
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` 看的是文件逻辑大小，稀疏文件可能显示很大，实际分配的磁盘块却没有那么多。对选中的单个文件再比较：

```bash
ls -lh -- /var/log/example.log
sudo du -h -- /var/log/example.log
```

这里的 `example.log` 是占位路径，执行时换成已经确认的真实文件。逻辑大小和实际占用的区别，也意味着“最大的文件”不一定能释放最多空间。

## 第三步：排查 inode 耗尽与小文件积累

假设 `df -hT` 仍有较多空间，`df -i` 却显示某个文件系统的 inode 接近耗尽，可以按目录统计 inode 使用：

```bash
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) 手册](https://man7.org/linux/man-pages/man2/unlink.2.html)。

![普通文件生命周期示意：最后一个文件名删除后，进程仍持有打开引用时数据块保留；全部引用释放后再确认回收](https://img.iowill.com/file/系统与运维/Linux服务器空间不足排查/body2.png)

路径消失不等于空间释放：需要核对剩余链接、打开引用及目标文件系统的实际变化。

有 `lsof` 且具备相应权限时，可以检查：

```bash
sudo lsof -nP +L1
```

`+L1` 选择链接数小于 1 的打开文件，用来查找已经 unlink 的文件。结合 `COMMAND`、`PID`、`FD`、`DEVICE`、`SIZE/OFF` 和 `NAME` 判断哪个进程持有目标文件。[lsof 官方手册](https://github.com/lsof-org/lsof/blob/master/docs/manpage.md)列出了该选项的含义。

教学用的简化输出中，进程、编号和大小均为示例：

```text
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)`。先核对进程身份和文件描述符：

```bash
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 占用：

```bash
sudo journalctl --disk-usage
```

它统计的是 journal 文件，不能代表整个 `/var/log`。应用自己写出的文本日志、压缩包和备份仍需分别核对。

确认旧 journal 可以按保留要求清理后，才执行下面的操作。**这一步会删除旧日志，先确认故障取证、审计和留存需求；1G 只是示例上限。**

```bash
sudo journalctl --rotate --vacuum-size=1G
sudo journalctl --disk-usage
```

vacuum 针对已归档文件；配合 rotate 可以先轮转当前日志。即便如此，也不要把结果理解成整个 `/var/log` 必须精确降到 1G。具体行为见 [journalctl 官方文档](https://www.freedesktop.org/software/systemd/man/latest/journalctl.html)。

长期控制可按实际空间预算配置 journald 的保留上限，例如持久日志的 `SystemMaxUse` 和 `SystemKeepFree`；配置位置、优先级及生效方式需对照本机版本。[journald.conf 官方文档](https://www.freedesktop.org/software/systemd/man/latest/journald.conf.html)说明了这些选项。

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

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

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

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

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

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

```bash
docker info --format '{{.DockerRootDir}}'
docker system df -v
docker ps --size
```

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

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

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

```bash
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 服务中显式配置日志轮转，例如：

```yaml
services:
  app:
    # 合并到已有服务定义，保留原有 image、volumes 等配置
    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "3"
```

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

Docker 管理的日志文件不适合用外部工具直接编辑或截断，这可能干扰日志系统。[JSON File 驱动文档](https://docs.docker.com/engine/logging/drivers/json-file/)给出了相应说明。

同样，`RECLAIMABLE` 或“未使用”不等于业务允许删除。按对象确认回滚镜像、停止容器、卷和缓存的用途，再选择清理范围；[system prune 的删除对象](https://docs.docker.com/reference/cli/docker/system/prune/)也不应被当作线上默认清理清单。

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

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

例如，MySQL 的 binlog 涉及复制和按时间点恢复。判断能否清理时，要同时核对副本进度、备份恢复链和保留策略，使用对应版本支持的数据库管理机制。不能仅根据文件年龄就从文件系统删除，也不能因为文件名中带 `log` 就当作普通应用日志。[MySQL 的 PURGE BINARY LOGS 文档](https://dev.mysql.com/doc/refman/8.0/en/purge-binary-logs.html)说明了相关条件。

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

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

操作完成后，复查原先确认的文件系统：

```bash
df -hT /var/log
df -i /var/log
```

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

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

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

容量确实不足时，可以继续看本站的[云服务器 Debian 12 根分区扩容记录](https://www.iowill.com/blog/2025/04/cloud-debian12-root-resize/)。那篇记录包含特定环境下的迁移和扩容过程，执行前仍需核对自己的分区、LVM、文件系统及引导方式。

## 容量管理与防复发措施

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

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

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

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

## 事后复盘

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

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

复盘模板中的时间和影响范围需要根据真实记录补齐：

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

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

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

### 分析触发因素与防护缺口

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

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

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

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

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

候选行动模板按本次真实原因选用，不代表已经实施：

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

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

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

## 参考资料

-   [GNU Coreutils：df](https://www.gnu.org/software/coreutils/manual/html_node/df-invocation.html)、[du](https://www.gnu.org/software/coreutils/manual/html_node/du-invocation.html)、[df 与 du 差异说明](https://www.gnu.org/software/coreutils/faq/coreutils-faq.html)
-   [GNU Findutils：文件大小判断](https://www.gnu.org/software/findutils/manual/html_node/find_html/Size.html)
-   [lsof 官方手册](https://github.com/lsof-org/lsof/blob/master/docs/manpage.md)
-   [Linux unlink(2)：文件名删除与打开文件的生命周期](https://man7.org/linux/man-pages/man2/unlink.2.html)
-   [systemd：journalctl](https://www.freedesktop.org/software/systemd/man/latest/journalctl.html)、[journald.conf](https://www.freedesktop.org/software/systemd/man/latest/journald.conf.html)
-   [Docker：磁盘统计](https://docs.docker.com/reference/cli/docker/system/df/)、[存储驱动](https://docs.docker.com/engine/storage/drivers/)、[日志配置](https://docs.docker.com/engine/logging/configure/)、[JSON File 驱动](https://docs.docker.com/engine/logging/drivers/json-file/)、[system prune](https://docs.docker.com/reference/cli/docker/system/prune/)
-   [MySQL 8.0：PURGE BINARY LOGS](https://dev.mysql.com/doc/refman/8.0/en/purge-binary-logs.html)
-   [Google SRE：事后复盘实践](https://sre.google/workbook/postmortem-culture/)
