从 SSH 超时到系统恢复:鲲鹏 ARM 服务器 XFS 根分区修复实战

在这里插入图片描述
一台运行麒麟 V10 的 ARM 服务器突然无法通过 SSH 连接。起初看起来像网络故障:Ping 不通,Xshell 连接超时,接上管理口后也不知道该从哪里排查。接上兼容的显示器后,才看到主机停在启动应急环境,阻塞启动的是 XFS 根分区的日志恢复错误。随后通过 iBMC KVM 接管,继续完成救援与修复。

这次处理没有重装系统。我们先在救援环境中保护原卷,使用内存中的写时复制快照试修,再在确认风险后对原卷执行修复。最终服务器重新进入麒麟登录界面,Ping 和 SSH 恢复响应。

这篇文章记录完整判断过程,尤其说明三个容易踩坑的地方:管理口与业务口的区别、XFS“只读挂载”的边界,以及 xfs_repair -L 的实际风险。

本文是一次具体故障的恢复复盘,不是适用于所有磁盘故障的一键脚本。网络地址已替换;逻辑卷名称、软件版本和关键报错保留。涉及块设备的命令必须先核对实际目标。全文的“恢复”指系统启动与 SSH 恢复,不代表业务数据已全部验证完整。

一、故障环境与最终结果

项目本次环境
服务器KunTai A722,鲲鹏 920,ARM64 / aarch64
操作系统Kylin Linux Advanced Server V10
原系统内核4.19.90-52.42.v2207.ky10.aarch64
根文件系统XFS,位于 LVM 逻辑卷 /dev/mapper/klas-root
根卷大小约 1.04 TiB,已有数据约 944 GB
远程管理iBMC HTML5 KVM、虚拟光驱
救援环境Rocky Linux 9.8 aarch64,5.14 系列内核
修复工具xfs_repair 6.4.0
内存条件约 125 GiB,足以开展有限规模的 RAM COW 试验
恢复时间2026 年 11 月 4 日

处理完成后,原卷的修复和只读复查都返回退出码 0;原系统正常启动,业务 IP 可以 Ping 通,SSH 端口返回 SSH-2.0-OpenSSH_8.2。随后也确认可以远程连接服务器。

与此同时,7 个失去目录关联的 inode 被重新连接到 /lost+found。这不能解释为“只有 7 个文件受影响”,也不能据此承诺所有文件和数据库完整。

二、先厘清连接路径:iBMC 能访问,不等于系统已经启动

本次排查一开始被“连接不上”这个表面现象干扰。服务器有两类不同用途的接口:

接口作用能证明什么
MGMT / iBMC 管理口访问管理页面、查看硬件、使用 KVM 和虚拟光驱管理链路和管理控制器可用
业务网口Linux 使用业务 IP 提供 SSH、应用等服务还取决于主机系统、网络和服务状态

iBMC 独立于主机操作系统运行。即使 Linux 停在启动应急环境,管理页面仍可能正常打开。电脑直连 MGMT,也通常不会自动经由这个接口访问服务器的业务网络。

本例排查过程中,为了验证原业务网口,曾临时将电脑直连该口;测试结束后,恢复业务口与交换机的原连接,电脑通过另一根线连接 MGMT。最初依靠本地显示器查看启动错误,拿到有效的管理账号后,才通过 iBMC KVM 继续操作,不再依赖现场逐条输入命令。

这里的经验是:SSH 超时只是现象,先确认主机是否完成启动,再区分链路、地址、路由和服务问题。

三、控制台给出的线索:根卷无法完成 XFS 日志恢复

主机控制台出现了两个关键错误片段:

Failed to recover intents
Structure needs cleaning

目标是系统的 XFS 根卷:

/dev/mapper/klas-root

XFS 使用日志记录文件系统的元数据事务。发生非正常中断后,挂载过程通常需要回放日志,使元数据回到一致状态。本次在恢复日志中的意图记录时失败,导致根文件系统不能正常挂载。根卷挂载失败,后面的系统服务自然无法启动,SSH 也就无从提供。

Structure needs cleaning 是文件系统一致性相关的错误,不是让用户清理垃圾文件,也不能只凭这一行就认定硬盘已经损坏。

为什么没有直接跳过错误启动?

这次出问题的是根文件系统,里面有系统启动必需的程序和配置。给其他非关键数据盘设置 nofail,或者跳过某个挂载项,不能让一个无法正常挂载的根卷继续完成原系统启动。

XFS 的 norecovery 选项确实能跳过日志回放,但它只能配合只读挂载。它适合在条件允许时做诊断、查看或拷贝可见文件,不能让文件系统变得一致,更不能作为恢复正常业务运行的替代方案。

断电是不是根因?

据当时操作人员回忆,服务器曾卡死,随后被按电源键关机重启。这提供了发生非正常中断的线索,但尚不能回答服务器为什么先卡死,也不能证明断电是 XFS 异常的唯一原因。

需要区分两个问题:

  1. 为什么这次启动不了? 已确认:XFS 根卷日志恢复失败,根卷不能正常挂载。
  2. 为什么最初卡死、为什么日志最终无法恢复? 尚未查明,需要故障前的内核、存储和硬件事件日志支持。

这一区分会影响后续处理:恢复启动是当前目标,调查故障起因仍需单独取证。

四、进入匹配架构的救援系统,先保护原卷

因为服务器是 ARM64,救援镜像也必须匹配 aarch64,不能直接使用常见的 x86_64 镜像。架构匹配之后,还要确认救援内核能够识别该机型的存储、网卡和控制器。

本次通过 iBMC 虚拟光驱启动 Rocky Linux 9.8 aarch64 救援环境。镜像完成校验后使用;为了直接进入救援模式,另制作了调整 GRUB 启动配置及对应校验元数据的救援专用副本,内核、initramfs 和 EFI 可执行程序保持不变。普通场景优先使用发行版提供的救援入口,无须照搬制作镜像的过程。

进入救援菜单后选择 Skip to shell,避免自动挂载原系统。

接下来先识别设备和挂载关系。以下是相关检查命令示例,而不是一段需要连续执行的脚本:

lsblk -f
lvs -a -o lv_name,vg_name,lv_path,lv_size
findmnt
dmsetup info /dev/mapper/klas-root
cat /proc/swaps

本次确认目标根卷未挂载,device-mapper 的打开计数为 0,也没有其他进程正在检查该卷。随后设置内核层的块设备只读保护:

blockdev --setro /dev/mapper/klas-root
blockdev --getro /dev/mapper/klas-root
# 本次返回 1

blockdev --setro 是块设备层的内核保护,不是硬件写保护,也不是整块物理盘的取证级保证。使用前仍要排除已挂载卷、已有写入者和其他访问路径。本次没有使用 lvchange --permission r 代替它,因为后者会改变 LVM 元数据。

一个关键细节:mount -o ro 不一定等于磁盘零写入

对需要恢复日志的 XFS,普通只读挂载仍可能执行日志回放。因此,这次没有依靠 mount -o ro 来保护原卷,而是先给原卷设置块设备只读保护,再把需要回放或修复的操作放到可写快照上。

ro 与 ro,norecovery 的区别可以概括为:

方式是否尝试日志回放本次用途
普通 ro 挂载可能尝试在可写副本上测试恢复;修复后验证正常挂载路径
ro,norecovery 挂载跳过仅在需要时用于诊断和读取可见数据,不算修复成功

五、不急着改原盘:先在 RAM COW 快照上试修

这台服务器的数据接近 1 TB,当时没有可用的完整备份位置。直接动原卷意味着放弃一部分回退能力,因此先利用服务器内存,对修复路径做一次隔离验证。

这里使用的是 device-mapper 写时复制快照,不是把 1 TB 数据完整拷贝进内存:

                  读取未修改的数据
只读原根卷 ───────────────────────┐
                                  │
                                  ▼
                            可写快照设备
                                  │
                         回放 / 修复产生的写入
                                  ▼
                         tmpfs 中的 COW 文件

