Vagrant + Ansibleでなんちゃって負荷分散構成を作る


## 概要
仮想環境構築ツールVagrantと、構成管理ツールAnsibleを使ってローカル環境にWebアプリケーションサーバ２台、ロードバランサ1台を構築し、なんちゃって負荷分散構成を作った時のメモ書きです。
環境はMacを想定しています。他の環境を使用する場合は適宜読み替えて実行してください。

サンプルソースは以下のURLから参照してください。
https://github.com/yuizho/ansible_vagrant_balancer_sample


## 事前準備
### Ansibleのインストール
OS X用パッケージマネージャHomeBrewを使用してAnsibleをインストールする
HomeBrewをまだ導入していない場合、以下のURLを参考に。
http://brew.sh/index_ja.html

```
$ brew install ansible
```

### Vagrantのインストール
VagranとVirtualBoxのdmgファイルを公式サイトからダウンロードし、メッセージにしたがってインストールする。
https://www.virtualbox.org/wiki/Downloads
https://www.vagrantup.com/downloads.html

HomeBrewを使用する場合、HomeBerwを拡張下Caskを導入すればインストール可能とのこと。
http://qiita.com/moomoo-ya/items/30f9ffdb7dd34caf9ec0

## Vagrantのセットアップ
### Vagrantの初期化
以下のURLからOSのBoxファイルを選択し、VagrantへBoxを追加する。
その状態でinitコマンドを実行するとVagrantの初期化処理が実行され、Vagrantfileが生成される。

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

### Vagrantfileの設定
Vagrantfileにサーバ3台分の設定を記述する。

```Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  config.vm.box = "centos6.7_minimal"
  # appサーバ1
  config.vm.define :node1 do |node|
    node.vm.network :forwarded_port, guest: 22, host: 2001, id: "ssh"
    node.vm.network :forwarded_port, guest: 80, host: 8001, id: "http"
    node.vm.network :private_network, ip: "192.168.33.11"
  end
  # appサーバ2
  config.vm.define :node2 do |node|
    node.vm.network :forwarded_port, guest: 22, host: 2002, id: "ssh"
    node.vm.network :forwarded_port, guest: 80, host: 8002, id: "http"
    node.vm.network :private_network, ip: "192.168.33.12"
  end
  # balancer
  config.vm.define :node3 do |node|
    node.vm.network :forwarded_port, guest: 22, host: 2003, id: "ssh"
    node.vm.network :forwarded_port, guest: 80, host: 8003, id: "http"
    node.vm.network :private_network, ip: "192.168.33.13"
  end

end
```
### Vagrantの起動
Vagrantの起動コマンドを実行して、各サーバを起動する。

```bash
$ vagrant up
```

Vagrantのsshコマンドで、各サーバへのログインも可能。

```bash
# サーバ名を指定して、sshでログイン
$ vagrant ssh node1
```

