社内勉強会ネタ：2週間で完全にマスターしたAnsible入門まとめ

Ansible全然わからん

# はじめに

仕事でAnsibleを使ってサーバ構築を自動化する機会があり、1から覚え直しをしたので、PlaybookやInventoryなどの書き方・ディレクトリ構成などをまとめてみました。
モジュールの細かい説明はやってません。

これが正解というものではなくて、こんな感じに作るとこう動くよ、という内容を、順を追って説明してます。

環境

- CentOS 7.6 1810
    - 1台でもできるけど、Ansible実行用と処理対象用の複数台あるとわかりやすい
- Ansible 2.6 (最新は2.8)

参考図書

- [Ansible実践ガイド第2版 impress top gearシリーズ Kindle版](https://www.amazon.co.jp/gp/product/B07B2T24V4/)

# Ansibleインストールと環境設定

## VM

検証用であればVirtualBox上にVagrantを使って[Ansible検証環境に特化したVagrantを使ったVM構築 - 複数VM・VM間ssh公開鍵認証設定・共有フォルダ](https://qiita.com/zaki-lknr/items/cdf4eac2d2f2020ac7be)の通りやれば、最低限必要な環境は簡単に作れる。

## yumインストール

特に設定をいじっていないCentOS 7.6に`yum install ansible`すると、バージョン2.4のAnsibleがインストールされる。

ただし、`centos-release-ansible26`パッケージをインストールすることで2.6用のリポジトリが追加され、yumでAnsible 2.6がインストールできるので、こちらを使ってインストールした。

```
[root@ansible-controller ~]# yum install centos-release-ansible26
```

するとこんなファイルができる
`/etc/yum.repos.d/CentOS-ANSIBLE.repo`

これで`yum install ansible`すると、2.6バージョンがインストールされる。

```
[root@ansible-controller ~]# yum install ansible
```

```
[vagrant@ansible-controller ~]$ ansible --version
ansible 2.6.14
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Jun 20 2019, 20:27:34) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]
[vagrant@ansible-controller ~]$
```

AnsibleのインストールはAnsibleを実行するサーバのみでよく、処理対象サーバは何もいらない(sshが動いていること)

## ターゲットノードへのsshアクセス

Ansibleを使ってリモートサーバの環境構築を行うには、リモートサーバへパスフレーズなしのssh公開鍵認証ができている必要がある(設定によってそうでもないけど、`ansible`,`ansible-playbook`実行時に手間を減らすため、パスフレーズを省略すると操作が簡潔になる)

鍵設定の概要は[Ansible検証環境に特化したVagrantを使ったVM構築 - 複数VM・VM間ssh公開鍵認証設定・共有フォルダ](https://qiita.com/zaki-lknr/items/cdf4eac2d2f2020ac7be)の通り。

## sudo

リモートサーバでroot権限の処理を行う際には、`sudo`がパスワードなしで実行できるようになっている必要がある。
Vagrantで作成したVMで`vagrant`ユーザであれば不要になっているはず。

---

構成を図にするとこんな感じ

![ansible.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/f84db0d2-98d5-d032-dd58-d9b8b6fd3825.png)

# ansible.cfg

Ansibleの設定ファイル

```ini:ansible.cfg
[defaults]
host_key_checking = False
```

初めてのssh接続時、`known_hosts`にsshサーバの設定がないと「このホスト、今まで接続したことないけど繋いでもいいか？」と聞かれるが、これを設定しておくと省略される。
他にはログの出力設定や並列処理数などがある。

読まれる設定ファイルの優先順位は以下の通り

1. 環境変数`ANSIBLE_CONFIG`で指定されたパスのファイル
2. カレントディレクトリの`ansible.cfg`
3. 実行ユーザの`~/.ansible.cfg`
4. 実行ホストの`/etc/ansible/ansible.cfg`

[Ansible Configuration Settings — Ansible Documentation](https://docs.ansible.com/ansible/latest/reference_appendices/config.html)

# PlaybookとInventory

## Playbook

Playbookは「何をするか」を定義する。YAML形式。
様々なAnsibleモジュールがあり、それらを組み合わせてパッケージインストールや設定ファイル変更などを行う。

## Inventory

Inventoryは「処理対象は何か」を定義する。Windowsのini形式に近い。
対象ホストはグルーピングしたり、グループごとにグループ固有の設定用の変数を定義したりできる。
外部ファイルにYAML形式で定義することもできる。

# 最小構成のPlaybook+Inventory

最小といいつつ、ターゲットが2台あるけど、まぁそれは置いておいて

```ini:inventory.ini
[ansible_practice]
192.168.244.120
192.168.244.121
```

Inventoryには、処理対象のVMを記述する。
`[ansible_practice]`が「グループ名」で、Playbookにはこのグループ名を対象に実行するタスクを記述していく。
グループ名の記述の後に、処理対象のホスト

```yaml:playbook.yml
---
- hosts: ansible_practice
  tasks:
    - ping:
```

Ansible Playbookの実行
Playbookに記述したTaskを実行するには、`ansible-playbook`コマンドを使う。
必須の引数はPlaybookのファイル。Inventoryファイルは`-i Inventoryファイル名`で指定する(指定がないと`/etc/ansible/hosts`が使われる)

```
[vagrant@ansible-controller practice]$ ls
inventory.ini  inventory.yml  playbook.yml
[vagrant@ansible-controller practice]$ ansible-playbook -i inventory.ini playbook.yml

PLAY [ansible_practice] ********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.244.121]
ok: [192.168.244.120]

TASK [ping] ********************************************************************
ok: [192.168.244.120]
ok: [192.168.244.121]

PLAY RECAP *********************************************************************
192.168.244.120            : ok=2    changed=0    unreachable=0    failed=0
192.168.244.121            : ok=2    changed=0    unreachable=0    failed=0

[vagrant@ansible-controller practice]$
```

実行結果サマリが`PLAY RECAP`のところに表示される。

| 項目          | 意味                            |
| ----------- | ----------------------------- |
| ok          | 指定の状態にすでになっているため処理されなかった(冪等性) |
| changed     | 指定の状態に変更された                   |
| unreachable | 接続に失敗した                       |
| failed      | 処理に失敗した                       |

## host名指定

Inventoryにホスト名で指定

```ini:inventory.ini
[ansible_practice]
ansible-controller
ansible-node01
```

DNSでも`/etc/hosts`でも名前解決できれば良いが、そうでない場合は名前解決できずに当然エラーになる……はずだけど、最近のCentOSなのかVagrant環境がそうなのか、ホスト名だけだと`.localhost`が付加されて(ここまでは`/etc/resolv.conf`の`search`の設定)、さらに同セグメント内に謎DNSサーバが動いててそこが127.0.0.1を返すから想定しない設定で一応動くな…

とはいえ、説明してると横道にそれるので、Inventoryにホスト毎の設定を追加できる「ホスト変数」に、`ansible_host`という「[接続変数(behavioral inventory parameters)](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#connecting-to-hosts-behavioral-inventory-parameters)」にIPアドレスを書けばOK

```ini:inventory.ini
[ansible_practice]
ansible-controller ansible_host=192.168.244.120
ansible-node01     ansible_host=192.168.244.121
```

これで、Ansibleの実行時においては、「ansible-node01のアドレスは192.168.244.121」として処理される

```
[vagrant@ansible-controller practice]$ ansible-playbook -i inventory.ini playbook.yml

PLAY [ansible_practice] ********************************************************
TASK [Gathering Facts] *********************************************************
ok: [ansible-node01]
ok: [ansible-controller]

TASK [ping] ********************************************************************
ok: [ansible-controller]
ok: [ansible-node01]

PLAY RECAP *********************************************************************
ansible-controller         : ok=2    changed=0    unreachable=0    failed=0
ansible-node01             : ok=2    changed=0    unreachable=0    failed=0

[vagrant@ansible-controller practice]$
```

# Taskの追加

このplaybook.ymlにタスク(処理)を追加してみる

## ディレクトリ作成

`file`モジュールでディレクトリを作ってみる

```yaml:playbook.yml
---
- hosts: ansible_practice
  tasks:
    - ping:

    - file:
        path: /tmp/ansible/practice
        state: directory
        owner: vagrant
        mode: 0755
```

実行

```
[vagrant@ansible-controller practice]$ ansible-playbook -i inventory.ini playbook.yml

PLAY [ansible_practice] ********************************************************
TASK [Gathering Facts] *********************************************************
ok: [ansible-node01]
ok: [ansible-controller]

TASK [ping] ********************************************************************
ok: [ansible-controller]
ok: [ansible-node01]

TASK [file] ********************************************************************
changed: [ansible-controller]
changed: [ansible-node01]

PLAY RECAP *********************************************************************
ansible-controller         : ok=3    changed=1    unreachable=0    failed=0
ansible-node01             : ok=3    changed=1    unreachable=0    failed=0

[vagrant@ansible-controller practice]$
```

結果

```
[vagrant@ansible-controller practice]$ ls -al /tmp/ansible/
total 4
drwxr-xr-x.  3 vagrant vagrant   22 Jul 14 00:45 .
drwxrwxrwt. 10 root    root    4096 Jul 14 00:48 ..
drwxr-xr-x.  2 vagrant vagrant    6 Jul 14 00:45 practice
[vagrant@ansible-controller practice]$ ssh 192.168.244.121 ls -al /tmp/ansible/

total 4
drwxr-xr-x.  3 vagrant vagrant   22 Jul 14 00:45 .
drwxrwxrwt. 10 root    root    4096 Jul 14 00:45 ..
drwxr-xr-x.  2 vagrant vagrant    6 Jul 14 00:45 practice
```

## yumインストールとroot権限

ここから更に、`yum`で`ruby`パッケージをインストールしてみる。

```yaml:playbook.yml
---
# - hosts: ansible_host=192.168.244.121
- hosts: ansible_practice
  tasks:
    - ping:

    - file:
        path: /tmp/ansible/practice
        state: directory
        owner: vagrant
        mode: 0755

    - yum:
        name: ruby
        state: present
```

カンの良い人なら気付くと思いますが、、、

```
[vagrant@ansible-controller practice]$ ansible-playbook -i inventory.ini playbo
ok.yml

PLAY [ansible_practice] ********************************************************
TASK [Gathering Facts] *********************************************************
ok: [ansible-node01]
ok: [ansible-controller]

TASK [ping] ********************************************************************
ok: [ansible-controller]
ok: [ansible-node01]

TASK [file] ********************************************************************
ok: [ansible-controller]
ok: [ansible-node01]

TASK [yum] *********************************************************************
fatal: [ansible-node01]: FAILED! => {"changed": false, "msg": "You need to be root to perform this command.\n", "rc": 1, "results": ["Loaded plugins: fastestmirror\n"]}
fatal: [ansible-controller]: FAILED! => {"changed": false, "msg": "You need to be root to perform this command.\n", "rc": 1, "results": ["Loaded plugins: fastestmirror\n"]}
        to retry, use: --limit @/ansible/practice/playbook.retry

PLAY RECAP *********************************************************************
ansible-controller         : ok=3    changed=0    unreachable=0    failed=1
ansible-node01             : ok=3    changed=0    unreachable=0    failed=1

[vagrant@ansible-controller practice]$
```

root権限がないため、失敗している。
ついでに、前段の`file`によるディレクトリ作成、`changed`でなく`ok`(すでにその状態になっているため処理が行われていない)になっている。冪等性。

rootで実行するには、`become`を指定する([デフォルトは`false`](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#default-become))。
`become`を`true`にすると、[デフォルトでroot実行](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#default-become-user)となる。
`become`でroot権限で実行するには、そのホスト上で`sudo su -`がパスワードなしでrootでシェル起動できる設定になっている必要がある。

```yaml:playbook.yml
---
- hosts: ansible_practice
  become: true
  tasks:
    - ping:

    - file:
        path: /tmp/ansible/practice
        state: directory
        owner: vagrant
        mode: 0755

    - yum:
        name: ruby
        state: present
```

```
[vagrant@ansible-controller practice]$ ansible-playbook -i inventory.ini playbook.yml

PLAY [ansible_practice] ********************************************************
TASK [Gathering Facts] *********************************************************
ok: [ansible-node01]
ok: [ansible-controller]

TASK [ping] ********************************************************************
ok: [ansible-controller]
ok: [ansible-node01]

TASK [file] ********************************************************************
ok: [ansible-controller]
ok: [ansible-node01]

TASK [yum] *********************************************************************
changed: [ansible-node01]
changed: [ansible-controller]

PLAY RECAP *********************************************************************
ansible-controller         : ok=4    changed=1    unreachable=0    failed=0
ansible-node01             : ok=4    changed=1    unreachable=0    failed=0

[vagrant@ansible-controller practice]$
```

`ruby`がインストールされた。

```
[vagrant@ansible-controller practice]$ ruby --version
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]
[vagrant@ansible-controller practice]$ ssh 192.168.244.121 ruby --version
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]
[vagrant@ansible-controller practice]$
```

## nameによるタスクの説明

`ansible-playbook`実行時に、`file`とか`yum`とか、使用しているモジュール名が出力されるだけで、具体的に何をやっているかわかりづらいので、実行時に簡単に確認できるように、`name`でタスクの内容を簡潔に記述しておく。

```yaml:playbook.yml(抜粋)
    - name: create directory
      file:
        path: /tmp/ansible/practice
        state: directory
        owner: vagrant
        mode: 0755

    - name: install ruby package
      yum:
        name: ruby
        state: present
```

出力

```
TASK [create directory] ********************************************************
ok: [ansible-controller]
ok: [ansible-node01]

TASK [install ruby package] ****************************************************
ok: [ansible-node01]
ok: [ansible-controller]

PLAY RECAP *********************************************************************
ansible-controller         : ok=4    changed=0    unreachable=0    failed=0
ansible-node01             : ok=4    changed=0    unreachable=0    failed=0
```

## Apacheのインストールとポート番号の変更・サービス有効化

割とありがちなちょっとした例

```yaml:playbook.yml
- hosts: ansible_practice
  become: true
  tasks:
    - name: install httpd server
      yum:
        name: httpd
        state: present

    - name: configure httpd server
      replace:
        path: /etc/httpd/conf/httpd.conf
        regexp: ^#?Listen 80$
        replace: Listen 8080

    - name: enable httpd service
      systemd:
        name: httpd
        state: restarted
        enabled: yes
```

### replaceなど正規表現を使ったファイル編集の注意点

```yaml
    - name: configure httpd server
      replace:
        path: /etc/httpd/conf/httpd.conf
        regexp: Listen 80
        replace: Listen 8080
```

もしこのように書いてしまうと…
1回目の実行で

```diff
- Listen 80
+ Listen 8080
```

に更新されるが、2度目の実行でも`regexp: Listen 80`はこの行にマッチするため、今度は

```diff
- Listen 8080
+ Listen 808080
```

となってしまう。
繰り返し実行する際に対象外となるように気を付ける必要がある。
(この例であれば`^`・`$`を使って、行頭・行末を明示する、など)

# Inventoryファイルの設定

## グループ

ここまでの例は`[ansible_practice]`グループしか使っていなかったけど、当然複数グループ定義できるし、あるノード(処理対象ホスト)を複数のグループに設定することもできる。

```ini:inventory.ini
[ansible_practice]
ansible-controller ansible_host=192.168.244.120
ansible-node01     ansible_host=192.168.244.121

[php-server]
ansible-node01
```

こう書けば、`[ansible_practice]`グループには`ansible-controller`と`ansible-node01`が対象となり、`[php-server]`グループには、`ansible-node01`のみが含まれる。

`ansible-node01`は`[ansible_practice]`と`[php-server]`の両方に含まれる。

つまり、Playbookで`hosts`に`ansible_practice`と指定すれば`ansible-controller`と`ansible-node01`に対して処理され、`php-server`と指定すれば、`ansible-node01`のみ処理される。

ちなみに、`ansible_host`を使ったIPアドレス指定は、最初の1回のみで良いみたい。

## Inventory変数

Ansibleで行う処理の内容は基本的にPlaybookへ記述するが、環境ごとに複数のInventoryファイルを作成し、環境ごとに設定が異なる場合・・・例えば環境Aではwebサーバは8080/TCPで動かすが、環境Bでは8888/TCPで動かす、など・・・、環境ごとにPlaybookを用意してもいいけど、ポート番号以外のTaskの中身は同じなので、ポート番号を変数としてInventoryファイルに書けたら便利ですよね。(よね？)

Playbookは共通で、Inventoryの中に以下のように`[グループ名:vars]`という記述のあとに変数定義する。

```ini:inventory-a.ini
[ansible_practice]
ansible-controller ansible_host=192.168.244.120
ansible-node01     ansible_host=192.168.244.121

[ansible_practice:vars]
http_port=8080
```

```ini:inventory-b.ini
[ansible_practice]
ansible-controller ansible_host=172.16.10.10
ansible-node01     ansible_host=172.16.10.11

[ansible_practice:vars]
http_port=8888
```

`[ansible_practice:vars]`と記述することで、グループ`[ansible_practice]`に対する処理中は、`http_port`という変数名で値を参照することができる。

この変数を参照するように、前述webサーバのポート番号書き換えの処理のPlaybookを修正すると、こんな感じで環境に依存しない記述にできる。

```yaml:playbook.yml
    - name: configure httpd server
      replace:
        path: /etc/httpd/conf/httpd.conf
        regexp: ^#?Listen [0-9]+$
        replace: Listen {{ http_port }}
```

これで、Playbookにハードコーディングしていた設定値を、Inventory変数へ外だしできたので、環境Aの場合は`inventory-a.ini`を、環境Bの場合は`inventory-b.ini`を使って`ansible-playbook`を実行すればそれぞれの環境が構築できる。

ちなみに、グループ名でなく`[all:vars]`に書くことで、グループに関係なく参照できる値を定義することもできる。いわゆるグローバル変数。

## Inventory変数の外部ファイル化

前述通りiniファイルへkey=value形式でInventory変数を定義できるので、単発の設定であれば問題ないが、リスト(配列)やマップ(ハッシュ・ディクショナリ)形式では表現できない。

例えば、「`/etc/hosts`へ設定するホストとIPアドレスの一覧(件数不定)」とか「iptablesで許可する`--dport`で指定するポート番号一覧」とか「DHCPサーバで特定のクライアントに対して固定のアドレスを設定したいときのMACアドレスとIPアドレスのセット」とか。。

そういう場合は、PlaybookかInventoryファイルと同じディレクトリに`group_vars/グループ名.yml`ファイルを作成し、そこへYAML形式で任意の設定を記述することで、Playbookから参照できる。

### with_itemsとYAML形式のInventory変数を使ったループ処理

```ini:inventory.ini(抜粋)
[smbserver]
ansible-node01
```

Inventoryファイルに記述したグループ名と同名のYAMLファイルを`group_vars`ディレクトリ以下に作成

```yaml:group_vars/smbserver.yml
---
shared_folder:
  - name: logs
    comment: log directory
    path: /opt/logs
    create_mask: "0644"
    directory_mask: "0755"
    writable: "no"
    browseable: "no"
  - name: share
    comment: shared directory
    path: /share
    create_mask: "0664"
    directory_mask: "0775"
    writable: "yes"
    browseable: "yes"
```

このInventoryファイルがある状態で、次のようにPlaybookを定義。

```yaml:playbook.yml
- hosts: smbserver
  become: true
  tasks:
    - name: install samba server
      yum:
        name: samba
        state: present

    - name: configure samba server
      blockinfile:
        path: /etc/samba/smb.conf
        marker: "# {mark} {{ item.name }} configuration by ansible"
        block: |
          [{{ item.name }}]
                  comment = {{ item.comment }}
                  path = {{ item.path }}
                  create mask = {{ item.create_mask }}
                  directory mask = {{ item.directory_mask }}
                  writable = {{ item.writable }}
                  browseable = {{ item.browseable }}
      with_items: '{{ shared_folder }}'
```

これで`ansible-playbook`を実行すると、/etc/samba/smb.confの該当部分は以下の通り。

```ini:/etc/samba/smb.conf
# BEGIN logs configuration by ansible
[logs]
        comment = log directory
        path = /opt/logs
        create mask = 0660
        directory mask = 0770
        writable = no
        browseable = no
# END logs configuration by ansible
# BEGIN share configuration by ansible
[share]
        comment = shared directory
        path = /share
        create mask = 0644
        directory mask = 0755
        writable = yes
        browseable = yes
# END share configuration by ansible
```

task内で`with_items`に配列形式の変数を指定することで、ループ内(`with_items`の上のブロック)で`{{ item }}`という変数名で参照できる。

プログラミング言語でいうと、`foreach(var item in shared_folder) { ... }` と動きは同じ。(`item`というループ内変数は固定)

![ansible _ with_items (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/fd7b9856-cdcd-3b67-71be-cd6665dddef9.png)

上記の設定では`shared_folder`の要素は`name`,`comment`,...などのマップ形式なので、`{{ item.comment }}`などで各設定値を参照できる。

[`blockinfil`モジュールについてはドキュメント参照](https://docs.ansible.com/ansible/latest/modules/blockinfile_module.html)。

`with_items`を使ったループは、Inventory変数で定義した配列参照だけでなく、その場で定義したリストをループ処理もできる。構文はこんな感じ。

```yaml
      with_items:
        - foo
        - bar
        - baz
```

これでループのブロック内で`{{ item }}`と書いた箇所が、ループ毎に`foo`,`bar`,`baz`となる。

※ [Ansible 2.5以降は、`with_items`などの`with_X`によるループ処理は`loop`に置き換わっている](https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#migrating-from-with-x-to-loop)

# TaskをRoleへ分割

ここまでの例ではPlaybookの内容は以下のようになっている

- ping
- ディレクトリ作成
- rubyのyumインストール
- httpdのyumインストール
- httpd.confの編集
- httpdサービスの有効化
- sambaのyumインストール
- smb.confの設定

この内容がすべてplaybook.ymlの中に記述されており、プログラミングでいうと、`main()`関数の中にすべての処理が記述されている状態と同様で、メンテナンス性や可読性が(これからさらに処理内容が増える場合も考えると)とてもよろしくない。

そこで、プログラミングだと処理単位ごとに関数を作ってmain()からそれをコールするように、PlaybookもRoleという

※ main()関数とか別関数化というのは筆者の個人的な感覚・たとえです

図にするとこんな感じ

![ansible _ role.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/b94145f4-cea7-4005-b2e3-67486502fc8e.png)

## Roleの作成

httpdのインストール・設定・サービス設定をRoleに分離してみる。
Playbook内のTaskをRoleに作成するファイルは`roles/ロール名/tasks/main.yml`

```yaml:roles/install_httpd/tasks/main.yml
---
- name: install httpd server
  yum:
    name: httpd
    state: present

- name: configure httpd server
  replace:
    path: /etc/httpd/conf/httpd.conf
    regexp: ^#?Listen [0-9]+$
    replace: Listen {{ http_port }}

- name: enable httpd service
  systemd:
    name: httpd
    state: restarted
    enabled: yes
```

同様にsambaインストール

```yaml:roles/install_samba/tasks/main.yml
---
- name: install samba server
  yum:
    name: samba
    state: present

- name: configure samba server
  blockinfile:
    path: /etc/samba/smb.conf
    marker: "# {mark} {{ item.name }} configuration by ansible"
    block: |
      [{{ item.name }}]
              comment = {{ item.comment }}
              path = {{ item.path }}
              create mask = {{ item.create_mask }}
              directory mask = {{ item.directory_mask }}
              writable = {{ item.writable }}
              browseable = {{ item.browseable }}
  with_items: '{{ shared_folder }}'

- name: enable samba service
  systemd:
    name: smb
    state: restarted
    enabled: yes
```

## Roleの参照(呼び出し)

作ったRoleをPlaybookから呼び出す。
関数コールみたいな感じ。

```yaml:playbook.yml
- hosts: wwwserver
  become: true
  roles:
    - install_httpd

- hosts: smbserver
  become: true
  roles:
    - install_samba
```

関数コールと同じように、複数個所から呼ぶことももちろんできる。
例えば上記ではwebサーバとsambaサーバを別グループにしているが「ホットスタンバイ用のサーバを1台用意して、web/sambaを1台で動かす」という場合でも、Roleは同じものを使用して、InventoryとPlaybookを変更して

```yaml:playbook.yml
- hosts: wwwserver
  become: true
  roles:
    - install_httpd

- hosts: smbserver
  become: true
  roles:
    - install_samba

- hosts: standby
  become: true
  roles:
    - install_httpd
    - install_samba
```

のようにもできる。
この場合、webサーバのポート番号設定を`group_vars/wwwserver.yml`には`http_port: 8080`、`group_vars/standby`には`http_port: 28080`とすることで、スタンバイサーバの設定を変更することもできる。

# モジュール

Ansibleでは[標準でさまざまなモジュールが用意されている](https://docs.ansible.com/ansible/latest/modules/modules_by_category.html)。
OSの設定変更、パッケージ管理、設定ファイルの更新など、たいていの操作はモジュールの組み合わせで実現できる。はず。

少しだけ説明

## shell

[shell – Execute shell commands on targets — Ansible Documentation](https://docs.ansible.com/ansible/latest/modules/shell_module.html)

Linuxコマンドを実行する。
ただし、冪等性は担保してくれない(実行が不要かどうかは検査されない)ので、実行前に検査用のtaskを入れたり工夫が必要。

実行したいコマンドを実現できるモジュールがないか、まずは探してみましょう。
モジュールの探し方としては、カテゴリページから探すのもいいけど、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/2bbebd95-cbb5-b9fc-e7a9-3bf030ae45f0.png)

この検索欄にコマンド(うまくヒットしなければ関連キーワード)を入れて検索すると、用意されていれば良い感じのモジュールがヒットする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/fd70e43c-a44a-f8c9-9301-73783a8991c4.png)

ヒットしたら、モジュールの説明ページの「Parameters」と「Examples」を確認すれば、だいたいなんとかなる。(アバウトですみません)

## template

Jinja2というPythonのテンプレートエンジンを使って、テンプレートファイルをInventory変数などで定義した変数に置き換えつつ、対象サーバへファイルコピーしてくれる。

`replace`や`lineinfile`などのファイル変更処理と異なり、冪等性については難しく考えなくて済むので扱いが簡単。

設定ファイル書き換えでも、変更部分が大半で書式が単純なkey-value形式でない場合は、`template`を使った方が楽かもしれない。

例としてDHCPサーバ(dhcp-4.2.5-68.el7.centos.1.x86_64)の設定

```configuration
option domain-name "ansible.practice.localhost";
option domain-name-servers 192.168.244.120;
default-lease-time 3600;
max-lease-time 86400;
ddns-update-style none;
log-facility local7;

subnet 192.168.244.0 netmask 255.255.255.0 {
        option subnet-mask 255.255.255.0;
        option broadcast-address 192.168.244.255;
        option routers 192.168.244.1;
        range 192.168.244.60 192.168.244.99;
}

host client01 {
  hardware ethernet 00:00:5e:00:53:01;
  fixed-address 192.168.244.31;
}
host client02 {
  hardware ethernet 00:00:5e:00:53:02;
  fixed-address 192.168.244.32;
}
```

こんなファイルを作りたい場合は…

```j2:roles/install_dhcpd/templates/dhcpd.conf.j2
option domain-name "{{ domain_name }}";
option domain-name-servers {{ dns }};
default-lease-time 3600;
max-lease-time 86400;
ddns-update-style none;
log-facility local7;

subnet {{ network_addr }} netmask {{ subnet_mask }} {
        option subnet-mask {{ subnet_mask }};
        option broadcast-address {{ broadcast }};
        option routers {{ gateway }};
        range {{ range_begin }} {{ range_end }};
}

{% for host in hosts_conf %}
host {{ host.name }} {
  hardware ethernet {{ host.hwaddr }};
  fixed-address {{ host.fixedaddr }};
}
{% endfor %}
```

テンプレートファイルは、`tasks`ディレクトリと同じ階層に`templates`ディレクトリを作り、そこに`.j2`拡張子を付けて配置。

```yaml:roles/install_dhcpd/tasks/main.yml
---
- name: install dhcp server
  yum:
    name: dhcp
    state: present

- name: configure dhcp server
  template:
    src: dhcpd.conf.j2
    dest: /etc/dhcp/dhcpd.conf
    owner: root
    group: root
    mode: "0644"
```

roleで`template`モジュールを指定、パラメタの`src`でテンプレートファイル(.j2)を指定する。パスを指定しなければ、`templates`ディレクトリが基準となる。

```yaml:group_vars/dhcpserver.yml
---
domain_name: ansible.practice.localhost
dns: "192.168.244.120"

network_addr: "192.168.244.0"
subnet_mask: "255.255.255.0"
broadcast: "192.168.244.255"
gateway: "192.168.244.1"
range_begin: "192.168.244.60"
range_end: "192.168.244.99"

hosts_conf:
  - name: client01
    hwaddr: 00:00:5e:00:53:01
    fixedaddr: "192.168.244.31"
  - name: client02
    hwaddr: 00:00:5e:00:53:02
    fixedaddr: "192.168.244.32"
```

テンプレートファイルのパラメタとなるInventory変数はこんな感じ。
これでテンプレート(.j2ファイル)の`{{ network_addr }}`と書いた箇所が`192.168.244.0`に置き換わる。

リスト定義している`hosts_conf`は、テンプレートの`for`構文でループ処理できる。

```
{% for host in hosts_conf %}

{% endfor %}
```

`with_items`が`item`固定だったのに対して、テンプレートだとループ内の変数は自由に設定できる(上の例だと`host`)

全体のファイル構成

```
.
├── ansible.cfg
├── group_vars
│   ├── dhcpserver.yml
│   ├── smbserver.yml
│   └── wwwserver.yml
├── inventory.ini
├── playbook.yml
└── roles
    ├── install_dhcpd
    │   ├── tasks
    │   │   └── main.yml
    │   └── templates
    │       └── dhcpd.conf.j2
    ├── install_httpd
    │   └── tasks
    │       └── main.yml
    └── install_samba
        └── tasks
            └── main.yml
```