FAQ-11:磁盘空间不足(no space left on device)

预计阅读时间:19 分钟

📖 目录

问题速查表

问题解决章节
磁盘块空间被日志、缓存等占满原因分析、清理系统缓存和临时文件
df -h 显示有空间但仍无法写入(inode 耗尽)第三步:检查 inode 耗尽情况、处理 inode 耗尽
已删除但未释放的文件占用空间第四步:查找已删除但未释放的文件
多用户环境下配额限制无法写入第五步:检查磁盘配额
文件系统损坏导致写入失败第六步:检查文件系统是否损坏
如何快速找到大文件并清理第二步:找到空间占用大户、查找并删除大文件

No space left on device 是 Linux 系统中最令人紧张的错误之一。当系统无法在磁盘上创建新文件或写入数据时,就会报出这个错误。它可能出现在安装软件包、写入日志、保存文件、甚至启动服务时,严重时会导致系统完全无法正常工作。

值得注意的是,磁盘空间不足不一定意味着物理磁盘真的被文件占满了。Linux 文件系统有两个关键限制:磁盘块(block)空间和 inode 数量。即使磁盘还有剩余空间,如果 inode 耗尽(通常由于存在海量小文件),系统同样无法创建新文件。此外,文件系统损坏、磁盘配额限制、以及已删除但未释放的文件(被进程持有)也是常见但容易被忽视的原因。

在生产环境中,磁盘空间不足可能导致数据库崩溃、日志写入失败、备份中断、Web 应用返回 500 错误等一系列连锁反应。对于运行中的服务器,紧急清理需要兼顾业务连续性,不能盲目删除文件。下面从排查到解决逐步展开。

原因分析

磁盘块空间耗尽是最直接的原因。日志文件持续增长、应用程序产生大量临时文件、软件包缓存未清理、Docker 镜像和容器层不断累积,都会逐渐吞噬磁盘空间。

inode 耗尽是一种更隐蔽的情况。df -h 可能显示磁盘还有大量可用空间,但 df -i 会显示 inode 已用尽。inode 是文件系统中存储文件元数据的数据结构,每个文件(包括空文件和目录)都占用一个 inode。当系统中存在大量小文件时(如邮件队列、PHP session 文件、node_modules 等),inode 可能先于磁盘块空间被耗尽。

已删除但未释放的文件是另一个常见原因。当一个文件被删除但仍有进程在使用它时,磁盘空间不会被释放。这种情况在日志文件和服务输出中尤为常见。使用 lsof 可以找到这些被占用的已删除文件。

磁盘配额限制在多用户环境中可能导致特定用户无法写入,即使磁盘整体仍有空间。使用 quota 命令可以检查当前用户的配额使用情况。

排查决策树

磁盘空间不足的排查方向取决于报错的形态,先跑三个诊断命令,再按下面的决策树分流:

写入失败:No space left on device
│
├─ ① 先看 df -h 块空间是否已满
│    ├─ Use% = 100% → 块空间耗尽 → du 逐层定位大目录
│    │    └─ 清理日志 / 缓存 / 大文件(见解决方案)
│    └─ Use% 正常(如 60%)→ 进入 ②
│
├─ ② 再看 df -i 是否 inode 耗尽
│    ├─ IUse% = 100% → 海量小文件占满 inode
│    │    └─ find 定位小文件密集目录 → 清理
│    └─ IUse% 正常 → 进入 ③
│
├─ ③ 查找已删除但未释放的文件
│    │    lsof +L1 / lsof | grep '(deleted)'
│    │    ├─ 有大文件 → 重启持有该文件的进程即可释放
│    │    └─ 无 → 进入 ④
│
└─ ④ 检查配额与文件系统状态
     ├─ quota -s(多用户环境是否撞配额)
     ├─ mount | grep " / "(是否被只读重新挂载)
     └─ dumpe2fs -h | grep state(文件系统是否损坏)

排查步骤

第一步:快速确认磁盘整体使用情况

# 查看各分区空间使用率
df -h
# 关注 Use% 列,接近或达到 100% 的分区需要处理

# 同时查看 inode 使用率
df -i
# 如果 IUse% 接近 100%,即使 df -h 显示有空间也无法创建新文件

第二步:找到空间占用大户

# 查看一级目录大小(从根目录开始)
du -h --max-depth=1 / 2>/dev/null | sort -hr | head -10

# 逐层深入占用最大的目录
du -h --max-depth=1 /var | sort -hr | head -10
du -h --max-depth=1 /var/log | sort -hr | head -10

