
2026/07/19 19:44
私のホームサーバーの死と再生
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
ファイルシステムエラーが発生し、起動停滞を招いた老化した NixOS を動作させる Raspberry Pi 4B で電源が落ちたことを受け、著者は将来の障害を防ぎ SD カードの寿命を延ばすための包括的な改修を実施しました。戦略はルートドライブへの書き込みを最小限に抑えることに焦点を当てており、systemd ログには揮発性ジャーナリングを使用し、
/tmp には RAM ディスク、スワップには ZRAM を使用するとともに、アクセス時刻の更新を無効化しています。重要なデータは外部ドライブに安全に保存されており、再作成が必要なのは .torrent ファイルとメディアキャッシュのみであり、その結果としてダウンタイムは最小限です。障害耐性を確保するため、Btrfs RAID1 セットアップで 2 つの HDD を使用して「ponkotsu」ストレージプールを作成しました。容量は最近 3 ドライブに拡張され、フルバランスの必要性なしに処理されました。サービスの管理は、個々のサービス(例:Immich)のための autosubvol モジュールによる宣言的アプローチと、sops-nix で設定された自動化された restic バックアップによって行われています。標準化されたファイルパーミッションは setgid を用いた「media」グループを適用して実施し、監視は定期 Btrfs スクラブおよび将来の Prometheus/Grafana 統合を通じて計画されています。このセットアップは、OS installation に新しい 64GB のハイスピードエンデュランス microSD カードに切り替え、直ちな SSH アクセスを復元することで最終化しました。このアプローチは、廉価なハードウェアと宣言的な OS 管理を活用して堅牢なホームラボを構築する趣味主義者や小規模チームにとってスケーラブルなブループリントを提供します。本文
Linux イソ(NixOS)再構築:故障した SD カードから外部 HDD アレイへ
数日前、パートナーと私は新しい Linux イソをインストールしたい気分になりました。Red Letter Systems や boundingboxd で好意的な評判を得ているため期待が高まっていましたが、インストール中に SMB モウントや SSH 接続が失敗し、デバッグは翌日までに持ち越しました。
翌日、専用ディスプレイで再起動を試みたところ、カーネルロード後にフリーズ。マイクロSD カードを抜き出し検査したところ、停電による致命的なダメージでカード寿命が尽きていることが分かりました。
- fsck.ext4 を実行すると多数のエラーと修正が発生しました。
- カードを再挿入してもエラーが続いたため、交換を決定しました。
現状把握と教訓
今回のマイクロSD カードの寿命は、予想通りでしたが驚きでした。
- 24 時間稼働:数年来ほぼ連続で動いていました。
- 脆弱性:ルート FS(
)としてマウントされており、書き込み最適化(/
など)が不足していました。noatime
データ損失への恐怖と対策:
- これまで最悪の事態は、重要なデータのないフラッシュドライブが死んだことでした。
- 今回の失敗を機に、Linux を運用する上で強力な予防策を講じる必要性を感じました。
- 外部ハードディスクでの冗長化。
- すべてのデータのバックアップ体制確立。
新しいセットアップの構成方針
再構築にあたり以下の原則を守りました:
- マイクロSD への書き込み最小化
- 外部 HDD による冗長性の確保
- 重要な資産の完全なバックアップ
揮発性データとスワップ管理 (ZRAM)
8GB の RAM を持つ Pi でも、SD カードへのスワップは避けたいため、以下の構成を採用しました。
- スワップ:
(圧縮された RAM ディスク)を使用。zram
はそのまま RAM ディスク(/tmp
)とし、スワップのみをtmpfs
で担う方式にしました。zram
- ログ:
に設定し、メモリ上に保存するよう最適化しました。journald.storage = "volatile"
# /etc/nixos/configuration.nix 或いはハードコアなモジュール構成内 { # メモリ内圧縮スワップデバイスを有効化 zramSwap.enable = true; # /tmp には RAM ディスクを使用 boot.tmp.useTmpfs = true; # ジャーナリングをメモリ上に保存 (揮発性) services.journald.storage = "volatile"; # ファイルシステムアクセス時の更新時刻記録を無効化 (/ のマウントオプション) fileSystems."/" = { device = "/dev/disk/by-label/takodachi"; fsType = "ext4"; options = [ "noatime" ]; }; }
外部 HDD: 「ポンコツ」(Ponkotsu) の構築
新しいドライブ購入を避け、既存のリソースを活用しました。2 つの古くなった 2.5 インチ HDD を使用:
- 500GB: Windows ファイルシステムだったものを救済。パートナーの Surface Pro に NixOS を導入した後のリサイクル品。
- 320GB: Apple ロゴの日立製ドライブ。2006 年製の MacBook 用と思われる。
これらをドッキングステーションに接続し、btrfs プール (RAID1) を作成しました。
- プール名:
(冗談めかした名称)。ponkotsu - フォーマット: btrfs (サブボリューム管理と柔軟性のため)。
# 例:2 つのディスク (sdx, sdy) を RAID1 プールにまとめて sudo mkfs.btrfs --data raid1 --metadata raid1 --label ponkotsu /dev/sdX /dev/sdY
バイナリ・サブボリューム管理
各サービスごとに btrfs サブボリュームを宣言的に管理しています。
autosubvol モジュールを作成し、以下の機能を実現しました。
- ディスクへのマウント。
- サブボリュームの存在確認(不存在の場合は自動作成)。
- サービス依存関係の定義(
ユニットの順序制御)。.mount
例:Immich の設定
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" ]; }; }; }; # サービス側の設定例 { services.immich.enable = true; services.autosubvol.disks.ponkotsu.subvolumes.immich = { mountPoint = config.services.immich.mediaLocation; mountOptions = [ "noatime" ]; requiredBy = [ "immich-server.service" ]; }; }
その他、
/var/cache 用のサブボリュームも確保し、マウント時に subvol=@cache を指定しました。
バックアップ戦略 (Restic)
Immich 設定時に作成した Nix モジュールで、Restic と sops-nix を連携させています。
internal.backups.restic.immich = { paths = [ "/var/lib/immich" ]; exclude = [ "/var/lib/immich/encoded-video" "/var/lib/immich/thumbs" ]; pruneOpts = [ "--keep-daily 7" "--keep-weekly 4" "--keep-monthly 3" ]; };
注: btrfs スナップショットとローカルバックアップは将来的な拡張として考慮していますが、現状では S3 バケットへのアップロードで十分としています。
グループと権限の統一
Transmission、Jellyfin、Navidrome などが同一のメディアグループにアクセスできるよう設定しました。
- media グループを作成し、該当ユーザやサービスに追加。
- Transmission のダウンロードディレクトリに対して
ビット(セット ID ビット)をオンにし、作成されたファイルが自動的にメディアグループ所有となるようにしました。setgid
users.groups.media = {}; # グループ定義 # ユーザー設定例 users.users.steenuil.extraGroups = [ "media" ]; users.users.jellyfin.extraGroups = [ "media" ]; services.transmission = { group = "media"; downloadDirPermissions = "2775"; # setgid (2) + rwx for owner/group, r-x for others settings.umask = "002"; };
モニタリングとヘルスチェック
- btrfs scrubs: プール全体のデータ整合性を定期的に確認。エラー発生時に S.M.A.R.T 統計の詳細な分析を行うトリガーとする。
- 監視計画:
を使用し、Scrutiny や Prometheus/Grafana で可視化することを検討中。smartd- グラファナは学習コストが高いが、統一的なダッシュボードとして有益。
プール容量の拡張 (YOLO)
回転数のある HDD への負荷を減らすために、1TB ディスクを追加・統合しました。
- 手順:
- 既存の半分(後半)にデータを保存。
- 前半をフォーマットしてプールに加え、データコピー。
を省略し、パーティション全体を埋めるまで拡張。btrfs balance
- 結果: 現在の構成は 1TB + 500GB + 320GB の RAID1 プールです。
将来のドライブ価格下落や障害発生時のために、追加の 500GB ディスクも用意しています。また、別の 10TB ディスク(Linux イソ用)は ext4 でフォーマットされており、プールには含めませんが、重要なデータのバックアップ先として独立させています。
ハードウェア更新とデプロイ
マイクロSD カードの交換
- 従来の 128GB から 64GB に縮小(書き込み負荷を大幅に減らしたため)。
- 「ハイエンドアバランシュ(High Endurance)」カードを選定。
- ※注文時のトラブルを経て、店舗購入となりましたが、散歩も楽しめました。
デプロイ手順 (ブートストロップ)
NixOS のインストールは初学者には intimidating ですが、慣れると脳が翻訳してくれます。公式イメージからの初期設定ではなく、必要なモジュールのみで構築:
-
SD カードイメージ作成:
# configuration.nix { imports = [ "${modulesPath}/installer/sd-card/sd-image-aarch64.nix" ]; sdImage.rootVolumeLabel = "takodachi"; } -
ビルドと書き込み:
nix build '.#nixosConfigurations.takodachi.config.system.build.sdImage' # 生成された .img ファイルを dd コマンドで SD カードに書き込む
仕上げ
- 新しい UPS に接続して起動。
- SSH でアクセスし、構成調整。
- 結果として、Navidrome が正常動作し、音楽ストリーミングが可能になりました。
休暇への出発を少し遅らせても、システムは安定しています。これで「Linux イソ」ライフは安心です。