restic でバックアップの全件読み取り・復元・破損検知を検証する

restic でバックアップの全件読み取り・復元・破損検知を検証する

バックアップ処理は成功したのに、復元時にファイルを読み出せない。その原因は、バックアップ完了後に生じたストレージ上のデータ破損かもしれません。スナップショット数の確認は、処理が実行されたかを確かめるには役立ちますが、データパックの完全性までは証明できません。ストレージ管理者には、リポジトリの構造が正しいこと、すべてのデータを読み出せること、復元した内容が基準データと一致することを、それぞれ独立した証拠で確認する必要があります。

本記事では Debian 12、restic、Python 3 を使い、一時ディレクトリ内で正常時の検査と破損検知を検証します。本番データの読み取り、ディスクのフォーマット、既存のバックアップリポジトリの変更は行いません。この演習の対象は、物理サーバー上のファイルバックアップです。稼働中のデータベースは、まずデータベース自身の仕組みで整合性のあるバックアップを作成してから、ファイルバックアップの処理に渡してください。

一、各検査で何を証明できるかを明確にする

restic はファイルの内容を暗号化されたデータパックに格納し、スナップショット、インデックス、参照関係を管理します。通常の check は主にリポジトリの構造を検査します。check --read-data は、それに加えてすべてのデータパックを読み取り、検証します。両者は互いの代わりにはならず、復元テストも省略できません。

復元後の比較で確認するのは、選択したスナップショットから、空のディレクトリに期待どおりのファイルを再現できるかどうかです。リポジトリ内にある過去のすべてのデータパックの完全性を確認するものではありません。一方、全件読み取りに成功しても、バックアップ対象のディレクトリが正しかったか、除外ルールによって必要なファイルまで除外されていなかったか、アプリケーションデータ自体に整合性があったかは証明できません。

ミラーリングは一部の機器障害が起きてもアクセスを継続するためのもの、バックアップは過去のバージョンを残すためのもの、整合性検査は異常を見つけるためのものです。いずれも、実際の復元に代わるものではありません。

OnuCloud の AMD 物理サーバーは、1TB NVMe を 2 台標準搭載しています。各ドライブは直接接続されており、ハードウェア RAID はありません。同じサーバーの 2 台のドライブに、ソースディレクトリとバックアップリポジトリを分けて配置すれば、単一ドライブの故障による影響を一部切り離せます。ただし、独立した障害ドメインに置く遠隔バックアップにはなりません。以下のローカルリポジトリは、操作手順の検証だけに使用します。

二、再現可能な隔離サンプルを用意する

まずツールをインストールします。その後の演習コマンドは、一時変数が失われないよう、同じ Bash セッション内で実行してください。インストールには管理者権限が必要ですが、演習自体は一般ユーザーで実行できます。

sudo apt-get update
sudo apt-get install restic python3

umask 077
LAB=$(mktemp -d /var/tmp/restic-lab.XXXXXXXX)
export LAB
export RESTIC_REPOSITORY="$LAB/repository"
export RESTIC_PASSWORD_FILE="$LAB/password"

mkdir -p "$LAB/source"
python3 - <<'PY'
import os
import secrets
from pathlib import Path

root = Path(os.environ["LAB"])
(root / "password").write_text(secrets.token_hex(32) + "\n")
(root / "source" / "record.txt").write_text(
    "backup verification\n", encoding="utf-8"
)
(root / "source" / "payload.bin").write_bytes(os.urandom(1024 * 1024))
PY

restic version
restic init
(
  cd "$LAB"
  restic backup source
)
restic snapshots

ランダムな内容のファイルを使うことで、サンプル全体が圧縮によって極端に小さくなるのを避け、リポジトリ内に実際のデータが格納されるようにします。パスワードファイルはリポジトリの外に置いているため、リポジトリを複製しても復号用の認証情報は一緒にコピーされません。

この演習にはスナップショットが 1 つしかないため、以降は latest を使えます。本番環境では、明示的なスナップショット ID を記録してください。そうしないと、検査中に新しいバックアップが完了した場合、復元対象が変わる可能性があります。また、この一時的な演習用パスワードを作成しただけで、正式な鍵管理まで整ったと考えてはいけません。本番リポジトリの復号用認証情報は、別途保管し、必要なときに確実に取り出せるようにする必要があります。

三、全件読み取りと復元内容をそれぞれ検証する

最初に構造を検査し、次にすべてのデータパックを読み取ります。終了コードを適切に処理しないまま出力をパイプに渡さないでください。パイプの最後のコマンドが成功すると、検査の失敗が見えなくなるおそれがあります。

restic check
restic check --read-data
restic restore latest --target "$LAB/restored"
diff -r -- "$LAB/source" "$LAB/restored/source"

4 つの処理は、すべて終了コード 0 で完了する必要があります。diff は一致していれば差分を出力しません。今回のサンプルには通常ファイルしか含まれないため、内容とディレクトリの比較で十分です。実際のバックアップでは、所有者、権限、リンク関係、アプリケーションが必要とする拡張属性も個別に確認してください。

