文件系统变只读排查实战:dmesg 读根因、fstab 四个陷阱与数据盘 fsck 恢复顺序

最让人措手不及的故障:系统还在跑,磁盘变只读了

有一类服务器故障,比宕机更折磨人:SSH 能连上,top 能看到 CPU 在跑,已打开的页面也还能访问,但任何写操作都失败。上传失败、数据库报错、日志不再追加、touch 一个文件都提示 Read-only file system。你重启一下,disk 检查可能要跑二十分钟,业务停摆这么久。

这个现象通常不是磁盘坏了,而是内核在检测到文件系统层面的错误后,主动把分区切成了只读。理解这个机制,才能分清"要换硬盘"和"虚惊一场"这两种截然不同的结局。

为什么内核要把文件系统改只读

ext4 默认带一个叫 errors=continue 的行为(实际上现代发行版多为 errors=remount-ro)。文件系统在读写过程中如果遇到元数据不一致——inode 位图对不上、目录项指向了不存在的 inode、超级块的时间戳比实际旧——内核会认为继续写下去会造成更大范围的破坏。这时候它有两个选择:

  • 继续,赌一把,可能把碎片化错误扩散成整盘不可恢复;
  • 立刻停止写入,把分区 remount 成只读,让数据维持在当前(可能局部损坏但不扩散)的状态。

内核选了后者。remount-ro 是一种保护策略,不是故障本身。所以看到只读,第一反应不该是"盘坏了",而应该是"内核发现了什么,我要去看 dmesg"。

第一步永远是 dmesg,不是重启

所有线索都在内核环形缓冲区里。重启会清掉它,所以现场第一件事:

dmesg -T | grep -iE 'ext4|xfs|I/O error|remount|read-only' | tail -40

-T 把时间戳转成人能读的格式,tail -40 只看最近的部分(老的错误可能来自几天前,别被带偏)。你要在输出里找三件事:

  1. 是 I/O 错误还是元数据不一致? 出现 I/O error, dev sda, sector ... 基本可以判定硬件问题;出现 ext4_iget: bad inode、EXT4-fs error (device ...): ext4_lookup 这类则是文件系统结构问题。
  2. 是哪个设备? sda、vdb、nvme0n1p2——先确认范围,是单块盘还是整机问题。
  3. 有没有后续的 remount 记录? EXT4-fs (sda1): Remounting filesystem read-only 这一行就是"切换动作"的发生点,它上面的几行才是根因。

如果 dmesg 已经被刷掉了(高流量机器日志涨得快),去系统日志里捞:

journalctl -k --since "2 hours ago" | grep -iE 'ext4|I/O error|remount'

分清三类根因,别一上来就换盘

第一类:真实硬件故障。特征很明确——dmesg 里有连续的 I/O error,同一块盘反复出现,而且错误扇区号在不断变化。这时候要立刻做的是把数据弄出来,不是修文件系统。检查 SMART 状态:

smartctl -a /dev/sda | grep -iE 'Reallocated|Pending|Uncorrectable|Health'

看到 Reallocated_Sector_Ct 或 Current_Pending_Sector 非零,基本可以确定盘在衰退。云主机上更简单——直接开新实例、挂载旧盘拷数据、然后把旧盘退役。

第二类:磁盘写满导致的连锁反应。这条最容易被忽略。ext4 在数据块耗尽时,可能连元数据的写都完成不了,进而触发保护性只读。所以你看到的"只读"根因其实是"满了":

df -h
df -i

容量和 inode 两个都要看(只读故障里 inode 耗尽的比例比想象中高)。如果确认是空间问题,清出空间后重新挂载即可恢复,不用重启。

第三类:fstab 配置错误导致的挂载问题。这类故障往往发生在重启之后——服务器开机就进了紧急模式,或者某个数据盘没挂上,应用写到了根分区的空目录里。先确认所有预期分区都在:

findmnt --verify
systemctl --failed

findmnt --verify 会直接指出 /etc/fstab 里的问题条目(拼错的设备名、引用了已经不存在的 UUID、选项冲突)。这是我见过的最高性价比的一条命令,它能在一秒内告诉你"为什么开机少挂了一个盘"。

fstab 里的四个经典陷阱

因为 fstab 改错而导致的故障,占了我处理过的挂载问题的一半以上。四条规则必须刻在脑子里。

一、用 UUID 而不是设备名。/dev/sdb1 这个名字是内核按探测顺序分配的,加一块盘、换一个 SATA 口、重启一次,它就可能变成 /dev/sdc1。用设备名的 fstab 意味着随时可能挂错分区,甚至挂载系统盘。查 UUID 和写正确条目:

blkid /dev/sdb1
# 输出示例: /dev/sdb1: UUID="a1b2c3d4-..." TYPE="ext4"

# fstab 里这样写
UUID=a1b2c3d4-...  /data  ext4  defaults,noatime  0  2

二、dump 和 pass 两列别乱填。倒数第二列(dump)现在基本都是 0;最后一列(pass)是 fsck 的检查顺序:根分区写 1,其他分区写 2,不检查的写 0。把数据盘写成 1 会导致开机时两块盘同时检查,启动时间翻倍;把 /data 这种大容量数据盘写成 0 则意味着它永远不会被 fsck——真出问题时你连修复的机会都没有。合理的默认是:系统盘 pass=1,数据盘 pass=2,网络/临时挂载 pass=0。

三、nofail 和 defaults 的区别决定服务器能不能开机。defaults 意味着挂载失败会导致启动流程卡住。如果这块盘是可选的数据盘或者外部存储,一旦它没插、没上电、文件系统有错,你的服务器就再也起不来了。加上 nofail 之后,挂载失败只会记一条日志,系统照常启动:

UUID=a1b2c3d4-...  /data  ext4  defaults,nofail,noatime  0  2

四、改完 fstab 必须验证,不能直接重启。这一条是保命规则。正确的验证方式是在不重启的情况下重新加载并测试挂载:

# 先做语法与设备校验
findmnt --verify --verbose

# 尝试重新挂载全部条目(fstab 有错会在这里暴露,而不是等到重启)
mount -a

# 如果上一条报错,立刻修;确认无误后再查看结果
findmnt --real

mount -a 会按 fstab 挂载所有未挂载的条目,有问题的条目立刻报错。这一步在改完 fstab 后必须在当前会话里执行——因为万一出错你还能通过已经打开的 SSH 会话去修,而重启后发现起不来,就只能进救援模式了。

数据盘变只读后的正确恢复顺序

假设你确认了根因不是硬件(没有 I/O error、没满、fstab 也对),只是文件系统元数据出过一次不一致被保护性切换。恢复流程是这样:

第一步,先把业务停掉,别在原盘上继续写。停止会写这个分区的服务(通常是数据库和应用),否则卸载会失败:

systemctl stop mysql php8.2-fpm

第二步,卸载分区。卸载要求没有任何进程在使用它:

umount /data
# 如果提示 target is busy,先查是谁在用
fuser -mv /data
lsof +D /data 2>/dev/null | head

fuser -mv 会列出占用该挂载点的进程;必要时对进程发 TERM 而不是 KILL,让它有机会把缓冲区刷下去。

第三步,备份再修复。这是很多人省掉、然后后悔的一步。fsck 是写操作,可能把"还能手工挽救的数据"直接抹平。有条件就先对整块盘做镜像:

# 备份到另一块盘,别备份到同一块
dd if=/dev/sdb of=/backup/sdb.img bs=4M status=progress

空间不够做整盘镜像时,至少把关键目录 tar 一份出来。

第四步,执行 fsck。注意:绝对不能对已挂载的文件系统跑 fsck,那会造成严重损坏。先卸载,再检查:

# -f 强制检查,-y 对所有询问回答 yes
fsck -fy /dev/sdb1

-y 在无人值守场景是必须的,否则 fsck 会停在第一个提问处等输入,而你以为它在跑。如果 fsck 输出大量修复记录,说明元数据确实坏了;修复完成后重新挂载并立即检查关键文件的完整性。

第五步,重新挂载并观察。挂上去之后不要马上恢复业务,先盯着 dmesg -w 看几分钟,确认没有新的错误刷出来,再启动服务。

预防:让下次故障能自愈而不是停机

做完恢复,顺手把三件事补上,下次就不会这么狼狈:

定时自检。ext4 可以定期做只读检查(不做修复),提前发现元数据异常:

# 设置每挂载 20 次或每 30 天检查一次(按需调整)
tune2fs -c 20 -i 30d /dev/sdb1

查看当前设置用 tune2fs -l /dev/sdb1 | grep -iE 'mount count|check'。注意这只是让 fsck 在该跑的时候跑,不要配合 errors=panic 一起用,那会让服务器直接宕机,得不偿失。

SMART 定时巡检。每周跑一次 SMART 自检,比等 I/O error 出现早得多:

smartctl -t short /dev/sda
# 两分钟后查看结果
smartctl -a /dev/sda | grep -A1 'Short self-test'

把挂载状态纳入监控。最容易被忽略的监控项:分区还在不在、是不是只读。findmnt 的退出码可以直接当判据:

findmnt -n -o OPTIONS /data | grep -q '^ro,\|,ro,\|,ro$' && echo "ALERT: /data is read-only"

接进你的告警脚本后,只读故障会在一分钟内通知你,而不是等到用户来投诉。

把排查顺序固定下来

这类故障的价值不在于记住多少命令,而在于固定一套动作顺序。我现在的反应链是:

  1. dmesg -T | grep -i remount —— 确认是不是保护性只读,以及根因行;
  2. df -h 加 df -i —— 排除"其实满了";
  3. smartctl —— 排除硬件衰退;
  4. findmnt --verify —— 排除 fstab 配置问题。

四步跑完,基本能定性到"换盘 / 清空间 / 修 fstab / 跑 fsck"四个方向之一。个人站长没有 7×24 的运维团队,靠的就是把这类判断固化成肌肉记忆。顺序对了,大部分"看起来要停机"的故障,其实当天就能恢复,而且不需要重装系统。

Last modification:September 26th, 2026 at 12:25 pm

Leave a Comment