AnsibleのDynamic Inventoryを使ってOpenStackやMackerelから適用先を自動取得する

AnsibleにDynamic Inventoryという機能があることを知った。Ansibleではインベントリという機能があり、適用先やグループ分けをファイルに書くことで適用するプレイブックを切り替えられる。しかし、これは静的なため、サーバーの台数が多いと管理が不便なこともある。Dynamic Inventoryを使えば動的に適用先を取得できるようだが、まだ試したことがなかったので、試してみる。

準備

まず、以下のようなサーバーを用意する。全てFloatingIPを付与していて、直接SSHできる。

+--------------------------------------+----------+--------+--------------------------------------+------------------------------------+-----------+
| ID                                   | Name     | Status | Networks                             | Image                              | Flavor    |
+--------------------------------------+----------+--------+--------------------------------------+------------------------------------+-----------+
| a36c1a4d-c74c-4417-82fa-f025efdd6b34 | node-001 | ACTIVE | admin-lan=172.16.0.4, 192.168.88.105 | ubuntu-16.04-server-cloudimg-amd64 | m1.medium |
| db0632d3-011c-41a5-b539-64449dda8ffa | node-003 | ACTIVE | admin-lan=172.16.0.7, 192.168.88.122 | ubuntu-16.04-server-cloudimg-amd64 | m1.medium |
| beb2780d-3793-4c46-b6b6-42c147db0355 | node-002 | ACTIVE | admin-lan=172.16.0.3, 192.168.88.119 | ubuntu-16.04-server-cloudimg-amd64 | m1.medium |
+--------------------------------------+----------+--------+--------------------------------------+------------------------------------+-----------+

復習: Static Inventory

しばらくAnsibleから離れていたので、Dynamic Inventoryの前にStaticな方の挙動を確認しておくことにする。SSHするユーザやプライベートキーをansible.cfgに設定。

[defaults]
remote_user = ubuntu
private_key_file = ~/.ssh/id_rsa

インベントリは以下のように、inventoryというファイルを用意してベタ書きする。

[node]
node-001 ansible_ssh_host=192.168.88.105 ansible_python_interpreter=/usr/bin/python3
node-002 ansible_ssh_host=192.168.88.122 ansible_python_interpreter=/usr/bin/python3
node-003 ansible_ssh_host=192.168.88.119 ansible_python_interpreter=/usr/bin/python3

各サーバーに対してpingするようなplaybookを用意する。

---

- name: Ping Ping Ping
  hosts: all
  tasks:
    - name: ping to instance
      ping:

これで、ansibleを実行すると、各サーバーにpingしていることが確認できる。

$ ansible-playbook -i inventory ./test.yml

PLAY [Ping Ping Ping] *****************************************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************************************
ok: [node-001]
ok: [node-003]
ok: [node-002]

TASK [ping to instance] ***************************************************************************************************************************************************************************
ok: [node-003]
ok: [node-001]
ok: [node-002]

PLAY RECAP ****************************************************************************************************************************************************************************************
node-001                   : ok=2    changed=0    unreachable=0    failed=0
node-002                   : ok=2    changed=0    unreachable=0    failed=0
node-003                   : ok=2    changed=0    unreachable=0    failed=0

本題1: Dynamic Inventory

次にDynamic Inventoryだ。OpenStackのダイナミックインベントリについての公式ドキュメントを見つつやってみる。openstack.pyをダウンロードしてきて、オプションに指定する。OpenStackのAPIを使えるように環境変数を設定しておくのを忘れないこと。また、ansible_python_interpreterはオプションで指定した。

$ ansible-playbook -i ./openstack.py ./test.yml --extra-vars ansible_python_interpreter=/usr/bin/python3

PLAY [Ping Ping Ping] *****************************************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************************************
ok: [beb2780d-3793-4c46-b6b6-42c147db0355]
ok: [a36c1a4d-c74c-4417-82fa-f025efdd6b34]
ok: [db0632d3-011c-41a5-b539-64449dda8ffa]

TASK [ping to instance] ***************************************************************************************************************************************************************************
ok: [beb2780d-3793-4c46-b6b6-42c147db0355]
ok: [a36c1a4d-c74c-4417-82fa-f025efdd6b34]
ok: [db0632d3-011c-41a5-b539-64449dda8ffa]

