For AI agents: the complete documentation index is available at https://www.tteam.icu/llms.txt, the full documentation bundle is available at https://www.tteam.icu/llms-full.txt, and this page is available as Markdown at https://www.tteam.icu/blog/ops/%E7%BA%BF%E4%B8%8A%E8%99%9A%E6%9C%BA%E6%96%AD%E7%94%B5%E5%90%8EXFS%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E6%8D%9F%E5%9D%8F%E4%BF%AE%E5%A4%8D%E8%AE%B0%E5%BD%95.md.
← Back to Blog

线上虚机断电后XFS文件系统损坏修复记录

AI SummaryAI Generated

线上虚机断电恢复后,Docker 容器显示运行中,但数据目录 /data 无法正常访问。重挂载只能短暂恢复,最终从内核日志中定位到 XFS 元数据损坏和文件系统主动关闭。

处理过程是停止业务、卸载数据盘、离线执行 xfs_repair,最后在普通修复被未重放日志阻断的情况下使用 -L 清除日志并修复。

-L 会丢弃尚未重放的元数据日志,存在数据丢失风险;文件系统修复结束后,还需要单独检查 Kafka 等业务的数据完整性。

前言

这次线上虚机发生断电,恢复后发现数据盘不能正常读写,第一反应是“磁盘坏了,变成只读了”。机器上通过 Docker 运行着 Kafka 和 Redpanda Console,数据盘挂载在 /data。

下面按历史命令整理当时的排查和处理过程,并补充再次遇到这类问题时应该做的检查。原记录止于修复后重新挂载、启动 Docker,后面的业务验证属于补充建议,不代表当时已经完成。

先明确一点:这次日志能确认的是 XFS 文件系统元数据损坏。断电是故障背景,但仅凭这些输出,还不能断定虚拟磁盘或底层物理磁盘已经发生硬件损坏。

容器还在运行,数据目录却打不开

最开始查看容器状态:

docker ps -a

Kafka 和 Redpanda Console 都显示 Up 2 days,看起来进程还活着。但进入数据目录后执行:

cd /data
ls -al

返回的却是:

ls: cannot open directory '.': Input/output error

这时排查重点就从容器进程转到了文件系统。Up 只能说明容器主进程还在运行,不能说明它能正常读写数据,也不能证明 Kafka 能正常提供服务。

Input/output error 也不是普通的权限不足。目录遍历已经失败,单纯调整权限无法解决这个问题。

第一次重挂载,只是短暂恢复

当时先停止 Docker 及其 socket,再卸载、重新挂载:

systemctl stop docker docker.socket
cd /
umount /data
mount -a

停止 docker.socket 是为了避免排查期间访问 Docker 时,由 socket 激活机制再次拉起服务。实际处理时,还要确认容器已经退出;如果启用了 Docker 的 live-restore,停止 daemon 后容器仍可能继续运行。

重新挂载后,ls -al /data 一度可以正常看到目录:

app
cache
docker

于是又启动了 Docker:

systemctl start docker
docker ps -a

容器重新显示运行中,但很快访问 /data 又出现了同样的 Input/output error。

这说明重新挂载并没有解决根本问题。能列出目录只是一个瞬时结果,一旦业务重新触发文件创建等操作,仍可能碰到损坏的元数据。

这里保留当时使用的 mount -a,它会按 /etc/fstab 尝试挂载所有适用条目。正式恢复单块数据盘时,在确认配置正确后,更适合使用 mount /data,将操作范围限制在目标挂载点。

从内核日志定位到XFS损坏

接着检查内核日志:

dmesg | tail -n 50

关键内容如下,省略调用栈和寄存器输出:

XFS (sda1): Corruption of in-memory data (0x8) detected at xfs_trans_cancel...
XFS (sda1): Please unmount the filesystem and rectify the problem(s)
XFS (sda1): Unmounting Filesystem
XFS (sda1): Mounting V5 Filesystem
XFS (sda1): Starting recovery (logdev: internal)
XFS (sda1): Ending recovery (logdev: internal)
XFS (sda1): Corruption detected! Free inode 0x85ee875 not marked free! (mode 0x81a4)
XFS (sda1): Internal error xfs_trans_cancel ... Caller xfs_create...
XFS (sda1): Corruption of in-memory data (0x8) detected ... Shutting down filesystem.
XFS (sda1): Please unmount the filesystem and rectify the problem(s)

