Check! Ansible で /etc/hosts ファイルを作る

こんにちは、 @dz_ こと大平かづみです。

Prologue - はじめに
====

マシン間で ssh 通信するとき、`/etc/hosts` を用意すると、IPアドレスを調べる手間が省けたり、shell でタブ補完ができたりなど何かと便利ですよね。

Ansible でちょちょいと作れるようになったので、共有します！╭( ･ㅂ･)و̑ ｸﾞｯ

検証環境の準備
====

検証用マシンは、 Azure 上につくりました。つかった ARM テンプレートをこちらに用意しましたので、ご興味あればご自身のアカウントでもお試しいただけます。

[![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fgist.githubusercontent.com%2Fdzeyelid%2F1ad72444d7855a62577dbf070bcc6f44%2Fraw%2Ffd9f5b32b45f9465bb7b4061e4db400dff30ab56%2Fazure-deploy.json)

また、Ansible 実行環境については別途作りましたが、お手軽に使いたい場合は Azure Cloud Shell でも Ansible を利用できます。ぜひお試しください♪

- [Run Ansible with Bash in Azure Cloud Shell | Microsoft Docs](https://docs.microsoft.com/en-us/azure/ansible/ansible-run-playbook-in-cloudshell)

ポイント
====
さて、先にいくつかポイントをご紹介します。

`run_once` で1回だけ処理する
----

複数対象があっても、1回だけ処理したい場合に使います。 `delegate_to` と組み合わせて、特定のホストだけで処理したい場合にも利用できます。

- [Run Once - Delegation, Rolling Updates, and Local Actions — Ansible Documentation](http://docs.ansible.com/ansible/latest/playbooks_delegation.html#run-once)

`ansible_play_hosts` でホスト一覧を取得する
----
`ansible_play_hosts` を利用すると、そのとき実行中のホスト一覧を取得できます。

`groups.all` でも全ホストを取得できますが、グループ分けしてる場合は不都合があるので、 `ansible_play_hosts` が便利と思います。

また、 `batch` ごとの対象一覧を取得したい場合は、
 `ansible_play_batch` を使えるそうです。詳しくは下記ドキュメントをご参照ください。

- [Magic Variables, and How To Access Information About Other Hosts - Variables — Ansible Documentation](http://docs.ansible.com/ansible/latest/playbooks_variables.html#magic-variables-and-how-to-access-information-about-other-hosts)

jinja2 フィルタ関連
----

### `default()` フィルタ

`default()` フィルタは、対象の変数が定義されtない場合に初期値を設定してくれます。

今回は、 `_nodes | default([])` とすることで、ループの最初で `_nodes` が定義されていなくても空のリストが設定されるようにしました。

### リストの結合は `+`

ついフィルタを探しがちですが、リストは `+` で結合します。

なお、 `dict` を結合する場合は `combine()` を利用できます。

- [Combining hashes/dictionaries - Filters — Ansible Documentation](http://docs.ansible.com/ansible/latest/playbooks_filters.html#combining-hashes-dictionaries)

検証
====
これらのポイントをもとに、下記のサンプルで検証しました。短いコードで、さっくり作れます。

サンプル
----
### `hosts` ロール

```yml:roles/hosts/tasks/main.yml
---
- name: Set node information
  set_fact:
    _nodes: "{{ _nodes | default([]) + [{'hostname': hostvars[item].ansible_hostname, 'address': hostvars[item].ansible_eth0.ipv4.address}] }}"
  run_once: yes
  with_items: "{{ ansible_play_hosts }}"
  when:
    - hostvars[item].ansible_eth0 is defined

- name: Distribute hosts file
  become: yes
  template:
    src: hosts.j2
    dest: /etc/hosts
    owner: root
    group: root
    mode: 0644
    backup: yes
```

```jinja:roles/hosts/templates/hosts.j2
{% for node in _nodes %}
{{ node.hostname }} {{ node.address }}
{% endfor %}
```

###  インベントリと playbook

```tsv:inventory/all
vm-ubuntu
vm-centos
```

```yml:create_hosts.yml
---
- name: All
  hosts: all
  roles:
    - hosts
```

実行結果
----

```bash
ansible-playbook -i inventory/all create_hosts.yml

PLAY [All] **************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************
ok: [vm-ubuntu]
ok: [vm-centos]

TASK [hosts : Set node information] *************************************************************************************
ok: [vm-ubuntu] => (item=vm-ubuntu)
ok: [vm-ubuntu] => (item=vm-centos)

TASK [hosts : Distribute hosts file] **************************************************************************************
changed: [vm-ubuntu]
changed: [vm-centos]

PLAY RECAP **************************************************************************************************************
vm-centos              : ok=2    changed=1    unreachable=0    failed=0
vm-ubuntu              : ok=3    changed=1    unreachable=0    failed=0
```

それぞれ、ちゃんと `/etc/hosts` ができました！

```bash
[user@vm-centos ~]$ cat /etc/hosts
vm-ubuntu 10.0.0.4
vm-centos 10.0.0.5
```

```bash
[user@vm-ubuntu ~]$ cat /etc/hosts
vm-ubuntu 10.0.0.4
vm-centos 10.0.0.5
```

Epilogue - おわりに
====
この方法を編み出す前は、結構ダサい作り方をしていてもどかしかったのですが、リストの結合など jinja2 のフィルタに慣れてきたおかげで改善することができました！