PLAY RECAP ****************************************************************************************************************************************************************************************
a36c1a4d-c74c-4417-82fa-f025efdd6b34 : ok=2    changed=0    unreachable=0    failed=0
beb2780d-3793-4c46-b6b6-42c147db0355 : ok=2    changed=0    unreachable=0    failed=0
db0632d3-011c-41a5-b539-64449dda8ffa : ok=2    changed=0    unreachable=0    failed=0

なるほど、ちゃんとAPIから適用先を取得して適用できる。適用先名がサーバーのIDになっていて読みにくいが、これはopenstack.pyに少し手を入れれば直せそうだ。

本題2: Dynamic Inventory を使って適用するプレイブックを変更する

さて、動的に適用先サーバーを取得できることがわかった。となると、次はメタデータを使って適用するロールを変更する、ということがやりたくなるのは自明である。実は、node-001〜node-003には以下のようにメタデータを設定してある。node-001とnode-002はgroup01、node-003はgroup02という具合だ。

$ openstack server list --long -c ID -c Name -c Properties
+--------------------------------------+----------+-----------------+
| ID                                   | Name     | Properties      |
+--------------------------------------+----------+-----------------+
| a36c1a4d-c74c-4417-82fa-f025efdd6b34 | node-001 | group='group01' |
| beb2780d-3793-4c46-b6b6-42c147db0355 | node-002 | group='group01' |
| db0632d3-011c-41a5-b539-64449dda8ffa | node-003 | group='group02' |
+--------------------------------------+----------+-----------------+

test.ymlに、group01とgroup02用の処理を書き足す。

---

- name: Ping Ping Ping
  hosts: all
  tasks:
    - name: ping to instance
      ping:

- name: apply group01 only
  hosts: group01
  tasks:
    - debug: msg="This is group 01"

- name: apply group02 only
  hosts: group02
  tasks:
    - debug: msg="This is group 02"

この状態でansible-playbookを実行すると、group01とgroup02で実行するタスクが違っていることが確認できる。なるほど便利だ…

takaishiryou-no-iMac% ansible-playbook -i ./openstack.py ./test.yml --extra-vars ansible_python_interpreter=/usr/bin/python3

PLAY [Ping Ping Ping] *****************************************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************************************
ok: [a36c1a4d-c74c-4417-82fa-f025efdd6b34]
ok: [db0632d3-011c-41a5-b539-64449dda8ffa]
ok: [beb2780d-3793-4c46-b6b6-42c147db0355]

TASK [ping to instance] ***************************************************************************************************************************************************************************
ok: [db0632d3-011c-41a5-b539-64449dda8ffa]
ok: [a36c1a4d-c74c-4417-82fa-f025efdd6b34]
ok: [beb2780d-3793-4c46-b6b6-42c147db0355]

PLAY [apply group01 only] *************************************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************************************
ok: [beb2780d-3793-4c46-b6b6-42c147db0355]
ok: [a36c1a4d-c74c-4417-82fa-f025efdd6b34]

TASK [debug] **************************************************************************************************************************************************************************************
ok: [beb2780d-3793-4c46-b6b6-42c147db0355] => {
    "msg": "This is group 01"
}
ok: [a36c1a4d-c74c-4417-82fa-f025efdd6b34] => {
    "msg": "This is group 01"
}

PLAY [apply group02 only] *************************************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************************************
ok: [db0632d3-011c-41a5-b539-64449dda8ffa]

TASK [debug] **************************************************************************************************************************************************************************************
ok: [db0632d3-011c-41a5-b539-64449dda8ffa] => {
    "msg": "This is group 02"
}

PLAY RECAP ****************************************************************************************************************************************************************************************
a36c1a4d-c74c-4417-82fa-f025efdd6b34 : ok=4    changed=0    unreachable=0    failed=0
beb2780d-3793-4c46-b6b6-42c147db0355 : ok=4    changed=0    unreachable=0    failed=0
db0632d3-011c-41a5-b539-64449dda8ffa : ok=4    changed=0    unreachable=0    failed=0

応用:Dynamic InventoryとしてMackerelを利用する

さて、openstack.pyが1ファイルのスクリプトであることから、自分でDynamic Inventoryを定義することもできる。今回はMackerelを使ってみる。OpenStackではメタデータからグループを取得していたが、Mackerelではロールを代わりに使ってみよう。まずはわかりやすくするため、サーバーに付与するIPアドレスを直接アクセス可能なものに変更する。

takaishiryou-no-iMac% openstack server list --long -c ID -c Name -c Networks -c Properties
+--------------------------------------+----------+-----------------------+-----------------+
| ID                                   | Name     | Networks              | Properties      |
+--------------------------------------+----------+-----------------------+-----------------+
| 69210e14-ce38-4b0a-80e8-f3a344078572 | node-001 | public=192.168.88.117 | group='group01' |
| 2b4c5516-f365-41f4-b579-bf2862548794 | node-002 | public=192.168.88.104 | group='group01' |
| 396192d8-30ce-4dcf-8d36-b932c24ff13e | node-003 | public=192.168.88.112 | group='group02' |
+--------------------------------------+----------+-----------------------+-----------------+

また、Mackerel上でそれぞれのサーバーにロールを設定する。OpenStackの時と同様、node-001と002がgroup01、node-003がgroup02だ。

DynamicInventoryの作り方の記事としては Ansible Dynamic Inventoryをつくろう! と Developing Dynamic Inventory Sources を参考にした。スクリプトは以下の通り。今回は試すだけなので、かなり手抜き…

#!/usr/bin/env python

import urllib2
import json

service="test"
api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
url = "https://mackerel.io/api/v0/hosts?service=" + service
headers = {"X-Api-Key": api_key}

req = urllib2.Request(url=url, headers=headers)
res = urllib2.urlopen(req)
hosts = json.loads(res.read())['hosts']

inventory = {"_meta": {"hostvars": {}}}

for host in hosts:
  name = host['name']
  group = host['roles'][service][0]
  ipaddr = host['interfaces'][0]['ipv4Addresses'][0]

  if inventory.get(group) is None:
    inventory[group] = []

  inventory["_meta"]["hostvars"][name] = {"ansible_host": ipaddr}
  inventory[group].append(name)

print(json.dumps(inventory))

このスクリプトを単体で実行すると、以下のようになる。

$ python mackerel.py | jq .
{
  "group02": [
    "node-003"
  ],
  "_meta": {
    "hostvars": {
      "node-003": {
        "ansible_host": "192.168.88.112"
      },
      "node-002": {
        "ansible_host": "192.168.88.104"
      },
      "node-001": {
        "ansible_host": "192.168.88.117"
      }
    }
  },
  "group01": [
    "node-002",
    "node-001"
  ]
}

最後に、ansible-playbookで動作確認。ロールによって適用されるタスクがちゃんと違っていることがわかる。なるほどねー。

$ ansible-playbook -i ./mackerel.py ./test.yml --extra-vars ansible_python_interpreter=/usr/bin/python3

PLAY [Ping Ping Ping] *****************************************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************************************
ok: [node-003]
ok: [node-001]
ok: [node-002]

TASK [ping to instance] ***************************************************************************************************************************************************************************
ok: [node-003]
ok: [node-001]
ok: [node-002]

PLAY [apply group01 only] *************************************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************************************
ok: [node-002]
ok: [node-001]

TASK [debug] **************************************************************************************************************************************************************************************
ok: [node-002] => {
    "msg": "This is group 01"
}
ok: [node-001] => {
    "msg": "This is group 01"
}

PLAY [apply group02 only] *************************************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************************************
ok: [node-003]

TASK [debug] **************************************************************************************************************************************************************************************
ok: [node-003] => {
    "msg": "This is group 02"
}

PLAY RECAP ****************************************************************************************************************************************************************************************
node-001                   : ok=4    changed=0    unreachable=0    failed=0
node-002                   : ok=4    changed=0    unreachable=0    failed=0
node-003                   : ok=4    changed=0    unreachable=0    failed=0

まとめ

AnsibleのDynamic Inventoryを試し、動的に適用先を取得できること、メタデータによって適用するプレイブックを変更できることを確認した。今回はansibleが用意していたOpenStackと、Mackerel用のスクリプトを書いてみた。他にもConsulのようなAPIも使えそうだ。また、ChefやPuppetにも同様の機能がないか調べてみたいところだ。

参考文献

Leave a Reply

Your email address will not be published. Required fields are marked *