这些输出把前面的现象串了起来:

  1. /dev/sda1 上的 XFS 检测到了元数据异常。
  2. 重挂载时执行了日志恢复,暂时又能访问目录。
  3. 随后的文件创建路径 xfs_create 再次触发 inode 一致性检查错误。
  4. XFS 为避免继续扩大损坏,主动关闭文件系统,后续访问返回 I/O 错误。

日志中的调用进程是 java,这与机器运行 Kafka 的背景相符,但不能据此认定 Kafka 就是损坏的原因。它也可能只是访问到了已经损坏的元数据。

“只读”和XFS主动关闭的区别

遇到无法写入时,可以补充查看:

findmnt -T /data -o SOURCE,TARGET,FSTYPE,OPTIONS
lsblk -f
journalctl -k -b --no-pager | tail -n 100

如果挂载选项中是 ro,说明该挂载确实处于只读状态。这次记录中的直接证据则是 Shutting down filesystem 和 Input/output error;XFS 主动关闭后,即便挂载选项仍显示 rw,也不代表文件系统还能正常使用。

因此,不能直接套用 mount -o remount,rw /data。元数据损坏没有修复,恢复写入并不能消除问题。

停止业务,确认需要修复的分区

确认文件系统损坏后,再次停止 Docker,并离开数据目录:

systemctl stop docker docker.socket
cd /

历史记录中尝试用 lsof 查看目录占用:

lsof +D /data

但目录本身已经无法读取,因此命令报错:

lsof: WARNING: can't stat(/data): Input/output error

lsof +D 需要遍历目录,在这种情况下不能依赖它判断是否还有进程占用。可以结合 fuser -vm /data、服务状态及容器进程排查,并停止其他使用该数据盘的服务。

当时接着执行了:

umount -l /data
fdisk -l

fdisk 输出显示,数据盘是 200 GiB 的 /dev/sda,其中分区是 /dev/sda1;系统盘则是另一块 8 GiB 的 /dev/sdb。

关于卸载和修复对象

umount -l 是懒卸载,只会先将挂载点从当前文件系统树中分离,旧引用可能仍然存在。它不是“现在可以安全修复”的保证,也不应作为离线修复的默认前置步骤。

推荐先释放占用,再正常执行 umount /data。如果已经懒卸载,或无法确定容器、其他挂载命名空间是否仍持有该文件系统,应进入救援环境,确认目标分区完全未挂载后再修复。不要在仍被使用的文件系统上运行 xfs_repair。

另外,fdisk 中的 Linux 分区类型并不能证明文件系统是 XFS。应在卸载前用 findmnt 确认挂载关系,再结合 lsblk -f、blkid /dev/sda1 和内核日志交叉核对。设备名可能变化,不能照搬这里的 /dev/sda1。

在进行会修改元数据的修复前,应先停止写入并保留故障盘快照或克隆。快照需要保存到独立存储,不能把同盘另一个目录当成备份。如果底层存储仍有持续 I/O 错误,应先处理宿主机或存储故障,必要时在磁盘副本上恢复。

普通修复被未重放日志阻断

确认目标分区离线后,当时执行:

xfs_repair -v /dev/sda1

工具在第二阶段停止,并给出了明确提示:

ERROR: The filesystem has valuable metadata changes in a log which needs to
be replayed. Mount the filesystem to replay the log, and unmount it before
re-running xfs_repair. If you are unable to mount the filesystem, then use
the -L option to destroy the log and attempt a repair.
Note that destroying the log may cause corruption -- please attempt a mount
of the filesystem before doing this.

意思是 XFS 日志中还有需要重放的元数据变更,普通离线修复不能直接处理。-v 只表示输出详细信息,这条命令不是只读检查;如果需要先做不修改磁盘的检查,可以在完全卸载后使用 xfs_repair -n /dev/sda1,但遇到脏日志时其检查能力也会受限。

历史记录中连续执行了两次普通修复,结果相同。条件没有变化,重复运行不会自动消除脏日志。

优先尝试正常日志重放

按照工具提示,更完整的处理路径是在保留快照、底层存储稳定且业务停止的前提下,尝试正常挂载,让内核重放日志,再正常卸载:

# 仅在确认 /dev/sda1 是目标数据分区且当前完全未挂载后执行
mkdir -p /mnt/xfs-recovery
mount /dev/sda1 /mnt/xfs-recovery
# 只有挂载成功且没有再次出现 shutdown,才继续正常卸载
umount /mnt/xfs-recovery
xfs_repair -v /dev/sda1

