rsync -Rで中間ディレクトリがシンボリックリンクな場合の注意点

複数サーバを管理する場合、管理コストの増加やオペレーションミスを避けるための施策として、「すべてのサーバの内容を同一に保つ」という管理方法があります。

サーバの内容を同一に保つには、小中規模ならrsyncと、パス指定の簡便化とミスを防ぐために-a -R (--relative)オプションを使うのがベストでしょう。

ただ、-Rを指定しているときに、同期コピー対象のパスにディレクトリへのシンボリックを含んでいる場合には注意が必要です。そこで今回はrsync 2.6とrsync 3のそれぞれについて、ふたつのケースについて検証してみたいと思います。

ケース1: 実ディレクトリが同じ

物理的なディレクトリ構造をそのまま見せるのではなく、論理的な意味あるディレクトリ名/構造として見せたい場合には、ディレクトリのシンボリックリンクを使うとよいです。

例えば:

RAID等のストレージにあるディレクトリの見せ方を変えたい

$ ls -F /raid/
photo/  mysql/
$ readlink -f /usr/irori/contents/service-A/data/photo/
/raid/photo/
$ readlink -f /var/lib/mysql/
/raid/mysql/

EBSブートなEC2インスタンスにおいて、エフェメラルディスクの見せ方を変えたい

# mount /dev/sda2 /var/ephemeral
# mkdir /var/ephemeral/servers/
# echo 'AP_SERVERS=("ap1" "ap2")' > /var/ephemeral/servers/ap
# ln -s /var/ephemeral/servers /usr/irori/etc/servers


さて、検証のため、/var/tmp/ の下に実ディレクトリreal_dirとreal_dirへのシンボリックリンクlink_dir、それからreal_dirの下にファイルfoo、を作ってみます。

sv1$ cd /var/tmp
sv1$ rm -fr real_dir link_dir; mkdir real_dir && hostname > real_dir/foo && ln -s real_dir link_dir

sv1からsv2へファイルfooをコピーする場合、

rsync -avR /var/tmp/real_dir/foo sv2:/
もしくは
rsync -avR /var/tmp/link_dir/foo sv2:/

と実行しますが、ここでは後者の、パスに実ディレクトリへのシンボリックリンク(link_dir)を含む場合について検証します。

予めsv1とsv2の両方で先ほどのように/var/tmpの下に同じディレクトリ/ファイルを作っておきます。

まず rsync 2.6.9 の場合、

sv1$ rsync -avR /var/tmp/link_dir/foo sv2:/

パーミッション関係のエラー等出ると思いますが、sv2上ではディレクトリ構造はそのままで、ファイルfooの内容だけが(sv1の内容に)変わります。

次に rsync 3.0.7 を使って同じように実行すると…なんとsv2上のlink_dirが実ディレクトリになり、実ディレクトリになったlink_dirの下のファイルfooだけが更新されてしまいます。これは期待した結果ではありません。

sv2$ ls -ld link_dir
drwxrwxr-x 2 hirose31 hirose31 4096 Apr 28 15:36 link_dir # 実ディレクトリになってる
sv2$ cat link_dir/foo # コピー元の内容になってる
sv1
sv2$ cat real_dir/foo # こっちは変更されてない。
sv2

さて、実際の運用では複数あるサーバ名を意識しなくていいように、同期対象のサーバのリストは外部変数にしておいて、for で回すなりシェル関数を作って簡単に実行できるようにするのが定石でしょう。

SYNC_SERVERS=('sv1' 'sv2')

for sv in ${SYNC_SERVERS[@]}; do
  rsync -avR /var/tmp/link_dir/foo $sv:/
done
  とか
SYNC_SYNC() {
  for f "$@"; do
    for sv in ${SYNC_SERVERS[@]}; do
      rsync -avR $f $sv:/
    done
  done
}
SYNC_SYNC /var/tmp/link_dir/foo

このとき同期対象サーバのリストには自分自身も含まれていることになります。

では、rsync 3.0.7 で自分自身にrsyncしてみましょう…

sv1$ rsync -avR /var/tmp/link_dir/foo sv1:/ # ←sv1でsv1にrsyncする
sending incremental file list
/var/tmp/link_dir/
file has vanished: "/var/tmp/link_dir/foo"
...

なにか不穏なメッセージが出ましたね… sv1上のディレクトリをのぞいてみると…

