Ansible: 変数の謎を追う

# はじめに

Ansible初心者のたわごとです。
詳しい方がみれば何を当たり前のことをと思われるかも知れませんがご容赦ください。

# Ansibleとは

- ansibleはタスクを定義して複数のホストでそれを実行させるプラットフォームです。
- 複数のタスクをまとめて Playbook とすることで、大量のホストに複雑なことをやらせることができます。
- 対象ホストにsshでログインできて、そこにPythonが入っていれば、あとはなんとかなるというものです。
- タスクは手続き型の記述ではなく、あるべき状態を示すので、正しく動作すればべき等性が保証されます。
- タスクはベタッとPlaybookの中に書くこともできますが、それを分割することもできます。C言語のようにincludeで引っ張り込むこともできますが、役割(role)ごとに分割するのが一般的です。

# 変数を使ったタスクの実行

Ansibleでは変数を定義することで、同様のタスクをいろんなホストにていろんなパラメータて実行することができます。一番単純な例として、変数を一つ定義して参照してみます。(認証周りのセットアップは省略してます、ご了承ください)

```yaml:test.yml
hosts: all
vars:
  foo: "this is test."
tasks:
 - name: test
   debug:
     msg: "{{ foo }}"
```

これを実行してみます。

```shell-session
$ echo localhost > host.local
$ ansible-playbook -i host.local test.yml

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

TASK [Gathering Facts] ******************************************************************************************************************************************
ok: [localhost]

TASK [test] *****************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "this is test."
}

PLAY RECAP ******************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

```

これはあちこちのブログでも例として掲示されています。これだけですべてわかるなら苦労はしないわけで、次行ってみます。

# Ansibleは変数がキモ

Ansibleを使ってるとうまく動かないことがあってデバッグするわけですが、全変数の内容を吐き出せれば、自分が何を間違ってるのかの把握がしやすくなります。全変数の吐き出し方は下記のようにします。

```yaml
- hosts: all
  tasks:
    - name: Dump all variables
      debug:
        msg: "{{ vars }}"
```

実行すると山盛りもりもり吐き出されます。
多すぎるのでリダイレクトするなりしてファイルに落としてから眺めるほうがいいと思います。

なお、システムが用意する変数だけを出力させる場合は下記のコマンドが使えるようです。

```
ansible -i hosts -m setup ホスト名
```

# あちこちで変数を定義して、どこに差し込まれるか観察してみた

Ansibleではいろんなところに変数を定義することができます。どこに定義して何がどうなるのか把握せずに書いてると訳がわからなくなるので、調べてみました。

1. Playbookに直接
1. `vars_files`のファイルの中
1. `group_vars/all`
1. `host_vars/ホスト名`

```yaml:test.yml
- hosts: all
  vars:
    foo1: "this is test.(vars)"
  vars_files:
   - vars.yml
  tasks:
   - name: test
     debug:
       msg: "{{ vars }}"
```

```yaml:vars.yml
foo2: "this is test (vars.yml)"
```

```yaml:group_vars/all
foo3: "this is test (group_vars/all)"
```

```yaml:host_vars/localhost
foo4: "this is test (host_vars/localhost)"
```

実行して吐かれた内容を確認してみます。全部だと果てしないので、かなーーり省略してます。

```json
{
        "ansible_all_ipv4_addresses": [
        ...
        },
        "environment": [],
        "foo1": "this is test (vars)",
        "foo2": "this is test (vars.yml)",
        "foo3": "this is test (group_vars/all)",
        "foo4": "this is test (host_vars/localhost)",
        ...
        "hostvars": {
            "localhost": {
                "ansible_all_ipv4_addresses": [
                ...
                },
                "discovered_interpreter_python": "/usr/bin/python3",
                "foo3": "this is test (group_vars/all)",
                "foo4": "this is test (host_vars/localhost)",
                ...
            }
        },
}
```

`{{ foo4 }}` と書いている`tasks`や`roles`が参照してるのは、上のほう(ホストによらないグローバルな変数)を参照していると思われます。

# 複数グループ、複数ホストを用意してみる

推測ですが、ホストによらないグローバル変数の値がホストごとに書き換わっているのではないかと思います。同じ変数名で値を変えてあちこちで定義して、どうなるか観察してみます。

```ini:hosts
[GroupA]
hostA
hostB

[GroupB]
hostC
hostD

[GroupC]
hostE
hostF
```

```yaml:test.yml
- hosts: all
  vars:
    foo: "this is test.(vars)"
  vars_files:
   - vars.yml
  tasks:
   - name: test
     debug:
       msg: "{{ vars }}"
```