# 查找大于 100MB 的文件
find / -xdev -type f -size +100M -exec ls -lh {} \; 2>/dev/null | sort -k5 -hr | head -20

# 交互式浏览(强烈推荐)
sudo apt install ncdu
ncdu /

第三步:检查 inode 耗尽情况

# 查看 inode 使用率
df -i

# 找到 inode 使用最多的目录
# 需要逐目录检查,通常问题出在 /var/mail、/var/spool、/tmp 等
find / -xdev -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -rn | head -10

第四步:查找已删除但未释放的文件

# 查找被进程占用的已删除文件
lsof 2>/dev/null | grep '(deleted)' | awk '{print $7, $9, $1, $2}' | sort -rn | head -20

# 简化写法:只看被删除文件的大小
sudo lsof +L1 2>/dev/null | awk 'NR>1{print $7, $9}' | sort -rn | head -20

如果发现有大文件被删除但仍被进程占用,重启该进程即可释放空间,无需重启整个系统。

第五步:检查磁盘配额

# 查看当前用户配额
quota -s

# 查看所有用户配额
sudo repquota -a

# 如果没有启用 quota,检查是否因为文件系统挂载选项限制
mount | grep " / "

第六步:检查文件系统是否损坏

# 检查文件系统状态
dumpe2fs -h /dev/sda1 | grep "Filesystem state"
# 正常应显示:clean

# 如果显示 filesystems with errors,需要在卸载后修复
# 注意:不能修复正在使用的根分区,需要从 Live CD 启动

解决方案

清理系统缓存和临时文件

# 清理 apt 包缓存
sudo apt clean
sudo apt autoremove

# 清理 systemd 日志(保留最近 500MB)
sudo journalctl --vacuum-size=500M
# 或者保留最近 7 天
sudo journalctl --vacuum-time=7d

# 清理 Docker 资源(镜像、容器、网络、构建缓存)
docker system prune -af
# 更彻底:同时清除 volumes
docker system prune -af --volumes

# 清理旧内核(Ubuntu/Debian)
sudo apt autoremove --purge

# 清理临时文件
sudo rm -rf /tmp/*
sudo rm -rf /var/tmp/*

# 清理用户缓存
rm -rf ~/.cache/thumbnails/*
rm -rf ~/.cache/mozilla/firefox/*/Cache*

查找并删除大文件

# 找到最大的日志文件
find /var/log -type f -name "*.log" -exec ls -lh {} \; 2>/dev/null | sort -k5 -hr | head -10

# 压缩大日志文件(而不是直接删除)
sudo gzip /var/log/syslog.1

# 清理已轮转的旧日志
sudo find /var/log -name "*.gz" -mtime +30 -delete
sudo find /var/log -name "*.[1-9]" -mtime +30 -delete

处理 inode 耗尽

# 查找小文件最多的目录
find / -xdev -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -rn | head -5

# 常见问题点及清理方法
# 1. 清理邮件队列
sudo postsuper -d ALL

