Ansible の synchronize でローカルで削除したファイルを削除

Apache の `/etc/httpd/conf.d/*.conf` のような、所定の形式の名前でファイルを置いておけば読まれるようになる設定ファイルを対象サーバにコピーしたい場合、`copy` モジュールでのディレクトリコピーや `with_fileglob` との組み合わせが便利です。

例えば次のようなディレクトリ構成で、

```
ansible.cfg
hosts
httpd.yml
files/
  etc/
    httpd/
      conf.d/
        aaa.conf
        bbb.conf
```

次の Playbook を、

```yaml
---
- hosts: all
  sudo: yes
  tasks:
    - copy: src={{ item }} dest=/etc/httpd/conf.d/
      with_fileglob:
        - files/etc/httpd/conf.d/*.conf
      notify:
        - httpd check
        - httpd reload

  handlers:
    - name: httpd check
      shell: httpd -t

    - name: httpd reload
      service: name=httpd state=reloaded
```

実行すると、

```console
$ ansible-playbook httpd.yml

PLAY [all] ********************************************************************

TASK: [copy src={{ item }} dest=/etc/httpd/conf.d/] ***************************
changed: [192.0.2.123] => (item=/path/to/ansible/files/etc/httpd/conf.d/aaa.conf)
changed: [192.0.2.123] => (item=/path/to/ansible/files/etc/httpd/conf.d/bbb.conf)

NOTIFIED: [httpd check] *******************************************************
changed: [192.0.2.123]

NOTIFIED: [httpd reload] ******************************************************
changed: [192.0.2.123]

PLAY RECAP ********************************************************************
192.0.2.123              : ok=3    changed=3    unreachable=0    failed=0
```

対象サーバに `conf.d` の中身がコピーされます。

```console
$ ls /etc/httpd/conf.d/
aaa.conf  bbb.conf
```

`with_fileglob` を使わなくても `src` でディレクトリを指定すれば大体同じことができます。

```yaml
---
- hosts: all
  sudo: yes
  tasks:
    - copy: src=files/etc/httpd/conf.d/ dest=/etc/httpd/conf.d/
      notify:
        - httpd check
        - httpd reload

  handlers:
    - name: httpd check
      shell: httpd -t

    - name: httpd reload
      service: name=httpd state=reloaded
```

ただ、この方法だとどのファイルに変更があったかがわからないのであまり使いません。


```console
$ ansible-playbook httpd.yml

PLAY [all] ********************************************************************

TASK: [copy src=files/etc/httpd/conf.d/ dest=/etc/httpd/conf.d/] ******************
changed: [192.0.2.123]

NOTIFIED: [httpd check] *******************************************************
changed: [192.0.2.123]

NOTIFIED: [httpd reload] ******************************************************
changed: [192.0.2.123]

PLAY RECAP ********************************************************************
192.0.2.123              : ok=3    changed=3    unreachable=0    failed=0
```

----

とても便利な `copy` モジュールですが、ローカルで削除したファイルを対象サーバでも自動的に削除したりすることはできません。

例えば、bbb.conf を削除したとして、

```
ansible.cfg
hosts
httpd.yml
files/
  etc/
    httpd/
      conf.d/
        aaa.conf
```

Playbook を実行しても削除はされません。

```console
$ ansible-playbook httpd.yml

PLAY [all] ********************************************************************

TASK: [copy src={{ item }} dest=/etc/httpd/conf.d/] ***************************
ok: [192.0.2.123] => (item=/path/to/ansible/files/etc/httpd/conf.d/aaa.conf)

PLAY RECAP ********************************************************************
192.0.2.123              : ok=1    changed=0    unreachable=0    failed=0
```

この状況で bbb.conf を対象サーバから削除したければ `file` モジュールで明示的に指定する必要があります。

```yaml
---
- hosts: all
  sudo: yes
  tasks:
    - copy: src={{ item }} dest=/etc/httpd/conf.d/
      with_fileglob:
        - files/etc/httpd/conf.d/*.conf
      notify:
        - httpd check
        - httpd reload

    - file: path={{ item }} state=absent
      with_items:
        - /etc/httpd/conf.d/bbb.conf
      notify:
        - httpd check
        - httpd reload

  handlers:
    - name: httpd check
      shell: httpd -t

    - name: httpd reload
      service: name=httpd state=reloaded
```

```console
$ ansible-playbook httpd.yml

PLAY [all] ********************************************************************

TASK: [copy src={{ item }} dest=/etc/httpd/conf.d/] ***************************
ok: [192.0.2.123] => (item=/path/to/ansible/files/etc/httpd/conf.d/aaa.conf)

TASK: [file path={{ item }} state=absent] *************************************
changed: [192.0.2.123] => (item=/etc/httpd/conf.d/bbb.conf)

NOTIFIED: [httpd check] *******************************************************
changed: [192.0.2.123]

NOTIFIED: [httpd reload] ******************************************************
changed: [192.0.2.123]

PLAY RECAP ********************************************************************
192.0.2.123              : ok=4    changed=3    unreachable=0    failed=0
```

----

さすがにこれだとめんどくさいしモレもありそうなので、`synchronize` モジュールを使ってローカルから削除されたファイルは対象サーバでも自動的に削除されるようにしました。

`synchronize` でやりたいことはファイルの削除だけなので、余計なことが行われないように `archive: no` や `rsync_opts: [ --ignore-existing ]` を指定しています。

```yaml
---
- hosts: all
  sudo: yes
  tasks:
    - copy: src={{ item }} dest=/etc/httpd/conf.d/
      with_fileglob:
        - files/etc/httpd/conf.d/*.conf
      notify:
        - httpd check
        - httpd reload

    - synchronize:
        src: files/etc/httpd/conf.d/
        dest: /etc/httpd/conf.d/
        archive: no
        delete: yes
        dirs: yes
        rsync_opts: [ --ignore-existing ]
      notify:
        - httpd check
        - httpd reload

  handlers:
    - name: httpd check
      shell: httpd -t

    - name: httpd reload
      service: name=httpd state=reloaded
```

```console
$ ansible-playbook httpd.yml

PLAY [all] ********************************************************************

TASK: [copy src={{ item }} dest=/etc/httpd/conf.d/] ***************************
ok: [192.0.2.123] => (item=/path/to/ansible/files/etc/httpd/conf.d/aaa.conf)

TASK: [synchronize ] **********************************************************
changed: [192.0.2.123 -> 127.0.0.1]

NOTIFIED: [httpd check] *******************************************************
changed: [192.0.2.123]

NOTIFIED: [httpd reload] ******************************************************
changed: [192.0.2.123]

PLAY RECAP ********************************************************************
192.0.2.123              : ok=4    changed=3    unreachable=0    failed=0
```

対象サーバで確認してみると、bbb.conf が削除されていることがわかります。

```console
$ ls /etc/httpd/conf.d/
aaa.conf
```

## copy と synchronize を併用する理由

`copy` を使わずに `synchronize` だけで設定ファイルをコピー＆削除することも可能です。

が、それだと、

- どのファイルに変更があったか
- `--check --diff` で差分の事前確認

ができないので、併用するようにしてみました（対象サーバの台数が多くなるとそんなのいちいち見ていられないのでしょうけど）。