はじめに

この記事は、Linuxサーバー間でファイル・ディレクトリを同期する際にrsyncを使っているが、「リモート側の接続ユーザーがroot以外のとき、なぜか所有者・グループ情報がすべてそのユーザーに上書きされてしまう」という問題に直面した方を対象にしています。

この記事を読むことで、非rootユーザーでrsyncを実行しても、元のUID/GIDを可能な限り維持するための--usermap--groupmapオプションの仕組みと、実践的なマッピングルールの書き方がわかるようになります。つまびらかしい動作を理解せずに--numeric-idsを付けて「なんか動いた」で終わらせがちな現場を、少しだけ確実で保守しやすい形にします。

なお、本稿では送受信両方がLinuxで、rsync 3.1.0以降を想定します。

前提知識

  • rsyncの基本的なコマンド構造(ローカル→リモート、リモート→ローカルのいずれかを実行したことがある)
  • Linuxのファイル所有者/グループ、UID/GIDの概念を知っている
  • /etc/passwd/etc/groupの存在を知っている
  • リモートへログインする際のSSH接続ユーザーと、ファイルの所有者が「別物」であることを理解している

非rootユーザーでrsyncするときの「所有者消失」問題

rsyncはデフォルトで「ファイルの内容+メタデータ(タイムスタンプ・パーミッション・所有者・グループ)」を極力忠実に再現しようとします。ただし、リモートの受け入れプロセス(rsyncdあるいはssh経由のrsync)がrootで動いていない限り、「所有者・グループを変更する」という操作はOS的に許可されません。

そのため、リモート側の実行ユーザーがbackupだった場合、

  1. ファイルが届く
  2. 所有権を「元のUID/GID」にしようとする
  3. 権限がないので失敗
  4. 結果的に「backup:backup」になる

という流れが起きます。--numeric-idsを付けると「3.の操作を諦める」ため、見かけ上はUID/GIDが保存されたように見えますが、実態は「リモート側で意味のある数字」ではなく単なる数字の文字列です。環境が変われば同じUIDが別のユーザー名を指す危険性があります。

マッピングファイルを使って「見た目」と「実体」を一致させる

rsync 3.1.0以降では--usermap--groupmapが使えるため、「送り側のUID/GID → 受け側のUID/GID」という変換テーブルをあらかじめ与えることで、非rootユーザーでも「正しい所有者」を維持できます。

ステップ1 送り側でUID/GIDリストを作成

Bash
# 送り側(今回はローカル)で対象ディレクトリのUID/GIDを一覧化 find /var/www -printf '%U:%G %p\n' | \ awk '{print $1}' | sort -u > /tmp/idmap.src

中身は

0:0
33:www-data
1000:1000

のように、必要最小限の行だけが並んでいればOKです。

ステップ2 受け側で同じリストを作成

Bash
# リモート(受け側)で同様の操作 ssh backup@remote.example.com \ "find /srv/backup_mirror -printf '%U:%G\n' 2>/dev/null | sort -u" \ > /tmp/idmap.dst

ステップ3 マッピングルールを組み立てる

両リストを見比べて「送り側UID:GID → 受け側UID:GID」の対応を決め、rsyncへ渡す文字列を生成します。

Bash
# 0:0 は 0:0 でOK、33:www-data は 1005:1005 に割り当てる、など echo '0:0:0:0 33:www-data:1005:1005 1000:1000:1006:1006' | tr ' ' '\n' > /tmp/usergroup.map

フォーマットは

送り側UID:送り側GID:受け側UID:受け側GID

でスペースまたは改行区切りです。

ステップ4 rsync実行

Bash
rsync -av \ --usermap=$(awk -F: '{print $1":"$3}' /tmp/usergroup.map | tr '\n' ',' | sed 's/,$//') \ --groupmap=$(awk -F: '{print $2":"$4}' /tmp/usergroup.map | tr '\n' ',' | sed 's/,$//') \ /var/www/ backup@remote.example.com:/srv/backup_mirror/

ポイント

  • --usermapは「送り側UID:受け側UID」のカンマ区切り
  • --groupmapは「送り側GID:受け側GID」のカンマ区切り
  • 存在しないUID/GIDへのマッピングはエラーになるため、事前にadduser/addgroupで作っておくか、受け側で別のマッピングを用意する

ステップ5 結果を検証

リモートで

Bash
ssh backup@remote.example.com "ls -ln /srv/backup_mirror"

としてUID/GIDが意図通りか確認します。名前解決が気になる場合は

Bash
getent passwd 1005 # マッピング先のUIDを調べる

で名前が正しく表示されていればOKです。

ハマった点・エラー解決

  1. 「Operation not permitted」が出てマッピングできない
    → 受け側でchownする権限がない=UID/GIDの変更がOS的に不可。
    対策:
    - 受け側のユーザーにCAP_CHOWN capabilityを与える(setcap
    - あるいは、あきらめてnumeric-idで退避し、後でもう一度rootでchownし直す

  2. 「id mapping not found」警告が大量に出る
    → 送り側に存在するUID/GIDのうち、マッピングルールに載っていないものがあった。
    対策:
    - 予めfindで網羅的にリストアップする
    - デフォルトルールを*:*:1005:1005のようにワイルドカードで書ける(rsync 3.2.4以降)

  3. ファイル数が多いとマッピング文字列が長すぎてエラー
    → shellの上限(ARG_MAX)を超える。
    対策:
    - マッピング文字列をファイルに入れて--usermap=@fileのように@付きで読み込ませる

解決策(まとめ)

  • 非rootユーザーでも--usermap/--groupmapを使えば、所有者情報を「別のUID/GID」に再マップできる
  • 送り側・受け側で「UID/GIDの対応表」を事前に作っておくと、後々の復元が楽
  • 完全な退避を目指すなら、getfacl/setfaclでACLも合わせて保存する運用を検討する

まとめ

本記事では、rsyncでリモートが一般ユーザーでも「ファイルの所有者情報を正しく維持する」方法を解説しました。

  • 非rootではchownできないため、デフォルトでは実行ユーザーに上書きされがち
  • --numeric-idsは「数字は残るが環境依存で危険」
  • --usermapgroupmapを使うことで「送り側UID/GID → 受け側UID/GID」の明示的変換が可能
  • 大規模環境では@file形式でマッピングを外部ファイル化すると安全

この知識があれば、バックアップサーバーやCIデプロイ先が「rootログイン禁止」でも、ファイルの所有権を意図通りに保ちながらミラーリングできます。次回は「ACL・xattrも含めた完全な保存・リストア方法」について取り上げる予定です。

参考資料

  • rsync公式マニュアル(日本語訳)
    https://rsync.samba.org/documentation.html
  • 権限管理で躓いたら見る図解Linux
    https://www.oreilly.co.jp/books/9784873119544/
  • 実践的なrsyncテクニックまとめ
    https://www.redhat.com/sysadmin/rsync-techniques