时间:2024-04-23 作者:剧中人
熟悉小剧的同学可能知道,小剧在家里部署了一些 Web 服务,并且借助于 Mac mini 的文件共享实现了一些简单的 NAS 功能。
关于这部分奇奇怪怪的骚操作,后面会单独出篇文章介绍。
某天早上,小剧骑着小电驴上班的路上,手机微信群突然响了两声,物业说接供电公司通知,即将停电半小时。
本来也没什么大不了的,毕竟家里主动、被动断电很多次了,也没出过问题。
到了公司后检查,发现家里已经恢复供电了,蒲公英和 H3C M1 也已经正常启动了。
只是 Immich 相册服务一直无法正常访问。
于是远程桌面连接家里的 Mac mini 查看,因为着急排查问题,顺手关掉了 Docker Desktop 弹出的错误提示。
接下来的画面就很懵逼了,Docker Desktop 里空空如也,系统 Volumes、Images、Containers 全部是空的。
按照我的理解,Docker 的 Containers 因为要实时运行,意外断电可能会导致容器损坏。而系统 Volumes 和 Images 因为存储在文件系统中,这两个不应该丢失才对。
换句话来说,即使系统 Volumes 和 Images 的索引丢失了,也应该在文件系统的某个位置找得到。
因此小剧并没有着急恢复服务,想着晚上回家花点时间把 Docker 恢复就完事了。
然而事实比我想的更糟糕!
经过一番研究了之后才发现,MacOS 上的 Docker 是运行在虚机里的,实体文件是 Docker.raw。
因为虚机是单文件,意外断电时如果导致虚机文件损坏,重启后是完全无法恢复的。
顺手关掉了 Docker Desktop 弹出的错误提示。
还记得前文提到被关掉的错误提示么,现在回想起来大概率是 Docker 重启后,检测到了虚机文件损坏,需要重新初始化 Docker 的提示。
在这个提示之前,Docker.raw 虚机文件很可能还是之前的损坏版本,抢救一下说不定还有戏。
但是点击 Confirm 关掉提示后,按照正常逻辑来想,接下来的操作是删掉旧的 Docker.raw 文件,并且使用新的虚机文件重新初始化。
但是嘞,第一我回不到点击 Confirm 前那个时间了。第二即使我回得去,面对 Docker.raw 这种深度封装的文件,我也不一定解得开。第三即使我解开了虚机文件,能不能顺利恢复数据也难说。
所以小剧遇到的情况是:Docker.raw 虚机损坏且被删除,系统 Volumes、Images、Containers 全部丢失。
包括 Immich 在内的六个服务全部损坏。
前面通过确认,恢复 Docker 已经不太可能。听起来很可怕,但其实问题并没有那么糟糕。
熟悉 Docker 的同学可能知道,Image 只是镜像文件,只要重新 Pull 就可以再次拉取得到。Image 在实例化成 Container 前,需要将一些副作用目录挂载到 Volume 上。
因此只要各个 Docker 服务的启动方式还在,Volume 目录还在,重新拉取镜像运行即可。
小剧前期运行的六个服务均是借助于 Docker Compose 配置的,恢复起来还算简单。
另外五个服务花点时间拉取镜像即可恢复成功。
最为麻烦的还是前文提到的 Immich 相册服务。因为 Immich 官方提供的 Compose 文件将数据库的 Volume 指向为系统 Volume,小剧也未修改过此处的配置。
前文提到 MacOS 的 Docker 运行在虚机内,伴随着此次断电引起的虚机文件损坏,Immich 的数据库 Volume 丢失,数据库服务无法恢复。
小剧使用的 Immich 相册服务,副作用目录有两部分,分别是文件和数据库。
文件包含备份上传的原始照片、视频,压缩后的各个尺寸的缩略图,以及统一编码后视频文件。
数据库则是所有已上传图片的索引,EXIF 信息,相册引用数据,照片 TAG,还有就是借助于 AI 解析出的人脸识别数据、图片内容搜索数据等。
因为数据库的丢失,压缩后的缩略图和统一编码后的视频文件没有了意义,这部分文件小剧直接删除了。
备份了原始照片、视频后,并且将 Docker Compose 中的数据库挂载点由系统目录更改为本地目录,重新初始化 Immich,自此 Immich 服务成了“新造的人”。
我希望你能 get 到 “新造的人”这个梗。
因为是全新的服务,Immich 的用户系统和照片全部是空空的。
为了让媳妇无感度过此次宕机过程,小剧给媳妇用原来的邮箱、密码重新创建了账号。
正如前文提到的,此时的 Immich 数据是空空的,小剧自己和媳妇的账号虽然重建了,但账号下没有一张照片,需要将此前的照片重新导入进 Immich 中。
如果你有用过 Immich 可能会知道,Immich 默认存储原始照片的目录结构为【年 > 月 > 日】三层结构。
小剧此前大概有四五万张照片,从已备份的目录中一层层找到照片备份不太现实,并且极容易遗漏。
借助于前东家的星火大模型,找了两段神奇的 Shell 脚本,分别是提取深层目录的文件至新目录,以及递归删除空目录脚本。
# 创建一个新的文件夹用来存放提取出来的文件
mkdir /path/to/new_directory
# 使用 find 命令查找所有的文件,并使用 -exec 选项对每个找到的文件执行 mv 命令
find /path/to/old_directory -type f -exec mv {} /path/to/new_directory \;
find . -type d -empty -exec rmdir {} \;
有了 Shell 脚本的辅助,文件整理变得极为容易。在提取完文件后,为了方便操作和记录,小剧按照照片年份分批导入。
第一次导入照片时,小剧使用的是 Macbook 操作的。
因为已备份文件存放在 Mac mini 电脑上,Macbook 是连接的家庭 WI-FI,借助于文件共享读取 Mac mini 上已备份的文件,再通过 WEB 访问位于 Mac mini 上的 Immich 服务,最后执行导入。
备份操作完整的数据流向大概是个环:Mac mini -> 交换机 -> 路由器 -> Macbook -> 路由器 -> 交换机 -> Mac mini。
虽然局域网操作很快,但毕竟上百 G 的零碎照片,还是能明显的感知到速度被网络禁锢住了。
第二次导入时,小剧直接在 Mac mini 上安装了 Chrome 浏览器,通过 Localhost 的方式导入照片。
跳过了家庭网络各个硬件“中间商”和软件共享的挂载后,整个导入速度极快,后面其他年份的导入工作也在不到十分钟完成。
接下来的人脸识别和照片内容搜索慢慢等 Immich Job 执行就好。
自此小剧断电后挂掉的的家庭服务全部恢复。
对了,新识别的人脸对应的姓名是空的。经常被 AI 认错的外甥和外甥女现在肯定又是乱糟糟的,这些都得等有空了慢慢标记。
此前整理的虚拟相册也得自己按照记忆慢慢整理,或者大概率放弃了。
前面的操作有一个很重要的点,就是放弃 Docker 的系统 Volume。这样在下次 Docker.raw 文件损坏时,不至于出现数据丢失。
但这个操作也仅仅是最大限度地在断电发生后,提高数据和服务恢复的可能性。
在一个 7*24 小时运行的系统内,断电的危害不仅仅是小剧遇到的 Docker.raw 虚机文件损坏这一种。
为了保障服务的稳定性,小剧“斥巨资”购入了 APC BK650 M2 这款后备式 UPS。
UPS 相信大家都知道,作为不间断电源主要有两个作用。
一个是在断电后提供一定时间的续航,保证服务短暂的可用。 另一个作用则是在稳妥的时间或者电量内,通知设备关机。减少意外断电对设备、服务和数据的损坏。
因为这款 UPS 并未明确说明支持 MacOS,只是介绍了兼容多个 Nas 品牌。下单之前问了淘宝旗舰店客服,被告知这款 UPS 不支持 Mac 系统,但是京东的客服又告诉我支持。
无所谓了,就当它不支持 MacOS 好了,小剧准备手撸断电检测的逻辑,有两个方案可选。
因为 UPS 和设备通信借助于 USB 接口,并且支持多种品牌 NAS,在 USB 连接的场景下,大概率是明文数据。
因此小剧准备借助于 NodeJS 的 USB 包,尝试写一写读取 USB 内 UPS 状态的逻辑,来辅助系统进行关机。
因为 Mac mini 隐藏在 UPS 后面,断电后一段时间内仍可以正常使用。
家里除了 Mac mini 作为服务器,能够提供服务的设备还有很多,比如主路由、小米中枢网关、玩客云盒子等等。
基于这样的环境,小剧可以整理一个 UPS 外的服务列表,在 Mac mini 上每隔几秒执行一次服务状态检测。
检测内容包括上述提到的设备,以及互联网状态。全部连接失败则可以判定为断电状态,但凡有一个服务能连接成功,都可以判定为市电正常状态。
京东发货速度还挺快,中午下单第二天上午就收到货了。在给柜子打孔穿线后 UPS 正式和 Mac mini 连接了起来。
开机后在系统设置的电源部分,竟然惊喜的发现是我这款 UPS 支持 MacOS 的。
又省了掉头发的断电检测逻辑的编写。
经过断电后的排查,和比较痛心的服务恢复之后,绝大多数的服务都已经顺利恢复。
这次重点提到的 Immich,丢失了灵魂的数据库部分,但好在照片原始文件均在,经历了照片导入,和漫长的 AI 重建人脸数据以及照片搜索后,服务也算再次跑了起来。
并且将数据库的 Volume 转移到了本地,出错的概率进一步降低。
最后再结合 UPS 的物理保障,让小剧对这套 DIY 服务又重新拾起了信心。
这次家庭服务器断电遇到的问题,以及后续面临断电的风险得到了解决。
最后,计划两个月之内补上定时备份 Immich 数据库的脚本。
参考资料: