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
を追加するなどしてエージェントフォワーディングを有効にするなどが必要です。