我的家庭服务器死而复生
原标题:The death and rebirth of my home server
速览
作者分享了自己家庭服务器遭遇严重故障后,通过一系列修复和重建工作使其重新运行的过程。文章详细记录了故障原因、解决步骤以及从中获得的经验教训,体现了DIY精神和技术韧性。
AI 深度解读
背景
作者是一位自建家庭服务器的爱好者,长期使用一台 Raspberry Pi 4B 运行 NixOS 系统,用于 BT 下载、媒体管理(Jellyfin、Navidrome、slskd)、照片备份(Immich)等。某天晚上,他和伴侣想安装一个 Linux 发行版(实为家庭娱乐场景),却发现无法通过手机上的 Tremotesf 管理 BT 下载、无法挂载 SMB 共享、也无法 SSH 登录。当时他未立即排查,猜测是近期 NixOS 配置改动导致的小问题,打算次日再处理。
核心内容
故障诊断与发现 SD 卡损坏
次日,作者将 Raspberry Pi 连接调试显示器,尝试重启。当前 NixOS 代启动到内核后死机,前一代同样无法启动。他取出 microSD 卡进行检查。
- 首先运行
fsck.ext4,发现并修复了大量错误;再次运行报告文件系统干净,但重新插拔后再次出现新错误。 - 最终判断:microSD 卡已损坏,之前的整栋楼停电事件(写入时断电)给了它致命一击。作者感叹“Press F 敬礼”。
数据损失评估
这台 Pi 几乎 7×24 小时运行数年,SD 卡寿命耗尽并不意外。作者承认自己未做任何减少写入的优化,直接将 microSD 作为根文件系统挂载。损失有限:
- 丢失的数据:
.torrent文件、Navidrome/Jellyfin/slskd 的缓存——均可轻松重建。 - 重要数据:位于外部 HDD 上的 Immich 照片备份(有定期备份)、以及 NixOS 配置(几乎全部在 Git 仓库中)均未丢失。
- 外置硬盘上的数据安全,但 SD 卡上的系统盘首次死亡,促使他决心采取更强防护措施。
新服务器配置方案
重建时,作者设定了三个目标:
- 尽量减少对 microSD 的写入。
- 外部硬盘数据实现冗余。
- 备份所有重要数据。
1. 减少 SD 卡写入:swap、/tmp 与日志
- 交换空间:不使用 SD 卡交换,而是启用 zram(内存压缩块设备,原称 compcache)。zram 在 RAM 中创建压缩的块设备,用于 swap 或 /tmp。作者配置
zramSwap.enable = true;,同时将/tmp设为普通的 RAM disk(Tmpfs),并让内核自动决定何时将冷页交换到压缩 swap。 - 日志:将 systemd journal 存储改为内存(
services.journald.storage = "volatile"),写入/run/log/journal,避免频繁写入 SD 卡。 - 文件系统挂载选项:在根分区挂载时加上
noatime,禁用访问时间更新,减少不必要的写入。
2. 外部硬盘池:btrfs RAID1 冗余
- 翻出闲置的旧硬盘:一块 500GB 的笔记本 HDD(原用于伴侣的旧笔记本,后换下),一块 320GB 的 Hitachi 硬盘(带苹果 logo,来自 2006 年的 MacBook 或 Mac mini)。
- 将两块硬盘通过 USB 扩展坞连接,创建 btrfs 池,采用 RAID1 数据镜像(
--data raid1 --metadata raid1),命名为ポンコツ(日语“破旧”之意)。 - 选择 btrfs 的原因:熟悉(桌面/笔记本已在用),支持子卷(subvolume),池拓扑灵活(可随时添加不同大小的硬盘)。
3. 子卷声明式管理(autosubvol 模块)
- 作者编写了一个 NixOS 模块
services.autosubvol,用于声明式地创建 btrfs 子卷并挂载。配置示例:services.autosubvol = { enable = true; disks.ponkotsu = { device = "/dev/disk/by-uuid/<pool uuid>"; subvolumes.immich = { mountPoint = "/var/lib/immich"; mountOptions = [ "noatime" ]; requiredBy = [ "immich-server.service" ]; }; }; }; - 模块自动生成 systemd 单元:
- 每个磁盘在
/run/btrfs-roots/<name>挂载一个.mount单元。 - 一个
autosubvol-ensure-<disk>-<subvolume>.service一次性单元,检查子卷是否存在,不存在则创建。 - 每个子卷的
.mount单元,依赖上述 ensure 服务,并设置Before和RequiredBy指向目标服务。
- 每个磁盘在
- 这样每个服务模块可以干净地引用子卷配置,例如 Immich 服务可以直接指定
services.autosubvol.disks.ponkotsu.subvolumes.immich的mountPoint为config.services.immich.mediaLocation。
4. 额外优化:将 /var/cache 也放入子卷
- 作者为
/var/cache单独创建了一个 btrfs 子卷,以隔离缓存写入,避免影响根分区。
关键要点
- SD 卡寿命有限:连续多年 24/7 运行、未做写入优化(swap、日志、atime 等),导致 microSD 卡最终损坏,关键教训是必须减少对 SD 卡的写入。
- 停电是最后一根稻草:写入时断电可能导致文件系统损坏,但 SD 卡本身已接近寿命终点,停电只是触发最终故障。
- 重要数据备份策略:作者将 Immich 数据存放在外置硬盘并定期备份,NixOS 配置版本化管理,因此故障后只丢失了可重建的缓存和种子文件。
- zram 替代 SD 卡交换:使用 zram 内存压缩交换,避免频繁写入 SD 卡,同时保留交换功能。
- 日志 volative:systemd journal 写入内存(
/run),减少 SD 卡写入,但可能丢失重启前的日志(需权衡)。 - btrfs RAID1 池:利用两块旧硬盘构建低成本冗余,btrfs 的灵活性和子卷功能适合家庭服务器场景。
- 声明式子卷管理:通过自制 NixOS 模块
autosubvol,实现子卷的自动创建和挂载,与服务配置解耦,保持 Nix 配置的整洁和可重复性。 - noatime 挂载选项:禁用文件访问时间更新,减少不必要的写入,延长 SD 卡寿命。
意义与影响
这篇文章虽然是个人家庭服务器的技术复盘,但折射出几个值得关注的趋势:
- 嵌入式设备作为长期服务器的可靠性挑战:Raspberry Pi 等单板电脑常用 SD 卡作为系统盘,其写入耐久度远低于 SSD 或 HDD。作者的经历为其他 Pi 用户提供了实用参考:如何通过配置优化(zram、日志 volative、noatime)将 SD 卡寿命延长
查看原文 →sgt.hootr.club
