はじめてのAnsible


今更ながらAnsibleでちょっと遊んでみたのでその備忘録です。
今回はこちらのページとSoftware Design 2014年11月号にお世話になりました。

[Ansibleチュートリアル](http://yteraoka.github.io/ansible-tutorial/)

[構成管理ツール Ansibleを使ってみる](http://dev.classmethod.jp/tool/ansible/)

#1. Ansibleとは

AnsibleはPythonで開発された構成管理ツールです。構成管理ツールといえばChefですが、Chefとの主な違いは下記のとおりです。

1. 設定される側にChefクライアントのようなアプリケーションをインストールする必要がない。Python(2.4以上)が導入されておりsshでログインできればOK。
2. Chefのレシピに比べると構成情報を書くのが楽。RecipeかいてJSONいじって・・・とかではなくてplaybook(Ansibleの構成情報を記述するファイル)にさらっとかける。
3. 構成情報はYAMLで書く。Ruby苦手でも大丈夫。

上述のように、Chefに比べ、管理される側に色々入れる必要がなく、構成情報の記述が楽なので、Infrastructure as Codeを始めるには持って来いじゃないでしょうか:-)

#2. 実行環境の整備
##2.1 実行環境
Macのvagrant上にCentOSを2台立てて、IPを下記のとおりに設定します。
VagrantでCeontOS 6.5のboxがすでに追加されていることを前提とします。

|役割|OS|ip addr|
|:-:|:-:|:-:|
|ホスト|OS X 10.9.5|-|
|Ansibleサーバ(nodeA)|CentOS 6.5|192.168.33.11|
|Ansibleに設定されるノード(nodeB)|CentOS 6.5|192.168.33.12|

##2.2 環境構築

今回は2台のノードを立てるので、下記のVagrantfileを利用します。

```ruby:Vagrantfile
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

 config.vm.box = "ceontos"
 
 config.vm.define :node1 do |node|
   node.vm.box = "ceontos"
   node.vm.network :forwarded_port, guest: 22, host: 2041, id: "ssh"
   node.vm.network :private_network, ip:"192.168.33.11"
 end

 config.vm.define :node2 do |node|
   node.vm.box = "ceontos"
   node.vm.network :forwarded_port, guest: 22, host: 2041, id: "ssh"
   node.vm.network :private_network, ip:"192.168.33.12"
 end

end
```

```vagrant up```し, node1, node2を起動します。

```bash:
$ vagrant up
```

node1,node2のssh周りの設定をします。最後のscpはnode1に秘密鍵をコピーしておき、node1からnode2へsshログインできるようにするためのものです。

```profile:
$ vagrant ssh-config node1 > ssh_config
$ vagrant ssh-config node2 >> ssh_config
$ scp -F ssh_config ~/.vagrant.d/insecure_private_key node1:.ssh/id_rsa
```

#3. Ansibleのインストール

AnsibleはPython 2.6がインストールされているマシンで動作します。
pipで入れる方法もありますが、今回はCentOSなのでyumで入れてみます。


```shell-session:nodeA
[vagrant@vagrant-centos65 ~]$ sudo yum install -y ansible
```

Ansibleのインストール作業は以上で完了です。

#4. 実際にやってみる

##4.1 動作確認

まずは動作確認として、Ansibleで管理対象とするノードに疎通テストをしてみましょう。 Ansibleはhostsという管理ノードのグループ/IPを記述したファイルを読み込んで、そのノードに対して指定した処理を実施します。では、hostsファイルを準備しましょう。グループはtest-serverとします。

```Ini:
[test-server]
192.168.33.12
```

次に下記のコマンドを打鍵して、nodeBに対して疎通確認します。

```shell-session:nodeA
[vagrant@vagrant-centos65 ~]$ ansible test-server -i hosts -m ping

192.168.33.12 | success >> {
    "changed": false, 
    "ping": "pong"
}
```


```pong``` が帰ってくれば、疎通確認はOKです。

```-i``` で利用するhostsファイルを指定します。また、```/etc/ansible/hosts```に上述のhostsの内容を記述すれば```-i``` でhostsファイルを指定する必要はありません。また、hostsの中のどのグループに指定の処理を実行するかは```ansible``` の後ろで指定します。

```-m``` で利用するモジュールを指定します。モジュールはansibleでの何らかの処理(yumでパッケージをインストール等)をする単位を指します。この場合は```ping``` モジュールを利用し、指定したノードに対してsshでの接続確認をします。


```bash:nodeA
[vagrant@vagrant-centos65 ~]$ ansible test-server -i hosts -m ping
```

よって上のコマンドの意味をまとめると、```-i``` で指定したhostsファイルの中のtest-serverに対して、疎通確認のモジュールであるpingモジュールを使い、疎通確認をするという意味になります。

##4.2 モジュール

Ansibleではモジュールを駆使することで、サーバ構成情報を記述します。モジュールは組み込みのものが多数あり、また自作することも可能です。ここではyumモジュール、commandモジュール、get_urlモジュールを取り上げます。

###4.2.1 yumモジュール
yumモジュールはyumでバイナリをインストールするモジュールです。

```bash:nodeA
[vagrant@vagrant-centos65 ~]$ ansible -i hosts 192.168.33.12 -m yum -s -a name=sl

192.168.33.12 | success >> {
    "changed": true, 
    "msg": "", 
    "rc": 0, 
    "results": [
        "Loaded plugins: fastestmirror\nLoading mirror speeds from cached hostfile\n * base: ftp.iij.ad.jp\n * epel: ftp.iij.ad.jp\n * extras: ftp.iij.ad.jp\n * updates: ftp.iij.ad.jp\nSetting up Install Process\nResolving Dependencies\n--> Running transaction check\n---> Package sl.x86_64 0:3.03-6.el6 will be installed\n--> Finished Dependency Resolution\n\nDependencies Resolved\n\n================================================================================\n Package       Arch              Version                  Repository       Size\n================================================================================\nInstalling:\n sl            x86_64            3.03-6.el6               epel             11 k\n\nTransaction Summary\n================================================================================\nInstall       1 Package(s)\n\nTotal download size: 11 k\nInstalled size: 14 k\nDownloading Packages:\nRunning rpm_check_debug\nRunning Transaction Test\nTransaction Test Succeeded\nRunning Transaction\n\r  Installing : sl-3.03-6.el6.x86_64                                         1/1 \n\r  Verifying  : sl-3.03-6.el6.x86_64                                         1/1 \n\nInstalled:\n  sl.x86_64 0:3.03-6.el6                                                        \n\nComplete!\n"
    ]
}

[vagrant@vagrant-centos65 ~]$ ansible -i hosts 192.168.33.12 -m yum -s -a name=sl

192.168.33.12 | success >> {
    "changed": false, 
    "msg": "", 
    "rc": 0, 
    "results": [
        "sl-3.03-6.el6.x86_64 providing sl is already installed"
    ]
}
````
すでにインストールされていると、もう一度インストールしようとはしません（冪等性）。

###4.2.2 commandモジュール

commandモジュールはリモート側で指定したコマンドを実行するモジュールです。

```profile:
[vagrant@vagrant-centos65 ~]$ ansible -i hosts 192.168.33.12 -m command -a pwd

192.168.33.12 | success | rc=0 >>
/home/vagrant
```

```-a``` に実行するコマンドを指定します。```-a``` はcommandモジュールにかぎらずモジュールに対する引数を指定するためのコマンドです。

###4.2.3 get_urlモジュール

get_urlモジュールはリモート側のサーバから、指定したURLのファイルをダウンロードするためのモジュールです。HTTP、HTTPS、FTPに対応しています。

```profile:nodeA
[vagrant@vagrant-centos65 ~]$ ansible -i hosts 192.168.33.12 -m get_url -a 'url=http://www.google.com dest=/home/vagrant'

192.168.33.12 | success >> {
    "changed": true, 
    "dest": "/home/vagrant/index.html", 
    "gid": 500, 
    "group": "vagrant", 
    "md5sum": "0f96747f0d71e58e1c0966d38872b7b7", 
    "mode": "0664", 
    "msg": "OK (unknown bytes)", 
    "owner": "vagrant", 
    "sha256sum": "", 
    "size": 17992, 
    "src": "/tmp/tmpnUBx2x", 
    "state": "file", 
    "uid": 500, 
    "url": "http://www.google.com"
}

```

これでnodeB（管理されるサーバ）の```/home/vagrant```にgoogle先生のトップページがダウンロード出来ました。

#5. Playbook

先の節では、コマンドラインでnodeBにモジュールを適応していました。実際には 設定対象のノードとモジュールで記述された構成情報をひとまとめにした **Playbook** を作成し、適応します。

PlaybookはYAMLで記述します。例えばtest-serverにyumでvimとwgetを入れるPlaybookは下記のとおりです。

```xml:test-playbook.yml
---
- hosts: test-server
  sudo: yes
  tasks:
   - name: be sure vim installed
     yum: name=vim
   - name: be sure wget installed
     yum: name=wget
```

```hosts: ``` に 構成管理されるサーバを指定します。ここでは test-serverを指定しています。```sudo:　yes``` でnodeB側でsudoでコマンドを実行するように指定します。```tasks:``` 以下にサーバ構成情報をモジュールで記述します。```-name```はモジュールで実施する個々の処理のコメントです。```-name```は必須ではなく同じ内容を次のように書き換えることができます。

```xml:test2-playbook.yml
---
- hosts: test-server
  sudo: yes
  tasks:
   - yum: name=vim
   - yum: name=wget
```

yumモジュールはnameでインストールするアプリケーションを指定します。入れるバージョンを最新のものにするか等、他にも指定できる引数があるので公式ドキュメントを確認すると良いと思います。

ではこのPlaybookを適応する前に、構文チェックとdry-runをしましょう。

```profile:nodeA
[vagrant@vagrant-centos65 ~]$ ansible-playbook test-playbook.yml --syntax-check

playbook: test-playbook.yml
```

これで構文チェックが通りました。構文チェックが通らない時は下記のようにエラーが出ます。

```profile:nodeA
[vagrant@vagrant-centos65 ~]$ ansible-playbook test-playbook.yml --syntax-check

ERROR: Syntax Error while loading YAML script, test-playbook.yml
Note: The error may actually appear before this position: line 11, column 1

nekogasuki
```

次はdry-runをして、実際にansibleが適応できる状態にあるか確認します。コマンドで実行した時と同様に```-i hosts```でhostsファイルを指定して、dry-runをします。

```profile:nodeA
[vagrant@vagrant-centos65 ~]$ ansible-playbook test-playbook.yml -i hosts --check

PLAY [test-server] ************************************************************ 

GATHERING FACTS *************************************************************** 
ok: [192.168.33.12]

TASK: [be sure vim installed] ************************************************* 
changed: [192.168.33.12]

TASK: [be sure wget installed] ************************************************ 
changed: [192.168.33.12]

PLAY RECAP ******************************************************************** 
192.168.33.12              : ok=3    changed=2    unreachable=0    failed=0   

````

適応しても問題なさそうですね。では適応してみましょう。

```profile:nodeA
[vagrant@vagrant-centos65 ~]$ ansible-playbook test-playbook.yml -i hosts                                                                                                                                          

PLAY [test-server] ************************************************************ 

GATHERING FACTS *************************************************************** 
ok: [192.168.33.12]

TASK: [be sure vim installed] ************************************************* 
changed: [192.168.33.12]

TASK: [be sure wget installed] ************************************************ 
changed: [192.168.33.12]

PLAY RECAP ******************************************************************** 
192.168.33.12              : ok=3    changed=2    unreachable=0    failed=0

````

dry-runのときの末尾につけていた--checkを外せばそのまま実際に適応することになります。これでnodeBにvimとwgetがインストールされました:-)

#6.UnixBenchをインストールしてみる

yumモジュール以外を使ってみるために、UnixBenchをインストールするPlaybookを作成してみましょう。具体的にはUnixBenchをgoogle codeからzipを```/opt```に落としてきて、解凍し、落としてきたzipファイルを削除します。

```xml:unixbrnch-playbook.yml
---
- hosts:test-server
  sudo: yes
  vars:
    install_path: /opt
  tasks:
   - name: install dependencies for UnixBench
     yum: name=perl-Time-HiRes
   - name: download UnixBench
     get_url: url=https://byte-unixbench.googlecode.com/files/UnixBench5.1.3.tgz dest=/{{ install_path }}/UnixBench.tar
   - name: unpackage UnixBench
     command: tar -zxf UnixBench.tar chdir={{ install_path }}
   - name: remove UnixBench zip file
     command: rm UnixBench.tar chdir={{install_path}}
``` 

```vars:``` でPlaybook内で使う変数を定義します。```install_path``` という変数に```/opt/```を代入します。


```tasks:``` にUnixBenchを導入方法を記述します。下記の手順になります。

1. yumモジュールでUnixBenchに必要なperl-Time-HiResをインストールします。
2. get_urlモジュールでzipをダウンロードしてきます。
3. commandモジュールで解凍します。
4. commandモジュールで落としてきたzipを削除します。

それではインストールしてみましょう。

```profile:nodeA
[vagrant@vagrant-centos65 ~]$ ansible-playbook -i hosts test-playbook.yml        

PLAY [test-server] ************************************************************ 

GATHERING FACTS *************************************************************** 
ok: [192.168.33.12]

TASK: [install dependencies for UnixBench] ************************************ 
changed: [192.168.33.12]

TASK: [download UnixBench] **************************************************** 
changed: [192.168.33.12]

TASK: [unpackage UnixBench] *************************************************** 
changed: [192.168.33.12]

TASK: [remove UnixBench zip file] ********************************************* 
changed: [192.168.33.12]

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

```

これでnodeBの```/opt/UnixBench``` にUnixBenchが配置されている状態になります。

#7.まとめ

AnsibleはChefに比べると記述・導入も非常に簡単なので、Infrastructure as Code に興味があればまずAnsibleから始めてみるのがいいと個人的には思います。

今回はツールのダウンロードだけでしたが、apacheのサービスの状態（サービスを有効にする、runlevelを変更する等）、ローカルからのファイルコピーも既存モジュールで記述可能です。こういうことをしたい、ああいうことがしたいというのがあれば[公式のドキュメント](http://docs.ansible.com)を漁ってみるのが一番手っ取り早いと思います。

またAnsibleには[ベストプラクティス](http://docs.ansible.com/playbooks_best_practices.html)とよばれる公式がおすすめしているPlaybookのフォルダ構成があります。Playbookが肥大化してきたら、参考にしたらよいかと思います。