sv1$ ls -ld link_dir
drwxrwxr-x 2 hirose31 hirose31 4096 Apr 28 15:36 link_dir # 実ディレクトリになってる
sv1$ cat link_dir/foo
cat: link_dir/foo: No such file or directory # ファイルが無くなってる!!
sv1$ cat real_dir/foo                        # こっちは変更されてない。
sv1

先にsv2にrsyncしたときと同じように、link_dirが実ディレクトリ化しているのですが、それに加えてlink_dirの下のファイルfooが消されてしまっています。

rsync 3.0.0のリリースノートによれば、rsync 3からパスの中間の暗黙的なディレクトリは、必ず実ディレクトリとして送り手から受け手に送られるようになったのですが、この仕様変更が先の現象の原因です。

rsync 3 でこの問題を回避するには、-K (--keep-dirlinks)オプションを指定すればとりあえずはいいのですが、manによれば-Kはセキュリティ上の危険性があるので、信頼できる環境においてのみ使用するか、もしくはシンボリックリンクではなくbind mountを使ってディレクトリの見せ方を変えるようにした方がいいだろうと書かれています。

ケース2: 実ディレクトリが異なる

ケース1は実ディレクトリが複数サーバ間で同じでしたが、ハードウエア構成(ディスクが大きいとか小さいとか)の理由などで、シンボリックリンク元の実ディレクトリがサーバ間で異なる場合もあるかと思います。

sv1$ readlink -f /usr/irori/contents/service-A/data/photo/
/raid/photo/

sv2$ readlink -f /usr/irori/contents/service-A/data/photo/
/ssd/photo/

これも検証のため、sv1とsv2で異なる名前の実ディレクトリとシンボリックリンクを作って試してみましょう。

sv1$ cd /var/tmp
sv1$ rm -fr real_dir* link_dir; mkdir real_dir_sv1 && hostname > real_dir_sv1/foo && ln -s real_dir_sv1 link_dir
sv2$ cd /var/tmp
sv2$ rm -fr real_dir* link_dir; mkdir real_dir_sv2 && hostname > real_dir_sv2/foo && ln -s real_dir_sv2 link_dir

まずはrsync 3でsv1からsv2へ。

sv1$ rsync -avR /var/tmp/link_dir/foo sv2:/

sv2$ ls -ld link_dir
drwxrwxr-x 2 hirose31 hirose31 4096 Apr 28 15:36 link_dir # 実ディレクトリになってる
sv2$ cat link_dir/foo # コピー元の内容になってる
sv1
sv2$ cat real_dir_sv2/foo # こっちは変更されてない。
sv2

続いてrsync 3でsv1からsv1(自分自身)へ。

sv1$ rsync -avR /var/tmp/link_dir/foo sv2:/

sv1$ ls -ld link_dir
drwxrwxr-x 2 hirose31 hirose31 4096 Apr 28 15:36 link_dir # 実ディレクトリになってる
sv1$ cat link_dir/foo
cat: link_dir/foo: No such file or directory # ファイルが無くなってる!!
sv1$ cat real_dir_sv1/foo                    # こっちは変更されてない。
sv1

結果はケース1の場合と同じで、これも同じく -K を指定すれば問題回避ができます。


さて、ケース1では問題がなかった rsync 2.6.9 でどうなるか試してみましょう。

まず、sv1からsv1(自分自身)への場合は問題ありません(ファイルfooは変更されないしディレクトリ構造も変わらない)。

ではsv2へのコピーの場合はどうなるでしょうか。

sv1$ rsync -avR /var/tmp/link_dir/foo sv2:/

sv2$ ls -ld link_dir
lrwxrwxrwx 1 hirose31 hirose31 12 Apr 28 16:13 link_dir -> real_dir_sv1
   # シンボリックリンクのままだが、リンク元が(sv2には存在しない)real_dir_sv1になってしまっている
sv2$ cat link_dir/foo
cat: link_dir/foo: No such file or directory # 当然存在しない
sv2$ cat real_dir_sv2/foo                    # こっちは変更されてない。
sv2

なんと今度はシンボリックリンクリンク元が送り手(sv1)側のものに変わってしまいました。

これもまた暗黙的なディレクトリが絡む問題で、--no-implied-dirs オプションを指定すれば回避できるようです。

まとめ

長々と書きましたが、自分の場合は

ことにしました。


てか、いきなりファイル消える(ケース1のrsync 3で自分自身への場合)とビビりますねー