> 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.

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


AI SummaryAI Generated

线上虚机断电恢复后，Docker 容器显示运行中，但数据目录 `/data` 无法正常访问。重挂载只能短暂恢复，最终从内核日志中定位到 XFS 元数据损坏和文件系统主动关闭。
处理过程是停止业务、卸载数据盘、离线执行 `xfs_repair`，最后在普通修复被未重放日志阻断的情况下使用 `-L` 清除日志并修复。
**`-L` 会丢弃尚未重放的元数据日志，存在数据丢失风险；文件系统修复结束后，还需要单独检查 Kafka 等业务的数据完整性。**

## 前言

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

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

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

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

最开始查看容器状态：

```shell
docker ps -a
```

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

```shell
cd /data
ls -al
```

返回的却是：

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

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

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

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

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

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

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

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

```text
app
cache
docker
```

于是又启动了 Docker：

```shell
systemctl start docker
docker ps -a
```

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

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

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

## 从内核日志定位到XFS损坏

接着检查内核日志：

```shell
dmesg | tail -n 50
```

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

```text
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主动关闭的区别

遇到无法写入时，可以补充查看：

```shell
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，并离开数据目录：

```shell
systemctl stop docker docker.socket
cd /
```

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

```shell
lsof +D /data
```

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

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

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

当时接着执行了：

```shell
umount -l /data
fdisk -l
```

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

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

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

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

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

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

确认目标分区离线后，当时执行：

```shell
xfs_repair -v /dev/sda1
```

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

```text
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`，但遇到脏日志时其检查能力也会受限。

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

### 优先尝试正常日志重放

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

```shell
# 仅在确认 /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清除日志并修复

当时最终执行的是：

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

工具首先提示：

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

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

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

```text
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 日志段的内容已经通过校验。

## 重新挂载与恢复验证

历史记录最后执行了：

```shell
mount -a
systemctl start docker
```

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

### 先验证数据盘

确认 `/etc/fstab` 中的目标条目正确后，挂载并检查：

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

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

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

```shell
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

```shell
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 副本告警，避免下次仍然只能依靠单盘修复恢复业务。
