バックアップとリストア:別 VM からサービスを復旧する
別 VM へ世代付きバックアップを作り、実際にデータを削除してから復元します。内容・権限・systemd・journal の 4 層で、サービスが本当に戻ったことまで証明します。
| 所要時間 | 約 90 分(概念 25 分+演習 65 分) |
|---|---|
| 前提知識 | Part 1 Ch3・Ch8・Part 3 Ch1(systemd サービス) |
| 使用機材 | 操作端末 workstation01、演習サーバ learner01、バックアップ先 learner02 |
| 関連章 | 前: Chapter 6「パッチを当てる・配る」/ Part 3 修了演習 |
learner02 に作った世代付きバックアップから、削除した learner01 のデータを復元し、内容・権限・サービス・ログの証拠をそろえて復旧を判定します。「バックアップ済みです」を、復元できる証拠に変える
月曜日の朝、運用中のサーバから設定ファイルと業務データが消えていました。
誰かが言います。
「スナップショットは取ってあります」
確かに スナップショットはあります。しかし、その スナップショットは元の仮想ディスクと同じストレージ上にある差分です。ファイルを消しただけなら短時間で巻き戻せても、ストレージそのものを失えば スナップショット も一緒に失われます。また、VM 全体を過去へ戻すと、障害発生後に届いた正常なデータまで巻き戻してしまうことがあります。
必要なのは、元の VM から離れた場所にあるもう一つのデータ実体です。
この章では learner01 に検証用サービスとデータを一から作り、rsync で別 VM の learner02 へ退避します。その後、learner01 のデータを実際に削除してサービスを壊し、snapshot には戻さず、learner02 のバックアップから復元します。
ただし、ファイルが戻っただけでは合格にしません。
壊す → 戻す → 検証する 削除 rsync ハッシュ・権限・systemd・journal
現場で怖いのは「復元コマンドは終わったのに、必要なファイルが欠けていた」「権限が変わってサービスが読めなかった」「起動したつもりで failed のままだった」という状態です。本章のゴールは、バックアップを作ることではなく、そのバックアップからサービスを戻せる証拠を残すことです。
Part 3 で学んだ systemd・ログ・権限・スナップショットを、最後に「サービスを継続させる」という一つの運用シナリオへまとめます。
この章を終えるとできること
バックアップ運用の 5 動詞を一つの流れで実行します。
① 止める → ② 退避する → ③ 壊す → ④ 戻す → ⑤ 証明する systemctl stop rsync + manifest rm rsync hash / stat / log
- スナップショットとバックアップの役割・障害範囲の違いを説明できる
learner01から別 VM のlearner02へ、世代を分けてデータをrsyncできる- 復元前に退避先のハッシュを検証し、「壊れたバックアップを使わない」判断ができる
- データ削除で systemd サービスを壊し、
journalctlで失敗理由を追える rsyncで復元した後、ハッシュ・所有者・グループ・モード・サービス状態を検証できる- 3-2-1 の各条件について、この演習で満たせる範囲と満たせない範囲を区別できる
- 演習で作ったサービス・鍵・バックアップ・スナップショットを撤去し、3台を clean な状態へ戻せる
概念説明
1. スナップショットとバックアップは、守る障害が違う
Ch4 では Proxmox スナップショットを使い、SSH と sudo の設定を壊す前の VM 全体へ戻しました。スナップショットは短時間で VM を巻き戻すのに強い仕組みです。一方で、スナップショットは独立したバックアップではありません。
| 観点 | Proxmox スナップショット | 別 VM への rsync バックアップ |
|---|---|---|
| データの置き場所 | 元ディスクと同じストレージ上の差分 | learner02 のファイルシステム上に別のデータ実体を作る |
| 得意な復旧 | 設定変更前へ VM 全体を素早く戻す | 必要なファイルやディレクトリだけを選んで戻す |
| 元ストレージ障害 | 同じ障害範囲に入り、一緒に失われ得る | 物理ストレージまで別なら耐えられる。別 VM だけでは物理分離を保証しない |
| 復旧時の影響 | VM 全体が スナップショット 時点へ戻る | 対象データだけを戻せる |
| 単体で十分か | いいえ | いいえ。世代・媒体・別拠点・検証も必要 |
スナップショットは悪い仕組みではありません。役割が違うのです。
- スナップショット: 変更作業を安全に戻すための、近くて速い復旧点
- backup: 元の障害範囲から離し、データを選んで戻すための複製
本章では両方を作ります。ただし、データ復元には snapshot rollback を使いません。learner02 のバックアップからファイルを戻すことで、両者の違いを手で確かめます。
重要: 稼働中 VM の スナップショットが、あらゆるアプリケーションの整合性まで自動で保証するわけではありません。メモリ上の書き込みや複数ファイル間の更新途中をどう止めるかは、アプリケーションごとの設計が必要です。本演習では検証用サービスのデータ更新を止めてから復旧点を作ります。
2. バックアップは「コピー」、リストアは「復旧試験」
rsync が終了コード 0 で終わっても、サービス継続が証明されたわけではありません。
バックアップ作成 ├─ データを別の場所へ転送する ├─ 転送先でハッシュを照合する └─ 復旧点の識別子を残す リストア試験 ├─ 退避先からデータを戻す ├─ 内容をハッシュで照合する ├─ 所有者・グループ・モードを照合する ├─ systemd サービスを起動する └─ journal で成功状態を読む
本章ではバックアップ前に SHA-256 の manifest を作ります。manifest は「どのファイルが、どの内容だったか」を固定する検査票です。復元後に sha256sum -c を実行し、全項目が一致して初めて内容面の合格とします。
ただし、ハッシュだけでは権限を検査できません。同じ内容でも、サービスユーザが読めないモードになればサービスは止まります。そのため、所有者・グループ・モード・サイズを別の metadata ファイルに記録し、復元後に diff します。
最後に systemd と journal を確認します。ここまで通して、初めて「戻った」と判定します。
3. 3-2-1 を、この演習で満たしたことにしない
3-2-1 は、バックアップの置き方を考えるための基本形です。
3: 元データを含めて 3 つのコピーを持つ 2: 2 種類の媒体・ストレージ方式に分ける 1: 1 つは別拠点など、同じ災害範囲の外へ置く
本演習は learner01 だけに閉じず、別 VM の learner02 へ実データを複製します。これは「同じディレクトリにコピーを置いただけ」より明確に強く、learner01 の OS や仮想ディスクだけを失う障害には備えられます。
一方で、別 VM であることと、完全な 3-2-1 を満たすことは同じではありません。
| 3-2-1 の条件 | 本演習で作るもの | 判定 |
|---|---|---|
| 3 コピー | learner01 の元データ + learner02 の rsync 複製。スナップショットは同じストレージ上の差分で、独立した複製には数えない | 未達。独立したデータ実体は 2 つ |
| 2 種類の媒体 | 2 台の VM の仮想ディスク | 未確認のため未達扱い。pvesm status では local-lvm を確認したが、両 VM の qm config <VMID> による配置の直接証拠は未取得 |
| 1 つを別の場所 | learner02 は別 VM | 未達。同じラボ・同じ拠点内であり、オフサイトではない |
したがって、本章は「3-2-1 を達成した」とは書きません。体験できるのは、元 VM の外へ複製し、別 VM から実際に戻すところまでです。本番で 3-2-1 にするには、さらに別媒体の第 3 コピーと、同じ設備障害・災害の影響を受けない別拠点保管が必要です。
撮影時点の Proxmox ストレージ状態は次のとおりです。
Name Type Status Total (KiB) Used (KiB) Available (KiB) % local dir active 98497780 21499300 71948932 21.83% local-lvm lvmthin active 353492992 103007857 250485134 29.14%
ただし、これはストレージ一覧であり、learner01 と learner02 の仮想ディスク配置を直接示す証拠ではありません。2 台が同一の物理 SSD 上にあるかは、両 VM の qm config <VMID> で scsi0 の配置を確認するまで未確認として扱います。したがって、現状を「2 種類の媒体」とは判定しません。
4. RPO と RTO(何分失い、何分止まるか)
サービス継続では、バックアップが「あるか」だけでなく、次の 2 つを決めます。
- RPO(Recovery Point Objective): どの時点までデータを戻せればよいか。バックアップ間隔が長いほど、失う可能性のあるデータが増える
- RTO(Recovery Time Objective): 障害発生から、サービスを使える状態へ戻すまでの目標時間
本章の復旧点は、BACKUP_ID を作って learner02 へ転送し、ハッシュ検証を通した時点です。削除後に作られたデータは、その復旧点には含まれません。
RTO は rsync が終わった時刻では測りません。ハッシュ・権限・systemd・journal の確認まで完了し、サービスが使えると判定した時刻までを含めます。
実機撮影で得た時刻と実測値は次のとおりです。
| 指標 | 実測 | 根拠となる時刻(UTC) |
|---|---|---|
| 復旧点の確定 | — | 15:10:03Z サービス停止・manifest 作成 |
| バックアップが使えると判定 | — | 15:13:24Z 退避先で manifest 検証が全件成功 |
| 障害発生 | — | 15:14:20Z データ削除の直前 |
| RPO(失う時間) | 4 分 17 秒 | 15:10:03Z → 15:14:20Z |
| RTO(止まる時間) | 4 分 53 秒 | 15:14:20Z → 15:19:13Z サービス active |
RTO には、復元後に判明した権限不一致を直す時間も含みます。権限を戻さないままではサービスは復旧せず、RTO の終了を測れません。
5. ITIL サービス継続管理の型
この章の流れは、ITIL の「サービス継続管理」を小さく再現しています。
対象を決める ↓ 復旧点を作る ── snapshot + 別 VM バックアップ ↓ 障害を起こす ── データ削除 ↓ 復旧する ── rsync restore ↓ 復旧を証明する ── hash + permission + systemd + journal ↓ 結果を記録し、RPO/RTO と設計不足を見直す
バックアップを毎日取得していても、リストアを一度も試していなければ、復旧時間も手順の穴も分かりません。継続管理では「取得ジョブが成功した」だけで終わらず、定期的なリストア試験まで運用に含めます。
試してみよう:別 VM へ退避し、削除したデータを戻す
learner01 に本章専用の systemd サービスとデータを作り、learner02 へ世代付きで退避します。退避先の完全性を確認してから元データを削除し、learner02 から rsync で復元します。
演習環境
| 項目 | 値 |
|---|---|
| 出発地点 | 踏み台 workstation01 |
| 演習サーバ | learner01(サービス稼働・障害発生・復元先) |
| 退避先 | learner02(バックアップ保管元・復元元) |
| ゲスト操作ユーザ | ops(sudo を使用) |
| Proxmox 操作 | <VMID> の スナップショット 作成・削除 |
| バックアップ方式 | rsync + 世代 ID + SHA-256 manifest |
| 所要時間 | 約 65 分(11 ステップ) |
| 転送量・ファイル数 | データ 2 ファイル/バックアップセット 5 ファイル・論理 323 バイト(撮影時) |
ホスト名で書きます:
learner01・learner02は踏み台workstation01の/etc/hostsに登録済みで、 そのまま名前で接続できます(既存の Part3 各章と同じ書き方)。<Proxmox管理IP>・<VMID>は受講環境の案内とqm listの結果に読み替えます。実 IP を教材本文へ記録しません。接続経路について:
learner01・learner02へは演習ネットワーク側の経路だけが通ります。 管理ネットワーク側はハイパーバイザで遮断されているため、IP を直接指定すると届きません。名前で書けば正しい経路に向きます。自己完結: この章は Ch1〜Ch6 のセッション成果物を使いません。サービス・データ・転送鍵・バックアップ先・スナップショットは、すべて本章内で作成し、最後に撤去します。
手順(11 ステップ)
① 3台の前提と残骸を確認する
実行ホスト: workstation01 → learner01 / learner02、および Proxmox ホスト
最初に、2 台へ接続できることと、演習で使うコマンドが存在することを確認します。rsync が無い場合に外部リポジトリからその場で追加する設計にはしません。演習環境の準備不足として止めます。
# workstation01 で実行: 2 台へ接続できることを確認
ssh ops@learner01 'hostname; id; date -u'
ssh ops@learner02 'hostname; id; date -u'
# learner01 に必要なコマンド
ssh ops@learner01 '
for cmd in rsync sha256sum systemctl journalctl ssh ssh-keygen \
find sort xargs namei stat wc; do
command -v "${cmd}" || exit 1
done
'
# learner02 に必要なコマンド
ssh ops@learner02 '
for cmd in rsync sha256sum ssh-keygen find stat du df; do
command -v "${cmd}" || exit 1
done
'
実機では 2 台とも必要なコマンドが /usr/bin にあり、停止条件には当たりませんでした。確認結果の要点は次のとおりです。
===== learner01 ===== learner01 /usr/bin/rsync /usr/bin/sha256sum /usr/bin/systemctl /usr/bin/journalctl /usr/bin/ssh /usr/bin/ssh-keygen /usr/bin/find /usr/bin/stat /usr/bin/du /usr/bin/df rsync version 3.2.7 protocol version 31 OpenSSH_9.6p1 Ubuntu-3ubuntu13.16, OpenSSL 3.0.13 30 Jan 2024 2026年 8月 9日 日曜日 14:55:32 UTC ===== learner02 ===== learner02 …(必要コマンドはすべて /usr/bin。略) rsync version 3.2.7 protocol version 31 OpenSSH_9.6p1 Ubuntu-3ubuntu13.16, OpenSSL 3.0.13 30 Jan 2024 2026年 8月 9日 日曜日 14:55:33 UTC
2 台の表示時刻の差は 1 秒で、逐次実行による範囲でした。
実機では learner01 と learner02 の両方に rsync 3.2.7 が存在することを確認済みです。
次に、前回中断した演習の残骸がないことを確認します。
# learner01 で実行 test ! -e /srv/part3-final test ! -e /etc/systemd/system/part3-final.service test ! -e /usr/local/sbin/part3-final-service test ! -e /usr/local/sbin/part3-final-validate ! getent passwd part3svc ! getent group part3ops # Proxmox ホストで実行 qm listsnapshot <VMID> | grep ch7-before-loss
test が失敗する、または ch7-before-loss が表示される場合は、前回の演習が残っています。新しい演習を上書きせず、STEP ⑪ のクリーンアップを該当箇所から実行してから戻ります。
停止条件:
learner01からlearner02への SSH がネットワークで遮断されている、learner02にopsが存在しない、rsyncが無い、時刻が大きくずれている場合は先へ進みません。これらは実機整備で確定するブロッカーです。
実機では learner01 から learner02 へ、一時鍵と StrictHostKeyChecking=yes を使った SSH 接続が成立しました。
② learner01 に検証用サービスとデータを作る
実行ホスト: learner01
workstation01 から learner01 へ接続します。
ssh ops@learner01
本章専用のサービスユーザ・グループ・データディレクトリを作ります。ops がデータの所有者、part3svc がグループ経由で読み取る設計です。
# 専用グループとサービスユーザ sudo groupadd part3ops sudo useradd --system \ --gid part3ops \ --home-dir /nonexistent \ --shell /usr/sbin/nologin \ part3svc # setgid を付け、配下へ part3ops グループを継承させる sudo install -d -o ops -g part3ops -m 2750 /srv/part3-final sudo install -d -o ops -g part3ops -m 2750 /srv/part3-final/data
検証用の設定ファイルと業務データを作ります。実在する認証情報や内部アドレスは入れません。
sudo -u ops tee /srv/part3-final/data/config.ini >/dev/null <<'EOF' [service] mode=training message=part3-complete EOF sudo -u ops tee /srv/part3-final/data/inventory.csv >/dev/null <<'EOF' service,status part3-final,ready EOF sudo chown ops:part3ops /srv/part3-final/data/config.ini \ /srv/part3-final/data/inventory.csv sudo chmod 0640 /srv/part3-final/data/config.ini \ /srv/part3-final/data/inventory.csv
サービス起動前に必要ファイルを検査するスクリプトと、journal へ health を出すサービス本体を作ります。
sudo tee /usr/local/sbin/part3-final-validate >/dev/null <<'EOF'
#!/usr/bin/env bash
set -eu
data_dir=/srv/part3-final/data
for required in config.ini inventory.csv; do
if [[ ! -r "${data_dir}/${required}" ]]; then
echo "health=failed missing_or_unreadable=${required}" >&2
exit 1
fi
done
EOF
sudo tee /usr/local/sbin/part3-final-service >/dev/null <<'EOF'
#!/usr/bin/env bash
set -eu
data_dir=/srv/part3-final/data
while true; do
file_count="$(find "${data_dir}" -maxdepth 1 -type f | wc -l)"
echo "health=ok readable_files=${file_count}"
sleep 15
done
EOF
sudo chown root:root /usr/local/sbin/part3-final-validate \
/usr/local/sbin/part3-final-service
sudo chmod 0755 /usr/local/sbin/part3-final-validate \
/usr/local/sbin/part3-final-service
systemd unit を作り、有効化して起動します。
sudo tee /etc/systemd/system/part3-final.service >/dev/null <<'EOF' [Unit] Description=Part 3 final backup and restore lab service After=local-fs.target [Service] Type=simple User=part3svc Group=part3ops ExecStartPre=/usr/local/sbin/part3-final-validate ExecStart=/usr/local/sbin/part3-final-service Restart=on-failure RestartSec=5s [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable --now part3-final.service systemctl is-enabled part3-final.service systemctl is-active part3-final.service sudo journalctl -u part3-final.service -n 10 --no-pager
実機では unit が enabled / active になり、起動時の検査も通りました。
enabled active …(略) 8月 10 00:02:06 learner01 systemd[1]: Started part3-final.service - Part 3 final backup and restore lab service. 8月 10 00:02:06 learner01 part3-final-service[2029]: health=ok readable_files=2
f: /srv/part3-final drwxr-xr-x root root / drwxr-xr-x root root srv drwxr-s--- ops part3ops part3-final Access: (2750/drwxr-s---) Uid: ( 1002/ ops) Gid: ( 1005/part3ops)
2750 の setgid と part3ops が、復元後にも戻すべき基準です。
ここで Ch1 の systemd、Ch2 の journal、Ch3 のグループと setgid を使い直しています。ただし成果物は引き継がず、本章で一から作っています。
③ learner01 から learner02 へ渡す一時鍵を準備する
実行ホスト: learner01、workstation01、learner02
バックアップ転送専用の一時 SSH 鍵を learner01 に作ります。普段のログイン鍵を流用せず、章末に失効させます。
# learner01 で ops として実行 install -d -m 0700 /home/ops/.ssh ssh-keygen -t ed25519 \ -N '' \ -C 'part3-ch7-temporary' \ -f /home/ops/.ssh/ch7_backup_ed25519
公開鍵だけを、踏み台経由で learner02 の ops に登録します。秘密鍵は learner01 の外へ出しません。同じ演習をやり直したときに鍵が重複しないよう、同じコメントの旧行を先に除きます。
# workstation01 で実行 ssh ops@learner01 'cat ~/.ssh/ch7_backup_ed25519.pub' | ssh ops@learner02 \ "umask 077; mkdir -p ~/.ssh; touch ~/.ssh/authorized_keys; \ sed -i '/ part3-ch7-temporary$/d' ~/.ssh/authorized_keys; \ cat >> ~/.ssh/authorized_keys"
接続先の取り違えを防ぐため、learner02 自身が示すホスト鍵 fingerprint と、learner01 から見える fingerprint を比較します。
# workstation01 から learner02 のローカル fingerprint を確認 ssh ops@learner02 \ 'sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub' # learner01 で、Ch7 専用 known_hosts を作る ssh-keyscan -t ed25519 learner02 > /home/ops/.ssh/ch7_known_hosts ssh-keygen -lf /home/ops/.ssh/ch7_known_hosts
2 つの fingerprint が一致したことを、人が目で確認してから進みます。不一致なら接続しません。
実機では次のように表示され、2 系統の SHA-256 fingerprint が一致しました。
256 SHA256:R+Kwa5SBz1fI…(環境により異なります) root@learner02 (ED25519) 256 SHA256:R+Kwa5SBz1fI…(環境により異なります) learner02 (ED25519)
末尾の root@learner02 と learner02 は鍵ファイルのコメントと known_hosts のラベルなので異なります。比較するのは中央の SHA256: から始まる値です。
learner02 にバックアップの親ディレクトリを作ります。
# learner02 で ops として実行
OPS_GROUP="$(id -gn)"
sudo install -d -o ops -g "${OPS_GROUP}" -m 0750 \
/srv/backups/part3-final
learner01 から一時鍵で接続を確認します。
# learner01 で実行 ssh \ -i /home/ops/.ssh/ch7_backup_ed25519 \ -o IdentitiesOnly=yes \ -o UserKnownHostsFile=/home/ops/.ssh/ch7_known_hosts \ -o StrictHostKeyChecking=yes \ ops@learner02 'test -d /srv/backups/part3-final && date -u'
一時鍵による接続は StrictHostKeyChecking=yes のまま成功しました。
File: /srv/backups/part3-final Access: (0750/drwxr-x---) Uid: ( 1002/ ops) Gid: ( 1002/ ops) 2026年 8月 9日 日曜日 15:09:28 UTC
④ バックアップ前の基準と復旧点 ID を作る
実行ホスト: learner01
データが更新途中にならないよう、まずサービスを停止します。停止時刻は RPO/RTO の証拠に残します。
date -u sudo systemctl stop part3-final.service systemctl is-active part3-final.service
バックアップ世代を識別する BACKUP_ID を UTC で作り、後の STEP でも読み直せるようファイルへ保存します。
BACKUP_ID="$(date -u +%Y%m%dT%H%M%SZ)"
printf '%s\n' "${BACKUP_ID}" | tee /srv/part3-final/backup-id
# 想定外の文字列を後のパス操作へ渡さない
case "${BACKUP_ID}" in
20??????T??????Z) ;;
*) echo 'invalid BACKUP_ID' >&2; exit 1 ;;
esac
ファイル内容の SHA-256 manifest と、権限を比較する metadata を作ります。パスを相対化しておくことで、退避先と復元先のどちらでも同じ manifest を使えます。
( cd /srv/part3-final/data find . -type f -print0 | sort -z | xargs -0 sha256sum ) | tee /srv/part3-final/manifest.sha256 ( cd /srv/part3-final/data find . -type f -printf '%P|%u|%g|%m|%s\n' | sort ) | tee /srv/part3-final/metadata.before sudo chown ops:part3ops \ /srv/part3-final/backup-id \ /srv/part3-final/manifest.sha256 \ /srv/part3-final/metadata.before sudo chmod 0640 \ /srv/part3-final/backup-id \ /srv/part3-final/manifest.sha256 \ /srv/part3-final/metadata.before
実機で作成した復旧点 ID と検査票は次のとおりです。
20260809T151003Z e6c0bb6fb61941f5785a390847c2cbca9c9df0a46e3dec043fa232f7cee2ef4d ./config.ini 2633b5b989d5b87b5663997bd98e11dfd4dff3022ea79cce04c5f76464ff94b4 ./inventory.csv config.ini|ops|part3ops|640|47 inventory.csv|ops|part3ops|640|33 --- ファイル数 5 --- 論理バイト数 323 /srv/part3-final
ファイル数 5 は、data/ の 2 件に backup-id・manifest.sha256・metadata.before を加えた数です。論理バイト数は du -sb の値で、ディレクトリ分を含みます。
なぜ停止するか: 今回のデータは小さく、サービス本体も読み取り中心ですが、「manifest を作った後にデータが変わる」競合を避けるため停止します。実務では停止できないサービスもあるため、アプリケーションの quiesce、データベースの整合バックアップ、ファイルシステム スナップショット などを組み合わせます。
⑤ Proxmox スナップショットを比較用の復旧点として作る
実行ホスト: Proxmox ホスト
workstation01 の別ターミナルから Proxmox ホストへ接続し、対象 VM を確認してから スナップショットを作ります。
ssh ops@<Proxmox管理IP> # 対象 VM を名前と状態で再確認 qm list qm config <VMID> | grep '^name:' # 比較用 snapshot。復元には使わない qm snapshot <VMID> ch7-before-loss \ --description 'Part 3 Ch7 before data deletion' qm listsnapshot <VMID>
受講環境では thin pool の過剰割当に関する WARNING が 3 行出ます。これは撮影時点で把握済みのストレージ状態を示す警告であり、この演習や snapshot 作成の失敗ではありません。
VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID
<VMID> learner01-<管理IP> running 2048 32.00 61133
name: learner01-<管理IP>
snapshotting 'drive-scsi0' (local-lvm:vm-<VMID>-disk-0)
WARNING: You have not turned on protection against thin pools running out of space.
WARNING: Set activation/thin_pool_autoextend_threshold below 100 to trigger automatic extension of thin pools before they get full.
WARNING: Sum of all thin volume sizes (1.42 TiB) exceeds the size of thin pool pve/data and the size of whole volume group (<464.00 GiB).
…(略)
created
(スナップショット一覧・末尾のみ)
`-> clean-<世代> 2026-08-08 11:14:57 (説明は省略)
`-> clean-<世代> 2026-08-09 23:47:22 (説明は省略)
`-> ch7-before-loss 2026-08-10 00:10:44 Part 3 Ch7 before data deletion
`-> current You are here!
作成は成功し、保存先は local-lvm の thin LV でした。
実機では Proxmox snapshot の作成・一覧・削除がすべて成功しました。
この snapshot は Ch4 と同じく「変更前へ VM 全体を戻す」選択肢です。しかし本章では rollback しません。learner01 のデータを削除した後、learner02 の別データ実体から必要なファイルだけを戻します。
安全確認:
<VMID>を思い込みで入力しません。qm listとqm config <VMID>の名前を照合してから実行します。
⑥ rsync で learner02 へ世代付きバックアップを作る
実行ホスト: learner01 → learner02
保存した BACKUP_ID を読み、形式をもう一度検査します。
BACKUP_ID="$(cat /srv/part3-final/backup-id)"
case "${BACKUP_ID}" in
20??????T??????Z) ;;
*) echo 'invalid BACKUP_ID' >&2; exit 1 ;;
esac
退避先にこの世代専用のディレクトリを作ります。
ssh \
-i /home/ops/.ssh/ch7_backup_ed25519 \
-o IdentitiesOnly=yes \
-o UserKnownHostsFile=/home/ops/.ssh/ch7_known_hosts \
-o StrictHostKeyChecking=yes \
ops@learner02 \
"mkdir -p /srv/backups/part3-final/${BACKUP_ID}/data"
データ本体を rsync します。--delete の範囲は、今作った空の世代ディレクトリだけです。バックアップ親ディレクトリや別世代には向けません。
rsync -a \
--delete \
--no-owner \
--no-group \
--itemize-changes \
-e "ssh -i /home/ops/.ssh/ch7_backup_ed25519 \
-o IdentitiesOnly=yes \
-o UserKnownHostsFile=/home/ops/.ssh/ch7_known_hosts \
-o StrictHostKeyChecking=yes" \
/srv/part3-final/data/ \
ops@learner02:/srv/backups/part3-final/${BACKUP_ID}/data/
続けて検証用ファイルを同じ世代へ送ります。
rsync -a \
--no-owner \
--no-group \
-e "ssh -i /home/ops/.ssh/ch7_backup_ed25519 \
-o IdentitiesOnly=yes \
-o UserKnownHostsFile=/home/ops/.ssh/ch7_known_hosts \
-o StrictHostKeyChecking=yes" \
/srv/part3-final/manifest.sha256 \
/srv/part3-final/metadata.before \
/srv/part3-final/backup-id \
ops@learner02:/srv/backups/part3-final/${BACKUP_ID}/
転送後の世代を誤って上書きしにくくするため、退避先を読み取り専用にします。これは改ざん耐性を完全にする仕組みではありません。所有者 ops は再び書き込み権限を付けられるため、本番の immutable backup の代わりにはなりません。
ssh \
-i /home/ops/.ssh/ch7_backup_ed25519 \
-o IdentitiesOnly=yes \
-o UserKnownHostsFile=/home/ops/.ssh/ch7_known_hosts \
-o StrictHostKeyChecking=yes \
ops@learner02 \
"chmod -R a-w /srv/backups/part3-final/${BACKUP_ID}"
実機でのデータ本体の転送結果は次のとおりです。
.d..tp..... ./ <f+++++++++ config.ini <f+++++++++ inventory.csv
データ本体 real 0m0.336s user 0m0.087s sys 0m0.004s 検証用ファイル real 0m0.318s user 0m0.083s sys 0m0.007s
time の結果は stderr に出るため、実際の画面では次の見出しの後ろへずれて表示されることがあります。上の 2 行は、それぞれ対応する rsync へ並べ直した値です。
読み取り専用化した後の退避先も確認します。
-r--r----- ops:ops 161 /srv/backups/part3-final/20260809T151003Z/manifest.sha256 -r--r----- ops:ops 17 /srv/backups/part3-final/20260809T151003Z/backup-id -r--r----- ops:ops 33 /srv/backups/part3-final/20260809T151003Z/data/inventory.csv -r--r----- ops:ops 47 /srv/backups/part3-final/20260809T151003Z/data/config.ini -r--r----- ops:ops 65 /srv/backups/part3-final/20260809T151003Z/metadata.before dr-xr-s--- ops:ops 4096 /srv/backups/part3-final/20260809T151003Z/data dr-xr-xr-x ops:ops 4096 /srv/backups/part3-final/20260809T151003Z
ここでファイルが -r--r-----、data が dr-xr-s--- になったことに注目してください。この読み取り専用の権限は、復元時に rsync -a でデータと一緒に運ばれます。STEP ⑩ では、内容が一致しても権限検証が不合格になることを予測できます。
⑦ learner02 側で、壊す前にバックアップを検証する
実行ホスト: learner01 から learner02 を検査
元データを削除する前に、退避先だけで manifest 検証が通ることを確認します。ここで不一致が出たら、STEP ⑧ へ進んではいけません。
BACKUP_ID="$(cat /srv/part3-final/backup-id)"
case "${BACKUP_ID}" in
20??????T??????Z) ;;
*) echo 'invalid BACKUP_ID' >&2; exit 1 ;;
esac
ssh \
-i /home/ops/.ssh/ch7_backup_ed25519 \
-o IdentitiesOnly=yes \
-o UserKnownHostsFile=/home/ops/.ssh/ch7_known_hosts \
-o StrictHostKeyChecking=yes \
ops@learner02 \
"cd /srv/backups/part3-final/${BACKUP_ID}/data && \
sha256sum -c ../manifest.sha256"
容量と配置も確認します。ここで見る df は learner02 側です。
ssh \
-i /home/ops/.ssh/ch7_backup_ed25519 \
-o IdentitiesOnly=yes \
-o UserKnownHostsFile=/home/ops/.ssh/ch7_known_hosts \
-o StrictHostKeyChecking=yes \
ops@learner02 \
"date -u; du -sb /srv/backups/part3-final/${BACKUP_ID}; \
df -h /srv/backups/part3-final/${BACKUP_ID}"
実機では退避先だけで 2 ファイルの検証が成功し、元と同じ 323 バイトになりました。
./config.ini: OK ./inventory.csv: OK 2026年 8月 9日 日曜日 15:13:24 UTC 323 /srv/backups/part3-final/20260809T151003Z Filesystem Size Used Avail Use% Mounted on /dev/mapper/ubuntu--vg-ubuntu--lv 15G 12G 2.7G 81% /
df は learner02 側の撮影例です。使用率 81% は環境や実行時点によって変わります。
全ハッシュが一致した時点を、このバックアップ世代の「利用可能な復旧点」とします。転送が終わった時点ではなく、退避先で検証が終わった時点であることがポイントです。
検証後、障害を起こす前にサービスを再開します。
sudo systemctl start part3-final.service systemctl is-active part3-final.service sudo journalctl -u part3-final.service -n 10 --no-pager
⑧ データを削除し、サービス障害を journal で確認する
実行ホスト: learner01
ここからが「壊す」です。削除対象を ls で確認し、本章専用ディレクトリの 2 ファイルだけを削除します。
ls -l /srv/part3-final/data/config.ini \ /srv/part3-final/data/inventory.csv date -u sudo rm -f -- \ /srv/part3-final/data/config.ini \ /srv/part3-final/data/inventory.csv
サービスを再起動し、必要ファイルが無いため起動できない状態を作ります。
sudo systemctl restart part3-final.service systemctl show part3-final.service \ -p ActiveState -p SubState -p NRestarts sudo journalctl -u part3-final.service -n 30 --no-pager
実機での削除前後と再起動結果は次のとおりです。
-rw-r----- 1 ops part3ops 47 8月 10 00:01 /srv/part3-final/data/config.ini -rw-r----- 1 ops part3ops 33 8月 10 00:01 /srv/part3-final/data/inventory.csv 障害発生時刻: 2026-08-09 15:14:20 UTC
合計 8 drwxr-s--- 2 ops part3ops 4096 8月 10 00:14 . drwxr-s--- 3 ops part3ops 4096 8月 10 00:10 .. Job for part3-final.service failed because the control process exited with error code. NRestarts=0 ActiveState=activating SubState=auto-restart
systemctl restart が exit=1 で失敗するのが、この障害再現では正しい結果です。Restart=on-failure により、その後は再起動ループへ入ります。
8月 10 00:14:20 learner01 part3-final-validate[3190]: health=failed missing_or_unreadable=config.ini 8月 10 00:14:20 learner01 systemd[1]: part3-final.service: Control process exited, code=exited, status=1/FAILURE …(略) 8月 10 00:14:20 learner01 systemd[1]: Failed to start part3-final.service - Part 3 final backup and restore lab service.
この時点の NRestarts=0 は reset-failed 前に取得した瞬間値です。再試行の実態は、後で journal に出る restart counter is at 13 で確認します。また、missing_or_unreadable は「無い」場合と「在るが読めない」場合の両方に出るメッセージです。
Restart=on-failure が設定されていても、必要なデータそのものは再生成されません。systemd はプロセスを再試行できますが、失われた業務データを戻す仕組みではないからです。
snapshot を使わない理由: この時点で
ch7-before-lossへ rollback すれば VM 全体は戻せます。しかし、それでは別 VM バックアップから選択的に戻す演習になりません。本章では snapshot を残したまま、次の STEP でlearner02から復元します。
⑨ learner02 の退避バックアップから rsync で復元する
実行ホスト: learner01。復元元は learner02
再起動ループと競合しないようサービスを止め、failed 状態をいったん解除します。
sudo systemctl stop part3-final.service sudo systemctl reset-failed part3-final.service
復元先と復元元を表示し、向きを確認します。learner02 が送信元、learner01 が受信先です。
BACKUP_ID="$(cat /srv/part3-final/backup-id)"
case "${BACKUP_ID}" in
20??????T??????Z) ;;
*) echo 'invalid BACKUP_ID' >&2; exit 1 ;;
esac
printf 'restore source: learner02:/srv/backups/part3-final/%s/data/\n' \
"${BACKUP_ID}"
printf 'restore destination: learner01:/srv/part3-final/data/\n'
データを復元します。--checksum を付け、サイズと更新時刻だけでなく内容も比較対象にします。
rsync -a \
--checksum \
--no-owner \
--no-group \
--itemize-changes \
-e "ssh -i /home/ops/.ssh/ch7_backup_ed25519 \
-o IdentitiesOnly=yes \
-o UserKnownHostsFile=/home/ops/.ssh/ch7_known_hosts \
-o StrictHostKeyChecking=yes" \
ops@learner02:/srv/backups/part3-final/${BACKUP_ID}/data/ \
/srv/part3-final/data/
manifest と metadata も退避先から取り戻します。障害前に learner01 に残った検査票だけへ依存せず、バックアップセットが自己検証に必要な情報を持つことを確かめます。
install -d -m 0750 /srv/part3-final/restore-evidence
rsync -a \
--no-owner \
--no-group \
-e "ssh -i /home/ops/.ssh/ch7_backup_ed25519 \
-o IdentitiesOnly=yes \
-o UserKnownHostsFile=/home/ops/.ssh/ch7_known_hosts \
-o StrictHostKeyChecking=yes" \
ops@learner02:/srv/backups/part3-final/${BACKUP_ID}/manifest.sha256 \
ops@learner02:/srv/backups/part3-final/${BACKUP_ID}/metadata.before \
/srv/part3-final/restore-evidence/
実機では矢印がバックアップ時の <f から復元時の >f に変わり、learner02 から受信していることを確認できました。
.d..tp..... ./ >f+++++++++ config.ini >f+++++++++ inventory.csv
データ本体(--checksum) real 0m0.929s user 0m0.087s sys 0m0.002s 検査票の取り戻し real 0m0.307s user 0m0.082s sys 0m0.008s
ここでも time は stderr に出るため、実際の画面では次の見出しの後ろへずれて表示されることがあります。上の値は正しい rsync との対応に並べ直しています。
復元直後の状態を確認すると、内容と一緒に退避先の権限も運ばれています。
/srv/part3-final/data/: -r--r----- 1 ops ops 47 8月 10 00:01 config.ini -r--r----- 1 ops ops 33 8月 10 00:01 inventory.csv /srv/part3-final/restore-evidence/: -r--r----- 1 ops part3ops 161 8月 10 00:10 manifest.sha256 -r--r----- 1 ops part3ops 65 8月 10 00:10 metadata.before
⑩ 内容・権限・サービス・ログの 4 層でリストアを検証する
実行ホスト: learner01
復元コマンドが終わっても、まだ修了ではありません。バックアップは、中身を戻しただけでは復旧ではありません。 所有者・グループ・モード・ディレクトリの setgid まで戻し、サービスが実際に起き上がって初めて復旧です。4 層を順に検証します。
1. 内容の検証
learner02 から戻した manifest を使って SHA-256 を照合します。
( cd /srv/part3-final/data sha256sum -c ../restore-evidence/manifest.sha256 )
./config.ini: OK ./inventory.csv: OK
1 行でも不一致や欠落があれば、サービスを起動せず原因を調べます。実機では 2 件とも一致しました。ただし、ここで安心してはいけません。ハッシュはファイル内容を証明しますが、サービスが読める権限までは証明しないからです。
2. 権限の検証
復元後の metadata を作り、バックアップセット内の障害前 metadata と比較します。
( cd /srv/part3-final/data find . -type f -printf '%P|%u|%g|%m|%s\n' | sort ) | tee /srv/part3-final/restore-evidence/metadata.after diff -u \ /srv/part3-final/restore-evidence/metadata.before \ /srv/part3-final/restore-evidence/metadata.after namei -l /srv/part3-final/data/config.ini namei -l /srv/part3-final/data/inventory.csv
実機では、ここで差が出るのが正しい結果でした。
config.ini|ops|ops|440|47 inventory.csv|ops|ops|440|33 --- diff (before vs after) --- /srv/part3-final/restore-evidence/metadata.before 2026-08-10 00:10:03.327000000 +0900 +++ /srv/part3-final/restore-evidence/metadata.after 2026-08-10 00:17:17.499000000 +0900 @@ -1,2 +1,2 @@ -config.ini|ops|part3ops|640|47 -inventory.csv|ops|part3ops|640|33 +config.ini|ops|ops|440|47 +inventory.csv|ops|ops|440|33 diff: 差分あり(exit=1)
f: /srv/part3-final/data/config.ini drwxr-xr-x root root / drwxr-xr-x root root srv drwxr-s--- ops part3ops part3-final dr-xr-x--- ops part3ops data -r--r----- ops ops config.ini
差分は、ファイルのグループが part3ops から ops へ変わり、モードが 640 から 440 へ変わったことを示しています。data ディレクトリも 2750 の drwxr-s--- から 550 の dr-xr-x--- へ落ち、setgid と書き込み権限を失っています。
原因は STEP ⑥ の chmod -R a-w です。退避先を誤上書きしにくくするために落とした書き込み権限が、復元時に rsync -a の -p で復元先へ運ばれました。さらに --no-owner --no-group を指定したため、ファイルのグループは受信側の既定である ops になりました。この状態で検査すると、ファイルは在るのに health=failed missing_or_unreadable=config.ini となり、サービスは起動しません。
2.5. metadata.before を使って権限を復旧する
先にディレクトリを戻し、setgid を含む 2750 を再設定します。次に、障害前の確認用として記録した metadata.before を 1 行ずつ読み、ファイルごとの所有者・グループ・モードを戻します。
sudo systemctl stop part3-final.service
sudo systemctl reset-failed part3-final.service
# ディレクトリを先に戻す(setgid を含む)
sudo chown -R ops:part3ops /srv/part3-final/data
sudo chmod 2750 /srv/part3-final/data
# metadata.before を 1 行ずつ読んで、ファイルごとに戻す
while IFS='|' read -r name owner group mode size; do
[ -n "$name" ] || continue
sudo chown "${owner}:${group}" "/srv/part3-final/data/${name}"
sudo chmod "0${mode}" "/srv/part3-final/data/${name}"
done < /srv/part3-final/restore-evidence/metadata.before
実機では 2 ファイルとも元の状態へ戻りました。
restored: config.ini -> ops:part3ops 0640 restored: inventory.csv -> ops:part3ops 0640
層 2 の metadata 作成・diff・namei をもう一度実行します。
config.ini|ops|part3ops|640|47 inventory.csv|ops|part3ops|640|33 diff: 差分なし ✅ drwxr-s--- ops part3ops part3-final drwxr-s--- ops part3ops data -rw-r----- ops part3ops config.ini
「確かめるため」に取った metadata.before が、そのまま「直すため」の復旧指示になりました。内容だけでなく、サービスが使うメタデータもバックアップセットに含める意味がここにあります。差分がなくなり、part3svc が part3ops グループ経由で読める状態へ戻ってから層 3 へ進みます。
3. systemd サービスの検証
sudo -u part3svc /usr/local/sbin/part3-final-validate sudo systemctl start part3-final.service systemctl is-enabled part3-final.service systemctl is-active part3-final.service systemctl show part3-final.service \ -p ActiveState -p SubState -p NRestarts
validate: OK ✅ start: OK enabled active NRestarts=0 ActiveState=active SubState=running
4. journal の検証
sudo journalctl -u part3-final.service \ --since '10 minutes ago' --no-pager date -u
8月 10 00:15:29 learner01 systemd[1]: part3-final.service: Scheduled restart job, restart counter is at 13. 8月 10 00:17:17 learner01 part3-final-validate[3491]: health=failed missing_or_unreadable=config.ini 8月 10 00:17:17 learner01 systemd[1]: Failed to start part3-final.service - Part 3 final backup and restore lab service. …(略) 8月 10 00:19:13 learner01 systemd[1]: Started part3-final.service - Part 3 final backup and restore lab service. 8月 10 00:19:13 learner01 part3-final-service[3695]: health=ok readable_files=2
restart counter is at 13 は、STEP ⑧ で見た瞬間値 NRestarts=0 の後にも再試行が続いていたことを示します。missing_or_unreadable の時点でもファイル自体は存在していましたが、part3svc が読めない権限でした。権限復旧後の health=ok readable_files=2 と active 状態を確認できた 15:19:13Z を復旧完了時刻とします。
障害時の health=failed と、復元後の health=ok を同じ journal の時間軸で追います。復元後の active 状態と成功ログを確認した UTC 時刻を、復旧判定時刻にします。
完了判定は次のすべてを満たすことです。
| 層 | 合格条件 |
|---|---|
| 内容 | learner02 から戻した manifest に対する sha256sum -c が全件成功 |
| 権限 | 初回の差分を確認し、metadata.before で復旧した後は障害前と一致し、part3svc が必要ファイルを読める |
| サービス | part3-final.service が enabled かつ active |
| ログ | journal に障害原因と、復元後の health=ok の両方が残る |
| 復元経路 | Proxmox snapshot rollback を使わず、learner02 から rsync した |
ここまで通れば「データを戻した」ではなく、サービスを復旧し、その証拠を残したと言えます。
⑪ 章末クリーンアップ(3台と スナップショットを clean に戻す)
実行ホスト: learner01、learner02、workstation01、Proxmox ホスト
検証結果を確認したら、本章で作ったものを全て撤去します。順序は「サービス停止 → 退避先削除 → 認証経路削除 → learner01 の成果物削除 → スナップショット 削除」です。
A. learner01 のサービスを停止する
# learner01 で実行 sudo systemctl disable --now part3-final.service systemctl is-active part3-final.service systemctl is-enabled part3-final.service
B. learner02 の本章バックアップ世代を削除する
削除対象は backup-id から読み、形式を検査します。/srv/backups 全体には削除を向けません。
# learner01 で実行。learner02 の本章世代だけを削除
BACKUP_ID="$(cat /srv/part3-final/backup-id)"
case "${BACKUP_ID}" in
20??????T??????Z) ;;
*) echo 'invalid BACKUP_ID; cleanup stopped' >&2; exit 1 ;;
esac
ssh \
-i /home/ops/.ssh/ch7_backup_ed25519 \
-o IdentitiesOnly=yes \
-o UserKnownHostsFile=/home/ops/.ssh/ch7_known_hosts \
-o StrictHostKeyChecking=yes \
ops@learner02 \
"test -d /srv/backups/part3-final/${BACKUP_ID} && \
chmod -R u+w /srv/backups/part3-final/${BACKUP_ID} && \
rm -rf -- /srv/backups/part3-final/${BACKUP_ID}"
learner02 で、本章の親ディレクトリが空なら削除します。他の内容があれば rmdir は失敗し、消しません。
# learner02 で実行 sudo rmdir /srv/backups/part3-final
C. 一時 SSH 鍵と認可を削除する
まず learner02 の authorized_keys から、本章コメントの行だけを削除します。
# workstation01 で実行 ssh ops@learner02 \ "test ! -e ~/.ssh/authorized_keys || \ sed -i '/ part3-ch7-temporary$/d' ~/.ssh/authorized_keys"
次に learner01 から秘密鍵・公開鍵・専用 known_hosts を削除します。
# learner01 で実行 rm -f -- \ /home/ops/.ssh/ch7_backup_ed25519 \ /home/ops/.ssh/ch7_backup_ed25519.pub \ /home/ops/.ssh/ch7_known_hosts
D. learner01 の unit・スクリプト・データ・ユーザを削除する
# learner01 で実行 sudo rm -f -- /etc/systemd/system/part3-final.service sudo rm -f -- \ /usr/local/sbin/part3-final-service \ /usr/local/sbin/part3-final-validate sudo systemctl daemon-reload sudo systemctl reset-failed sudo rm -rf -- /srv/part3-final sudo userdel part3svc sudo groupdel part3ops
E. Proxmox スナップショットを削除する
# Proxmox ホストで実行 qm delsnapshot <VMID> ch7-before-loss qm listsnapshot <VMID>
F. clean 状態を確認する
# learner01 で実行 test ! -e /srv/part3-final test ! -e /etc/systemd/system/part3-final.service test ! -e /usr/local/sbin/part3-final-service test ! -e /usr/local/sbin/part3-final-validate test ! -e /home/ops/.ssh/ch7_backup_ed25519 test ! -e /home/ops/.ssh/ch7_known_hosts ! getent passwd part3svc ! getent group part3ops # learner02 で実行 test ! -e /srv/backups/part3-final ! grep -q ' part3-ch7-temporary$' ~/.ssh/authorized_keys 2>/dev/null # Proxmox ホストで実行 ! qm listsnapshot <VMID> | grep -q ch7-before-loss
実機では、退避世代を読み取り専用から戻して削除し、一時鍵を失効させた後、サービス・ユーザ・グループ・snapshot を撤去しました。
世代削除 OK authorized_keys 残存=0 親ディレクトリ削除 OK …(略) Removed "/etc/systemd/system/multi-user.target.wants/part3-final.service". 撤去完了 Logical volume "snap_vm-<VMID>-disk-0_ch7-before-loss" successfully removed. 削除 OK --- 残存確認(0であること) 0
OK 不在: /srv/part3-final OK 不在: /etc/systemd/system/part3-final.service OK 不在: /usr/local/sbin/part3-final-service OK 不在: /usr/local/sbin/part3-final-validate OK 不在: /home/ops/.ssh/ch7_backup_ed25519 OK 不在: /home/ops/.ssh/ch7_known_hosts OK part3svc 不在 OK part3ops 不在 判定: OK=8 NG=0 OK 退避先 不在 OK 鍵 不在
qm listsnapshot <VMID> | grep -c ch7-before-loss は該当 0 件のとき 0 を表示しますが、grep 自体の終了コードは 1 です。この確認では「0 件」が削除成功を意味するため、表示件数と終了コードを取り違えないでください。
中断した場合: STEP ⑪ の A〜F は、存在するものだけを確認しながら進めます。
BACKUP_IDが読めない場合は推測でrm -rfせず、learner02で/srv/backups/part3-finalの直下を確認して対象世代を特定します。
ゴール確認
クリーンアップ前の STEP ⑩ で次の復旧証拠がそろい、クリーンアップ後に演習資源が残っていなければ修了です。
| 確認項目 | 期待する判定 |
|---|---|
| 退避先の完全性 | データ削除前に、learner02 上で manifest 検証が全件成功 |
| 障害の観測 | データ削除後、systemd と journal で必要ファイル欠落を確認 |
| 復元元 | learner02 の世代付きバックアップ |
| 内容 | 復元後の SHA-256 が manifest と一致 |
| 権限 | 初回の差分を観測し、metadata.before で戻した後は所有者・グループ・モード・サイズが障害前と一致 |
| サービス | systemd が active、journal に復元後の成功ログあり |
| スナップショット | 作成はしたが rollback には使わず、章末に削除済み |
| clean | 本章のサービス・データ・鍵・バックアップ・スナップショットが撤去済み |
失敗しても安全に戻せる方法
| 起きたこと | 対処 |
|---|---|
① で rsync が見つからない | その場で外部取得せず中止。learner01 / learner02 のどちらに無いか記録し、演習 baseline の整備項目にする |
| learner01 から learner02 へ接続できない | まず workstation01 から両 VM へ接続できるかを確認し、次に learner01 から learner02 の TCP/22 へ到達できるかを分けて確認。getent hosts learner02 で名前が引けるかも見る(引けない場合は演習環境の整備不足)。ネットワーク迂回は作らない |
| host key fingerprint が一致しない | 接続を中止。IP の取り違え、再構築による鍵変更、中間者の可能性を確認。StrictHostKeyChecking=no で回避しない |
Permission denied (publickey) | learner02 の ~/.ssh が 0700、authorized_keys が 0600、登録行末が part3-ch7-temporary か確認。秘密鍵をコピーして解決しない |
| rsync が退避先へ書けない | learner02 の /srv/backups/part3-final の所有者・モードと空き容量を確認。chmod 777 にはしない |
| ⑦ の退避先ハッシュが不一致 | 元データを削除せず停止。manifest 作成後にデータが変わっていないか、転送が完了したかを調査し、新しい BACKUP_ID で取り直す |
| snapshot 作成が失敗する | 対象 <VMID>・スナップショット 対応ストレージ・同名 スナップショットの残骸を確認。スナップショット なしで「取得済み」と扱わない |
| 削除後もサービスが active に見える | systemctl show と journal を確認。既存プロセスが次のデータ検査まで動いている場合は systemctl restart で起動時検証を通す |
| 復元 rsync の向きが不安 | 実行前に表示した source が learner02、destination が learner01 であることを読む。逆向きにすると正常バックアップを空の復元先で上書きする危険がある |
| 復元後の hash が不一致 | サービスを起動しない。退避先側の hash が今も一致するか確認し、転送ログと復元元 BACKUP_ID を照合する |
| metadata の group が違う | STEP ⑥ の読み取り専用化が rsync -a で運ばれた結果か確認し、STEP ⑩ の手順でディレクトリの setgid と、metadata.before に記録した所有者・グループ・モードを戻す |
| 復元後も unit が起動しない | sudo -u part3svc part3-final-validate を単独実行し、次に journalctl -u part3-final.service で内容・権限・unit の順に切り分ける |
cleanup で BACKUP_ID が不正と言われる | 安全停止が働いている。値を推測せず、learner02 の直下と記録した backup-id を照合してから対象を指定する |
AI に相談
「rsync の向きが不安」「hash は合うのに systemd が起動しない」「metadata の group だけ違う」といった演習中の詰まりを、章のコンテキスト(別 VM バックアップ・SHA-256・権限・systemd・journal)を踏まえて AI が回答します(他章と同じ本番 AI に接続)。
修了確認
問 1(スナップショットとバックアップ)
learner01 のデータを削除したとき、Proxmox snapshot からも戻せる可能性があります。それでも本章で learner02 へ rsync した理由を、障害範囲と復元単位の 2 点から説明してください。
解答を見る
- 障害範囲: Proxmox snapshot は元の仮想ディスクと同じストレージ上の差分であり、元ストレージの故障や喪失と障害範囲を共有します。
learner02への rsync は、少なくともlearner01の OS・仮想ディスクとは別のデータ実体を作ります。ただし、両 VM が同じ物理ストレージなら物理障害には同時に巻き込まれるため、完全な分離とは言えません。 - 復元単位: スナップショット rollback は VM 全体を過去の時点へ戻します。rsync backup は必要なファイルやディレクトリだけを選択して戻せるため、障害後に残った正常データや VM 全体の状態を不要に巻き戻さずに済みます。
スナップショットは変更作業の高速な戻し、別場所の backup はデータ複製と選択的復元、という役割分担です。
問 2(3-2-1 の正直な判定)
本演習には learner01 の元データ、Proxmox snapshot、learner02 の rsync バックアップがあります。これを「3-2-1 を満たした」と判定してよいでしょうか。3・2・1 をそれぞれ判定してください。
解答を見る
満たしたとは判定しません。
| 条件 | 判定理由 |
|---|---|
| 3 コピー | スナップショットは元ストレージと差分関係にあり、独立したデータ実体として数えません。元データと learner02 の複製だけなので未達です |
| 2 種類の媒体 | VM が 2 台でも、同じ物理ストレージ上なら媒体・障害範囲は分かれていません。物理配置の証拠がない限り未達または未確認です |
| 1 つを別の場所 | learner02 は同じラボ内の別 VM であり、別拠点保管ではないため未達です |
この演習で達成するのは「元 VM の外にデータ実体を作り、そこから復元できること」です。完全な 3-2-1 には、別媒体上の第 3 コピーとオフサイト保管を追加します。
問 3(Part 3 の技術を復旧へつなぐ)
復元後に ls でファイルが見えただけでは不合格です。本章で systemd・journal・権限・スナップショットをそれぞれ何のために使ったか、また最終的な復旧判定をどの順序で行うか説明してください。
解答を見る
| 技術 | 本章での役割 |
|---|---|
| systemd | サービスの停止、整合した復旧点の作成、障害の再現、復元後の起動確認 |
| journal | ファイル欠落による失敗と、復元後の成功を同じ時間軸で確認 |
| 権限 | 初回の差分を読み、metadata.before を使って所有者・グループ・モード・setgid を戻し、part3svc がグループ経由でデータを読める状態になったか確認 |
| スナップショット | VM 全体を戻せる近い復旧点を作り、別 VM backup との役割差を比較。今回は rollback しない |
復旧判定は、① SHA-256 で内容、② metadata と namei で権限差分を確認、③ metadata.before で権限を戻して再検証、④ systemd で active、⑤ journal で成功ログ、の順です。内容や権限が壊れたままサービスを起動しないため、この順序にします。
問 4(RPO・RTO と運用判断)
バックアップ転送が完了した時刻と、learner02 上で hash 検証が完了した時刻が異なる場合、どちらを「利用可能な復旧点」として扱いますか。また、RTO の終了は rsync restore の終了時刻でよいでしょうか。
解答を見る
復旧に使えると確認できたのは、learner02 上で hash 検証が完了した時点です。転送コマンドの終了だけでは、内容の欠落や不一致を排除できません。
RTO の終了も rsync restore の終了時刻ではありません。内容・権限・systemd・journal を検証し、サービスが使えると判定した時刻までを含めます。「コピーが終わった」と「サービスが戻った」を分けて測ることが重要です。
段階ヒント(問 1〜問 4 共通)
ヒント 1 段(軽く方向だけ)
この演習では、いま扱っているものを 4 つに分けて考えます。
- データ:
/srv/part3-final/data - 検査票:
manifest.sha256とmetadata.before - 転送経路: Ch7 専用 SSH 鍵と known_hosts
- 復旧点: Proxmox スナップショットと learner02 の rsync 世代
詰まったら「どの層まで成功したか」を確認してください。rsync の前に SSH、削除の前に退避先 hash、サービス起動の前に復元後 hash と権限、という順序を崩さないことが大切です。hash が一致して metadata に差が出たら、metadata.before を復旧指示として使います。
ヒント 2 段(確認コマンド)
# learner01: 元データ・サービス・検査票
find /srv/part3-final -maxdepth 2 -printf '%M %u %g %p\n'
systemctl show part3-final.service -p ActiveState -p SubState -p NRestarts
sudo journalctl -u part3-final.service -n 30 --no-pager
# learner01: 転送経路
ls -l ~/.ssh/ch7_backup_ed25519 ~/.ssh/ch7_known_hosts
ssh-keygen -lf ~/.ssh/ch7_known_hosts
# learner02: 復旧世代
BACKUP_ID="$(cat /srv/part3-final/backup-id)"
ssh \
-i ~/.ssh/ch7_backup_ed25519 \
-o IdentitiesOnly=yes \
-o UserKnownHostsFile=~/.ssh/ch7_known_hosts \
-o StrictHostKeyChecking=yes \
ops@learner02 \
"find /srv/backups/part3-final/${BACKUP_ID} -maxdepth 2 -type f -print"
# 復元後: 内容と権限
cd /srv/part3-final/data
sha256sum -c ../restore-evidence/manifest.sha256
diff -u ../restore-evidence/metadata.before \
../restore-evidence/metadata.after
確認で不一致が出た場所より先へ進まないでください。特に、退避先の hash が未確認のまま元データを削除してはいけません。
答えを見る
成功経路の要約:
learner01 でサービスとデータを作る ↓ systemctl / journal / permission を確認 Ch7 専用鍵を learner02 に登録し、host key を照合 ↓ サービスを止め、manifest・metadata・BACKUP_ID を作る ↓ Proxmox snapshot を作る(比較用。rollback しない) ↓ rsync で learner02 の世代ディレクトリへ転送 ↓ learner02 上で sha256sum -c 元データを削除し、systemd と journal で障害を確認 ↓ learner02 → learner01 の向きで rsync restore ↓ hash → permission の差分を確認 ↓ metadata.before で所有者・グループ・モード・setgid を復旧 permission を再検証 → systemd → journal ↓ 3台の成果物・一時鍵・snapshot を撤去
修了判定で使うコマンド群:
# 内容 (cd /srv/part3-final/data && \ sha256sum -c ../restore-evidence/manifest.sha256) # 権限 diff -u \ /srv/part3-final/restore-evidence/metadata.before \ /srv/part3-final/restore-evidence/metadata.after # サービス sudo -u part3svc /usr/local/sbin/part3-final-validate systemctl is-active part3-final.service # ログ sudo journalctl -u part3-final.service --since '10 minutes ago' --no-pager
初回の権限差分を確認し、STEP ⑩ の metadata.before を読む処理で復旧してから、この 4 層がすべて通り、かつ復元元が learner02 であることが本章の答えです。スナップショットの存在だけ、rsync の終了だけ、ファイルの見た目だけでは修了にしません。
次の章へ
これで Part 3「サーバ運用」修了です。
この Part では、サービスを常駐させ、ログから原因を読み、ユーザと権限を設計し、SSH と sudo を固め、容量を拡張し、変更を配り、最後に別 VM のバックアップからサービスを復旧しました。
動かす → 見る → 守る → 変える → 戻す
個々のコマンドを覚えることがゴールではありません。変更前に戻り道を作り、壊れたら証拠から原因を読み、復旧後に本当に戻ったかを検証する。この運用の型が Part 3 全体の成果です。
次の Part では、ここまで運用してきたサーバを他の基盤要素とつなぎ、より広いサービスの構成を扱います。
もっと知りたい方へ
- 世代管理と retention: 本章は UTC の
BACKUP_IDで世代を分けました。本番では「日次を何日、週次を何週、月次を何か月残すか」を容量と RPO に合わせて決めます。削除ジョブには対象パスの検証と dry-run が必要です - immutable backup: 本章の
chmod -R a-wは所有者が戻せるため、ランサムウェアや管理者権限の侵害には十分ではありません。Object Lock、WORM、別管理権限など「一定期間は変更できない」仕組みを検討します - 暗号化: 別拠点や外部媒体へ出すバックアップは、転送中だけでなく保管時の暗号化と鍵管理が必要です。バックアップ本体と復号鍵を同じ障害範囲へ置かない設計にします
- データベースの整合バックアップ: ファイルコピーだけでは、メモリ上のトランザクションや複数ファイル間の整合が取れないことがあります。DB 専用 dump、checkpoint、backup API を使い、復元時にはスキーマとトランザクションの整合も検証します
rsync --deleteの危険性: 同期先を元と同じ状態にする便利な機能ですが、source/destination を逆にすると正常データを消す恐れがあります。世代ディレクトリ、パス表示、dry-run、削除対象の限定を組み合わせます- ハッシュと真正性: SHA-256 は内容の一致を調べられますが、攻撃者がデータと manifest の両方を書き換えられる場合は検知できません。署名や HMAC、別権限で保管した検査情報が必要です
- 復旧訓練: 手順書は、担当者・時刻・判断・失敗を含む訓練記録で更新します。RPO/RTO は設計資料の数字だけでなく、定期訓練の実測で見直します
- スナップショットの整合性: VM スナップショット、ファイルシステム スナップショット、アプリケーション整合 backup は同じではありません。どの書き込みを止め、何を一緒に戻す必要があるかをサービス単位で決めます