← 返回信息流
AI 资讯Hacker News·13 小时前

我的家庭服务器死而复生

原标题: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 卡上的系统盘首次死亡,促使他决心采取更强防护措施。

新服务器配置方案

重建时,作者设定了三个目标:

  1. 尽量减少对 microSD 的写入。
  2. 外部硬盘数据实现冗余。
  3. 备份所有重要数据。

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 服务,并设置 BeforeRequiredBy 指向目标服务。
  • 这样每个服务模块可以干净地引用子卷配置,例如 Immich 服务可以直接指定 services.autosubvol.disks.ponkotsu.subvolumes.immichmountPointconfig.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 卡寿命。

意义与影响

这篇文章虽然是个人家庭服务器的技术复盘,但折射出几个值得关注的趋势:

  1. 嵌入式设备作为长期服务器的可靠性挑战:Raspberry Pi 等单板电脑常用 SD 卡作为系统盘,其写入耐久度远低于 SSD 或 HDD。作者的经历为其他 Pi 用户提供了实用参考:如何通过配置优化(zram、日志 volative、noatime)将 SD 卡寿命延长
查看原文 →sgt.hootr.club