rsync: 高度な同期と削除オプション

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

rsync: 高度な同期と削除オプション

1. 要件と前提

rsync コマンドを用いた高度なファイル同期と安全な削除戦略について解説します。DevOpsの観点から、冪等性、スクリプトの安全性、定期実行の自動化、および外部システムとの連携に焦点を当てます。

目的:

  • rsync の高度な同期オプションと削除オプションを理解し、適切に利用する。

  • 安全で冪等な bash スクリプトを作成する。

  • systemd を利用して rsync 処理を定期的に自動実行する。

  • jqcurl を用いて外部情報を取得し、rsync 処理に連携させる。

前提:

  • Linux環境(Debian/Ubuntu/CentOSなど)が稼働していること。

  • rsyncbashsystemdjqcurl がシステムにインストールされていること。

  • シェルスクリプトの基本的な知識があること。

root権限の扱いと権限分離の注意点: rsync は強力なツールであり、特に root 権限で実行する際には細心の注意が必要です。誤った操作はシステム全体に壊滅的な影響を与える可能性があります。

  • 最小権限の原則: 可能な限り、特定のタスクを実行するためだけの最小限の権限を持つ専用のユーザーアカウントを作成し、そのユーザーで rsync を実行することを推奨します。

  • sudo の利用: root 権限が必要な操作(例: システムファイルの同期、パーミッションの保持)では、sudo を用いて必要なコマンドのみを昇格させます。ただし、sudo の設定 (sudoers) は厳格に管理する必要があります。

  • --dry-run の徹底: 本番環境での実行前には必ず --dry-run オプションを付けて実行し、意図した変更が加えられるかを確認します。

2. 実装

2.1. 安全な rsync スクリプトの基本

安全な bash スクリプトは、予期せぬエラーや意図しない動作を防ぎます。以下のテンプレートを使用することで、冪等性と堅牢性を高めます。

#!/bin/bash


# スクリプト名: safe_rsync_sync.sh


# 目的: rsyncを用いて安全にファイルを同期する

# エラーハンドリングと安全性の確保

set -euo pipefail # 未定義変数の使用、コマンドの失敗、パイプライン内の失敗で即座に終了
trap 'echo "エラーが発生しました。終了します。" >&2; exit 1' ERR # エラー時にメッセージを出力し終了

# 一時ディレクトリの作成とクリーンアップ


# 一時ディレクトリはスクリプト終了時に自動削除される

tmpdir=$(mktemp -d -t rsync-XXXXXX)
trap 'rm -rf "$tmpdir"' EXIT # スクリプト終了時に一時ディレクトリをクリーンアップ

# ロギング関数

log_message() {
    local type="$1"
    local message="$2"
    echo "$(date '+%Y-%m-%d %H:%M:%S') [$type] $message" | tee -a "$LOG_FILE"
}

# --- 設定変数 ---

SOURCE_DIR="/path/to/source" # 同期元ディレクトリ (末尾に/を付けるとディレクトリの内容が同期される)
DEST_DIR="/path/to/destination" # 同期先ディレクトリ
LOG_FILE="/var/log/rsync/sync_$(date +%Y%m%d).log" # ログファイルのパス

# ログディレクトリが存在しない場合は作成

mkdir -p "$(dirname "$LOG_FILE")"

log_message "INFO" "同期処理を開始します。"
log_message "INFO" "ソース: $SOURCE_DIR"
log_message "INFO" "宛先: $DEST_DIR"

# --- rsync コマンドの実行 ---


# --archive (-a): シンボリックリンク、パーミッション、更新日時、グループ、オーナー、デバイスファイルを保持


# --compress (-z): 転送中にデータを圧縮


# --info=progress2: 転送の全体進捗を表示


# --delete: ソースにないファイルを宛先から削除


# --delete-before: 転送開始前に宛先のファイルを削除 (デフォルト)


# --dry-run: 実際に変更を加えず、何が行われるかをシミュレートする (本番実行前に必ず使用)

# 事前確認: --dry-run

log_message "INFO" "ドライラン実行中..."
if rsync -avz --dry-run --delete --exclude 'temp/*' "$SOURCE_DIR/" "$DEST_DIR" > "$tmpdir/dry_run_output.txt"; then
    log_message "INFO" "ドライランが正常に完了しました。変更点を確認してください。"
    cat "$tmpdir/dry_run_output.txt" | tee -a "$LOG_FILE"

    # ここでドライランの結果を分析し、変更があるか判断するロジックを追加


    # 例: 差分がない場合、本実行をスキップ

    if ! grep -qE '^(deleting|send|recv)' "$tmpdir/dry_run_output.txt"; then
        log_message "INFO" "変更点が見つかりませんでした。本実行をスキップします。"
        log_message "INFO" "同期処理を終了します。"
        exit 0
    fi
