はじめに
この記事は、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だった場合、
- ファイルが届く
- 所有権を「元のUID/GID」にしようとする
- 権限がないので失敗
- 結果的に「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実行
Bashrsync -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 結果を検証
リモートで
Bashssh backup@remote.example.com "ls -ln /srv/backup_mirror"
としてUID/GIDが意図通りか確認します。名前解決が気になる場合は
Bashgetent passwd 1005 # マッピング先のUIDを調べる
で名前が正しく表示されていればOKです。
ハマった点・エラー解決
-
「Operation not permitted」が出てマッピングできない
→ 受け側でchownする権限がない=UID/GIDの変更がOS的に不可。
対策:
- 受け側のユーザーにCAP_CHOWNcapabilityを与える(setcap)
- あるいは、あきらめてnumeric-idで退避し、後でもう一度rootでchownし直す -
「id mapping not found」警告が大量に出る
→ 送り側に存在するUID/GIDのうち、マッピングルールに載っていないものがあった。
対策:
- 予めfindで網羅的にリストアップする
- デフォルトルールを*:*:1005:1005のようにワイルドカードで書ける(rsync 3.2.4以降) -
ファイル数が多いとマッピング文字列が長すぎてエラー
→ shellの上限(ARG_MAX)を超える。
対策:
- マッピング文字列をファイルに入れて--usermap=@fileのように@付きで読み込ませる
解決策(まとめ)
- 非rootユーザーでも
--usermap/--groupmapを使えば、所有者情報を「別のUID/GID」に再マップできる - 送り側・受け側で「UID/GIDの対応表」を事前に作っておくと、後々の復元が楽
- 完全な退避を目指すなら、
getfacl/setfaclでACLも合わせて保存する運用を検討する
まとめ
本記事では、rsyncでリモートが一般ユーザーでも「ファイルの所有者情報を正しく維持する」方法を解説しました。
- 非rootでは
chownできないため、デフォルトでは実行ユーザーに上書きされがち --numeric-idsは「数字は残るが環境依存で危険」--usermap・groupmapを使うことで「送り側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