# 2. 清理 PHP session 文件
sudo rm -rf /var/lib/php/sessions/*

# 3. 清理 Node.js 模块缓存
find / -name "node_modules" -type d -exec rm -rf {} + 2>/dev/null

# 4. 清理系统临时文件
sudo find /tmp -type f -atime +7 -delete
sudo find /var/tmp -type f -atime +7 -delete

# 5. 清理 APT 缓存中的小文件
sudo apt clean

# 6. 清理 systemd journal 中的大量小文件
sudo journalctl --vacuum-files=10

真实案例

案例 A:日志文件被删除但进程仍持有,4.3GB 空间"隐形"占用

现象:应用开始报 No space left on device,写入全部失败:

$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   50G  36K 100% /

$ du -sh /usr /var /home /opt /tmp 2>/dev/null
4.2G    /usr
2.1G    /var
...     # 所有目录加起来远小于 50G,明显有隐藏占用

排查过程:目录实际大小与 df 数值对不上,高度怀疑"已删除但未释放"的文件,用 lsof 确认:

$ sudo lsof +L1 | awk 'NR>1{print $7, $10, $1, $2}' | sort -rn | head -5
4379812864 deleted java   18234
512294912  deleted nginx  3561

根因:Java 应用(PID 18234)以追加模式打开了日志文件,logrotate 轮转删除该文件后进程从未关闭文件描述符,4.3GB 空间被一直占用。文件在磁盘上无目录项(所以 du 看不到),但空间直到进程退出才会释放。

修复:重启持有文件的进程即可,无需重启整个系统:

$ sudo systemctl restart myapp
$ sudo lsof +L1 | wc -l      # 确认 deleted 文件已全部释放
0
$ df -h /
/dev/sda1  50G  43G  6.7G  87% /

验证:df 显示空间恢复,应用日志正常写入。后续把 logrotate 配置改为 copytruncate 模式,让 Java 这类不主动重开文件描述符的服务也能正确轮转,从根源上避免再次出现隐形占用。

案例 B:inode 耗尽——df -h 明明有空间,却一个文件都建不了

现象:应用批量报错,服务器上连临时文件都创建失败:

$ df -h /data
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb1        500G  120G  380G  24% /data

$ df -i /data
Filesystem     Inodes  IUsed IFree IUse% Mounted on
/dev/sdb1     3276800 3276800     0  100% /data

排查过程:df -h 明明还有 380G 可用,但 df -i 显示 inode 100%。定位小文件密集目录:

$ find /data -xdev -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -rn | head -3
2843612 /data/app/sessions
123456  /data/app/cache

根因:应用把每个用户会话存成一个独立小文件,累计 284 万个小文件提前耗尽了文件系统 inode 上限(数据量只有 120G,文件数量却已触顶)。ext4 默认每个 inode 占 256 字节,这个分区格式化时分配的 inode 数量不足以支撑这种"海量小文件"业务。

修复:先清理过期会话恢复服务,再改造存储方式:

$ find /data/app/sessions -type f -mtime +30 -delete   # 清理 30 天前的会话
$ df -i /data                                           # 确认 inode 释放
/dev/sdb1  3276800 1954321 1322479  60% /data

验证:inode 使用率回落到 60%,应用恢复写入。长期方案:会话改存 Redis、限制 session 保留天数、定期清理脚本入 cron;若业务必然产生海量小文件,则在新分区格式化时用 mkfs.ext4 -N 8000000 提高 inode 数量上限。

案例 C:Docker 镜像和构建缓存吃掉 20GB 磁盘空间

现象:CI 服务器频繁构建 Docker 镜像,半年后磁盘满了,docker system df 显示镜像和缓存占用 20GB。

$ docker system df
TYPE          TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images        156       12        18.5GB    16.2GB (87%)
Containers    45        3         1.2GB     980MB (81%)
Local Volumes 23        5         850MB     620MB (72%)
Build Cache   312       0         4.8GB     4.8GB (100%)

根因:CI 每次构建都生成新镜像和构建层缓存,但从未清理。悬空镜像(dangling images)和无用构建缓存累积了 20GB。

修复docker system prune -a --volumes 清理所有未使用的镜像、停止的容器、无用网络和构建缓存。后续在 CI 流水线末尾添加自动清理步骤。

案例 D:日志轮转配置错误导致日志文件无限增长

现象:Web 服务器的访问日志持续增长,logrotate 每天轮转但磁盘空间仍然被耗尽:

$ ls -lh /var/log/nginx/
-rw-r--r-- 1 root root 45G access.log
-rw-r--r-- 1 root root 2.3G access.log.1.gz
-rw-r--r-- 1 root root 1.8G access.log.2.gz
...
-rw-r--r-- 1 root root 890M access.log.30.gz

$ cat /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    postrotate
        # 错误:没有真正通知 Nginx 重新打开日志文件
        kill -USR1 $(cat /var/run/nginx.pid)
    endscript
}

排查过程:logrotate 确实在轮转文件,但旧日志没有被真正释放:

# 检查 Nginx 是否持有已删除的文件
$ sudo lsof +L1 | grep nginx | head -5
nginx   1234  root  5w  REG  8,1  48128348160  0  /var/log/nginx/access.log (deleted)

# Nginx 没有收到 USR1 信号,仍在写入旧的文件描述符

根因:logrotate 的 postrotate 脚本使用了错误的 PID 文件路径,kill 命令没有真正执行。Nginx 仍然持有旧文件的文件描述符,导致 45GB 空间无法释放。

修复

# 修正 logrotate 配置
/var/log/nginx/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    postrotate
        # 正确的信号发送方式
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 $(cat /var/run/nginx.pid)
        fi
    endscript
}

# 手动触发轮转验证
sudo logrotate -f /etc/logrotate.d/nginx

# 确认 Nginx 已切换到新日志文件
$ sudo lsof +L1 | grep nginx
# 应无输出,表示没有 deleted 文件被持有

预防:配置 logrotate 后立即用 logrotate -f 手动触发一次验证。关键服务的 postrotate 脚本添加错误检查,确保信号发送成功。

案例 E:数据库事务日志膨胀导致磁盘写满

现象:MySQL 服务器磁盘空间被耗尽,但 /var/lib/mysql 目录大小远小于 df 报告的使用量:

$ df -h /var
Filesystem      Size  Used Avail Use%
/dev/sda2       100G  100G    0 100%

$ du -sh /var/lib/mysql/
12G    /var/lib/mysql/

# 12G 远小于 100G,大量空间被隐藏占用
$ sudo lsof +L1 | grep mysql | awk '{sum+=$7} END {print sum/1024/1024/1024 " GB"}'
87.5 GB

排查过程:MySQL 持有大量已删除的事务日志文件:

$ sudo lsof +L1 | grep mysql | head -3
mysqld  2345  mysql  45w  REG  8,2  46200000000  0  /var/lib/mysql/ib_logfile0 (deleted)
mysqld  2345  mysql  46w  REG  8,2  45000000000  0  /var/lib/mysql/ib_logfile1 (deleted)

根因:MySQL InnoDB 的事务日志文件被删除但进程仍持有。这通常发生在手动执行 rm 删除日志文件而非通过 MySQL 配置调整大小时。

修复

# 方案一:重启 MySQL 释放文件锁(短暂中断服务)
sudo systemctl restart mysql

# 方案二:在 MySQL 中动态调整日志文件大小(推荐)
mysql> SET GLOBAL innodb_log_file_size = '1G';
# 这会触发 MySQL 自动轮转日志文件

# 验证空间释放
$ sudo lsof +L1 | grep mysql | wc -l
0
$ df -h /var
/dev/sda2  100G  12G  88G  12%

预防:不要手动删除 MySQL 的数据文件和日志文件。调整 InnoDB 参数时通过 MySQL 命令操作,让服务自动管理文件生命周期。

案例 F:/tmp 目录被大量临时文件占满导致服务启动失败

现象:系统重启后多个服务无法启动,报错信息包含 "No space left on device" 或 "Permission denied":

# 检查 /tmp 使用情况
$ df -h /tmp
Filesystem      Size  Used Avail Use%
/dev/sda1       2.0G  2.0G    0  100%

$ ls /tmp | wc -l
89234

# /tmp 被数万个小文件占满

排查过程

# 查看 /tmp 中的文件分布
$ find /tmp -maxdepth 1 -type d | head -10
/tmp
/tmp/systemd-private-...
/tmp/vmware-root-...

# 找到占用最大的目录
$ du -sh /tmp/*/ 2>/dev/null | sort -hr | head -5
280M    /tmp/php-session/
156M    /tmp/node-xxx/
89M     /tmp/pip-xxx/

