rsyncで世代バックアップを構成する
目標
rsyncでミラー同期と世代バックアップを構成し、--deleteのガードと負荷制御まで、実務の形に仕上げます。
なぜ重要なのか
--link-destを使った世代バックアップは、rsyncの最も美しい機能です。各世代のディレクトリが完全なスナップショットのように見えながら、ディスクを占めるのは変更分だけです。原理はハードリンクで、そのためduで測ると予想よりずっと小さく出ます。同じinodeを重複して数えないからです。
また、--deleteは必ずガードと一緒に使う必要があります。コピー元が空だったり、マウントが外れた状態で実行されると、コピー先のデータをすべて消します。実務で実際に繰り返される事故です。
ステップ
/root/rs/srcの下にapp/main.py、conf/app.yaml、logs/app.log、tmp/cache.binを作成し、/root/rs/dst/に同期してください。結果として/root/rs/dst/app/main.pyが存在する必要があります。- dry-runとitemizeを併用して実行し、
/root/rs/itemize.txtに保存してください。コピー先は、まだ同期していない新しいディレクトリでなければなりません。 ステップ1のdst/をもう一度指定すると、すでに同じ状態なのでrsyncが1行も出力せず、ファイルが空になります。itemizeは「何がなぜ変わるか」を見るためのものなので、変わるものがあってはじめて見るものもあります。 /root/rs/backup.shを作成してください。set -euo pipefailで始め、コピー元が空なら終了コード1で中断し、--deleteで/root/rs/src/を/root/rs/mirror/に同期する必要があります。コピー元のパスは最初の引数として受け取り、引数がなければ/root/rs/src/を使います。採点ツールが空のディレクトリを最初の引数として渡し、ガードが実際に働くかを確認します。実行して/root/rs/mirrorを作成してください。/root/rs/exclude.txtに除外パターン(logs/とtmp/)を書き、それを使って/root/rs/clean/に同期してください。/root/rs/clean/logsと/root/rs/clean/tmpがあってはいけません。- 世代バックアップを作成してください。
/root/rs/gen/daily.1/にフル同期したあと、--link-destで/root/rs/gen/daily.0/を作成してください。変更されていないファイルは、2つの世代が同じinodeを共有している必要があります。 --numeric-idsを使って/root/rs/numeric/に同期し、app/main.pyの所有者のUIDを数字1行で/root/rs/uid.txtに書いてください。/root/rs/throttled.shを作成してください。ionice、nice、--bwlimitの3つの要素がすべて入ったrsyncコマンドでなければなりません。実行して/root/rs/slow/を作成してください。/root/rs/report.txtを次の4行で作成してください。MIRROR_FILES=<mirror 안 파일 수>/CLEAN_HAS_LOGS=no/SHARED_INODE=yes/GEN_COUNT=2(プレースホルダーはmirror内のファイル数です)
参考
rsync -ain <원본>/ <대상>/が、dry-runとitemizeを組み合わせた形です(プレースホルダーは、順にコピー元とコピー先です)。- 除外リストは
--exclude-from=/root/rs/exclude.txtで指定します。 - inodeを共有しているかどうかは、
stat -c %i <두 세대의 같은 파일>で比較して確認します(プレースホルダーは、2つの世代にある同じファイルです)。 - 負荷制御の例:
ionice -c 3 nice -n 19 rsync -a --bwlimit=1M <원본>/ <대상>/(プレースホルダーは、順にコピー元とコピー先です) - よくある間違い1: ステップ3のスクリプトがコピー元のパスをハードコードしていて、空のディレクトリのテストを通過できない場合です。最初の引数でコピー元のパスを受け取るように作成してください(引数がなければ既定値を使います)。
- よくある間違い2: ステップ5で
--link-destを相対パスで指定すると、コピー先を基準に解釈されてリンクが張られません。
基本のミラー同期を行う
/root/rs/srcの下にapp/main.py、conf/app.yaml、logs/app.log、tmp/cache.binを作成し、/root/rs/dst/に同期してください。結果として/root/rs/dst/app/main.pyが存在する必要があります。
末尾のスラッシュのルールを思い出してください。コピー先の中にコピー元のディレクトリ名ができてはいけません。
変更内容を表示する
dry-runとitemizeを併用して実行し、/root/rs/itemize.txtに保存してください。コピー先は、まだ同期していない新しいディレクトリでなければなりません。 ステップ1のdst/をもう一度指定すると、すでに同じ状態なのでrsyncが1行も出力せず、ファイルが空になります。itemizeは「何がなぜ変わるか」を見るためのものなので、変わるものがあってはじめて見るものもあります。
dry-runとitemizeを一緒に使うと、何がどう変わるかが1行ずつ表示されます。
ガード付きのバックアップスクリプトを書く
/root/rs/backup.shを作成してください。set -euo pipefailで始め、コピー元が空なら終了コード1で中断し、--deleteで/root/rs/src/を/root/rs/mirror/に同期する必要があります。コピー元のパスは最初の引数として受け取り、引数がなければ/root/rs/src/を使います。採点ツールが空のディレクトリを最初の引数として渡し、ガードが実際に働くかを確認します。実行して/root/rs/mirrorを作成してください。
コピー元が空なら、すぐに中断する必要があります。終了コードで区別してください。
除外リストのファイルを使う
/root/rs/exclude.txtに除外パターン(logs/とtmp/)を書き、それを使って/root/rs/clean/に同期してください。/root/rs/clean/logsと/root/rs/clean/tmpがあってはいけません。
パターンをファイルにまとめておくと管理しやすくなります。1行に1パターンです。
世代バックアップを作る
世代バックアップを作成してください。/root/rs/gen/daily.1/にフル同期したあと、--link-destで/root/rs/gen/daily.0/を作成してください。変更されていないファイルは、2つの世代が同じinodeを共有している必要があります。
--link-destは絶対パスで指定するほうが安全です。検証はリンク数で行います。
数値の所有者IDを維持する
--numeric-idsを使って/root/rs/numeric/に同期し、app/main.pyの所有者のUIDを数字1行で/root/rs/uid.txtに書いてください。
名前の代わりに数値のIDを使うオプションがあります。復元先のシステムが異なる場合に備えるためのものです。
負荷を制御する
/root/rs/throttled.shを作成してください。ionice、nice、--bwlimitの3つの要素がすべて入ったrsyncコマンドでなければなりません。実行して/root/rs/slow/を作成してください。
帯域幅を制限するオプションと、ディスクの優先度を下げるコマンドを一緒に使います。コマンド全体をスクリプトとして残してください。
世代ごとの容量レポートを作る
/root/rs/report.txtを次の4行で作成してください。
MIRROR_FILES=<mirror 안 파일 수> / CLEAN_HAS_LOGS=no / SHARED_INODE=yes / GEN_COUNT=2(プレースホルダーはmirror内のファイル数です)
ハードリンクのせいで、duの結果が予想より小さくなります。その事実そのものが、このラボの結論です。