復元先には、新しく用意した空のディレクトリを使い、ソースファイルを上書きしないでください。既存ファイルがあるとバックアップ漏れを見逃す可能性があり、復元したデータが本番ディレクトリに混入するおそれもあります。完了後は、スナップショット ID、ツールのバージョン、検査出力、比較結果を保存します。「処理成功」のスクリーンショットだけでは、具体的に何を復元したのかを後から検証できません。

check の時点で失敗した場合は、その後の正常系の検証を中止し、状態を保全してください。構造検査は成功しても全件読み取りが失敗する場合は、データパックの読み取りと内容の完全性を重点的に調査します。スナップショット一覧が正常に表示されるという理由だけで、そのリポジトリを使い続けてはいけません。

四、複製だけを破損させ、検査が失敗することを確認する

異常系テストでは、検査の一連の流れで本当に問題を検知できることを確かめます。まず演習用リポジトリへの書き込みを止め、独立した複製を作成します。ハードリンクによるコピーは禁止です。複製を変更したつもりでも、元のリポジトリまで変更される可能性があるためです。以下のコマンドでは、コピーオンライトを明示的に無効にしています。演習データは小規模なので、2 つのデータが独立していることも把握しやすくなります。

cp -a --reflink=never "$LAB/repository" "$LAB/damaged"

python3 - <<'PY'
import os
from pathlib import Path

data = Path(os.environ["LAB"]) / "damaged" / "data"
pack = next(p for p in sorted(data.rglob("*")) if p.is_file())
with pack.open("r+b") as f:
    f.seek(pack.stat().st_size // 2)
    pos = f.tell()
    original = f.read(1)
    if not original:
        raise RuntimeError("Empty data pack")
    f.seek(pos)
    f.write(bytes([original[0] ^ 1]))
print(pack.name)
PY

if restic -r "$LAB/damaged" check --read-data \
  >"$LAB/damaged-check.log" 2>&1; then
  printf '%s\n' 'UNEXPECTED_SUCCESS'
else
  printf '%s\n' 'EXPECTED_FAILURE'
fi
cat "$LAB/damaged-check.log"
restic check --read-data

期待する結果は、複製側の検査が 0 以外の終了コードで終わり、ログにデータパックの破損が示されることです。元のリポジトリの検査は、引き続き成功する必要があります。スクリプトが失敗を示す文字列を出力しただけでは不十分です。失敗の原因が、パスワードの間違い、パスの誤り、権限不足ではなく、実際に破損であることを確認してください。

この手順で証明できるのは、破損を検知できることであり、修復できることではありません。データパック内のあるバイトが変わると、1 つまたは複数のオブジェクトに影響する可能性があります。この結果から本番障害の影響範囲の割合を推定したり、この複製を再びバックアップ先として使ったりしないでください。

五、演習結果を本番環境での検査方針に落とし込む

全件読み取りはディスクのスループットを消費し、リモートリポジトリではネットワーク帯域も消費します。実行前にリポジトリのサイズ、検査範囲、業務負荷を記録し、低負荷の時間帯を選んで、業務処理の遅延を監視してください。転送量が無制限でも、転送速度が無限になるわけではなく、整合性スキャンのリソース消費がなくなるわけでもありません。

大規模なリポジトリではデータパックを分割して読み取ることもできますが、各回の対象範囲を記録し、最終的にすべてのデータを網羅する必要があります。一度の抜き取り検査に成功しただけで、リポジトリ全体が検査に合格したと記録してはいけません。また、検査タスクを、クリーンアップ、修復、その他のリポジトリ内容を変更する処理と無計画に並行実行しないようにしてください。

破損を見つけたら、まずリポジトリを変更するタスクを一時停止し、ログと複製を保全したうえで、ファイルシステム、機器、転送経路を確認します。復旧に役立つ証拠を減らさないよう、forget、prune、修復コマンドをすぐに実行しないでください。

最終的な合格条件は、正常なリポジトリをすべて読み取れること、指定したスナップショットを復元して内容比較に合格すること、破損した複製が明確に不合格になることの 3 点です。演習ファイルは、記録を保存した後、パスを目視で確認してから削除できます。パスワード、ログ、復元サンプルはいずれも、管理された手順で取り扱う必要があります。

よくある質問

restic check が成功すれば全データを読み取ったことになりますか?

いいえ。通常の check は主に構造と参照関係を検査します。全データパックの読み取りと検証には --read-data が必要です。隔離先への復元と内容比較も別途実施します。

破損したリポジトリはすぐに整理してよいですか?

まず変更を加える処理を止め、ログと複製を保存して、ストレージと転送経路を調査します。準備なしに forget、prune、修復処理を実行してはいけません。

AMD EPYC 7543P 専用サーバー

$472/月から

OnuCloud 物理サーバーを申し込む OnuCloud の料金を確認

負荷に合わせた物理サーバー構成

AMD EPYC 7543Pを搭載した専用サーバーです。ストレージの冗長化とデータのバックアップは、管理者が個別に計画してください。