一次惊心动魄的数据拯救之旅

技术

作为一个苦逼的创业公司,我们对开发服务器硬件的态度是能省就省。于是我们利用各自垃圾闲置硬件攒出了一个堪用的集群,来供公司平时开发使用。

IMG_8779.jpg

可以看出这分属不同厂商,不同年代,不同配置的服务器及网络设备体现了深深的历史厚重感。里面最耀眼当属 4 台银光闪闪的 Mac mini,把它们加进来还是我的主意,毕竟跑跑持续集成又不是不能用。但此次事件的主角不是它们,而是下方那台贴了黄色符咒的 ThinkStation。符咒是@明城贴的,这台机器也是他攒的,作为一个资深垃圾佬当然是用不起不会用 ThinkStation 这么高端的货色,所以如你所见它只是个机箱,里面早就面目全非了。不过为了方便起见我后面都称其为 ThinkStation 了。

流年不利

周四那天首先有同事报告公司 Gitlab 打不开了,作为我们内部开发使用的主要服务,它一直作为 KVM 虚拟机跑在这台 ThinkStation 上,于是我立马 ssh 登陆上母机去。一顿猛操作发现虚拟机控制台已经进不去了,用虚拟机重启命令也毫无反应,看起来是彻底死机了。这个时候我们只好祭出大杀器,重启服务器,于是轻车熟路敲下 poweroff 然后准备等它关闭,可奇怪的是我都喝完一杯茶了这服务器还丝毫没有关闭的迹象,看起来母机也死掉了。这个时候只有最后一招,关闭电源。

长按电源键关机之前,我看了一眼符咒,心想这次只能靠你了。按住电源键几秒后,听见继电器咔哒一声脆响,然后所有闪烁的灯光都熄灭了。深吸一口气,再次按下电源键,耳边响起硬盘电机开始加速的声音,回到位置上准备如往常一样再次 ssh 连接上去。但这次等待的时间有点长,十分钟以后我依然无法连接。不仅如此,连 ping 都没法到达这台服务器。

情况有些不对,我再次重复了几次物理开关机的过程,服务器依然如一潭死水无法连接。事情大条了!于是我赶紧联系明城让他看看这台一手打造的服务器到底出了什么问题。于是再经过他的一顿猛烈操作,告诉我一个噩耗,硬盘 RAID 挂了,数据损坏导致母机内核都崩溃了😱。

WechatIMG1004.jpg

这是两块 2T 的磁盘做了 RAID 1,母机用的是 ZFS 的文件系统,按道理说安全性已经拉满了,怎么会一声不响都挂掉了?不过现在首要任务是把里面的数据弄出来,毕竟现在没地方提交代码了。虽然我们做了数据备份,但备份是按天来计的,如果能拿到实时的数据是最好不过了。

拯救数据

IMG_9706.jpg

于是拆下其中的一块硬盘,插到我自己的黑苹果上。也幸亏我在公司用的是黑苹果,还有地方给我插这块硬盘,但马上我遇到了第一个问题,怎么在 MacOS 下挂载一个 ZFS 的磁盘。幸好我发现了 OpenZFS, 而且已经有开发者做了 MacOS 的实现 OpenZFS on OSX,用 brew 安装起来也非常简单丝滑:

brew install openzfs

因为涉及到扩展安装,所以中途会跳出安全警告,记得要到 “安全性与隐私” 处忽略这个警告继续安装。安装好以后你还需要手动挂载这块硬盘,ZFS 跟一般的文件系统还有点不同,它有个 pool 池的概念,你要先使用命令

sudo zpool import [poolname]

来导入 ZFS 池,如果你不知道 pool 的名字,可以运行 sudo zpool import 来查看所有待导入的 pool。导入以后按说还有个 sudo zfs mount 的动作来挂载,不过在 MacOS 上已经自动挂载好了。

WechatIMG1024.jpg

这三个最重要的 KVM 磁盘映像文件就映入眼帘了,分别对应了三台运行在这台服务器上的虚拟机,而 Gitlab 就在这台 wuhan(武汉)上,大小有 128G 左右,最理想的情况是我直接把它拷贝出来就行了。然而

WechatIMG1031.jpg

我试了三四次,每次都在拷贝到 116G 左右的时候就遇到 IO 错误,看来问题就出在这里了。根据我的猜测这个文件应该不是完全损坏,坏掉的部分可能只是一小块,如果它对应的不是系统核心文件(这个概率很大,毕竟 128G 的文件核心只占一小部分),那这个映像完全还能使用。因此我需要一个更底层的拷贝工具来跳过错误部分,它就是 dd

dd if=source_file of=target_file bs=1M conv=noerror,sync

这里 bs (block size) 我设置了 1M,也就是每个区块大小为 1M,遇到错误就跳过它。执行这个命令后,果然在读取到 116G 左右的时候报了两个错误,跳过之后顺利拷贝完成了。现在我在自己的 MacOS 上得到一个未知的 KVM 磁盘映像,其实工作按理说已经告一段落了。但我看了自己性能过剩的黑苹果主机,冒出一个想法,能不能让它暂时运行在我的电脑上,起码先让大家恢复代码提交。

在 Mac 上运行 QEMU

qcow2 是常用的虚拟机镜像,在 MacOS 上我们可以用 QEMU 来运行它,通常情况下我们可以直接用 brew install qemu 来安装它。但现在有个更好的选择:UTM,这是一个 MacOS 上开源的 QEMU 的图形界面实现。我选择它的原因是图形界面能帮助我省掉不少时间,毕竟 QEMU 的参数非常复杂,如果有人帮你做好了适配可以省掉不少时间。

但是 UTM 现在有个问题,它不能直接用一个 qcow2 文件来作为磁盘启动,创建虚拟机的时候必须选择一个启动镜像,并且新建一块虚拟磁盘。为了解决这个问题,你可以在创建虚拟机的时候随便选一个文件作为启动光盘,创建完以后编辑虚拟机,把这块镜像删掉即可。

现在还需要解决如何让它使用我这块已经拷贝好的 qcow2 文件作为硬盘,千万不要使用它的导入磁盘映像功能,因为这只会让它把镜像拷贝过去,然后转换成自己的格式。经过我的摸索可以这样做,首先把当前磁盘映像的 Interface 修改成 virtio,因为这也是 KVM 创建映像时的类型,既然我们要直接读取它就必须保持一致。改完后记得点保存,因为后面要直接操作文件。

截屏2023-12-23 18.46.35.png

然后我们要把这块硬盘替换成拷贝好的 qcow2 文件,首先找到 UTM 为这个虚拟机自动创建好的磁盘映像,它位于目录

~/Library/Containers/com.utmapp.UTM/Data/Documents/{你的虚拟机名称}.utm/Images/

这里有个已经存在的 qcow2 文件,给它重命名一下,然后用 ln -s 命令把原来系统的 qcow2 文件软链接过来替换已经存在的文件,这一步的目的就是“骗” UTM,让它以为硬盘是自己那块,其实已经被我们给换掉了。

然后还得对网络做一番修改,我专门接了一块 USB 网卡来做桥接,所以把这里的网络模式改为“桥接”,选择对应的网卡,然后 "Emulated Network Card" 这一项选择 "virtio-net-pci",这也是为了和 KVM 的模式兼容。

截屏2023-12-23 18.46.58.png

选择好以后保存,咱们就可以大喊原神启动。

截屏2023-12-22 21.02.19.png

牛X!启动成功,可以看到操作系统已经检测到了文件错误,尝试修复成功之后终于进入到了熟悉的登录界面。正常进入系统之后就是一些常规的操作了,总之经过一番折腾我把运行在服务器上的 Gitlab 成功迁移至本地运行,性能居然还不错。

1393545561.png

结论

  1. 任何系统都有可能出故障,重要的数据最好做异地多活。这次是硬件故障,万一哪天硬盘直接被拿走就废了。
  2. 磁盘故障不一定代表文件不可用,越大的文件其中不重要的部分占比就越大,因此还是有很大几率抢救成功的。
  3. 越开放的技术,工具和资料也越多,其实健壮性也越好。比如 KVM 用的 qcow2,我拷贝到 MacOS 上照样可以通过 QEMU 启动它。
  4. 符咒没起作用,封建迷信害死人🤦
已有 10 条评论
  1. 怡红公子
    怡红公子

    你说有没有一种可能,符没起作用是因为没找高僧开光[狗头]

  2. 大大的小蜗牛
    大大的小蜗牛

    居然认真看完了。

  3. flyisland
    flyisland

    厉害厉害,总算有惊无险。
    可以考虑用gitlab geo做热备份,这样一个部署崩了也不会丢数据

  4. 富贵险中求啊,还好数据惊险的找回了

  5. 你不是RAID1么,两块盘都坏在同样的地方?为啥看你的操作,只尝试拷贝了其中一块?理论上还有另一块盘 他们的数据不会坏在一样的地方的吧?

  6. 安同学
    安同学

    其实Raid1并不保险,因为同一批硬盘(一般组Raid会这样选)在同样的运行环境下,同时或先后(间隔短)损坏的概率还是比较大的,还是异地灾备更加稳妥点,最后符咒要开光(哈哈

  7. 灵符没开光

  8. 湘铭呀
    湘铭呀

    有惊无险

  9. GTX690战术核显卡导弹
    GTX690战术核显卡导弹

    没想到您也是垃圾佬啊,2333。
    按理来说RAID1应该很安全才对,为什么还会丢这一点点数据了?难道是因为没能同步成功吗?我家的NAS也是RIAD1,不过硬盘比你的大一点,3TB的,我全部数据都放上面了。还有另一台机器定期拉取数据作为备份。希望长长久久别出问题。(笑)
    [chocola@Neko-Server ~]$ lsblk
    NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
    sda 8:0 0 465.8G 0 disk
    ├─sda1 8:1 0 100M 0 part /boot/efi
    ├─sda2 8:2 0 600M 0 part /boot
    ├─sda3 8:3 0 32G 0 part /
    ├─sda4 8:4 0 2G 0 part [SWAP]
    └─sda5 8:5 0 431.1G 0 part /home
    sdb 8:16 0 111.8G 0 disk
    └─sdb1 8:17 0 111.8G 0 part /mnt/ssd
    sdc 8:32 0 2.7T 0 disk
    └─sdc1 8:33 0 2.7T 0 part
    └─md127 9:127 0 2.7T 0 raid1 /mnt/3t-r1
    sdd 8:48 0 2.7T 0 disk
    └─sdd1 8:49 0 2.7T 0 part
    └─md127 9:127 0 2.7T 0 raid1 /mnt/3t-r1