Ansible で大量のファイルをコピーしたいときは copy よりも synchronize の方が早いですが、
- hosts: all become: yes tasks: - name: synchronize many files synchronize: src: many-files/ dest: /tmp/many-files/
これだけだと rsync の -a が指定された状態になるので不必要に changed になることがあります。
タイムスタンプ
rsync -a はタイムスタンプも同期されるため、ファイルのタイムスタンプが異なるだけで changed になります。Git はファイルのタイムスタンプは維持しないので、ソースをチェックアウトし直しただけで changed になってしまいます。
タイムスタンプを同期しないよう times: no にしつつ、比較でタイムスタンプが無視されるように checksum: yes を付けます。
- hosts: all become: yes tasks: - name: synchronize many files synchronize: src: many-files/ dest: /tmp/many-files/ checksum: yes times: no
UID/GID
rsync -a はリモート側が root で実行されていると(become: yes なり remote_user: root なり)ファイルの UID/GID も同期されるため、異なるホストで異なる UID/GID でチェックアウトしたソースだと changed になります。そのような運用は避けて Ansible を実行するための専用ホスト&アカウントを設ければ解決ではあるのですが、そうは言っても手元で実行したいこともあるでしょう。
そもそも Ansible で synchronize するときにローカルのファイルの UID/GID とターゲットホストのファイルの UID/GID を一致させたいということはあまりないでしょう(ターゲットが localhost だったり、後述のように delegate_to を使うなら別ですけど)。
ので、owner: no と group: no を付けて UID/GID が同期されないようにします。
- hosts: all become: yes tasks: - name: synchronize many files synchronize: src: many-files/ dest: /tmp/many-files/ checksum: yes times: no owner: no group: no
もしくは、rsync_opts で --chown=USER:GROUP でユーザー・グループを指定します。
- hosts: all become: yes tasks: - name: synchronize many files synchronize: src: many-files/ dest: /tmp/many-files/ checksum: yes times: no rsync_opts: - --chown=nobody:nobody
パーミッション
rsync -a はファイルのパーミッションも同期されます。Git は実行属性だけは維持されますがそれ以外の属性は維持されないので、チェックアウト時の umask が異なると changed になります。
ので、perms: no を付けてパーミッションが同期されないようにします。
- hosts: all become: yes tasks: - name: synchronize many files synchronize: src: many-files/ dest: /tmp/many-files/ checksum: yes times: no owner: no group: no perms: no
もしくは、rsync_opts で --chmod=CHMOD で指定します。
ファイルとディレクトリで固定の値を指定するなら次のように。
- hosts: all become: yes tasks: - name: synchronize many files synchronize: src: many-files/ dest: /tmp/many-files/ checksum: yes times: no rsync_opts: - --chown=nobody:nobody - --chmod=D755,F644
ファイルに実行属性付きのスクリプトなどが混在しているのなら次のように。
- hosts: all become: yes tasks: - name: synchronize many files synchronize: src: many-files/ dest: /tmp/many-files/ checksum: yes times: no rsync_opts: - --chown=nobody:nobody - --chmod=u=rwX,go=rX
むしろ archive: no にして必要なものだけ指定するほうが良いかもしれません。最低限なら次のように。
- hosts: all become: yes tasks: - name: synchronize many files synchronize: src: many-files/ dest: /tmp/many-files/ archive: no recursive: yes checksum: yes
UID/GID やパーミッションを同期させるなら次のように。
- hosts: all become: yes tasks: - name: synchronize many files synchronize: src: many-files/ dest: /tmp/many-files/ archive: no recursive: yes checksum: yes owner: yes group: yes perms: yes rsync_opts: - --chown=nobody:nobody - --chmod=u=rwX,go=rX
copy モジュールは mode とか owner とか group とかで指定しない限り同期されないわけなので、synchronize でも同期したいものだけ指定するほうがわかりやすいです。
use_ssh_args
本題とは関係ないのですが、synchronize には use_ssh_args というオプションがあり、これを指定すると下記などで指定された ssh のコマンドライン引数が rsync の ssh にも付与されます。
ansible.cfgの[ssh_connection]セクションのssh_args- 変数
ansible_ssh_common_args - 変数
ansible_ssh_extra_args
Bastion ホストを中継するために ansible_ssh_common_args などで -J bastion などとしているときは use_ssh_args: yes にしておかないとこの引数が追加されないため失敗します。
- hosts: all become: yes tasks: - name: synchronize many files synchronize: src: many-files/ dest: /tmp/many-files/ archive: no recursive: yes checksum: yes owner: yes group: yes perms: yes rsync_opts: - --chown=nobody:nobody - --chmod=u=rwX,go=rX use_ssh_args: yes
また、ドキュメントには無いのですが ssh_args で直接コマンドライン引数を指定できます。
https://github.com/ansible/ansible/blob/v2.9.2/lib/ansible/modules/files/synchronize.py#L407
- hosts: all become: yes tasks: - synchronize: src: many-files/ dest: /tmp/many-files/ archive: no recursive: yes checksum: yes owner: yes group: yes perms: yes rsync_opts: - --chown=nobody:nobody - --chmod=u=rwX,go=rX ssh_args: -C -J bastion
use_ssh_args: yes と併用すると use_ssh_args: yes が優先されます。というか use_ssh_args: yes によってモジュールの ssh_args オプションが設定される形なので ssh_args を直接は使わないほうが良いです。
https://github.com/ansible/ansible/blob/v2.9.2/lib/ansible/plugins/action/synchronize.py#L384-L390
接続の共通は使用されない
本題とは関係ないのですが、synchronize では ssh のコマンドラインに -S none が固定で追加されるため ControlMaster による接続の共有は使用されず、毎回新規に接続されます。
https://github.com/ansible/ansible/blob/v2.9.2/lib/ansible/modules/files/synchronize.py#L523
ssh_cmd = [module.get_bin_path('ssh', required=True), '-S', 'none']
これは下記の問題に対応するために追加されたようです。
プレイブックを実行する ssh と rsync の ssh が競合するため?
ControlMaster の接続の共有は1本の接続を複数の ssh で共有できるので大丈夫そうですけど・・なにが問題なのかは良くわかりませんでした。
delegate_to
本題とは関係ないのですが、synchronize モジュールは delegate_to を指定したときの動きが他のモジュールと異なります。
synchronize モジュールは、要するにローカルホスト上でローカルファイルをソースとしてターゲットホストを宛先に rsync するものですが、delegate_to を指定すると rsync を実行するローカルホストを別のホストに変更することができます。
例えば、copy だと delegate_to はコピーする先のターゲットホストになりますが、synchronize だと delegate_to はコピーする元のソースホストになります。つまり、delegate_to で指定したホストから本来のターゲットホストへの rsync になります。
- hosts: are become: yes tasks: - synchronize: src: /tmp/many-files/ dest: /tmp/many-files/ delegate_to: ore
この例では ore から are へ rsync されます。もちろん ore から are への ssh を通す必要があります。ansible.cfgの ssh_connection.ssh_args で -A を追加するなどしてエージェントフォワーディングを有効にするなどが必要です。