Ansible2.3->2.4でgroup_vas/host_varsの参照パスが変わっていた件について

# はじめに

Ansibleを2.3から2.4にバージョンアップしたら既存のPlaybookが動かなくなった！
group_vars/host_varsに定義してある変数が読み込めてないらしい。

```:エラー
"VARIABLE IS NOT DEFINED!"
```
Ansible2.3までは問題なく読み込めていたのに。。。

原因は`group_vars`と`host_vars`のディレクトリのパスでした。
個人的にハマったので書いておきます。

# Summary
- Ansible2.3->2.4でgroup_vars,host_varsの参照パスが変わった
  - 2.3まで
     - 起点となるplaybook（site.yml）からの相対パス
  - 2.4以降
     - インベントリファイルからの相対パス
  - [公式ドキュメント](https://docs.ansible.com/ansible/devel/porting_guides/porting_guide_2.4.html#initial-playbook-relative-group-vars-and-host-vars)

- group_vars/host_varsにグループ名/ホスト名のサブディレクトリを置くと、配下の任意のファイルに定義した変数を読み込める

# 本題

## 問題が発生した環境

### ディレクトリ構成
インベントリを環境ごとに分けているので、`inventories`配下に`環境名`としてインベントリファイルを置いていました。
group_varsやhost_varsなどのファイルの内容はイメージです。Roleは省略。

```:問題発生時のディレクトリ構成
├── ansible.cfg
├── group_vars
│   ├── web.yml
│   ├── db.yml
│   └── ...
├── host_vars
│   ├── staging-web01.yml
│   ├── staging-db01.yml
│   └── ...
├── inventories
│   ├── staging
│   ├── production
│   └── ...
├── playbooks
│   ├── web.yml
│   ├── db.yml
│   └── ...
└── site.yml
```

### ansible.cfg
```ini:ansible.cfg
[defaults]
hash_behaviour = merge
gathering = false

[privilege_escalation]
become = True
```

### インベントリ
```ini:inventories/staging
[web]
staging-web01

[db]
staging-db01
```

### group_vars
```yaml:group_vars/web.yml
---
my_vars:
  nginx:
    server_name: www.example.com
```

### host_vars
```yaml:host_vars/staging-web01.yml
---
my_vars:
  db:
    host: staging-db01
    user: dbuser
    pass: STAGING_PASS
```

### 実行コマンド
```bash
$ ansible-playbook -i inventories/staging -l web site.yml
```

### 実行するとエラー
```:エラーメッセージ
"VARIABLE IS NOT DEFINED!"
```
## 検証

結論から言うと、[公式ドキュメント](https://docs.ansible.com/ansible/devel/porting_guides/porting_guide_2.4.html#initial-playbook-relative-group-vars-and-host-vars)に書いてありました。
> Similar functionality can still be achieved by using vars_files, include_vars, or group_vars and host_vars placed relative to the inventory file.

インベントリファイルからの相対パスになった模様。。。

また、[こちら](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#splitting-out-host-and-group-specific-data)には

> As an advanced use case, you can create directories named after your groups or hosts, and Ansible will read all the files in these directories in lexicographical order. 

group_vars/host_varsディレクトリ配下にグループ名/ホスト名のサブディレクトリがあれば、その中に置いた複数のファイルの読み込みも可能とあります。これは知らなかった。

せっかくなのであわせて各バージョンでの挙動を調べてみます。

### 検証用環境
検証用の環境は以下の通り。
- 起点となるplaybook(site.yml)と同じ階層にインベントリファイルと`group_vars`, `host_vars`ディレクトリを置く
- サブディレクトリにインベントリファイルと`group_vars`, `host_vars`ディレクトリを置く
上記2パターンを同居させ、それぞれに書いたvarsがどう読み込まれるか試してみました。

それぞれの変数には読み込まれたファイルがわかるように、自身のパスを書いておきます。

#### ディレクトリ構成

```
├── ansible.cfg
├── group_vars
│   ├── group1
│   │   └── any.yml
│   └── group1.yml
├── host_vars
│   ├── host1
│   │   └── any.yml
│   └── host1.yml
├── hosts           
├── inventories
│   ├── group_vars
│   │   ├── group1
│   │   │   └── any.yml
│   │   └── group1.yml
│   ├── host_vars
│   │   ├── host1
│   │   │   └── any.yml
│   │   └── host1.yml
│   └── hosts       
├── playbooks
│   └── load_vars_test.yml
└── site.yml
```

#### ansible.cfg
```ini:ansible.cfg
[defaults]
display_skipped_hosts = True
display_args_to_stdout = True
hash_behaviour = merge
gathering = false

[privilege_escalation]
become = True
```
#### インベントリ(hosts, inventories/hosts)
```ini:hosts,inventories/hosts
[group1]
host1 ansible_host=localhost
```

#### site.yml
```yaml:site.yml
---
- include: playbooks/load_vars_test.yml
```

#### playbook

`debug`モジュールで、実行時に読み込んだgroup_varsとhost_varsを表示させます。

```yaml:playbooks/load_vars_test.yml
---
- name: load vars test
  hosts: all
  tasks:
    - name: load group_vars
      debug: var=groupvar
    - name: load host_vars
      debug: var=hostvar
```

#### site.ymlと同階層のgroup_vars
```yaml:group_vars/group1.yml
---
groupvar: group_vars/group1.yml
```
#### site.ymlと同階層のgroup_vars(グループ名サブディレクトリ配下のファイル)
```yaml:group_vars/group1/any.yml
---
groupvar: group_vars/group1/any.yml
```
#### site.ymlと同階層のhost_vars
```yaml:host_vars/host1.yml
---
hostvar: host_vars/host1.yml
```
#### site.ymlと同階層のhost_vars(ホスト名サブディレクトリ配下のファイル)
```yaml:host_vars/host1/any.yml
---
hostvar: host_vars/host1/any.yml
```
#### インベントリファイルを置いたサブディレクトリのgroup_vars
```yaml:inventories/group_vars/group1.yml
---
groupvar: inventories/group_vars/group1.yml
```
#### インベントリファイルを置いたサブディレクトリのgroup_vars(グループ名サブディレクトリ配下のファイル)
```yaml:inventories/group_vars/group1/any.yml
---
groupvar: inventories/group_vars/group1/any.yml
```
#### インベントリファイルを置いたサブディレクトリのhost_vars
```yaml:inventories/host_vars/host1.yml
---
hostvar: inventories/host_vars/host1.yml
```
#### インベントリファイルを置いたサブディレクトリのhost_vars(ホスト名サブディレクトリ配下のファイル)
```yaml:inventories/host_vars/host1/any.yml
---
hostvar: inventories/host_vars/host1/any.yml
```


### group_vars/host_varsの参照パスについて検証

group_vars/host_vars内のサブディレクトリは一旦リネームして除外しておきます。

#### 1. インベントリファイルをsite.ymlと同じディレクトリに格納している場合
##### Ansible2.3
```bash
$ ansible-playbook -i hosts site.yml

PLAY [load vars test] **********************************************************

TASK [load group_vars var=groupvar] ********************************************
ok: [host1] => {
    "groupvar": "group_vars/group1.yml"
}

TASK [load host_vars var=hostvar] **********************************************
ok: [host1] => {
    "hostvar": "host_vars/host1.yml"
}
```
site.ymlと同階層のgroup_vars/host_varsが読み込まれました。

##### Ansible2.4以降
```bash
$ ansible-playbook -i hosts site.yml

PLAY [load vars test] **********************************************************

TASK [load group_vars var=groupvar] ********************************************
ok: [host1] => {
    "groupvar": "group_vars/group1.yml"
}

TASK [load host_vars var=hostvar] **********************************************
ok: [host1] => {
    "hostvar": "host_vars/host1.yml"
}
```
こちらもsite.ymlと同階層のgroup_vars/host_varsが読み込まれました。
2.3と2.4以降で結果的に同じ挙動になります。

#### 2. インベントリファイルをサブディレクトリに格納している場合
##### Ansible2.3
```bash
$ ansible-playbook -i inventories/hosts site.yml

PLAY [load vars test] **********************************************************

TASK [load group_vars var=groupvar] ********************************************
ok: [host1] => {
    "groupvar": "group_vars/group1.yml"
}

TASK [load host_vars var=hostvar] **********************************************
ok: [host1] => {
    "hostvar": "host_vars/host1.yml"
}
```
先ほどと同じく、site.ymlと同階層のgroup_vars/host_varsが読み込まれました。
つまりインベントリファイルのパスは無関係ということです。

##### Ansible2.4以降
```bash
$ ansible-playbook -i inventories/hosts site.yml

PLAY [load vars test] **********************************************************

TASK [load group_vars var=groupvar] ********************************************
ok: [host1] => {
    "groupvar": "inventories/group_vars/group1.yml"
}

TASK [load host_vars var=hostvar] **********************************************
ok: [host1] => {
    "hostvar": "inventories/host_vars/host1.yml"
}
```
たしかにインベントリファイルを置いたサブディレクトリ内のgroup_vars/host_varsが読み込まれています。

#### group_vars/host_varsにサブディレクトリを作り、group_varsを記載したファイルを置いた場合の検証

サブディレクトリは`group_vars/GROUP_NAME`,`host_vars/HOST_NAME`のように、`グループ名/ホスト名`にする必要があります。サブディレクトリ内のファイル名は任意です。

```bash:サブディレクトリあり
$ ansible-playbook -i hosts site.yml

PLAY [load vars test] **********************************************************

TASK [load group_vars var=groupvar] ********************************************
ok: [host1] => {
    "groupvar": "group_vars/group1/any.yml"
}

TASK [load host_vars var=hostvar] **********************************************
ok: [host1] => {
    "hostvar": "host_vars/host1/any.yml"
}
```
サブディレクトリに置いたファイルの変数が読み込まれています。
ちなみに、この挙動はver2.3〜2.7まで共通でした。

ここで、group_vars/host_varsのサブディレクトリをグループ名/ホスト名からリネームすると

```bash:サブディレクトリをリネーム
$ ansible-playbook -i hosts site.yml

PLAY [load vars test] **********************************************************

TASK [load group_vars var=groupvar] ********************************************
ok: [host1] => {
    "groupvar": "group_vars/group1.yml"
}

TASK [load host_vars var=hostvar] **********************************************
ok: [host1] => {
    "hostvar": "host_vars/host1.yml"
}
```
group_vars/host_varsのファイルが読み込まれました。サブディレクトリの方が優先度が高いようです。


## まとめ
### Ansible2.3

- トップレベルplaybookのディレクトリからの相対パス
- インベントリファイルのパスは関係ない
- group_vars,host_varsディレクトリのサブディレクトリ（グループ名/ホスト名）のYAMLファイル（ファイル名は任意）が優先、なければ直下のYAMLファイル（グループ名/ホスト名）を読む

#### group_vars
1. TOPLEVEL-PLAYBOOK-DIR/group_vars/GROUP_NAME/ANY.yml
1. TOPLEVEL-PLAYBOOK-DIR/group_vars/GROUP_NAME.yml

#### host_vars
1. TOPLEVEL-PLAYBOOK-DIR/host_vars/HOST_NAME/ANY.yml
1. TOPLEVEL-PLAYBOOK-DIR/host_vars/HOST_NAME.yml


### Ansible2.4以降（2.5,2.6,2.7も同様の挙動）

- インベントリファイルのディレクトリからの相対パス

#### group_vars
1. INVENTORYFILE-DIR/group_vars/GROUP_NAME/ANY.yml
1. INVENTORYFILE-DIR/group_vars/GROUP_NAME.yml

#### host_vars
1. INVENTORYFILE-DIR/host_vars/HOST_NAME/ANY.yml
1. INVENTORYFILE-DIR/host_vars/HOST_NAME.yml