dynamic inventoryのススメ

[Ansible Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ansible) の9日目記事担当させていただきます。

dynamic inventoryについて最近触れる機会があったので、備忘録も兼ねてアドカレで出しておこうかなーと思います。

ただ、概要とかについては [こちらの記事](https://qiita.com/t_nakayama0714/items/91c2d711a397df68dddc) が詳しいので、簡易的な使い方とハマった点を主に話せればと思います。

# はじめに

みなさんは、Ansible書く上でinventoryファイルをどうしていますか？普通は下記のようにini形式のファイルを用意すると思います。

```ini
[group01]
vm01 ansible_host=192.168.10.11
vm02 ansible_host=192.168.10.12
```

ただ、大規模な環境やinventoryの内容がよく変わるような環境ではinventoryを管理していくのは大変かと思います。

そこで、AnsibleにはDynamic inventoryという機能があります。

# Dynamic Inventory

先程も書きましたが概要については [こちらの記事](https://qiita.com/t_nakayama0714/items/91c2d711a397df68dddc) をどうぞ。

本記事では重要な点だけ抜粋すると下記になります。

```
Dynamic Inventoryとは、 JSON形式 のデータを標準出力する スクリプトであればよい
```

つまり、Ansibleではinventoryをjson形式で受け取ることが可能です。

形式については、概ね下記のようにすれば問題ないです。`_meta`を使えば`hostvars`が書けるので、必要であれば書くという感じで使います。

```json
{
    "<グループ名>" : {
        "children" : [ "<子グループ1>", "<子グループ2>"]
        "hosts"    : [ "<ホスト1>", "<ホスト2>" ],
        "vars"     : {
            "<変数名1>" : "<値1>"
        }
    },
    "_meta": {
        "hostvars": {
            "<ホスト1>": { "ansible_host": "<IP>" },
            "<ホスト2>": { "ansible_host": "<IP>" }
        }
    }
}
```

# 使い方

ここからは[サンプル用のリポジトリ](https://github.com/sinnershiki/ansible-adcale2018)を用意したので、それを元に説明します

vagrantでVMを2台用意しました

```ruby
Vagrant.configure("2") do |config|
  config.vm.box = "bento/centos-7.4"

  config.vm.define "node1" do |node|
    node.vm.network :private_network, ip: "192.168.10.11"
  end

  config.vm.define "node2" do |node|
    node.vm.network :private_network, ip: "192.168.10.12"
  end
end
```

このとき、ini形式でのinventoryでは下記のようになります（説明用に適当な変数も定義しています）

```ini
[all:vars]
ansible_ssh_user=vagrant
ansible_ssh_pass=vagrant

[parent1:children]
child1
child2

[parent1:vars]
parent_num=1

[child1]
vm-node1 ansible_host=192.168.10.11 node_var=hoge

[child1:vars]
child_num=1

[child2]
vm-node2 ansible_host=192.168.10.12 node_var=fuga

[child2:vars]
child_num=2
```

vmが2台あって、それぞれchild1・2のグループに属しており、それぞれが変数を持ってる感じですね。これをdynamic inventoryのjson形式にすると下記になります。

```json
{
  "all": {
    "vars": {
      "ansible_ssh_user": "vagrant",
      "ansible_ssh_pass": "vagrant"
    }
  },
  "parent": {
    "children": ["child1", "child2"],
    "vars": {
      "parent_num": "1"
    }
  },
  "child1": {
    "hosts": ["vm-node1"],
    "vars": {
      "child_num": "1"
    }
  },
  "child2": {
    "hosts": ["vm-node2"],
    "vars": {
      "child_num": "2"
    }
  },
  "_meta": {
    "hostvars": {
      "vm-node1": {
        "ansible_host": "192.168.10.11",
        "node_var": "hoge"
      },
      "vm-node2": {
        "ansible_host": "192.168.10.12",
        "node_var": "fuga"
      }
    }
  }
}
```

例としてこんなplaybookを用意してみました。やることとしては、さっき適当に設定した変数を表示するだけです。

```yml
- hosts: all
  tasks:
    - name: debug var
      debug:
        msg: "p-num={{parent_num}}, c-num={{child_num}}, node-var={{node_var}}"
```

実際に実行してしてみると差分等ないことが分かりますね

```shell_session
# ini版
% ansible-playbook -i inventory/hosts.sh playbooks/playbook.yml

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

TASK [Gathering Facts] ***********************************************************************
ok: [vm-node2]
ok: [vm-node1]

TASK [debug var] *****************************************************************************
ok: [vm-node2] => {
    "msg": "p-num=1, c-num=2, node-var=fuga"
}
ok: [vm-node1] => {
    "msg": "p-num=1, c-num=1, node-var=hoge"
}

PLAY RECAP ***********************************************************************************
vm-node1                   : ok=2    changed=0    unreachable=0    failed=0
vm-node2                   : ok=2    changed=0    unreachable=0    failed=0

```

```shell-session
# dynamic inventory版
% ansible-playbook -i inventory/hosts.ini playbooks/playbook.yml

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

TASK [Gathering Facts] ***********************************************************************
ok: [vm-node2]
ok: [vm-node1]

TASK [debug var] *****************************************************************************
ok: [vm-node1] => {
    "msg": "p-num=1, c-num=1, node-var=hoge"
}
ok: [vm-node2] => {
    "msg": "p-num=1, c-num=2, node-var=fuga"
}

PLAY RECAP ***********************************************************************************
vm-node1                   : ok=2    changed=0    unreachable=0    failed=0
vm-node2                   : ok=2    changed=0    unreachable=0    failed=0
```

#　ハマりポイント

最後に実際にハマったことのあるポイントをいくつか紹介しておきます。

## dynamic inventoryでは引数を受け取れない
先程実行したようにdynamic inventoryでは、実行可能でjsonを返すスクリプトであればよいというのが書かれていたと思います。

しかし、inventoryファイルの指定には特別なオプションが用意されているわけではなく通常のinventory指定のオプションを利用します。

そのため、ファイルを指定することしかできず、dynamic inventoryかどうかの判別はそのファイルが実行可能化どうかで判別しているのではと思います。

ただ、ファイルを指定する仕様なのでその実行可能ファイルが引数を受け取りたくともそういったことはできません。

下記みたいなことはできないということですね。

```sh
% ansible-playbook -i "inventory/hosts.sh [引数]" playbooks/playbook.yml
```

対応策としては、引数を使わないしか現状ないかなと思います。

そのうちちゃんと仕様読んでおきたいなーとは思います。

## json以外の内容が出力されるとコケる

まあ当然といえば当然の話なのですが、実行可能ファイルでjson以外の情報が出力されるとコケます

そのため、実行可能ファイルで事前に何らかの処理をしておく場合はjson結果以外の標準出力は/dev/nullあたりに投げ込みましょう

以前はこんな感じで対応しました。

```shell
#!/bin/bash
bundle install --path vendor/bundle > /dev/null 2>&1
bundle exec ruby inventory.rb
```

# まとめ
dynamic inventoryを利用すればスクリプトを用いて柔軟にinventoryが管理できるようになります。

inventory以外の方法でサーバの内容を管理している場合は、その情報をスクリプトで拾えば二重管理を防げるので、積極的に利用していって問題ないと思います。

ぜひ利用してみてください。