知識０からAnsibleハンズオンをやってみた

#**はじめに**
[ラクス Advent Calendar 2017](https://qiita.com/advent-calendar/2017/rakus)の8日目の記事です。
12月も1週間が終わり、確実にクリスマスが近づいてきていますね！
昨日は[osamu_fujikake](https://qiita.com/osamu_fujikake)さんの[超過酷！大阪港開港記念イベントフェリーハッカソンに行ってきた！！](http://036-yakudachi-info.com/blog/hackathon/ferry-hackathon)でした。
本日はQiita初投稿の私が担当いたします。

さて本日のトピックですが、最近社内のいろんな場所で「Ansible」というワードを耳にすることが多いのですが、、、

Ansible? 構成管理ツール？ バージョン管理でなく？

こんな状態でしたので、今回0から学習してみることにしました。

本記事は[Ansibleをはじめる人に。](https://qiita.com/t_nakayama0714/items/fe55ee56d6446f67113c)に記載のハンズオンに取り組んで得た知識＋色々調べたことを初学者の言葉でざっくりと残し、大枠を捉えることを目的とします。
注）対象の読者は「What's "Ansible"?」な方一択ですのであしからず・・・

#**Ansibleとは**


[Ansible](https://www.ansible.com/)は構成管理ツールと呼ばれているものの1種であり、類似ツールにはPuppetやChefなどが挙げられます。
構成管理ツールとは、シェルスクリプトのように「マシンの処理を自動化する」という観点ではなく、「マシンを目的の状態にする」という観点で作成されたツールのことを指します。
これだけ言われてもよくわからないなぁという感じだと思いますので、「対象のマシンにユーザを追加する」を例に違いを確認します。


「マシンの処理を自動化する」という観点では「ユーザーを追加する」という操作を実行してしまうため、既にユーザーが存在する場合、何もエラーハンドリングがなされていなければ再実行時に「既にユーザーが存在する」というエラーとなってしまいます。
対して、「マシンを目的の状態にする」という観点では「ユーザーが追加されている状態にする」ということが重要になります。「ユーザーが存在するかどうか」という判断は構成管理ツールが行ってくれるためエラーにはなりません。

どうでしょう、違いはご理解いただけたでしょうか？
エラーになるかならないかだけじゃないか？エラーをなめちゃいけません。えぇいけませんとも。

#**Ansibleの特徴**
Ansibleの特徴として以下のものがよく挙げられています。

* エージェントレスである
* 何度同じ操作をしても同じ状態になる（冪等性）
* 設定ファイルがシンプルに記述できる

実際にハンズオンに取り組んでみると「設定ファイルがシンプルに記述できる」の部分は多少知識を必要とはしますが、書いてある内容は**「なるほど」**となるのではないでしょうか。

#**Ansibleハンズオン**
Ansibleの説明が長くなってしまいましたが本題はここからです。
[Ansibleをはじめる人に。](https://qiita.com/t_nakayama0714/items/fe55ee56d6446f67113c)で紹介されていたものを実際に試して見ました。
構成管理と言った知識を持たない私ではわからないものもありましたのではじめに登場人物を整理しましょう。

#####**ハンズオンで使われているソフトウェア**
- **VirtualBox**：言わずと知れた仮想化ソフト
- **Vagrant**：VirtualBoxなどの仮想化ソフトの操作を代行してくれます。使用方法までは割愛しますがOSインストール済みVMの作成、ネットワーク設定やSSH環境の整備までやってくれちゃいます。（素晴らしい）
- **Ansible**：本日の主人公です。ここまで律儀に読んでくださった方~~、読み飛ばした方~~には説明不要でしょう。

大きくはこの3つが登場人物です。
Vagrant＋VirtualBoxでハンズオン用VMのを用意し、Ansibleを試していく。
これがハンズオンの流れとなっています。
自分のPCからはハンズオン用VMにsshで接続してハンズオンを実行していくのでなんとも手軽に試すことができました。（幸せ）

#**Ansibleハンズオン用VMの準備**
さて、Ansibleハンズオンを行うためにVagrantでVMの準備を行って行きます。
Vagrantでは**Box**と**Vagrantfile**をもちいてVM環境を作成します
**Box**とはVMのベースになるイメージですハンズオンではCentOS 6.7でしたが他のものも色々用意されているようです。
**Box**は以下のコマンドで用意しました。

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

ではもう1つのVMの構成を記述しておく**Vagrantfile**の中身を見ていきます。

```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
```
なんとなく意味合いはわかりますね。

**Vagrantfile**の中身からも予想できるかと思いますがハンズオンではcontrollerとtargetの環境を準備します。
controller（管理用マシン）でAnsibleを実行し、target（管理対象マシン）の状態を操作するわけですね。
あとはVagrantfileと同じ階層で```vagrant up```コマンドを実行しでVM環境を構築します。
ここまでが完了すると以下のような状態が出来上がっているわけです。

![Ansibleハンズオン図解.001.jpeg](https://qiita-image-store.s3.amazonaws.com/0/195788/2397d803-c277-4b43-a127-d79b8565be26.jpeg)

#**Ansibleを準備する**
ハンズオンの環境もできたのでAnsibleを準備していきましょう。
controllerにSSH接続してAnsibleをインストールしていきます。

Ansibleの管理用マシンには、
**Python 2（バージョン2.6または2.7）またはPython 3（バージョン3.5以上）がインストールされているマシン**
という要件があります。[[詳細]](http://docs.ansible.com/ansible/latest/intro_installation.html#control-machine-requirements)
ですので先にPythonのバージョンを確認しましょう。

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

やっとAnsibleの出番です。
yumコマンドでインストールします。

```
[root@controller ~]# yum install ansible
  省略
[root@controller ~]# ansible --version
ansible 2.4.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.6/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.6.6 (r266:84292, Jul 23 2015, 15:22:56) [GCC 4.4.7 20120313 (Red Hat 4.4.7-11)]
```

Ansibleがインストールできました。

Ansibleを使うための設定をしていきます。
AnsibleではSSH接続によって管理対象のマシンに実行していくため、パスワード無しでログインできるほうが何かと便利なためその設定（公開鍵を送り込む）をしていきます。詳しくは[こちら](https://qiita.com/kentarosasaki/items/aa319e735a0b9660f1f0)

```
[root@controller ~]# ssh-keygen -t rsa
特に設定しないのでEnter
[root@controller ~]# ssh-copy-id root@192.168.100.20
```

#**Ansibleを試す①**
やっとAnsibleを試すことができる状態になりました。
後は設定ファイルを記述してコマンドを実行するだけです。
Ansibleでは、管理対象をまとめて記述しておく**inventoryファイル**と
管理対象の構成内容を記述しておく**Playbook**という2つの設定ファイルが存在します。

まずは管理対象をまとめて記述しておく**inventoryファイル**から記載していきます。

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

```rb:hosts
[target]
192.168.100.20
```
今回targetが1つだけなので非常にシンプルです。
targetというグループに192.168.100.20が属しているという意味合いになります。
（本来ならグループ分けが必要だったりと簡単ではないらしいですが・・・）

次に、管理対象の構成内容を記述しておく**Playbook**を作っていきます。
PlaybookはAnsibleのメイン部分と言っても過言ではないでしょう。~~多分!~~



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

fruits:
  apples:
    amount: 10
  bananas:
    amount: 20
  oranges:
    amount: 30
```
`targets.yml`はPlaybookではないですよ。
グループ変数を記述しているファイルになります。
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 }}"
```
こちらの`test.yml`がPlaybookです。実行するグループと実行するユーザ、実行内容が記載されています。
実行結果を見た方がわかりやすいので```ansible-playbook```コマンドで実行します。

```
[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
```

おぉ、なんだか見覚えのあるものが実行されました。
実行したコマンドをみて見ます。
```ansible-playbook -i inventory/hosts test.yml```
引数に指定されているのは

* inventory
* Playbook

の2つです。
ただ、どう見ても```targets.yml```で記述したグループ変数が使われているようです。（不思議）

test.ymlで実行対象として```hosts: targets```というグループを指定したため、group_varsディレクトリから自動的にtargets.ymlが読み込まれ、定義した変数が反映されるのだとか・・・

この仕組みはAnsibleが持っている依存関係だそうで、決まったディレクトリなどから暗黙的に情報を取得します。~~（これは理解するのに骨が折れそう）~~

#**Ansibleを試す②**
先ほどの実行では出力をしただけでしたが、
次はAnsibleが持つモジュールを利用してtargetに対し

* パッケージのインストール > yamモジュール
* ジョブの設定 > cronモジュール
* ディレクトリの作成、ファイルの配置 > fileモジュール 
* ファイルのコピー > copyモジュール

を行っていきます。

```rb: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
```

このPlaybookは

* jq, ruby, httpdをインストール
* pingを実行するjobの設定
* ansible,vagrantディレクトリの作成
* hogeファイルのコピー

を行います。

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

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

TASK [Gathering Facts] ********************************************************************************************************************************************************************************************
ok: [192.168.100.20]

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

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

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

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

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

```
正常に実行されました！目的の4件の変更があったこともわかりやすいです。

#**冪等性**
ここまでのハンズオンに取り組むとAnsibleの特徴である

* エージェントレスである
* 設定ファイルがシンプルに記述できる

最後に**何度同じ操作をしても同じ状態になる（冪等性）**の確認をして締めましょう。
もう一度同じ```main.yml```のPlaybookを実行します。

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

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

TASK [Gathering Facts] ********************************************************************************************************************************************************************************************
ok: [192.168.100.20]

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

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

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

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

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

1つも変更はなかったという結果が得られました。この**対象のマシンがすでに目的の状態にあるとき**はなんの変更も行わない**何度同じ操作をしても同じ状態になる**
この性質が**冪等性**と呼ばれるものです。
パッケージのインストールもしませんし、ファイルのコピーもしません。
必要のない処理は全部スキップしてしまうのです。

#**おわりに**
今回一部ハンズオンの解説を省いた部分がありますが、**大枠を捉える**を目的としていますのでご容赦ください。

社内で話題のツールであったというところから取り組んでみたハンズオンでしたが、全くの知識0ベースからでも気軽に取り組め、一定ラインまでは理解できたかと思います。~~（個人の感想です）~~
これもAnsibleの特徴故かと思います。

私が言うのも変な話ですが、皆様がAnsibleに触れるきっかけになれば幸いです。

#**参考資料**
- Ansibleをはじめる人に。
    - https://qiita.com/t_nakayama0714/items/fe55ee56d6446f67113c
- サーバ管理者も開発者も知っておきたい構成管理ツールとAnsibleの基礎知識
    - http://www.atmarkit.co.jp/ait/articles/1603/24/news014.html
- エージェントレスでシンプルな構成管理ツール「Ansible」入門
    - https://knowledge.sakura.ad.jp/3124/
- VagrantとDockerについて名前しか知らなかったので試した
    - https://qiita.com/hidekuro/items/fc12344d36d996198e96
- ssh-copy-idで公開鍵を渡す
    - https://qiita.com/kentarosasaki/items/aa319e735a0b9660f1f0