rsyncで同期する
目標
rsyncの核心的なオプションを、手で確認します。特に、末尾のスラッシュの規則、--deleteの危険とガード、--link-destの世代バックアップの3つを、自分で作ります。
なぜ重要なのか
rsyncは、最もよく使われ、最もよく間違えられるツールです。-aがACL・拡張属性・ハードリンクを含まないこと、元のパスの末尾のスラッシュ1つがディレクトリの深さを変えること、--deleteが元が空のときに対象を全滅させること。この3つは、すべて実際の事故につながったことがあります。--link-destは、逆にrsyncの最も美しい機能です。各世代が完全なスナップショットのように見えながら、ディスクは変更分だけを占めます。
ステップ
/root/sync/srcの下に、次のものを作成してください。app/main.py、app/util.py、conf/app.yaml、logs/app.log、logs/error.log。各ファイルには、任意の内容を1行以上入れてください。/root/sync/src/を/root/sync/dst1/へ同期するdry-runを、変更内容の表示付きで実行して、/root/sync/dryrun.txtに保存してください。- 同じ同期を、SSH経由で実際に実行してください。宛先は
root@127.0.0.1:/root/sync/dst1/で、ポートは2222、鍵は/root/.ssh/id_labhubです。完了後、/root/sync/dst1/app/main.pyが存在する必要があります。 - 末尾のスラッシュの違いを実証してください。
/root/sync/src(スラッシュなし)を/root/sync/dst2/へ送ると、/root/sync/dst2/src/app/main.pyができる必要があります。 /root/sync/backup.shスクリプトを作成してください。set -euo pipefailで始め、元が空なら終了コード1で中断する検査を含め、--deleteを使って/root/sync/src/を/root/sync/dst3/へ同期する必要があります。元のパスは第1引数で受け取り、引数がなければ/root/sync/src/を使います。採点ツールが、空のディレクトリを第1引数として渡して、ガードが実際に動作するかを確認します。スクリプトを実行して、/root/sync/dst3を作成してください。logs/の下を除外して、/root/sync/src/を/root/sync/dst4/へ同期してください。/root/sync/dst4/logsは存在してはいけません。- 世代バックアップを作成してください。まず
/root/sync/gen/daily.1/へ全体を同期し、そのあと--link-destで/root/sync/gen/daily.0/を作成してください。変更されていないファイルは、2つの世代が同じinodeを共有する必要があります。 /root/sync/final.txtを次の3行で作成してください。PENDING=<3번과 같은 동기화를 다시 dry-run 했을 때 전송 예정 파일 수>/DST4_HAS_LOGS=no/SHARED_INODE=yes(プレースホルダーは、ステップ3と同じ同期をもう一度dry-runしたときの、転送予定のファイル数です)PENDINGは0である必要があります。
参考
rsync -ain <원본> <대상>が、dry-run + itemizeの組み合わせです(プレースホルダーは元と宛先です)。- SSHの指定は
-e 'ssh -p 2222 -i /root/.ssh/id_labhub'です。 - inodeの共有の確認は、
stat -c '%i %h' <파일>で、2つの世代のファイルを比較すればよいです(プレースホルダーはファイルです)。 - よくある間違い1: ステップ3で元の末尾のスラッシュを抜くと、
dst1/src/が作られて、検査に失敗します。 - よくある間違い2: ステップ7で
--link-destを相対パスで指定すると、宛先ディレクトリを基準に解釈されて、リンクが張られません。絶対パスを使ってください。
元のツリーの作成
/root/sync/srcの下に、次のものを作成してください。app/main.py、app/util.py、conf/app.yaml、logs/app.log、logs/error.log。各ファイルには、任意の内容を1行以上入れてください。
サブディレクトリとログファイルを混ぜておくと、後のステップの除外ルールが意味を持ちます。
まずシミュレーション
/root/sync/src/を/root/sync/dst1/へ同期するdry-runを、変更内容の表示付きで実行して、/root/sync/dryrun.txtに保存してください。
実際には変更せず、何が変わるかだけを表示するオプションと、変更内容を1行ずつ表示するオプションを、一緒に使います。
SSH経由で実際に同期
同じ同期を、SSH経由で実際に実行してください。宛先はroot@127.0.0.1:/root/sync/dst1/で、ポートは2222、鍵は/root/.ssh/id_labhubです。完了後、/root/sync/dst1/app/main.pyが存在する必要があります。
rsyncには、リモートシェルのコマンドを丸ごと指定するオプションがあります。ポートと鍵を、その中に入れてください。
末尾のスラッシュの違いの実証
末尾のスラッシュの違いを実証してください。/root/sync/src(スラッシュなし)を/root/sync/dst2/へ送ると、/root/sync/dst2/src/app/main.pyができる必要があります。
同じ元を、スラッシュの有無だけを変えて2つの宛先にそれぞれ送り、結果の構造を比較してください。
ガード付きの--delete
/root/sync/backup.shスクリプトを作成してください。set -euo pipefailで始め、元が空なら終了コード1で中断する検査を含め、--deleteを使って/root/sync/src/を/root/sync/dst3/へ同期する必要があります。元のパスは第1引数で受け取り、引数がなければ/root/sync/src/を使います。採点ツールが、空のディレクトリを第1引数として渡して、ガードが実際に動作するかを確認します。スクリプトを実行して、/root/sync/dst3を作成してください。
元が空ならすぐに中断する検査を、スクリプトの先頭に入れてください。set -euo pipefailも一緒に。
除外ルールの適用
logs/の下を除外して、/root/sync/src/を/root/sync/dst4/へ同期してください。/root/sync/dst4/logsは存在してはいけません。
除外パターンは、複数回指定することも、ファイルから読み込むこともできます。ログファイルが宛先にない必要があります。
ハードリンクによる世代バックアップ
世代バックアップを作成してください。まず/root/sync/gen/daily.1/へ全体を同期し、そのあと--link-destで/root/sync/gen/daily.0/を作成してください。変更されていないファイルは、2つの世代が同じinodeを共有する必要があります。
--link-destは、相対パスで指定すると、宛先を基準に解釈されます。絶対パスが安全です。確認は、statのリンク数で行います。
同期完了の証明
/root/sync/final.txtを次の3行で作成してください。
PENDING=<3번과 같은 동기화를 다시 dry-run 했을 때 전송 예정 파일 수> / DST4_HAS_LOGS=no / SHARED_INODE=yes(プレースホルダーは、ステップ3と同じ同期をもう一度dry-runしたときの、転送予定のファイル数です)
PENDINGは0である必要があります。
正しく同期されたなら、同じコマンドをもう一度dry-runで実行したとき、転送するものがない必要があります。