4. 文件系统异常排查:磁盘挂载失败、文件系统损坏、inode耗尽问题
文件系统出问题,说实话是运维日常里最让人头疼的几类故障之一。你想想看,应用跑着跑着突然写不了日志了,数据库报磁盘空间不足但 df -h 一看还有几十 G 空闲……这种时候,十有八九是文件系统层面出了幺蛾子。我这些年处理过的类似 case 少说也有几十个,今天就把最常见的三类问题——挂载失败、文件系统损坏、inode 耗尽——掰开揉碎讲清楚。
核心思路:遇到文件系统异常,先别急着重启。按「挂载状态 → 文件系统完整性 → 空间与 inode 使用率」的顺序排查,能省下大量试错时间。
4.1 磁盘挂载失败
挂载失败这个问题,我遇到过最离谱的一次是凌晨三点被电话叫醒——客户说所有业务都挂了,远程一看,根分区还在,但数据盘全没挂上。后来发现是机房断电重启后,磁盘设备名变了,/etc/fstab 里写的 /dev/sdb1 对不上号了。
排查挂载失败,我个人的习惯是三步走:
- 先看 dmesg 和 journalctl——内核会告诉你为什么挂不上。比如设备不存在、文件系统类型不对、超级块损坏等。
- 检查 fstab 配置——重点看设备标识、挂载点、文件系统类型、挂载选项。
- 手动尝试挂载——用
mount -t ext4 /dev/sdb1 /data看具体报错。
我的习惯:生产环境的 fstab 里,设备标识一律用 UUID 而不是 /dev/sdX。因为设备名在重启后可能变,UUID 是唯一的。用 blkid 查看 UUID,一劳永逸。
常见的挂载失败原因,我整理了一张表:
| 报错信息 | 可能原因 | 快速解决 |
|---|---|---|
mount: special device does not exist |
设备节点不存在或设备名变了 | 用 lsblk 确认设备名,更新 fstab |
mount: wrong fs type |
文件系统类型指定错误 | 用 blkid 查看实际类型,修正 -t 参数 |
mount: you must specify the filesystem type |
内核无法自动识别文件系统 | 手动指定 -t ext4 或 -t xfs |
mount: /data: mount point does not exist |
挂载点目录不存在 | mkdir -p /data 创建目录 |
mount: /data: device is busy |
挂载点或设备被占用 | lsof /data 或 fuser -m /data 找出占用进程 |
注意:千万不要在生产业务高峰期执行 mount -a 重挂所有 fstab 条目。我曾经见过有人手滑把根分区重新挂载到空目录上,系统直接失联。稳妥的做法是先手动挂载测试,确认没问题再写进 fstab。
4.2 文件系统损坏
文件系统损坏,说白了就是磁盘上的元数据结构乱了。表现通常是:读写报 I/O error、目录打不开、df 卡住不动。我记得有一次,一台跑了三年的 NFS 服务器突然报 Structure needs cleaning,用户全在叫——文件读不出来也删不掉。
为什么会损坏?常见原因有:
- 非正常关机——断电、强制重启,日志没来得及回放。
- 磁盘硬件故障——坏道、控制器异常。
- 内核 bug 或驱动问题——少见,但遇到了就是大坑。
- 超频或内存错误——写入时数据出错,污染了文件系统。
修复工具,Linux 下主要靠 fsck。但不同文件系统有不同玩法:
| 文件系统 | 修复命令 | 注意事项 |
|---|---|---|
| ext3/ext4 | fsck.ext4 -y /dev/sdb1 |
必须卸载后执行,-y 自动回答 yes |
| XFS | xfs_repair /dev/sdb1 |
同样需要卸载,XFS 的修复速度通常比 ext4 快 |
| btrfs | btrfs check --repair /dev/sdb1 |
建议先 btrfs check 只读检查,确认问题再修复 |
重要原则:修复前一定要先做磁盘镜像或快照。尤其是生产环境,fsck 修错了可能丢更多数据。我个人的流程是:先 dd 整盘到备用盘,再在副本上跑修复。
超级块损坏是比较特殊的情况。ext 文件系统在每个块组都存了超级块副本,主超级块坏了可以用备份的:
# 查看备份超级块位置(ext4 为例)
mke2fs -n /dev/sdb1
# 使用备份超级块挂载
mount -o sb=32768 /dev/sdb1 /mnt
# 或者用 fsck 指定备份超级块修复
fsck.ext4 -b 32768 /dev/sdb1
嗯,这里要注意:mke2fs -n 只是模拟创建,不会真的格式化磁盘。它会把备份超级块的位置列出来,你挑一个离主超级块最近的用就行。
4.3 inode 耗尽问题
inode 耗尽,这是个经典坑。你 df -h 一看磁盘还剩 50G,但应用就是报 No space left on device。为什么会这样?因为文件系统能存的文件数量是有限的,这个上限由 inode 数量决定。
我遇到过最夸张的一次,是某个日志系统没配轮转,一晚上生成了 800 多万个几字节的小文件。第二天早上整个分区 inode 用尽,连 touch 一个新文件都做不到。
排查方法很简单:
# 查看 inode 使用情况
df -i
# 找出哪个目录 inode 消耗最多
for i in /*; do echo $i; find $i -xdev -type f | wc -l; done
# 更精细的排查——统计子目录文件数
find /data -xdev -type d | while read dir; do
count=$(find "$dir" -maxdepth 1 -type f | wc -l)
echo "$count $dir"
done | sort -rn | head -20
我的经验:df -i 显示的 IUsed% 超过 80% 就要警惕了。尤其是邮件队列、临时文件目录、容器 overlay 层,这些地方最容易 inode 爆炸。我曾经在 /var/spool/postfix 里清理过 200 多万封退信……
清理 inode 的核心思路是删文件,但要注意:
- 普通删除——
rm -f直接删,但文件太多时rm *会报Argument list too long。 - 批量删除——用
find配合xargs或delete参数:
# 删除 7 天前的小文件
find /data/tmp -type f -mtime +7 -delete
# 或者用 xargs 分批删
find /data/tmp -type f -name "*.log" -print0 | xargs -0 rm -f
# 如果文件数量实在太多,用 perl 或 rsync 清空目录
rsync -a --delete empty_dir/ /data/tmp/
避坑指南:我曾经在清理 inode 时直接用 rm -rf /data/tmp/*,结果因为文件太多,rm 进程跑了几小时还没结束,期间系统负载飙升。后来学乖了,用 find ... -delete 或 rsync --delete,效率高得多。另外,如果文件被进程占用,删了文件但 inode 不会释放——得先 kill 进程或重启服务。
预防 inode 耗尽,我建议从这几个方面入手:
- 创建分区时规划好 inode 数量——
mkfs.ext4 -N 10000000 /dev/sdb1可以指定 inode 数量,或者用-T small优化小文件场景。 - 配置日志轮转——logrotate 是标配,别偷懒。
- 监控 inode 使用率——Zabbix、Prometheus 都支持,阈值设到 80% 告警。
- 定期清理临时目录——
/tmp、/var/tmp这些地方最容易藏污纳垢。
最后说一句,文件系统的问题,很多时候是「防大于治」。你花十分钟配好监控和轮转,可能就避免了一次凌晨三点的大故障。嗯,这些都是我踩过的坑换来的经验,希望对你有用。