```yaml:vars.yml
foo: "this is test (vars.yml)"
```

```yaml:group_vars/all
foo: "this is test (group_vars/all)"
```

```yaml:group_vars/GroupA
foo: "this is test (group_vars/GroupA)"
```

```yaml:group_vars/GroupB
foo: "this is test (group_vars/GroupB)"
```

```yaml:host_vars/hostA
foo: "this is test (host_vars/hostA)"
```

```yaml:host_vars/hostB
foo: "this is test (host_vars/hostB)"
```

```yaml:host_vars/hostC
foo: "this is test (host_vars/hostC)"
```

```yaml:host_vars/hostE
foo: "this is test (host_vars/hostE)"
```
想像としては、グローバルの値が下記ではないかと考えています。

- `hostA`: hostAの定義
- `hostB`: hostBの定義
- `hostC`: hostCの定義
- `hostD`: GroupBの定義
- `hostE`: hostEの定義
- `hostF`: group_vars/allの定義

では実行。

### 実行してみた(1)

`hostA`の結果から見てみます。

```json
{
        ...
        "environment": [],
        "foo": "this is test (vars.yml)",
        ...
}
```

あれ? そうなの? じゃあ `msg: "{{ foo }}" にしてみたらどうなるか見てみます。

```
ok: [hostA] => {
    "msg": "this is test (vars.yml)"
}
```

あらー。ってことは、ホストごとに違う値を入れたい変数と同じ名前の変数を`vars.yml`で定義しちゃいけないと。`vars.yml`をどけたらどうなるか見てみます。

```
ok: [hostA] => {
    "msg": "this is test (vars)"
}
```

ですよねー。`vars`からも削除してみます。

### 実行してみた(2)


```yaml:test.yml
- hosts: all
  tasks:
   - name: test
     debug:
       msg: "{{ foo }}"
```

結果は下記です。

```json
TASK [test] *****************************************************************************************************************************************************
ok: [hostA] => {
    "msg": "this is test (host_vars/hostA)"
}
ok: [hostB] => {
    "msg": "this is test (host_vars/hostB)"
}
ok: [hostC] => {
    "msg": "this is test (host_vars/hostC)"
}
ok: [hostD] => {
    "msg": "this is test (group_vars/GroupB)"
}
ok: [hostE] => {
    "msg": "this is test (host_vars/hostE)"
}
ok: [hostF] => {
    "msg": "this is test (group_vars/all)"
}
```
想像していたとおりの結果が得られました。

出力内容を `{{ vars }}`に変更して覗いてみると、それぞれのホストでの実行時にグローバルの`"foo"`の値が書き換わっていることがわかります。そしてそれは`hostvars[ホスト名].foo`の値と一致しています。

# まとめ

- ホストごとに違う値の定義は`host_vars/ホスト名`に書きましょう。他では書けません。
- 他ホストと共通の値はグループなり`vars:`なりで定義すればよいです。
- 優先順位にしたがってホストごとにグローバルにコピーした状態でタスクが実行されます。

共通の値をグループに書いておき、特定ホストだけ例外扱いすることもできるということで、優先順位を書くと下記になります。

1. `vars_files`に書かれた定義があればそれが使われる。
1. 上記の定義がなければ`vars`に書かれた定義が使われる。
1. 上記の定義がなければ`host_vars/ホスト名`から定義がコピーされる。
1. 上記の定義がなければ`group_vars/所属グループ`から定義がコピーされる。
1. 上記の定義がなければ`group_vars/all`から定義がコピーされる。

変数の定義のしかたとしては上記の他にコマンドラインで指定したりインベントリに書いたり`host_vars/ホスト名`をディレクトリにしてその下のファイルに書いたりとかいろいろありますが、そこは省略。それらを含めた細かい優先度が気になる方は公式ドキュメントをご参照ください。

ホストごとに定義される変数は無理やり`{{ hostvars[inventory_hostname].foobar }}`などとして参照することも可能ですが、各々のホストに合わせた値がグローバルに設定されるのでそれを参照すればよく、つまり単に`{{ foobar }}`と書けば値を得られる、ということでした。

だから(他の人が作ったroleなどと併用することを考えたとき)変数名が重複しないよう気をつける必要がある、と。なるほど納得です。

# さいごに

公式ドキュメントを読んでも世間様のブログや解説記事を読んでもなにがなんだかよくわからなかったので調べたのですが、世間の皆様はそういうのでするっと理解できているんでしょうか、皆さんすごいなあと思います。