どうやら普通にsftpも使えるみたい。
[ファイル転送ツールでVagrantの仮想マシンにアクセスする
](http://fingaholic.github.io/posts/2013-10-03-vagrant.html)

```bash
# node1のサーバに対してsftpを実行
$ sftp vagrant@192.168.33.11
```


## Ansibleのセットアップ
### Inventoryファイルの作成
Vagrantで作成したサーバに対して、Inventoryファイル(hosts)の設定を行う。
[Ansible公式ドキュメント Inventory](http://docs.ansible.com/ansible/intro_inventory.html)

app, balancerのurlとssh秘密鍵をそれぞれ設定し、すべてのサーバに共通の設定(sshのport, username)については[all:vars]セクション内に設定する。

```hosts
[app]
192.168.33.11 ansible_ssh_private_key_file=.vagrant/machines/node1/virtualbox/private_key
192.168.33.12 ansible_ssh_private_key_file=.vagrant/machines/node2/virtualbox/private_key

[balancer]
192.168.33.13 ansible_ssh_private_key_file=.vagrant/machines/node3/virtualbox/private_key

[all:vars]
ansible_ssh_port=22
ansible_ssh_user=vagrant
```

### Ansibleを介したコマンドの実行
Inventryファイル(hosts)を指定して、すべてのファイルに対してpingコマンドを実行し、疎通確認を行う。

```bash
$ ansible -i hosts all -m ping
```

![ping.png](https://qiita-image-store.s3.amazonaws.com/0/12603/25f35adc-a510-1aca-3f53-0dad24a4c941.png "ping.png")


### Playbookの作成
#### appサーバのPlaybook

appサーバ(Inventoryで[app]セクションに指定されたサーバ)に対して、以下の設定を行っている。

+ デプロイ用のフォルダをサーバにコピー
+ デプロイしたhtmlファイルの中身を書き換え(デプロイ先のサーバを特定するために、{{inventory_hostname}}変数が表示されるよう値を置き換えている)
+ httpサーバを起動(asyncオプションを使用して半永久的に起動している)
  
  [Ansible忘備録 -起動したはずのプロセスが死んでいる-](http://qiita.com/gacha-ru/items/035ee8ad754394b5c83f)

```yml
---
- hosts: app
  sudo: yes
  remote_user: vagrant
  tasks:
      - name: アプリファイルをコピー
        copy: src=./web dest=/home/vagrant/
      - name: index.htmlのホスト名を設定
        lineinfile:
          dest: /home/vagrant/web/index.html
          regexp: '<li><strong>hostname:</strong> </li>'
          line: '<li><strong>hostname:</strong> {{inventory_hostname}}</li>'
      - name: SimpleHTTPServerを起動
        command: python simpleserver.py 80 & chdir=/home/vagrant/web/
        register: launch_app
        # 超長い時間を指定して半永久的に起動
        async: 999999999999999
        poll: 0
```

なお、今回はテスト用にPythonのSimpleHTTPServer上に、htmlファイルを動作させている。
詳細は[サンプルソース](https://github.com/yuizho/ansible_vagrant_balancer_sample/tree/master/web)参照。

#### balancerのplaybook
ロードバランシング用にnginxをインストールし、事前に用意した各設定ファイルをコピーしたあと、サービスを再起動している。
設定ファイルの詳細は[サンプルソース](https://github.com/yuizho/ansible_vagrant_balancer_sample/tree/master/config)参照。

```yml
---
- hosts: balancer
  sudo: yes
  remote_user: vagrant
  tasks:
      - name: nginxをインストール
        yum: name=nginx state=latest
      - name: /etc/nginx/nginx.confの設定
        copy:
          src: config/nginx.conf
          dest: /etc/nginx/
          owner: root
          mode: 0644
          backup: yes
      - name: /etc/nginx/conf.d/default.confの設定
        copy:
          src: config/default.conf
          dest: /etc/nginx/conf.d/
          owner: root
          mode: 0644
          backup: yes
        notify: nginxを再起動
  handlers:
      - name: nginxを再起動
        service: name=nginx state=restarted
```

#### app, balancerのPlaybookをincludeした実行用のPlaybook

複数のPlaybookをincludeして、一括で実行させることができる。

```yml
- include: apps_playbook.yml
- include: balancers_playbook.yml
```

### Playbookの実行

__app, balancerのPlaybookをincludeした実行用のPlaybook__で作成した、all.ymlを指定してすべてのサーバに対して、Playbookを実行。
Playbookに記述した各タスクが実行されることが確認できた。

```bash
$ ansible-playbook -i hosts all.yml
```

![ansible1.png](https://qiita-image-store.s3.amazonaws.com/0/12603/ca1b3b8b-dede-5f39-f08e-a8275f9a6924.png "ansible1.png")


再度Playbookを実行すると、差分がないため、以下のように必要な箇所のみ実行される(黄色くchangedと表示される箇所が減る)。

![ansible2.png](https://qiita-image-store.s3.amazonaws.com/0/12603/a3cc856d-c62e-cb80-2e86-dfaacea5ac0d.png "ansible2.png")



## 動作確認
ホストPCのブラウザから192.168.33.13(node3: balancer)へ何度かアクセスすると、hostnameの値が切り替わり、balancerが動作していることが確認できた！

![192_168_33_11.png](https://qiita-image-store.s3.amazonaws.com/0/12603/668e0a01-51fb-1c11-cfd8-a3b3cf5520a9.png "192_168_33_11.png")


![192_168_33_12.png](https://qiita-image-store.s3.amazonaws.com/0/12603/1b70b67c-0223-ad69-057e-a46561574855.png "192_168_33_12.png")