快照的大部分读取仍来自原卷。对快照的修改进入单独的 COW 存储,原卷保持不变。这个机制适合验证“某种修复后是否能够挂载”,但不能替代备份。

本次试验的边界

  • COW 文件放在经确认的 tmpfs 中;救援环境的交换空间已关闭,避免这些内存页被换出到磁盘。
  • 原卷保持未挂载、不再变化,并设置块设备只读保护。
  • 使用普通快照映射,没有启用快照合并,也没有将 discard 透传给原卷。
  • 快照块设备本身可写;挂载时使用文件系统只读选项,从而允许日志回放的写入进入 COW。
  • 定期查看 df -h /mnt/cow 和 dmsetup status,确认内存文件系统、COW 空间和快照状态。

本次 tmpfs 上限为 24 GiB,两份 COW 文件各有 16 GiB 的稀疏逻辑大小,实际合计使用约 565 MiB。这不代表预留了 32 GiB 内存;两份 COW 不可能在这个 tmpfs 里同时填满。遇到大量元数据改写,必须重新评估空间,不能照抄本例容量。

COW 或 tmpfs 耗尽可能导致快照失效并返回 I/O 错误。此时应停止试验、核对快照状态,不能继续写入或合并,更不能仅凭这类错误就断言原盘又出现坏块。

快照参数中的 P 也不会让 tmpfs 数据跨重启保存。只要内存丢失,试验副本就随之丢失;如果原卷损坏或被覆盖,快照同样不能作为独立副本使用。

为避免误操作,本文不提供硬编码的底层 dm 表、主次设备号和 loop 编号。它们都来自现场的实时设备映射,不能在另一台机器上直接复用。

第一次试验:正常日志恢复仍然失败

先在独立快照 rescue152 上使用普通只读挂载路径,测试较新的救援内核能否完成日志回放。结果仍然失败。

这至少说明:本次故障不能仅靠换一个较新的救援内核来消除。 但它仍然不能证明最初没有内核缺陷,也不能直接排除存储硬件问题。

第二次试验:清日志修复后,副本可以正常挂载

接着在另一份独立快照 rescuefix 上测试修复。以下命令中的目标是已创建并核对过的试验快照,不是原根卷:

xfs_repair -L /dev/mapper/rescuefix

-L 的含义是清除日志。它会丢弃未回放的元数据更新,可能造成文件或目录变更丢失,并影响文件内容的可用性,不能当成无风险的常规选项。

试修完成后观察到:

  • 修复返回成功;7 个失去目录关联的 inode 被重新连接到副本的 /lost+found。
  • 空闲空间等文件系统元数据也被重建。
  • 副本可以通过普通 ro,nouuid 挂载,主要目录能够列出,/sbin/init 链接存在,系统版本文件可以读取。
  • 卸载副本后执行 xfs_repair -n,完成全部检查阶段,退出码为 0。

nouuid 用于处理快照与原卷文件系统 UUID 相同的问题,不负责修复一致性错误。-n 的通过也只说明此次检查未检测到需要修复的元数据不一致,不能验证所有文件内容或数据库事务。

试验阶段原根卷相应块设备统计中的写入、写入扇区和 discard 计数保持为 0。这是该逻辑卷访问路径下原卷未被写入的一项佐证,不能扩大成“整台存储设备绝对没有任何写入”。

六、正式修复原根卷:先结束试验,再操作原卷

副本试修成功后,原系统仍然没有修复。我们没有把试修快照直接合并回去,而是在确认无完整备份、明确说明清日志风险并获得授权后,单独修复原卷。

正式操作前完成了以下检查:

  1. 原卷和两份快照都未挂载,之前的检查进程已经结束。
  2. 普通移除两份快照映射;没有使用强制移除或 snapshot merge。
  3. 核对 loop 对应的是内存 COW 文件后再逐一解除,没有碰救援系统自己的 loop 设备。
  4. 再次核对原根卷的大小、映射和打开计数,确认无占用。

下面的 -L 会不可逆地清除原卷日志。 这一步记录的是本次在知情授权后的取舍。它不能用于已挂载根卷,也不应在目标不明、存储仍报 I/O 错误或尚有可行的保全方案时直接照抄。

解除此前设置的块设备只读保护,再对已确认的原卷执行修复:

blockdev --setrw /dev/mapper/klas-root
blockdev --getro /dev/mapper/klas-root
# 本次返回 0

xfs_repair -L /dev/mapper/klas-root \
  > /mnt/cow/repair-original.log 2>&1
repair_rc=$?
printf 'repair exit code: %s\n' "$repair_rc"

本次实际通过后台进程等待结果,确认修复退出码为 0;上面用前台写法展示关键调用与退出码保存方式。/mnt/cow 是此前已创建的 RAM 工作目录,日志必须在重启前转存。

正式修复同样把 7 个失去目录关联的 inode 重新连接到原卷 /lost+found。inode 可能对应文件或目录,原路径和文件名不保证保留。清日志和重建元数据的潜在影响也不保证仅限这 7 个记录。

随后在仍未挂载原卷的状态下做只读复查:

xfs_repair -n /dev/mapper/klas-root \
  > /mnt/cow/verify-original.log 2>&1
verify_rc=$?
printf 'verify exit code: %s\n' "$verify_rc"

复查完成全部阶段,退出码为 0,日志没有提示仍需执行的 would 修改项。退出码必须在命令结束后立即保存,不能先运行其他命令,再把后者的 $? 当作修复结果。

七、修复结束还不够:分层验证恢复结果

在这里插入图片描述

1. 验证正常挂载路径

在修复和只读复查都通过后,使用普通只读方式挂载原卷,没有使用 norecovery:

mkdir -p /mnt/test
mount -o ro /dev/mapper/klas-root /mnt/test

ls -l /mnt/test/sbin/init
ls -l /mnt/test/lib/systemd/systemd
cat /mnt/test/etc/fstab

本次正常挂载成功,系统初始化程序存在;fstab 中根卷对应正确,/boot 与 /boot/efi 为独立分区,另一个 /backup 卷设为 noauto。

如果正常挂载仍然失败,应该停在这里保留新错误,不能把“修复命令结束”直接等同于“系统可以启动”。

2. 保存日志,再卸载和重启

确认根卷可用后,短暂重新挂载为可写,把四份试修与正式修复日志保存到原系统的持久目录:

/root/server152-recovery-20261104/
├── repair-test.log
├── verify-test.log
├── repair-original.log
└── verify-original.log

原盘修复日志复制前后核对了 SHA-256,一致后执行 sync,并正常卸载根卷。随后在 iBMC 中选择硬盘启动,从救援系统执行正常 reboot;启动过程中断开救援虚拟光驱。

这次没有再次用强制断电来测试恢复。

3. 验证主机启动与 SSH

KVM 中观察到原麒麟系统进入正常图形登录界面。网络验证时,业务地址 Ping 两次均收到回复,丢包率为 0%;SSH 的 22 端口成功建立连接,返回:

SSH-2.0-OpenSSH_8.2

至此可以确认:文件系统修复后能够正常挂载,原系统已经启动,业务网络和 SSH 服务恢复响应。数据库及应用层的数据一致性,需要恢复后另行检查。

八、附带解决的网络问题:管理直连与业务地址撞了网段

系统恢复后还遇到一个独立问题:服务器的 SSH 已经起来,但电脑可能仍把访问业务地址的流量发错接口。

脱敏后的网络关系如下:

角色示例配置
服务器 iBMC192.168.20.96
服务器业务 IP192.168.20.152
电脑有线口192.168.20.10/24,直连 MGMT,无网关
电脑 Wi-Fi192.168.10.118,网关 192.168.10.1

管理链路和业务网络物理上分开,却使用相同网段。Windows 根据有线口的直连路由,会把访问 192.168.20.152 的流量也送向 MGMT 链路。

先查看实际选路:

Get-NetIPConfiguration
Find-NetRoute -RemoteIPAddress 192.168.20.152

本次先确认 Wi-Fi 网关确实能路由到服务器的业务网络,然后添加了一条只针对业务地址、仅当前运行期有效的主机路由。以下是与现场处理等价的 PowerShell 示例,需要管理员权限,接口名称和网关应按实际环境修改:

$wifiIf = (Get-NetIPInterface -AddressFamily IPv4 -InterfaceAlias 'WLAN').InterfaceIndex

New-NetRoute -DestinationPrefix '192.168.20.152/32' `
    -InterfaceIndex $wifiIf `
    -NextHop '192.168.10.1' `
    -PolicyStore ActiveStore

Find-NetRoute -RemoteIPAddress 192.168.20.152
Test-NetConnection 192.168.20.152 -Port 22

/32 比有线口的 /24 路由更精确,只改变该业务 IP 的访问方向,管理地址仍走直连线。ActiveStore 不写入持久路由,电脑重启后失效。它的前提是 Wi-Fi 网关具有正确的路由和服务器回程路径,不能在任意网络里照搬。

这个选路问题会干扰 SSH 连通性判断,但它与之前的 XFS 启动失败是两件事,不能拿它解释主机为什么卡死。

九、恢复之后,怎样继续调查真正的事故起因?

这次恢复过程中,smartctl -H /dev/sda 曾返回 SMART Health Status: OK。但在带 RAID 的服务器上,操作系统看到的可能只是控制器暴露的逻辑设备。一条健康摘要不能排除阵列成员盘、控制器、缓存或供电问题。

目前最准确的结论仍是:根卷日志恢复失败已经证实,最初卡死和日志异常的起因尚未确认。

下面是恢复后可进行的只读调查方向,属于后续排查清单,不是本文已经读取到的故障证据:

# 查看系统保存了哪些启动记录,再选择对应 boot ID
journalctl --list-boots

# -1 只是“上一次已保存启动”,先核对它是不是事故前那次
journalctl -b -1 -k --no-pager
journalctl -b -1 -p warning --no-pager

# 关机、重启记录仅作时间线参考
last -x

# 查看传统日志、崩溃转储和 pstore 是否留有证据
ls -lh /var/log/messages* /var/log/kern* 2>/dev/null
ls -lah /var/crash /sys/fs/pstore 2>/dev/null

本次经历了多次启动尝试与救援,不能机械地把 -b -1 当作最初卡死前的那次运行。如果 journal 没有持久化,旧启动日志也可能已经不存在。

调查时建议把这些信号放到同一时间线上:

方向需要寻找的证据
文件系统与块设备XFS CRC / metadata corruption、log I/O error、forced shutdown、I/O error
存储链路SCSI sense、medium / hardware error、timeout、reset、abort、设备掉线
内核与资源panic、Oops、watchdog、lockup、OOM
ARM 硬件错误SError、EDAC / ECC,以及平台硬件事件
RAID / iBMC盘掉线、阵列降级、重建、缓存或电池异常、供电和温度事件

还要校对操作系统与 iBMC 的时钟、时区,避免把相同事件错开。-L 已经清除了原来的文件系统日志,原始故障现场不再完整,后续结论必须受现存证据约束。

十、这次恢复带来的几条经验

  1. 先确认主机启动状态。 Ping 或 SSH 不通,不一定先从防火墙和网卡配置下手;KVM 常能更快缩小范围。
  2. 保护原卷,再验证修复路径。 普通只读挂载不等于绝对无写入;RAM COW 是试验工具,不能替代独立备份。
  3. 把 -L 当作明确取舍。 它通过丢弃未回放日志来推进修复,可能牺牲近期元数据变更,并非“强制修一下就没事”。
  4. 验证要分层。 元数据检查通过、正常挂载、系统启动、SSH 可用、应用数据完整,是不同层级的结果。
  5. 恢复启动之后仍要追因。 本次已经让服务器恢复运行,但最初卡死原因尚无定论,不能用修复成功替代故障分析。

这次的有效路径是:识别根卷故障 → 保护现场 → 在隔离副本上试修 → 评估并接受风险 → 修复原卷 → 验证挂载、启动和远程连接。 对于重要生产环境,独立备份、恢复演练以及可持久保存的系统和硬件日志,仍然是缩短下一次故障处理时间的基础。

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