state:absentとpath:/directoryの挙動

Ansible Version: 2.3

Ansibleのfileモジュールのstateでabsentを指定すると、pathがディレクトリの場合、そのディレクトリの下にファイルがあったとしても削除されてしまう。

If absent, directories will be recursively deleted

参照: https://docs.ansible.com/ansible/latest/modules/file_module.html

つまり、シェルコマンドでいうとディレクトリの中が空の場合に削除してくれるrmdirではなくrm -rfを実行しているのと同じであり、安易に使用するとデータを失いかねない。

障害ケース

例えばMySQLやMongoDBなどのインストールとセットアップのために以下のようなtaskを作成する。

## 1. yum installする
- name: install
  yum:
    name: "{{ item }}"
    state: installed
    enablerepo: "mysql57-community"
  with_items:
    - mysql-community-server
    - mysql-community-devel
## /var/lib/mysqlが自動で作成される

## 2. データ領域を外部ディスクに作成するため、/var/lib/mysqlのディレクトリを消す
- stat:
    path: /var/lib/mysql
  register: mysql_dir
- file:
    path: /var/lib/mysql
    state: absent
  when:
    - mysql_dir.stat.isdir is defined
    - mysql_dir.stat.isdir

## 3. 外部ディスクへ/var/lib/mysqlからsymlinkをはる
- file:
    src: /DISK01/mysql
    dest: /var/lib/mysql
    state: link

## 4. MySQLを起動する
- service:
    name: mysqld
    state: started
    enabled: yes
## /var/lib/mysqlにデータが作成される

このplaybookであれば何度実行してもmysql_dir.stat.isdirの条件により初回以外はsymlinkになっている/var/lib/mysqlは削除されない。しかし、運用していく中で、外部ディスクを外して/var/lib/mysqlに直接データを置くような作業を手動で実行してしまった場合など、/var/lib/mysqlがsymlinkからディレクトリに戻ってしまったときにAnsibleを再実行するとMySQLのデータ丸ごと削除してしまう。

手動で構成変更しているのが悪いと言えば悪いのだが、playbookにバグを作りこんでしまう可能性もあるだろうし、重要なデータがある箇所でrm -rfになりかねないstate: absentを使わない方がいいだろう。

ワークアラウンド

shellモジュールを使ってディレクトリの中が空であれば(念のために再帰的に)削除するようにする。再帰的に削除する方法は空のディレクトリを再帰的に消すを参考にfindの-empty -deleteを使う。特に再帰的に削除しなくてよければrmdir /var/lib/mysqlでも全く問題ない。

## 2. データ領域を外部ディスクに作成するため、/var/lib/mysqlのディレクトリを消す
- stat:
    path: /var/lib/mysql
  register: mysql_dir
- shell: |
    find /var/lib/mysql -type d -empty -delete
  when:
    - mysql_dir.stat.isdir is defined
    - mysql_dir.stat.isdir