根因:PHP 会话文件、Node.js 临时文件、pip 构建缓存累积未清理。系统重启时这些文件不会被自动删除,反复累积后占满分区。

修复

# 清理 /tmp 中超过 7 天的文件
sudo find /tmp -type f -atime +7 -delete
sudo find /tmp -type d -empty -delete

# 清理特定应用的临时文件
sudo rm -rf /tmp/php-session/*
sudo rm -rf /tmp/node-*

# 设置 systemd-tmpfiles 自动清理
# 编辑 /etc/tmpfiles.d/local.conf
# d /tmp/php-session 1777 root root 7d
sudo systemd-tmpfiles --clean

预防:配置 systemd-tmpfiles 或 cron 任务定期清理 /tmp。考虑将 /tmp 挂载为 tmpfs(内存文件系统),避免占用磁盘空间。

预防措施

  • 配置磁盘使用率监控告警(建议 80% 告警、90% 紧急),参考 3.12:系统监控与告警 监控
  • 同时监控 df -i 的 inode 使用率,同样设置 80%/90% 阈值,避免"有空间但建不了文件"的隐蔽故障
  • 配置 logrotate 限制日志文件大小和保留天数,参考 2.7:日志与故障排查 日志管理
  • 重要分区使用 LVM 便于在线扩展磁盘空间,参考 rhcsa01 磁盘分区与 LVM
  • 定期使用 ncdu / 进行可视化磁盘空间巡检,养成主动管理的习惯
  • 配置 cron 任务定期清理系统缓存和临时文件
  • 为数据库等关键服务配置独立的数据分区,避免数据增长影响系统分区
  • 多用户环境启用磁盘配额(quota),限制单用户可用空间,防止一人写满全盘
  • 大日志服务的 logrotate 启用 copytruncate,避免进程持有已删除文件导致空间无法释放

进阶排查技巧

使用 ncdu 进行可视化空间分析

ncdu(NCurses Disk Usage)是交互式磁盘分析的利器:

# 安装 ncdu
sudo apt install ncdu  # Debian/Ubuntu
sudo yum install ncdu  # CentOS/RHEL

# 分析指定目录
ncdu /var/log

# 交互操作:
# ↑/↓ 导航    Enter 进入目录
# g 切换显示模式    d 删除文件
# n 按名称排序    s 按大小排序
# q 退出

使用 inotifywait 实时监控文件增长

对于持续增长的文件,可以实时监控其大小变化:

# 安装 inotify-tools
sudo apt install inotify-tools

# 监控文件大小变化
inotifywait -m -e modify /var/log/app.log --format '%T %w%f %e' --timefmt '%Y-%m-%d %H:%M:%S'

# 监控目录中的文件创建事件
inotifywait -m -e create /var/data/ --format '%T %w%f %e' --timefmt '%Y-%m-%d %H:%M:%S'

使用 fuser 定位占用文件的进程

当需要安全删除被占用的文件时:

# 查找占用特定文件的进程
fuser -v /var/log/app.log

# 强制终止占用文件的进程(谨慎使用)
fuser -k /var/log/app.log

# 查找占用特定端口的进程
fuser -v 3306/tcp

使用 find 批量清理文件

# 删除 30 天前的日志文件
find /var/log -name "*.log.*" -mtime +30 -delete

# 删除大于 100MB 的 core dump 文件
find / -name "core.*" -size +100M -delete 2>/dev/null

# 删除 7 天前的临时文件,排除最近修改的
find /tmp -type f -mtime +7 ! -name ".X*" -delete

# 查找并删除空目录
find /var/cache -type d -empty -delete

常见误区与陷阱

  • 不要盲目使用 rm -rf / 或类似命令:这会删除整个文件系统,造成不可逆的灾难
  • 不要直接删除被进程占用的日志文件:应该先轮转或重启进程释放文件锁
  • 不要忽略 inode 耗尽df -h 显示有空间不代表能创建文件,必须同时检查 df -i
  • 不要在生产环境使用 rm -rf 清理大量文件:可能导致 inode 释放缓慢,使用 find -delete 更安全
  • 不要忽略 Docker 的存储增长:容器镜像和构建缓存会持续累积,必须定期清理

故障复现与验证清单

修复磁盘空间问题后,使用以下清单验证:

# 1. 验证磁盘空间释放
df -h
df -i

# 2. 验证大文件已清理
find / -xdev -type f -size +1G -exec ls -lh {} \; 2>/dev/null

# 3. 验证无 deleted 文件占用
lsof +L1 2>/dev/null | wc -l

# 4. 验证服务正常运行
systemctl status 服务名

# 5. 验证日志正常写入
tail -f /var/log/syslog

最佳实践

  • 分区策略/var/home/tmp 单独分区,避免日志或用户文件占满根分区导致系统无法启动
  • 日志管理:所有服务日志都应接入 logrotate,设置合理的轮转周期和压缩策略,避免单个日志文件无限增长
  • Docker 定期清理:容器化环境中,docker system prune 应加入定期维护计划,避免镜像和构建缓存无限累积
  • inode 规划:格式化文件系统时根据预期文件数量合理设置 inode 大小(-N 参数),尤其是存储大量小文件的场景
  • 容量规划:新部署服务前评估其磁盘需求,预留 20% 以上的安全余量

延伸阅读

附录:磁盘空间清理命令速查表

清理目标命令预计释放空间
APT 包缓存sudo apt clean数百 MB ~ 数 GB
systemd 日志sudo journalctl --vacuum-size=500M按配置
Docker 未使用资源docker system prune -a --volumes数 GB ~ 数十 GB
临时文件sudo rm -rf /tmp/*不定
旧内核sudo apt autoremove --purge数百 MB
用户缓存rm -rf ~/.cache/*数百 MB
邮件队列sudo postsuper -d ALL不定
Node 模块find / -name node_modules -exec rm -rf {} +数 GB

附录:inode 使用率分析命令

# 查看各分区 inode 使用率
df -i

# 找到 inode 使用最多的目录
find / -xdev -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -rn | head -10

# 统计特定目录的文件数量
find /var/mail -type f | wc -l

# 清理小文件密集目录
# 邮件队列
sudo find /var/mail -type f -mtime +30 -delete

# PHP session 文件
sudo find /var/lib/php/sessions -type f -atime +1 -delete

# 临时文件
sudo find /tmp -type f -atime +7 -delete

附录:磁盘监控脚本示例

#!/bin/bash
# 简单的磁盘空间监控脚本

THRESHOLD=80
LOG_FILE="/var/log/disk-monitor.log"

while true; do
    # 检查各分区使用率
    df -h | awk 'NR>1{print $5, $6}' | while read usage mount; do
        usage_num=${usage%\%}
        if [ "$usage_num" -ge "$THRESHOLD" ]; then
            echo "$(date): WARNING: $mount is ${usage}% full" >> "$LOG_FILE"
            # 可以添加邮件告警或 Slack 通知
        fi
    done
    
    # 检查 inode 使用率
    df -i | awk 'NR>1{print $5, $6}' | while read usage mount; do
        usage_num=${usage%\%}
        if [ "$usage_num" -ge "$THRESHOLD" ]; then
            echo "$(date): WARNING: $mount inode usage is ${usage}%" >> "$LOG_FILE"
        fi
    done
    
    sleep 3600  # 每小时检查一次
done

附录:LVM 磁盘扩容命令

# 查看物理卷
sudo pvs
sudo pvdisplay

# 查看卷组
sudo vgs
sudo vgdisplay

# 查看逻辑卷
sudo lvs
sudo lvdisplay

# 扩展逻辑卷(在线扩容)
sudo lvextend -L +10G /dev/mapper/vg0-root
sudo resize2fs /dev/mapper/vg0-root    # ext4
sudo xfs_growfs /dev/mapper/vg0-root   # xfs

# 添加新物理卷到卷组
sudo pvcreate /dev/sdb1
sudo vgextend vg0 /dev/sdb1
sudo lvextend -l +100%FREE /dev/mapper/vg0-root
sudo resize2fs /dev/mapper/vg0-root

案例 G:systemd journal 日志文件耗尽 inode

现象:服务器运行一段时间后报 No space left on device,但 df -h 显示磁盘空间充足:

$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       50G   30G  18G  63% /

$ df -i /
Filesystem     Inodes   IUsed  IFree IUse% Mounted on
/dev/sda1     3276800  3276800     0  100% /

排查过程:inode 100% 但块空间充足,说明存在海量小文件:

$ find / -xdev -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -rn | head -3
  3102456 /var/log/journal/remote
   128456 /var/log/journal/abc123...
     2345 /var/log

根因:systemd-journald 的持久化日志在 /var/log/journal 中为每个系统启动创建大量小文件,长期运行且未配置日志清理策略时,inode 被耗尽。

修复

# 限制 journal 大小
sudo journalctl --vacuum-size=500M

# 或限制保留天数
sudo journalctl --vacuum-time=30d

# 永久配置:编辑 /etc/systemd/journald.conf
[Journal]
SystemMaxUse=500M
MaxRetentionSec=30day

# 重启 journald 使配置生效
sudo systemctl restart systemd-journald

预防:新系统部署时立即配置 journald.conf 的日志大小和保留策略。定期用 df -i 检查 inode 使用率,设置告警阈值。

案例 H:/var/log/btmp 无限增长耗尽磁盘空间

现象:SSH 暴力破解攻击导致 /var/log/btmp(记录失败登录)文件持续增长,最终磁盘写满:

$ ls -lh /var/log/btmp
-rw-rw---- 1 root utmp 45G Jul 15 10:00 /var/log/btmp

$ du -sh /var/log/btmp
45G    /var/log/btmp

# btmp 记录了所有失败的登录尝试
$ lastb | wc -l
2847563

根因/var/log/btmp 记录所有失败的 SSH 登录尝试。遭受暴力破解攻击时,该文件会快速增长。由于没有配置日志轮转,文件大小不受限制。

修复

# 清空 btmp 文件
sudo truncate -s 0 /var/log/btmp

# 配置 logrotate 限制 btmp 大小
cat | sudo tee /etc/logrotate.d/btmp << 'EOF'
/var/log/btmp {
    monthly
    create 0660 root utmp
    rotate 1
    size 10M
}
EOF

# 安装 fail2ban 防止暴力破解
sudo apt install -y fail2ban
sudo systemctl enable fail2ban

预防:部署服务器时立即配置 fail2ban 或类似的入侵防御工具。将 /var/log/btmp 加入 logrotate 配置,限制文件大小。定期用 lastb | wc -l 检查失败登录数量。

↑ 回到顶部