else
    log_message "ERROR" "ドライラン中にエラーが発生しました。"
    exit 1
fi

# 本番実行 (ドライランで問題がないことを確認してからコメント解除)

log_message "INFO" "本番同期実行中..."
if rsync -avz --delete --delete-before --exclude 'temp/*' "$SOURCE_DIR/" "$DEST_DIR"; then
    log_message "INFO" "同期が正常に完了しました。"
else
    log_message "ERROR" "同期中にエラーが発生しました。"
    exit 1
fi

log_message "INFO" "同期処理を終了します。"

2.2. 高度な同期と削除オプション

rsync の主要なオプションを以下に示します。

  • --archive (-a): 再帰的にファイルを同期し、シンボリックリンク、パーミッション、更新日時、グループ、オーナー、デバイスファイルを保持します。通常、このオプションから開始し、必要に応じて微調整します。

  • --compress (-z): ネットワーク経由での転送時にデータを圧縮し、帯域幅の使用を減らします。ローカル同期では不要な場合もあります。

  • --info=progress2: 転送全体の進行状況を表示します。多数のファイルを扱う際に便利です。

  • --exclude='PATTERN' / --include='PATTERN': 特定のファイルやディレクトリを除外/含めるためのパターンを指定します。複数指定可能で、先にマッチしたルールが適用されます。

  • --delete: 送信元に存在しないファイルを送信先から削除します。

    • --delete-before (デフォルト): 転送開始前に削除します。

    • --delete-after: 転送後に削除します。

    • --delete-during: 転送中に削除します。一般的には --delete-before または --delete-after を使用します。

    • --delete-excluded: --exclude で除外されたファイルも宛先から削除します。慎重に利用する必要があります。

  • --link-dest=DIR: 指定されたディレクトリ DIR に存在するファイルをハードリンクとして利用し、変更されたファイルのみをコピーします。これにより、ディスクスペースを効率的に利用した増分バックアップが可能です。

2.3. 外部サービス連携 (jq/curl)

curl でAPIから設定情報を取得し、jq でJSONをパースして rsync のオプションを動的に生成する例です。

#!/bin/bash


# スクリプト名: dynamic_rsync_config.sh


# 目的: APIからrsync設定を取得し、動的に同期を実行する

set -euo pipefail
trap 'echo "エラーが発生しました。終了します。" >&2; exit 1' ERR

API_ENDPOINT="https://api.example.com/rsync/config" # 設定APIエンドポイント
SOURCE_BASE_DIR="/data/app" # 同期元ベースディレクトリ
DEST_BASE_DIR="/backup/app" # 同期先ベースディレクトリ
LOG_FILE="/var/log/rsync/dynamic_sync_$(date +%Y%m%d).log"

mkdir -p "$(dirname "$LOG_FILE")"
log_message() {
    local type="$1"
    local message="$2"
    echo "$(date '+%Y-%m-%d %H:%M:%S') [$type] $message" | tee -a "$LOG_FILE"
}

log_message "INFO" "APIから同期設定を取得中..."

# curl を用いてAPIから設定を取得


# --retry: リトライ回数


# --retry-delay: 最初のリトライまでの秒数


# --retry-max-time: リトライの総時間上限


# --tlsv1.2: TLSv1.2 を強制

CONFIG_JSON=$(curl -s --fail --retry 5 --retry-delay 3 --retry-max-time 30 --tlsv1.2 "$API_ENDPOINT")

if [[ -z "$CONFIG_JSON" ]]; then
    log_message "ERROR" "APIから設定を取得できませんでした。またはJSONが空です。"
    exit 1
fi

log_message "INFO" "取得した設定JSON: $CONFIG_JSON"

# jq を用いてJSONから設定を抽出


# 例えば、APIが {"sync_targets": [{"source_path": "prod", "destination_path": "prod_backup", "excludes": ["cache", "tmp"]}]} のような構造を返す場合

SYNC_TARGETS=$(echo "$CONFIG_JSON" | jq -c '.sync_targets[]')

if [[ -z "$SYNC_TARGETS" ]]; then
    log_message "ERROR" "設定JSONから同期ターゲットを抽出できませんでした。"
    exit 1
fi

