BtrfsとSnapper完全ガイド:サブボリューム設計、自動スナップショット、高速ロールバック

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

カーネルやパッケージのアップグレード失敗時に直面する課題

ブートローダーが起動しなくなるカーネル更新や、重要デーモンをクラッシュさせる共有ライブラリのアップグレードほど、業務を急停止させるトラブルはそうありません。rsyncによるミラーリング、ディスククローンイメージ、tarアーカイブといった従来のディザスタリカバリツールでは、データの取得やリストアに時間がかかりすぎます。ギガビットLAN経由で100 GBのシステムリストアを行う場合、原因究明を始める前に20〜30分ものダウンタイムが発生してしまいます。

必要なのは、ファイルシステムレベルで動作する信頼性の高い仕組みです。正常稼働している状態を瞬時に固定(フリーズ)でき、余分なディスク容量を消費することなく、障害を引き起こした変更を数秒で巻き戻せることが求められます。

BtrfsとSnapperによるシステム状態の保護メカニズム

Btrfs(B-tree File System)はLinuxカーネル本体に直接組み込まれています。ext4のような従来のファイルシステムとは異なり、Copy-on-Write(CoW)設計を採用してストレージを管理します。

1. サブボリュームと通常のディレクトリの違い

Btrfsサブボリュームは通常のディレクトリのように見えますが、カーネルからは独立したファイルシステムのルートとして扱われます。各サブボリュームは固有のinodeツリーを持ち、個別のマウントオプションを適用したり、サブボリュームごとのストレージクォータを設定したり、個別にスナップショットを作成したりできます。

2. Copy-on-Write(CoW)スナップショット

Btrfsスナップショットの作成にかかる時間は5ミリ秒未満です。CoWアーキテクチャのおかげで、新規スナップショット作成時にファイルブロックが複製されることはありません。既存のデータブロックを指すルートメタデータツリーを複製するだけです。システムが変更された4 KBのファイルブロックを書き込む際、Btrfsは空き領域に新しいブロックを書き込み、アクティブなポインタを更新します。スナップショット側は元のブロックへのポインタを維持します。そのため、作成直後のスナップショットは追加のストレージ容量をほとんど消費しません。

3. Snapperによるスナップショットの自動管理

cronを使った手動のスナップショットスクリプトは、すぐに管理が破綻してしまいます。もともとopenSUSEによって開発されたSnapperは、システムデーモンとして動作します。タイムラインスナップショットの作成、パッケージマネージャー向けの更新前後のフック処理、ディスク容量の枯渇を防ぐ自動クリーンアップポリシーなどを包括的に管理します。

セットアップ手順:サブボリューム、Snapper、ロールバック

ステップ1:パーティショニングとサブボリュームの設計

対象のディスクパーティション(例: /dev/nvme0n1p2)をBtrfsでフォーマットします:

# パーティションのフォーマット
sudo mkfs.btrfs -L sys_storage -f /dev/nvme0n1p2

# 最上位ルート(サブボリュームID=5)をマウント
sudo mount /dev/nvme0n1p2 /mnt

# システムおよびユーザーデータ用のサブボリュームを作成
sudo btrfs subvolume create /mnt/@
sudo btrfs subvolume create /mnt/@home
sudo btrfs subvolume create /mnt/@var_log
sudo btrfs subvolume create /mnt/@snapshots

# 一時マウントポイントをアンマウント
sudo umount /mnt

パフォーマンスを考慮したオプションを指定して、これらのサブボリュームを /etc/fstab にマウントします:

# /etc/fstab の設定エントリ
LABEL=sys_storage  /          btrfs  subvol=@,defaults,noatime,compress=zstd:1  0  0
LABEL=sys_storage  /home      btrfs  subvol=@home,defaults,noatime,compress=zstd:1  0  0
LABEL=sys_storage  /var/log   btrfs  subvol=@var_log,defaults,noatime  0  0
LABEL=sys_storage  /.snapshots btrfs  subvol=@snapshots,defaults,noatime,compress=zstd:1  0  0

ステップ2:Snapperのインストールと設定

使用しているディストリビューションに合わせてSnapperパッケージをインストールします:

# Ubuntu/Debian系の場合
sudo apt update && sudo apt install -y snapper

# Rocky Linux、AlmaLinux、Fedoraの場合
sudo dnf install -y snapper

rootファイルシステム用のベースライン設定プロファイルを生成します:

# root用プロファイルの初期化
sudo snapper -c root create-config /

# 有効な設定一覧を確認
sudo snapper list-configs

ディスク使用量を予測可能な範囲に抑えるため、/etc/snapper/configs/root を開いて保持ポリシーを調整します:

# /etc/snapper/configs/root 内の保持ポリシー設定
TIMELINE_MIN_AGE="1800"
TIMELINE_LIMIT_HOURLY="12"
TIMELINE_LIMIT_DAILY="7"
TIMELINE_LIMIT_WEEKLY="4"
TIMELINE_LIMIT_MONTHLY="2"
TIMELINE_LIMIT_YEARLY="0"

Snapperのsystemd自動化タイマーを有効化します:

sudo systemctl enable --now snapper-timeline.timer
sudo systemctl enable --now snapper-cleanup.timer

ステップ3:パッケージマネージャーの事前/事後フックの設定

Snapperはパッケージの変更前後に自動でスナップショットのペアを作成できます。FedoraやRHELでDNFを使用している場合、snapper のプラグインは自動的に登録されます。DebianやUbuntuでは、フックスクリプトを設定するか libpam-snapper をインストールします:

# 既存のスナップショット履歴を確認
sudo snapper -c root list

アップデート前後のシステム変更を比較します。例えば、アップグレード前にスナップショット 18、アップグレード後に 19 が作成された場合、変更されたファイルの一覧確認やUnified diffによる詳細な差分表示が直接行えます:

# スナップショット18と19の間で変更・追加・削除されたファイルを表示
sudo snapper -c root status 18..19

# 変更された設定ファイルの具体的な差分(diff)を表示
sudo snapper -c root diff 18..19 /etc/nginx/sites-available/default

ステップ4:システムのロールバック実行

アップグレードによってOpenSSLなどのサービスが破損したりカーネルパニックが発生したりした場合は、正常に動作していた直近のスナップショットへロールバックします。

一覧から復元対象のスナップショットIDを確認します:

sudo snapper -c root list

openSUSEの構成や、デフォルトのサブボリュームID経由でマウントしているシステムでは、以下を実行します:

# 正常なスナップショット #18 へrootをロールバック
sudo snapper -c root rollback 18
sudo reboot

Snapperは破損した現在の状態のバックアップを作成し、スナップショット18から書き込み可能なスナップショットを生成して、それを新しいデフォルトのrootサブボリュームとして設定します。

Live USBを使用したディザスタリカバリ

/etc/fstab で明示的に subvol=@ マウントを使用していてシェルにアクセスできない場合は、任意のLinux Live環境から起動して手動でサブボリュームを切り替えます:

# 最上位ファイルシステムをマウント
sudo mount -o subvolid=5 /dev/nvme0n1p2 /mnt

# 破損したrootサブボリュームを退避
sudo mv /mnt/@ /mnt/@_corrupted_$(date +%F)

# スナップショット #18 から書き込み可能な新しいサブボリュームを作成
sudo btrfs subvolume snapshot /mnt/@snapshots/18/snapshot /mnt/@

# クリーンアップして再起動
sudo umount /mnt
sudo reboot

本番運用のベストプラクティス

  • 書き込み負荷の高いパスの除外: /var/lib/docker、/var/lib/containers、/var/lib/mysql などの書き込み頻度が高いディレクトリは、必ず独立したサブボリュームに分離してください。また、データベース領域ではCoWを無効化(chattr +C /path)し、B-treeの深刻な断片化(フラグメンテーション)を防ぎましょう。
  • メタデータと容量マージンの監視: Btrfsは新規書き込み時のメタデータ割り当てのために空き容量を必要とします。Btrfsでディスクフル(空き容量枯渇)エラーが発生すると、スナップショットの削除すら困難になる場合があります。ボリューム使用率は常に80%未満に維持してください。
  • 定期的なスクラブ(Scrub)のスケジュール: btrfs scrub start / を月1回実行してください。スクラブ処理はデータブロックとパリティブロックをスキャンし、SHA256/CRC32cチェックサムを検証することで、サイレントデータ破損(ビットロッド)が本番データに影響を及ぼす前に検出・修復します。
Share: