演習進捗 0 / 5 未完了
⏱ 約 90 分 · 概念 25 分+演習 65 分
Part 3Chapter 07/サーバ運用

バックアップとリストア:別 VM からサービスを復旧する

別 VM へ世代付きバックアップを作り、実際にデータを削除してから復元します。内容・権限・systemd・journal の 4 層で、サービスが本当に戻ったことまで証明します。

教材 ✓ 公開中登録不要 実機演習 ― 提供していないProxmox(仮想化基盤)側の操作が必要なためです。教材は読んで学べます
所要時間約 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

概念説明

1. スナップショットとバックアップは、守る障害が違う

Ch4 では Proxmox スナップショットを使い、SSH と sudo の設定を壊す前の VM 全体へ戻しました。スナップショットは短時間で VM を巻き戻すのに強い仕組みです。一方で、スナップショットは独立したバックアップではありません。

観点Proxmox スナップショット別 VM への rsync バックアップ
データの置き場所元ディスクと同じストレージ上の差分learner02 のファイルシステム上に別のデータ実体を作る
得意な復旧設定変更前へ VM 全体を素早く戻す必要なファイルやディレクトリだけを選んで戻す
元ストレージ障害同じ障害範囲に入り、一緒に失われ得る物理ストレージまで別なら耐えられる。別 VM だけでは物理分離を保証しない
復旧時の影響VM 全体が スナップショット 時点へ戻る対象データだけを戻せる
単体で十分かいいえいいえ。世代・媒体・別拠点・検証も必要

スナップショットは悪い仕組みではありません。役割が違うのです。

本章では両方を作ります。ただし、データ復元には 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 つを決めます。

本章の復旧点は、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 が無い場合に外部リポジトリからその場で追加する設計にはしません。演習環境の準備不足として止めます。

bash · コマンド
# 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 が存在することを確認済みです。

次に、前回中断した演習の残骸がないことを確認します。

bash · コマンド
# 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 へ接続します。

bash · コマンド
ssh ops@learner01

本章専用のサービスユーザ・グループ・データディレクトリを作ります。ops がデータの所有者、part3svc がグループ経由で読み取る設計です。

bash · コマンド
# 専用グループとサービスユーザ
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

検証用の設定ファイルと業務データを作ります。実在する認証情報や内部アドレスは入れません。

bash · コマンド
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 を出すサービス本体を作ります。

bash · コマンド
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 を作り、有効化して起動します。

bash · コマンド
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 に作ります。普段のログイン鍵を流用せず、章末に失効させます。

bash · コマンド
# 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 の外へ出しません。同じ演習をやり直したときに鍵が重複しないよう、同じコメントの旧行を先に除きます。

bash · コマンド
# 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 を比較します。

bash · コマンド
# 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 にバックアップの親ディレクトリを作ります。

bash · コマンド
# learner02 で ops として実行
OPS_GROUP="$(id -gn)"
sudo install -d -o ops -g "${OPS_GROUP}" -m 0750 \
  /srv/backups/part3-final

learner01 から一時鍵で接続を確認します。

bash · コマンド
# 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 の証拠に残します。

bash · コマンド
date -u
sudo systemctl stop part3-final.service
systemctl is-active part3-final.service

バックアップ世代を識別する BACKUP_ID を UTC で作り、後の STEP でも読み直せるようファイルへ保存します。

bash · コマンド
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 を使えます。

bash · コマンド
(
  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 を確認してから スナップショットを作ります。

bash · コマンド
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 を読み、形式をもう一度検査します。

bash · コマンド
BACKUP_ID="$(cat /srv/part3-final/backup-id)"
case "${BACKUP_ID}" in
  20??????T??????Z) ;;
  *) echo 'invalid BACKUP_ID' >&2; exit 1 ;;
esac

退避先にこの世代専用のディレクトリを作ります。

bash · コマンド
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 の範囲は、今作った空の世代ディレクトリだけです。バックアップ親ディレクトリや別世代には向けません。

bash · コマンド
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/

続けて検証用ファイルを同じ世代へ送ります。

bash · コマンド
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 の代わりにはなりません。

bash · コマンド
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 ⑧ へ進んではいけません。

bash · コマンド
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 側です。

bash · コマンド
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% は環境や実行時点によって変わります。

全ハッシュが一致した時点を、このバックアップ世代の「利用可能な復旧点」とします。転送が終わった時点ではなく、退避先で検証が終わった時点であることがポイントです。

検証後、障害を起こす前にサービスを再開します。

bash · コマンド
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 ファイルだけを削除します。

bash · コマンド
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

サービスを再起動し、必要ファイルが無いため起動できない状態を作ります。

bash · コマンド
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 状態をいったん解除します。

bash · コマンド
sudo systemctl stop part3-final.service
sudo systemctl reset-failed part3-final.service

復元先と復元元を表示し、向きを確認します。learner02 が送信元、learner01 が受信先です。

bash · コマンド
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 を付け、サイズと更新時刻だけでなく内容も比較対象にします。

bash · コマンド
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 に残った検査票だけへ依存せず、バックアップセットが自己検証に必要な情報を持つことを確かめます。

bash · コマンド
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 を照合します。

bash · コマンド
(
  cd /srv/part3-final/data
  sha256sum -c ../restore-evidence/manifest.sha256
)
期待される出力
./config.ini: OK
./inventory.csv: OK

1 行でも不一致や欠落があれば、サービスを起動せず原因を調べます。実機では 2 件とも一致しました。ただし、ここで安心してはいけません。ハッシュはファイル内容を証明しますが、サービスが読める権限までは証明しないからです。

2. 権限の検証

復元後の metadata を作り、バックアップセット内の障害前 metadata と比較します。

bash · コマンド
(
  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 行ずつ読み、ファイルごとの所有者・グループ・モードを戻します。

bash · コマンド
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 サービスの検証

bash · コマンド
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 の検証

bash · コマンド
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 のサービスを停止する

bash · コマンド
# 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 全体には削除を向けません。

bash · コマンド
# 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 は失敗し、消しません。

bash · コマンド
# learner02 で実行
sudo rmdir /srv/backups/part3-final

C. 一時 SSH 鍵と認可を削除する

まず learner02 の authorized_keys から、本章コメントの行だけを削除します。

bash · コマンド
# workstation01 で実行
ssh ops@learner02 \
  "test ! -e ~/.ssh/authorized_keys || \
   sed -i '/ part3-ch7-temporary$/d' ~/.ssh/authorized_keys"

次に learner01 から秘密鍵・公開鍵・専用 known_hosts を削除します。

bash · コマンド
# 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・スクリプト・データ・ユーザを削除する

bash · コマンド
# 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 スナップショットを削除する

bash · コマンド
# Proxmox ホストで実行
qm delsnapshot <VMID> ch7-before-loss
qm listsnapshot <VMID>

F. clean 状態を確認する

bash · コマンド
# 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 に相談

AIバックアップ・復元・権限の詰まりを質問できます
この章の別 VM バックアップ・SHA-256・権限・systemd・journal を前提に答えます

「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 つに分けて考えます。

  1. データ: /srv/part3-final/data
  2. 検査票: manifest.sha256 と metadata.before
  3. 転送経路: Ch7 専用 SSH 鍵と known_hosts
  4. 復旧点: Proxmox スナップショットと learner02 の rsync 世代

詰まったら「どの層まで成功したか」を確認してください。rsync の前に SSH、削除の前に退避先 hash、サービス起動の前に復元後 hash と権限、という順序を崩さないことが大切です。hash が一致して metadata に差が出たら、metadata.before を復旧指示として使います。

ヒント 2 段(確認コマンド)
bash · コマンド
# 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 を撤去

修了判定で使うコマンド群:

bash · コマンド
# 内容
(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 では、ここまで運用してきたサーバを他の基盤要素とつなぎ、より広いサービスの構成を扱います。

もっと知りたい方へ