echo "$SYNC_TARGETS" | while read -r target; do
    SOURCE_PATH=$(echo "$target" | jq -r '.source_path')
    DEST_PATH=$(echo "$target" | jq -r '.destination_path')
    EXCLUDES_ARRAY=$(echo "$target" | jq -c '.excludes[]' | xargs -I {} echo "--exclude='{}'")

    FULL_SOURCE_DIR="${SOURCE_BASE_DIR}/${SOURCE_PATH}"
    FULL_DEST_DIR="${DEST_BASE_DIR}/${DEST_PATH}"

    log_message "INFO" "同期ターゲット: ${FULL_SOURCE_DIR} -> ${FULL_DEST_DIR}"

    # rsync コマンド構築

    RSYNC_CMD="rsync -avz --delete --delete-before ${EXCLUDES_ARRAY} ${FULL_SOURCE_DIR}/ ${FULL_DEST_DIR}"

    log_message "INFO" "実行コマンド: ${RSYNC_CMD}"

    # ドライラン

    if eval "$RSYNC_CMD --dry-run" > "$tmpdir/dry_run_output.txt"; then
        log_message "INFO" "ドライラン完了 for ${SOURCE_PATH}. 変更点を確認してください。"
        cat "$tmpdir/dry_run_output.txt" | tee -a "$LOG_FILE"
        if ! grep -qE '^(deleting|send|recv)' "$tmpdir/dry_run_output.txt"; then
            log_message "INFO" "変更点なし for ${SOURCE_PATH}. スキップします。"
            continue
        fi
    else
        log_message "ERROR" "ドライラン失敗 for ${SOURCE_PATH}."
        continue
    fi

    # 本番実行

    if eval "$RSYNC_CMD"; then
        log_message "INFO" "同期成功 for ${SOURCE_PATH}."
    else
        log_message "ERROR" "同期失敗 for ${SOURCE_PATH}."
    fi
done

log_message "INFO" "すべての同期処理が完了しました。"

2.4. systemdによる定期実行

systemdunittimer を使用して、上記の safe_rsync_sync.sh スクリプトを定期的に実行します。

2.4.1. Unit ファイル (/etc/systemd/system/rsync-daily.service)

[Unit]
Description=Daily rsync backup service
Documentation=man:rsync(1)
After=network.target # ネットワークが利用可能になってからサービスを開始

[Service]
Type=oneshot # 一度だけ実行されるサービス
ExecStart=/usr/local/bin/safe_rsync_sync.sh # 実行するスクリプトのパス
User=rsync_user # スクリプトを実行するユーザー (root権限を避けるため推奨)
Group=rsync_group # スクリプトを実行するグループ
WorkingDirectory=/tmp # 作業ディレクトリ
StandardOutput=journal # 標準出力をsystemd journalに送る
StandardError=journal # 標準エラー出力をsystemd journalに送る

# 環境変数などを必要に応じて設定

Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

[Install]
WantedBy=multi-user.target
  • User=rsync_user: スクリプトを実行するユーザーを指定します。事前に sudo useradd -r rsync_user などで作成し、必要なディレクトリへの読み書き権限を付与してください。

2.4.2. Timer ファイル (/etc/systemd/system/rsync-daily.timer)

[Unit]
Description=Runs daily rsync backup service
Requires=rsync-daily.service # rsync-daily.service が必要

[Timer]
OnCalendar=*-*-* 03:00:00 # 毎日午前3時に実行
Persistent=true # タイマーが停止した場合でも、次回のスケジュール時に実行を試みる

[Install]
WantedBy=timers.target

2.4.3. systemdの設定と起動

  1. スクリプトの配置と権限設定: safe_rsync_sync.sh/usr/local/bin/ に配置し、実行権限を付与します。

    sudo cp safe_rsync_sync.sh /usr/local/bin/
    sudo chmod +x /usr/local/bin/safe_rsync_sync.sh
    
  2. rsync_user の作成 (もし存在しない場合):

    sudo useradd -r -s /sbin/nologin rsync_user # ログインシェルなしでシステムユーザーを作成
    
    # 必要に応じて、同期元/先のディレクトリにrsync_userの読み書き権限を付与
    
    
    # sudo chown -R rsync_user:rsync_group /path/to/source /path/to/destination
    
  3. systemd設定ファイルの配置: 上記の .service.timer ファイルを /etc/systemd/system/ に配置します。

  4. systemdデーモンのリロード:

    sudo systemctl daemon-reload
    
  5. タイマーの有効化と起動:

    sudo systemctl enable rsync-daily.timer # 自動起動を有効化
    sudo systemctl start rsync-daily.timer # タイマーを今すぐ起動
    
  6. ステータスの確認:

    systemctl status rsync-daily.timer
    systemctl status rsync-daily.service
    