每一步都需要检查结果,不能在挂载失败后直接继续。这种正常挂载可能写盘以重放日志,不属于只读取证操作。

这次前面已经出现过挂载恢复后再次损坏的现象,但原记录没有包含普通修复报错后,再单独尝试一次隔离挂载的过程。这里补上工具建议的路径,避免把 -L 写成遇到脏日志就必须执行的常规步骤。

最后使用-L清除日志并修复

当时最终执行的是:

# 最后手段:已保留快照,确认完全卸载,并接受丢弃未重放元数据日志的风险
xfs_repair -L /dev/sda1

工具首先提示:

ALERT: The filesystem has valuable metadata changes in a log which is being
destroyed because the -L option was used.

-L 会清零 XFS 日志,丢弃尚未重放的元数据变更,然后尝试修复。它可能导致文件或目录丢失,不能理解成单纯“强制执行一下”。只有正常日志重放无法完成、已做好数据保护并接受相应风险时,才考虑这一步。

后续输出中可以看到空闲空间和 inode 映射扫描、元数据修正,以及目录连接关系检查:

clearing needsrepair flag and regenerating metadata
correcting imap
Phase 5 - rebuild AG headers and trees...
Phase 6 - check inode connectivity...
        - moving disconnected inodes to lost+found ...
disconnected dir inode 134526733, moving to lost+found
disconnected inode 402893163, moving to lost+found
Phase 7 - verify and correct link counts...
resetting inode 425708 nlinks from 2 to 3
Maximum metadata LSN (6151:7014) is ahead of log (1:2).
Format log to cycle 6154.
done

其中 moving to lost+found 很关键:修复发现了与目录树失去连接的 inode,并将其放到了文件系统根目录下的 lost+found。重新挂载后,这里对应 /data/lost+found,需要检查其中是否有业务文件,不能直接当垃圾删除。

最后的 done 表示这次文件系统修复流程结束,不代表所有业务数据都完整,也不代表 Kafka 日志段的内容已经通过校验。

重新挂载与恢复验证

历史记录最后执行了:

mount -a
systemctl start docker

到这里能确认的是修复命令执行结束,并进行了挂载和服务启动操作。更稳妥的恢复流程应先检查文件系统,再恢复业务。

先验证数据盘

确认 /etc/fstab 中的目标条目正确后,挂载并检查:

mount /data
findmnt -T /data -o SOURCE,TARGET,FSTYPE,OPTIONS
ls -al /data
ls -al /data/lost+found

必须先确认 /data 实际挂载到了预期分区,再启动 Docker。否则挂载失败时,业务可能往系统盘上的同名目录写入数据。

确认挂载源和 rw 状态后,可以在数据盘上新建一个临时文件,测试写入、同步和读取:

check_file=$(mktemp /data/.xfs-recovery-check.XXXXXX)
if [ -n "$check_file" ]; then
    printf 'xfs recovery check\n' > "$check_file" &&
        sync && cat "$check_file" && rm -- "$check_file"
fi
dmesg | tail -n 100

检查是否出现新的 XFS corruption、shutdown 或块设备 I/O 错误。一次读写成功只说明基本访问恢复,仍需要观察后续业务负载下的表现。

再验证Docker和Kafka

systemctl start docker
docker ps -a
docker logs --tail 200 kafka
docker logs --tail 100 redpandadata-console

除了容器是否启动,还要检查 Kafka 是否持续重启、是否出现日志段损坏或目录不可写,以及 topic、分区和副本状态是否正常。使用业务已有的测试 topic 做生产、消费验证,并核对关键数据和消费进度。

文件系统修复解决的是磁盘上的元数据一致性,Kafka 的数据恢复还要结合副本、备份和应用日志判断。不要为了让容器启动就直接删除 Kafka 数据目录或异常日志段。

这次排查留下的经验

这次最容易误判的地方,是容器一直显示运行中,以及重新挂载后目录曾经恢复访问。如果只看到这两个现象就结束处理,业务下一次写入时仍会再次触发故障。

以后遇到类似情况,我会先把容器状态、挂载状态和内核日志一起看。确认 XFS 损坏后,先停止写入、保留快照,再离线修复;普通日志重放能解决时优先重放,-L 留作最后手段。

后续还需要检查宿主机和底层存储的断电记录、写缓存保护及磁盘健康状态,并完善数据盘挂载失败时阻止业务启动的机制。同时保留独立备份、关注文件系统错误和 Kafka 副本告警,避免下次仍然只能依靠单盘修复恢复业务。