Ansibleをはじめる人に。

# この記事で書いていること

この記事では **Ansibleをはじめる人** を対象に、下記の Ansible 入門的な内容についてまとめています。

1. **Ansibleとは**
2. **Ansibleを調べる**
3. **Ansibleを試す**
4. **Ansibleをもう少し試す**

既にわかっている人向けに用語を使えば、

「 **jinja templateによるファイルの動的配置などとroleによるタスクの切り出しまでをハンズオン** 」

となります。
なお、環境は **Python 2.6.6 + Ansible 1.9.4** です。

# **Ansibleとは**

Ansibleは **"構成管理ツール"** と呼ばれ、最近(少なくとも私の中で)話題のツールです。
**Infrastructure as a Code** なんていう文脈の中では **Chef, Puppet** などと並んで必ず出てくるツールの1つかと思います。

このあたりの話は話しだすとそれだけで記事が書けてしまうので、既に公開されているありがたい資料へのリンクを貼らせていただきます。

- **[AnsibleによるInfrastructure as code入門 ( @kk_Ataka )](http://www.slideshare.net/kk_Ataka/ansibleinfrastructure-as-code)**

資料にも述ありますが、**構成管理ツールAnsible**、という点に限って特徴を述べると

- **エージェントレスな構成管理ツール**
- **サーバはPython 2.6+、ホストはPython 2.4+があれば動作可能**
- **設定ファイルが少なく、記述がシンプル**
- **シンプルさ故に複雑構成は苦手**

というような点が挙げられるかと思います。

## **Ansibleの由来**

個人的に製品名とか略称の元ネタが気になってしまうタチなので、 Ansible も調べてみました。

- **[アンシブル - wikipedia](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%B3%E3%82%B7%E3%83%96%E3%83%AB)**

どうやらこいつらしい。 **SF小説に出てくる "超光速通信" の技術名**。

「 **まるでアンシブルのように超光速でサーバ構築をびゅーん** 」

みたいな認識でいいんでしょうか。

- **[Ansibleが登場するSF小説「エンダーのゲーム」読んだ](http://open-groove.net/science-fiction/ansible-enders-game/)**

どう見てもIT界隈の人が読んでとっても面白いそうなのでポチッてみようかしら。

# **Ansibleを調べる**

Ansibleのことを調べたい場合、ググる以外では大きく分けて以下の方法があるかと思います。

- **Ansible本を読む**
- **公式ドキュメントを読む**
- **Ansible(のソースコード)を読む**

## **Ansible本を読む**

- **[入門Ansible](http://www.amazon.co.jp/dp/B00MALTGDY)**

意外とAnsibleをメインに扱った日本語の本は多分まだこれしかありません[^1]。
とはいえ、この記事でまとめているような内容に加え、より発展的ところも書かれているので特に不自由はないかと思います。

[^1]: (2016/12/24追記) Ansible関連情報をまとめた **[記事](http://qiita.com/t_nakayama0714/items/726130abd079e2d926e0)**を書きました。

この本では執筆時における最新版 **Ansible 1.6.6** をベースに書かれているので、その点だけ注意が必要です。
ただ、近々リリースされる **Ansible 2.0** でも互換は維持されるのであまり気にする必要もないかもしれません。

## **公式ドキュメントを読む**

やはりどのようなソフトウェアであっても、公式ドキュメントが大事と思います。

- **[Ansible Documentation](http://docs.ansible.com/ansible/index.html)**

残念ながら英語でしか書かれていませんが、 **Sphinxで書かれたきれいな構成** と **なんとなく読みやすいフォント** で個人的にはとても読みやすいと思っています。

...すみません、章立てがよくできているからとても読みやすいのだと思います。

Ansibleを実際に使う中では **[Playbooks](http://docs.ansible.com/ansible/playbooks.html)** とか **[Module Index](http://docs.ansible.com/ansible/modules_by_category.html)** をよく読みました。

## **Ansible(のソースコード)を読む**

- **[ansible/ansible - GitHub](https://github.com/ansible/ansible)**

半分冗談、半分マジみたいな話ではありますが、本や公式ドキュメントでダメならソースを読むのが理論上は最高です。
ただ、まだそこまで切羽詰まったことがないので真面目に読み解いたことはないです。
開発中のバージョンもここにあるのでAnsible 2.0を試す際に立ち入ったりしています。

# **Ansibleを試す**

やはりツールを知る上では **百聞は一見に如かず** ということでハンズオンが一番よいかと思います。
Ansibleは最小2ファイルからで動かせるのでハンズオンの敷居も非常に低いです。

さて、ハンズオン環境は **Vagrant + VirtualBox** で整えることにしましょう。

## **Vagrant + VirtualBoxの導入**

- **[Vagrant](https://www.vagrantup.com/)**
- **[VirtualBox](https://www.virtualbox.org/)**

それぞれ環境に合ったものをダウンロード、インストールしてください。
基本的にインストーラに沿って進めれば問題はないかと思います。

インストール後は下記のようにコマンドでバージョンが確認できればOKです。
(Windows風に書いていますが、MacやらLinuxの方は適宜読み替えてください)

```shell-session
C:\> vagrant -v
Vagrant 1.7.4

C:\> VBoxManage -v
5.0.12r104815
```

というわけでハンズオン環境は上のバージョンで作っていきます。

## **Ansibleハンズオン用VMの準備**

適当なフォルダにVagrant関係のファイルを作ります。

```shell-session
C:\> mkdir vagrant
C:\> cd vagrant
C:\vagrant> vagrant init
```

vagrant initを実行すると、カレントディレクトリに **vagrantfile** が生成されます。
vagrantfileはvagrantから起動するVMの設定ファイルで、今回は下記のように書いて下さい。

```rb:vagrantfile
Vagrant.configure(2) do |config|
  config.vm.define "controller" do |node|
        node.vm.box = "centos6.7"
        node.vm.hostname = "controller"
        node.vm.network :private_network, ip: "192.168.100.10"
        node.vm.network :forwarded_port, id: "ssh", guest: 22, host: 2210
  end
  config.vm.define "target" do |node|
        node.vm.box = "centos6.7"
        node.vm.hostname = "target"
        node.vm.network :private_network, ip: "192.168.100.20"
        node.vm.network :forwarded_port, id: "ssh", guest: 22, host: 2220
  end
end
```

詳しい記述については割愛しますが、多分なんとなく内容は想像つくかと思います。
用途としてはcontroller側でAnsibleを実行し、targetがその対象になるという感じです。

さて、それぞれのVMは **centos6.7** というBoxをベースにすると書いてありますが、これは準備する必要があります。
下記のコマンドを実行しましょう。

```sh
vagrant box add centos6.7 https://github.com/CommanderK5/packer-centos-template/releases/download/0.6.7/vagrant-centos-6.7.box
```

これはvagrantにおけるVMテンプレートである **Box** を手元に追加しています。
今回は特に意味もなくCentOS 6.7でやりますが、 **[Vagrantbox.es](http://www.vagrantbox.es)** でお好みのものを選んでもよいかと思います。
その際には **box addするときの名前** と **vagrantfileに指定するbox名を揃える** ようにして下さい。

それではBoxがダウンロードできたら、 **vagrantfileのあるフォルダ** で **vagrant up** コマンドを実行しましょう。

```shell-session
C:\vagrant>vagrant up
Bringing machine 'controller' up with 'virtualbox' provider...
Bringing machine 'target' up with 'virtualbox' provider...

(略)
```

念のためVMのステータスを確認します。 **vagrant status** コマンドでどうぞ。

```shell-session
C:\vagrant>vagrant status
Current machine states:

controller                running (virtualbox)
target                    running (virtualbox)

This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.
```

## **Ansibleのインストール**

それでは **controllerにAnsibleを導入** しましょう。
controllerへのSSHはlocalhost:2210が転送されるようにvagrantfileで設定しています。

が、考えるのが面倒なのでteratermマクロを作りました。よかったら使ってやって下さい。
(当然接続するのは Putty でも MobaXterm でもなんでもよいです)

```login.ttl
USERNAME = 'root' 
PASSWORD = 'vagrant' 

MESSAGE = 'Please choose a connection host.'#13#13 
strconcat MESSAGE ' 1. controller'#13 
strconcat MESSAGE ' 2. target'#13 
inputbox MESSAGE 'Input host number.'

str2int hostnum inputstr

if hostnum = 1 then 
    HOSTADDR = '127.0.0.1:2210' 
elseif hostnum = 2 then 
    HOSTADDR = '127.0.0.1:2220' 
else 
    messagebox 'Input number 1 or 2.' 'Input error' 
    end 
endif

COMMAND = HOSTADDR
strconcat COMMAND ' /ssh /2 /auth=password /user=' 
strconcat COMMAND USERNAME 
strconcat COMMAND ' /passwd=' 
strconcat COMMAND PASSWORD

connect COMMAND

end
```

AnsibleサーバのAnsible導入要件は **python 2.6+** でしたので念のため確認します。


```shell-session
[root@controller ~]# python --version
Python 2.6.6
```

あとはyumを使ってAnsibleをインストールしましょう。

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

(略)

Installed:
  ansible.noarch 0:1.9.4-1.el6

Dependency Installed:
  PyYAML.x86_64 0:3.10-3.1.el6                          libyaml.x86_64 0:0.1.3-4.el6_6
  python-babel.noarch 0:0.9.4-5.1.el6                   python-crypto.x86_64 0:2.0.1-22.el6
  python-crypto2.6.x86_64 0:2.6.1-2.el6                 python-httplib2.noarch 0:0.7.7-1.el6
  python-jinja2.x86_64 0:2.2.1-2.el6_5                  python-keyczar.noarch 0:0.71c-1.el6
  python-paramiko.noarch 0:1.7.5-2.1.el6                python-pyasn1.noarch 0:0.0.12a-1.el6
  python-setuptools.noarch 0:0.6.10-3.el6               python-simplejson.x86_64 0:2.0.9-3.1.el6
  sshpass.x86_64 0:1.05-1.el6

Complete!
```

依存関係によって結構色々とインストールされます。

```shell-session
[root@controller ~]# ansible --version
ansible 1.9.4
```

というわけで **Ansible 1.9.4** がインストールされました。

と、ここで ansible までタイプしてからTab補完をしてみてください。

```shell-session
[root@controller ~]# ansible
ansible           ansible-doc       ansible-galaxy    ansible-playbook  ansible-pull      ansible-vault
```

色々出てきますね。ただし今回のハンズオンで使うのは基本的に **ansible-playbookだけ** です。悪しからず。

## **Ansibleの実行テスト**

Ansibleでは **Playbook** と呼ばれる単位で対象の構成手順を記述しますが、一旦ワンライナーで疎通確認をします。
Ansibleはエージェントレスな構成管理ツールでSSHを使います。
ですので、Ansibleサーバから対象にSSHでログインできるようにするため、公開鍵を送り込んでおきましょう。

```shell-session
[root@controller ~]# ssh-keygen -t rsa
(パスフレーズは入力しない)

[root@controller ~]# ssh-copy-id root@192.168.100.20
(接続確認が出るのでyes)
```

こうすることで、controllerから `ssh root@192.168.100.20` と実行してもパスワードの入力を求められずに済むかと思います。
(ハンズオンのためとりあえずrootでAnsibleを動かしています。モラルについてはお察しです。ご注意ください。)

これでAnsibleの実行は目前です。対象ホストをまとめて記述したinventoryファイルを作りましょう。

```shell-session
[root@controller ~]# mkdir /ansible
[root@controller ~]# cd /ansible
[root@controller ansible]# mkdir inventory
[root@controller ansible]# vi inventory/hosts
```

```inventory/hosts
[targets]
192.168.100.20
```

このinventoryファイルでは、targetsという **グループ** に192.168.100.20という **ホスト** を属させています。
今回のハンズオンではホスト1台しか扱わないのですが、実際に使う際にはグループ分けであれやこれや悩むことになるかと思います。

それでは長い道のりでしたが疎通確認しましょう。

```shell-session
[root@controller ansible] ansible all -i inventory/hosts -m ping
192.168.100.20 | success >> {
    "changed": false,
    "ping": "pong"
}
```

このコマンドでは先ほど記述したinventoryファイル中にある全て(all)のホストに対してpingモジュールで疎通確認をしています。
結果は [ホスト] | [結果] >> [メッセージ] というように表示されるかと思います。

## **Playbookを試してみる**

さて、ここから **Playbook** を作りながら真面目にハンズオンを進めましょう。
以下のファイルを配置してみましょう。

```yaml:group_vars/targets.yml
message: "Hello Ansible !"

fruits:
  apples:
    amount: 10
  bananas:
    amount: 20
  oranges:
    amount: 30
```

このファイルではグループ変数を記述しています。
拡張子からもわかるように **[YAML](http://www.yaml.org)** という形式で記述します。

**YAML** は YAML Ain't Markup Language の略で、IT業界ではたまに目にする **再帰定義** の1つですね。
他には**GNU**(GNU is Not Unix), **PHP**(PHP: Hypertext Processor), **RPM**(RPM Package Manager)なんかが有名でしょうか。何言うてまんねん的な感じが割と好きです。
公式サイトを見ると分かるように非常にきっちりした定義があるのですが、Ansibleでは「 **配列** 」「 **シーケンス** 」とか限られた範囲がわかっていれば十分です。

**[YAML Syntax](http://docs.ansible.com/ansible/YAMLSyntax.html)**

```yaml:test.yml
- hosts: targets
  user: root
  tasks:
    - name: output message.
      debug: msg="{{ message }}"

    - name: output fruits
      debug: msg="We want {{ item.value.amount }} {{ item.key }} !" 
      with_dict: "{{ fruits }}"
```

こちらがPlaybookと呼ばれるファイルです。こちらもYAMLで簡潔に記述します。
解説は後にしてまず実行してみましょう。

```shell-session
[root@controller ansible]# ansible-playbook -i inventory/hosts test.yml
PLAY [targets] ****************************************************************

GATHERING FACTS ***************************************************************
ok: [192.168.100.20]

TASK: [output message.] *******************************************************
ok: [192.168.100.20] => {
    "msg": "Hello Ansible !"
}

TASK: [output fruits] *********************************************************
ok: [192.168.100.20] => (item={'key': 'apples', 'value': {'amount': 10}}) => {
    "item": {
        "key": "apples",
        "value": {
            "amount": 10
        }
    },
    "msg": "We want 10 apples !"
}
ok: [192.168.100.20] => (item={'key': 'oranges', 'value': {'amount': 30}}) => {
    "item": {
        "key": "oranges",
        "value": {
            "amount": 30
        }
    },
    "msg": "We want 30 oranges !"
}
ok: [192.168.100.20] => (item={'key': 'bananas', 'value': {'amount': 20}}) => {
    "item": {
        "key": "bananas",
        "value": {
            "amount": 20
        }
    },
    "msg": "We want 20 bananas !"
}

PLAY RECAP ********************************************************************
192.168.100.20             : ok=3    changed=0    unreachable=0    failed=0
```

というような感じに動きます。
実行したコマンドに注目していただきたいのですが、コマンドで与えたのは

- **inventoryファイル**
- **Playbook**

の2つです。
実行結果を見ると、**group_varsでtargetsグループに定義した変数が反映されている** ことがわかると思います。
このパターンではtest.ymlで実行対象として`hosts: targets`というグループを指定したため、group_varsから自動的にtargets.ymlが読み込まれています。

このように、Ansibleでは決まったディレクトリなどから情報を **暗黙的に取得** してくるため、そういった依存関係を理解しておく必要があります。
ただ、一度理解してしまえば簡潔な記述で多彩に使い回しができるようになるため、しっかりと理解することを強くおすすめします。

また、それぞれのtaskではdebugモジュールを使って、変数を出力しているだけですが、ディクショナリ形式で書いた`fruits`の使い方に注目してみてください。

```yaml
fruits:
  apples:
    amount: 10
  bananas:
    amount: 20
  oranges:
    amount: 30
```

```yaml
- name: output fruits
  debug: msg="We want {{ item.value.amount }} {{ item.key }} !" 
  with_dict: fruits
```

まず、このタスクは `fruits` 変数の値を `with_dict` で取り出しながらdebugモジュールでメッセージを出力していきます。
ループ毎に取り出された要素は `item` という定義済み変数に格納され、処理中に使うことができます。
このとき `fruits` 直下にある果物名は `item.key` で取得でき、果物名の下に持たせている数量は `item.value.amount` で取得されます。

このあたりについては少し慣れないかもしれませんが、書いているうちになんとなく身についてきますのでdebugモジュールを使いながら地道に試してみるのがいいかと思います。

# **Ansibleをもう少し試す**

ここまででinventoryファイルとPlaybook, group_varsなどで実際にAnsibleを動かしてみました。
実際にパッケージインストールやファイル作成などを行って、構成管理を実施してみましょう。

## **パッケージ**

パッケージ操作は [yumモジュール](http://docs.ansible.com/ansible/yum_module.html) 等で行えます。下記のようなイメージです。

```yaml
- name: install packages from yum
  yum: name={{ item }} state=latest
  with_items:
    - jq
    - ruby
    - httpd
```


これは最もシンプルな書き方で、指定パッケージの最新版(latest)をインストールします。オプションで使用するyumリポジトリの指定なども可能です。
また、上のコードでは「 **jq, ruby, httpdがインストールされていること** 」を示しますが、 `state=absent` とすれば **インストールされていないこと** を示すコードになります。

## **cron**

[cronモジュール](http://docs.ansible.com/ansible/cron_module.html) を使ってジョブを設定することもできます。

```yaml
- name: register cron job
  cron: name="check ping" day="*/2" hour="12" minute="0" job="ping -c 3 192.168.100.10"
```

個人的にはcron書く時に日時をどの順で書くか毎回調べてしまうので、これはわかりやすくてとてもよいです(無能)

## **ファイル・ディレクトリ**

Ansibleではもちろんディレクトリの作成やファイルの配置を行うことができます。

### **ディレクトリ作成**

ディレクトリ作成は [fileモジュール](http://docs.ansible.com/ansible/file_module.html) で実施します。

```yaml
- name: create directories
  file: path={{ item.path }} owner={{ item.owner }} group={{ item.group }} mode=0{{ item.mode }} state=directory
  with_items:
    - { "path":"/opt/ansible", "owner":"root", "group":"root", "mode":"755" }
    - { "path":"/opt/vagrant", "owner":"vagrant", "group":"vagrant", "mode":"755" }
```

ディレクトリ作成の場合は `state=directory` を指定しますが、他には `link`, `file` などもあります。
こちらもyumの時と同様に `state=absent` を指定すると `path` のものが削除されるように動きます。
ドキュメントにも記載がありますが、fileモジュールとして受け取る **`mode` は8進数**であることに注意してください。

> remember that modes are actually octal numbers (like 0644). Leaving off the leading zero will likely have unexpected results.
> (modeで指定する値が8進数であることを忘れないで下さい。もし先頭の0を忘れた場合、予期せぬ動きになります。)

また、 `mode` に関しては `u+rwx` など **シンボルで指定する** ことも **Ansible 1.8以降** で可能になったようです。

### **静的ファイル配置**

静的ファイル配置と言っていますが要するにファイルのコピーです。
[copyモジュール](http://docs.ansible.com/ansible/copy_module.html) はAnsibleサーバ側に保持しているファイルをホスト側に配置します。

```yaml
- name: copy files
  copy: src=./files/hoge dest=/opt/ansible/hoge owner=root group=root mode=0755
```

### **動的ファイル配置**

こちらは **[templateモジュール](http://docs.ansible.com/ansible/template_module.html)** を用いて、ファイルのコピー時に変数を展開しながら配置します。

```yaml
- name: copy template files
  template: src=./templates/fuga.j2 dest=/opt/ansible/fuga owner=root group=root mode=0755
```

コピー元として使用されるテンプレートファイルは **[jinja2](http://jinja.pocoo.org/docs/dev/)** 形式で記述する必要があります。

```jinja:templates/fuga.j2
This is a jinja template file.

{{ message }}

jinja template can extract variables. like, ...

{% for key,value in fruits.iteritems() %}
We want {{ value.amount }} {{ key }} !
{% endfor %}
```

jinja2 も YAML 同様に決まり事は多いのですが、 これも全てを把握する必要はなく、

**[Template Designer Documentation](http://jinja.pocoo.org/docs/dev/templates/)**

の内容を使えれば大体OKかと思います。
jinja template においても Playbook 内とほぼ大差ない形で変数を使うことができます。
`with_dict` のようにfor文でイテレーションも可能ですが、こちらは `item` を挟まずに使えます。

## **動かしてみよう**

これまでの内容を全て詰め込んでみましょう。

```yaml:main.yml
- hosts: targets
  user: root
  tasks:
  - name: install packages from yum
    yum: name={{ item }} state=latest
    with_items:
      - jq
      - ruby
      - httpd

  - name: register cron job
    cron: name="check ping" day="*/2" hour="12" minute="0" job="ping -c 3 192.168.100.10"

  - name: create directories
    file: path={{ item.path }} owner={{ item.owner }} group={{ item.group }} mode=0{{ item.mode }} state=directory
    with_items:
      - { "path":"/opt/ansible", "owner":"root", "group":"root", "mode":"755" }
      - { "path":"/opt/vagrant", "owner":"vagrant", "group":"vagrant", "mode":"755" }

  - name: copy files
    copy: src=./files/hoge dest=/opt/ansible/hoge owner=root group=root mode=0755

  - name: copy template files
    template: src=./templates/fuga.j2 dest=/opt/ansible/fuga owner=root group=root mode=0755
```

ここまでやると多分こんな感じになっているかと思います。

```
ansible/
├─inventory/
│　└─hosts
├─group_vars/
│　└─targets.yml
├─files/
│　└─hoge
├─templates/
│　└─fuga.j2
├─test.yml
└─main.yml
```

ただ、色々書いて不安なので一旦 **[Check Mode](http://docs.ansible.com/ansible/playbooks_checkmode.html)** で動かしてみましょう。
Check Mode とは実際に変更を行わないモードで、 Playbook を実行した際にどの部分に変更が加わるのかを確認することができます。一般的には **Dry run** と呼ばれるものです。
オプションで `--check` を与えると Check Mode で動きます。

```shell-session
[root@controller ansible]# ansible-playbook --check -i inventory/hosts main.yml

PLAY [targets] ****************************************************************

GATHERING FACTS ***************************************************************
ok: [192.168.100.20]

TASK: [install packages from yum] *********************************************
changed: [192.168.100.20] => (item=jq,ruby,httpd)

TASK: [register cron job] *****************************************************
skipping: [192.168.100.20]
ok: [192.168.100.20]

TASK: [create directories] ****************************************************
changed: [192.168.100.20] => (item={'owner': 'root', 'path': '/opt/ansible', 'group': 'root', 'mode': '755'})
changed: [192.168.100.20] => (item={'owner': 'vagrant', 'path': '/opt/vagrant', 'group': 'vagrant', 'mode': '755'})

TASK: [copy files] ************************************************************
changed: [192.168.100.20]

TASK: [copy template files] ***************************************************
changed: [192.168.100.20]

PLAY RECAP ********************************************************************
192.168.100.20             : ok=5    changed=4    unreachable=0    failed=0

```

さて、おそらくコマンド自体は動いたかと思いますが cron の部分が少し気になります。
`skipping` と表示されていますが、これはなんでしょうか。

もうすこし詳細に見るために `-v` オプションを使いましょう。

```shell-session
[root@controller ansible]# ansible-playbook --check -i inventory/hosts main.yml -v

(略)

TASK: [register cron job] *****************************************************
skipping: [192.168.100.20]
ok: [192.168.100.20] => {"changed": false, "msg": "remote module does not support check mode", "skipped": true}

(略)

```

ということでした。
Check Mode のドキュメントにもありますが、全てのモジュールが Check Mode に対応しているわけではないため、確認がスキップされた、ということのようです。

> ‘check mode’ (which contains most of the primary core modules, but it is not required that all modules do this)
> (多くのコアモジュールでcheck modeがサポートされますが、全てのモジュールがそうというわけではありません。)

ちなみに `-v` オプションですが、vを重ねる毎にメッセージが詳細になり、`-vvv` ではSSHで具体的にどう動いているのかまで分かるようになります。
それでは Playbook を実行してみましょう。

```shell-session
[root@controller ansible]# ansible-playbook -i inventory/hosts main.yml

PLAY [targets] ****************************************************************

GATHERING FACTS ***************************************************************
ok: [192.168.100.20]

TASK: [install packages from yum] *********************************************
changed: [192.168.100.20] => (item=jq,ruby,httpd)

TASK: [register cron job] *****************************************************
changed: [192.168.100.20]

TASK: [create directories] ****************************************************
changed: [192.168.100.20] => (item={'owner': 'root', 'path': '/opt/ansible', 'group': 'root', 'mode': '755'})
changed: [192.168.100.20] => (item={'owner': 'vagrant', 'path': '/opt/vagrant', 'group': 'vagrant', 'mode': '755'})

TASK: [copy files] ************************************************************
changed: [192.168.100.20]

TASK: [copy template files] ***************************************************
changed: [192.168.100.20]

PLAY RECAP ********************************************************************
192.168.100.20             : ok=6    changed=5    unreachable=0    failed=0
```

無事終了したようです。
結果としては GATHERING FACTS を含めて6項目のタスクがあり、 **問題なく終了(ok)** したこと、またうち5項目で **変更が行われた(changed)** ことを示しています。

### **jinja templateの中身チェック**

念のため想定通りに jinja template が展開されたか見てみましょう。

```shell-session
[root@controller ansible]# ssh root@192.168.100.20 cat /opt/ansible/fuga
This is a jinja template file.

Hello Ansible !

jinja template can extract variables. like, ...

We want 10 apples !
We want 30 oranges !
We want 20 bananas !
```

想定通り、いいですね。

## **冪等性(べきとうせい)**

ここでもう一度同じ Playbook を実行してみましょう。

```shell-session
[root@controller ansible]# ansible-playbook -i inventory/hosts main.yml

(略)

PLAY RECAP ********************************************************************
192.168.100.20             : ok=6    changed=0    unreachable=0    failed=0
```

当然1件も変更が行われませんでしたが、これは対象が Playbook で指定した **あるべき状態** になっているということを示します。
また、既に対象があるべき状態にあったため、何の変更も行われなかった、 **冪等性** が保たれたということになります。

この冪等性というものが構成管理ツールでは重要であるとされていて、例えば copy モジュールはファイルの **上書きすら行っていない** のですが、これを単に `cp` コマンドでやっていたりすると上書きをしてしまうことでしょう。
もちろん、シェルスクリプトの作りこみで担保することは可能ですが、余計な記述が増えることになり、 **色々と面倒** です。
その面倒な冪等性の担保をツールがやってくれるというのが構成管理ツールの旨味といえます。
(ただし、shellモジュールやcommandモジュールはAnsible側で冪等性を担保できないため、利用には注意が必要です)

## **roleの利用**

ここまででタスクは1つの YAML ファイルにまとめて記述していますが、これでは似たホストを作る際に使い回しが利きません。
そこで、一部のタスクを **role** として切り出し、部品化をしてみましょう。

```yaml:master.yml
- hosts: targets
  user: root
  tasks:
  - name: register cron job
    cron: name="check ping" day="*/2" hour="12" minute="0" job="ping -c 3 192.168.100.10"

  roles:
  - role-common
  - role-web
```

```yaml:roles/role-common/tasks/main.yml
- name: install packages from yum
  yum: name={{ item }} state=latest
  with_items:
    - jq
    - ruby

- name: create directories
  file: path={{ item.path }} owner={{ item.owner }} group={{ item.group }} mode=0{{ item.mode }} state=directory
  with_items:
    - { "path":"/opt/ansible", "owner":"root", "group":"root", "mode":"755" }

- name: copy files
  copy: src=./files/hoge dest=/opt/ansible/hoge owner=root group=root mode=0755
```

```yaml:roles/role-web/tasks/main.yml
- name: install packages from yum
  yum: name={{ item }} state=latest
  with_items:
    - httpd

- name: create directories
  file: path={{ item.path }} owner={{ item.owner }} group={{ item.group }} mode=0{{ item.mode }} state=directory
  with_items:
    - { "path":"/opt/vagrant", "owner":"vagrant", "group":"vagrant", "mode":"755" }

- name: copy template files
  template: src=./templates/fuga.j2 dest=/opt/ansible/fuga owner=root group=root mode=0755
```

単純に切り出しただけなので `with_items` の扱いが雑だとかは言わないで下さい(笑)

```
ansible/
├─inventory/
│　└─hosts
├─group_vars/
│　└─targets.yml
├─files/
│　└─hoge
├─templates/
│　└─fuga.j2
├─roles/
│　├─role-common/
│　│　└─tasks/
│　│　   └─main.yml
│　└─role-web/
│　 　└─tasks/
│　　    └─main.yml
├─test.yml
├─main.yml
└─master.yml
```

roles で呼び出した role は rolesディレクトリ配下の名前で探しだされ、tasks直下のmain.ymlが実行されます。
role では依存関係を持たせる **dependencies** なども使えますが今回は扱いません。

では実行しましょう。

```shell-session
[root@controller ansible]# ansible-playbook --check -i inventory/hosts master.yml

(略)

TASK: [role-common | install packages from yum] *******************************
ok: [192.168.100.20] => (item=jq,ruby)

(略)

PLAY RECAP ********************************************************************
192.168.100.20             : ok=7    changed=0    unreachable=0    failed=0
```

内容的には先ほどまでと変わっていないため changed は出ませんが、 role に含まれているタスクの表示が変わったかと思います。

# **おわりに**

ハンズオンを終えたら、VMのシャットダウンを忘れずに。

```shell-session
C:\vagrant>vagrant halt
```

また最初から遊びたい人は無慈悲に **vagrant destroy** してもいいでしょう。
(元々 **[sahara](https://github.com/jedi4ever/sahara)** プラグイン使ったらもっと楽チンにできるんですけどね、説明が面倒でね...)

この記事を書き始めたのは **[Ansible Advent Calendar 2015](http://qiita.com/advent-calendar/2015/ansible)** の **枠が空いてしまって気持ち悪かったから** でした。
ちょうど自分の周りで今後Ansibleを触ってもらう人にどんな資料がいいかな～と思っていたところでもあったので、練習を兼ねて書いてみようと思った次第です。

記事を書き始めた当初は「 **入門レベルだからそんなに分量ないだろ～** 」と思っていましたが、順を追って説明していくと思った以上に増えてしまいました。
ただ、今回説明した内容でAnsibleとして大体の部分は押さえられているかと思うので、あとは必要に応じて個別に調べていけるのかなと思います。

Ansibleが **もっともっと盛り上がる** 助けになれれば幸いです。