3. 検証

  • --dry-run の徹底: 本番環境での同期前に、必ず --dry-run オプションを付けてスクリプトを実行し、意図しない削除や変更がないか詳細にログを確認します。

  • systemdサービスのログ確認: systemctl status rsync-daily.servicejournalctl -u rsync-daily.service コマンドで、スクリプトの実行状況と出力されたログを確認します。

  • テスト環境での実行: 実際に同期されるファイルやディレクトリ、パーミッションが期待通りになるか、テスト環境で複数回実行して検証します。

  • リカバリテスト: 万が一の事態に備え、バックアップからデータを正常に復元できるか、リカバリ手順を検証しておくことが重要です。

4. 運用

4.1. 監視と通知

  • journalctl を利用して rsync-daily.service のログを継続的に監視します。エラーログや同期の完了ステータスを、Slackやメールなどの通知システムと連携させることが推奨されます。

  • スクリプト内で curl を使用して、同期結果を監視サービスやAPIエンドポイントに送信する仕組みを組み込むことも可能です。

4.2. 権限管理

  • systemdUser= オプションで指定したユーザーの権限は、厳密に管理し、rsync 処理に必要な最小限の権限のみが付与されていることを定期的に確認します。

  • 同期元/先のディレクトリに対するアクセス権限が、適切に設定されているかを検証します。

4.3. バックアップ戦略

  • --link-dest を活用した増分バックアップは、効率的なスナップショット管理に役立ちます。複数のスナップショットを保持し、世代管理を行います。

  • バックアップの定期的なテストリストアは不可欠です。

5. トラブルシュート

5.1. rsync 関連

  • rsync ログの確認: スクリプト内で出力される rsync コマンドの標準出力/エラー出力を確認します。特に --verbose--dry-run の出力は詳細な情報を提供します。

  • パーミッションエラー: 宛先ディレクトリへの書き込み権限、またはソースディレクトリからの読み込み権限がない場合によく発生します。systemd サービスを実行しているユーザー (User= で指定したユーザー) の権限を確認してください。

  • 接続エラー: リモートホストとの同期の場合、SSHキーの認証失敗、ネットワークの問題、ファイアウォールの設定などが原因となることがあります。

  • ディスク容量不足: 宛先ディスクに十分な空き容量がない場合、同期が失敗します。事前に容量を確認するロジックをスクリプトに追加することも検討してください。

5.2. systemd 関連

  • systemctl status: サービスやタイマーの現在の状態を確認します。

  • journalctl -u SERVICE_NAME.service: サービスの実行ログを詳細に確認します。-f オプションでリアルタイムにログを追跡できます。

  • systemctl cat SERVICE_NAME.service: ロードされているユニットファイルの正確な内容を表示し、設定ミスがないか確認します。

  • systemctl list-timers: 有効なすべてのタイマーと次回の実行時刻を確認します。

5.3. スクリプト関連

  • set -euo pipefail: このオプションにより、スクリプトはエラーで即座に終了するため、問題の特定が容易になります。エラーメッセージを注意深く読み、どのコマンドが失敗したかを確認します。

  • 一時ファイルのクリーンアップ: trap 'rm -rf "$tmpdir"' EXIT が機能しているか確認し、一時ファイルが残り続けていないかチェックします。

6. まとめ

本記事では、rsync の高度な同期および削除オプションをDevOpsの観点から深く掘り下げ、堅牢で冪等な bash スクリプトの作成方法を解説しました。set -euo pipefailtrap を用いた安全なスクリプト設計、jqcurl による外部設定連携、そして systemd unit/timer による自動化まで、実践的なアプローチを紹介しました。

特に、--dry-run を用いた事前検証の徹底と、root 権限を避けた最小権限での運用は、本番環境でのリスクを最小限に抑えるために不可欠です。これらの技術とベストプラクティスを組み合わせることで、システムの安定性と信頼性を向上させ、効率的なファイル同期戦略を実現できるでしょう。


rsync 同期処理フロー

graph TD
    A["処理開始"] --> B{"設定取得"};
    B -- APIからJSON取得|curl| --> C{"JSONパース"};
    C -- パラメータ抽出|jq| --> D["rsync実行前チェック"];
    D --> E{"rsync --dry-run 実行"};
    E -- 成功|差分あり| --> F["rsync 本実行"];
    E -- 成功|差分なし| --> G["同期完了|変更なし|"];
    E -- 失敗 --> H["エラー通知"];
    F --> I["同期完了|成功|"];
    I --> J["ログ記録"];
    G --> J;
    H --> J;
    J --> K["処理終了"];
ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました