Vagrant + Ansible で環境構築+ベンチマークテスト実行自動化（IDCFクラウド）

## 1. 概要
 　[クラウドサーバーサービス(IaaS)の性能比較・ディスク編～IOPSの計測～](http://qiita.com/quickguard/items/49e73e424b68bb11280c) の番外編です。

　Vagrantでクラウド上にインスタンスを作成し、Ansibleでアプリインストール・実行・結果取得まで自動化します。
　今回はIDCFクラウドを対象に記事をまとめます。

### 1.1 実行環境（ホスト側）
 - OS      : Linux AMI release 2017.03
 - Vagrant : 1.9.4
 - Ansible : ansible 2.3.0.0

### 1.2 構築対象
 - Iaas: IDCFクラウド
 - OS:CentOS 7.3
 - インストール及び実行するアプリケーション: fio
 
## 2. 事前準備
### 2.1. Vagrantにdotenvプラグインをインストール
　Vagrant上で```.env```から変数の読み込みができるように、dotenvプラグインをインストールしておきます。

　```$ vagrant plugin install dotenv```

> 参考：[dotenvを利用して環境ごとでVagrantfileの設定値を変更してみる](http://blog.glidenote.com/blog/2014/02/26/vagrant-dotenv/)

### 2.3 VagrantにCloudStackのプラグインをインストール
　VagrantからIDCFクラウドのインスタンスを扱うために、CloudStackのプラグインをインストールします。
バージョン指定でインストールしています。

```$ vagrant plugin install vagrant-cloudstack --plugin-version 1.3.0```

## 3. Vagrantの設定
### 3.1 Vagrantfile
　Vagrantの設定ファイルであるVagrantfile（最初は大文字、拡張子なし）を作成します。
以下の例では、CloudStackを使ってIDCFクラウド上に仮想サーバを作成し、完了後にAnsibleを呼び出しています。

```ruby:Vagrantfile
Dotenv.load

Vagrant.configure(2) do |config|
  config.vm.box = "dummy"

  # CloudStack Setting For IDCF Cloud
  config.vm.provider :cloudstack do |cloudstack, override|

    # Dummy Box
    override.vm.box = "dummy"
    override.vm.box_url = "https://github.com/klarna/vagrant-cloudstack/raw/master/dummy.box"

    # API Parameters
    cloudstack.host                  = "compute.jp-east-2.idcfcloud.com"
    cloudstack.path                  = "/client/api"
    cloudstack.port                  = "443"
    cloudstack.scheme                = "https"
    cloudstack.api_key               = "#{ENV['CLOUDSTACK_API_KEY']}"
    cloudstack.secret_key            = "#{ENV['CLOUDSTACK_SECRET_KEY']}"
    expunge_on_destroy               = true

    # Virtual Machine Settings
    cloudstack.display_name          = "Vagrant-CentOS-7"
    cloudstack.template_name         = "CentOS 7.3 64-bit"
    cloudstack.zone_name             = "#{ENV['CLOUDSTACK_ZONE_NAME']}"
    cloudstack.service_offering_name = "#{ENV['COUDSTACK_SERVICE_OFFERING_NAME']}"

    # Network Settings
    cloudstack.pf_ip_address_id      = "#{ENV['CLOUDSTACK_PF_IP_ADDRESS_ID']}"
    cloudstack.pf_public_port        = "2201"
    cloudstack.keypair               = "#{ENV['CLOUDSTACK_SSH_KEYPAIR']}"
    override.ssh.private_key_path    = "#{ENV['VAGRANT_SSH_PRIVATE_KEY']}"
    override.ssh.username            = "#{ENV['VAGRANT_SSH_USERNAME']}"

    cloudstack.instance_ready_timeout = 1800
  end

  #
  # Run Ansible from the Vagrant Host
  #
  config.vm.provision "ansible" do |ansible|
    ansible.playbook                 = "site.yaml"
    ansible.inventory_path           = "hosts"
    ansible.limit = 'all'
    ansible.verbose = true
  end

end
```

>CloudStack 参考 ：
 [Vagrantを使ってCloudStack上の環境構築をしてみよう](http://blog.idcf.jp/entry/cloud/vagrant-cloudstack/)
[vagrant-cloudstackを試してみた](http://nushu123.blogspot.jp/2013/12/vagrant-cloudstack.html)

>Ansible呼び出し参考 ： 
[Vagrant公式 : Ansible Provisioner](https://www.vagrantup.com/docs/provisioning/ansible.html)


### 3.2 .env
　認証情報等の変数を .env というファイルに記述し、.envはVagrantfileと同じ場所に保存しておきます。

```.env:.env
CLOUDSTACK_API_KEY               = '**********'
CLOUDSTACK_SECRET_KEY            = '**********'
CLOUDSTACK_ZONE_NAME             = 'lux'
COUDSTACK_SERVICE_OFFERING_NAME  = 'light.S1'
CLOUDSTACK_PF_IP_ADDRESS_ID      = '**********'
CLOUDSTACK_SSH_KEYPAIR           = '**********'
VAGRANT_SSH_USERNAME             = 'root'
VAGRANT_SSH_PRIVATE_KEY          = '~/.ssh/id_rsa'
ANSIBLE_PLAYBOOK                 = 'site.yml'
ANSIBLE_INVENTORY_PATH           = 'hosts'
```

## 4. Ansibleの設定
### 4.1 Hostsファイル
　Vagrantfileの中の ```ansible.inventory_path``` で指定した場所に置いておきます。今回の例では Vagrantfileと同じ階層にあります。

```hosts:hosts
***.***.**.***:2201
```

 - Vagrantで作成する仮想マシンのIPアドレスを指定します。
 - Vagrantfileの中の ```cloudstack.pf_public_port``` でパブリックポートを```2201``` に変更しているので、IPアドレスの後ろにポート番号を指定します。未指定の場合Ansibleはデフォルトの ```22``` でSSH接続するので、接続に失敗してしまいます。


>参考：[Ansible公式・Inventory](http://docs.ansible.com/ansible/intro_inventory.html)


### 4.2 PlayBook
 
　自動構築したい内容を、AnsibleのPlaybook（拡張子.yml）に書いていきます。

#### 4.2.1 Role
　AnsibleはPlaybookをRoleごとに分けて書くことができます。決まったルールで roles のディレクトリを構成しておくと、自動的にPlaybookに include される仕組みです。実行内容ごとにRoleを分けて作っておけば、可読性や再利用性が高まります。
　今回はVagrantfileを含め以下のようなファイル配置にしました。

```
hosts
roles/
  iops/　# roleの名前（任意）
    files/  # role内で使用するファイルを配置
      Rand-Read-4k.fio
      Rand-ReadWrite-4k.fio
      Rand-Write-4k.fio
    tasks/ # roleで実行されるtaskの定義
      main.yml
    vars/  # role内で使用する変数定義
      main.yml
site.yml  # Vagrantから呼ばれるPlaybook
Vagrantfile
```


#### 4.2.2 最初に呼ばれるPlaybook
　Vagrantfileの中の ```ansible.playbook``` に指定したPlaybookが最初に実行されます。今回の例では Vagrantfileと同じ階層にある```site.yaml```という名前のPlaybookです。
このPlaybookの中では、```iops```というRoleを呼び出しています。

```site.yml
---
- hosts: servers
  become: true
  roles:
    - iops
```

#### 4.2.3 RoleごとのPlaybook (tasks/main.yml）
　```site.yaml```から呼び出されるPlaybookです。
今回はIOPSテストを行うため、以下の作業を実施します。

 - fioをインストール
 - fioの計測パラメータファイルをリモートに送信
 - fioを実行
 - 結果ファイルを受信


```main.yml
---
    - name: install epel 
      yum: 
        name: epel-release 
        state: installed
    - name: install fio
      yum: 
        name: fio 
        state: installed
      
    - name: copy Rand-Read-4k.fio
      copy: 
        src: Rand-Read-4k.fio
        dest: "{{ dest_dir }}"
    - name: copy Rand-ReadWrite-4k.fio
      copy: 
        src: Rand-ReadWrite-4k.fio
        dest: "{{ dest_dir }}"
    - name: copy Rand-Write-4k.fio
      copy: 
        src: Rand-Write-4k.fio
        dest: "{{ dest_dir }}"

    - name: run fio Rand-Read-4k
      command: "fio -f /tmp/Rand-Read-4k.fio -filename=/tmp/fio_test -output Rand-Read-4k.result"
    - name: run fio Rand-ReadWrite-4k
      command: "fio -f /tmp/Rand-ReadWrite-4k.fio -filename=/tmp/fio_test -output Rand-ReadWrite-4k.result"
    - name: run fio Rand-Write-4k
      command: "fio -f /tmp/Rand-ReadWrite-4k.fio -filename=/tmp/fio_test -output Rand-Write-4k.result"

    - name: search files
      find: 
        paths: "/root"
        patterns: "*.result"   
      register: files_to_copy

    - name: receive result
      fetch:
        src: "{{ item.path }}"
        dest: "{{ receive_dir }}"
        flat: yes
      with_items: "{{ files_to_copy.files }}"
```

#### 4.2.4 変数定義（vars/main.yml）
　RoleごとのPlaybookで使用する変数を定義します。

```
dest_dir: /tmp/
receive_dir: ./results/
```

#### 4.2.5 Playbookの確認テスト
　これでAnsibleの設定は終了なので、間違いがないか確認しておきましょう。

 ■ Playbookの構文をチェック：
 　ansibleコマンドに「--syntax-check」オプションをつけて実行すると、Playbookの構文をチェックできます。
 
```
$ ansible-playbook site.yml --syntax-check
```

 ■ Dry Run：
　ansibleコマンドに「--check」オプションをつけて実行すると、対象への変更は行わずに、Playbookを適用した結果起きる変化を確認できます。

```
$ ansible-playbook site.yml --check
```

 ■ 冗長モード：
 　もしインスタンスを起動した状態であれば、以下のようにAnsible単独で実行して確認することも可能です。ansibleコマンドに「-v（--verbose）」 オプションをつけて実行すると、詳細な出力を確認することができます。「-vvv」と指定すると、さらに詳細な出力になります。

```
$ ansible-playbook -i hosts site.yml --private-key=~/.ssh/id_rsa -u root -v
```
なお、前述のVagrantfile内のAnsibleの呼び出し部分で``ansible.verbose = true``とすると、冗長モードを指定したことになります。

■ ansible-lint：
　Ansibleの機能ではありませんが、Playbookのチェックをしてくれるツールです。別途インストールが必要ですが、Playbookの改善点も指摘してくれます。
下記のように使います。
```
$ ansible-lint site.yml
```

>参考：
[Ansible：さらにPlaybookをきわめる](https://thinkit.co.jp/article/9990)
[Ansible公式：Check Mode (“Dry Run”)](http://docs.ansible.com/ansible/playbooks_checkmode.html)
[GitHub - willthames/ansible-lint: Best practices checker for Ansible](https://github.com/willthames/ansible-lint)

## 5. 実行 
　さあ、Vagrantから実行してみましょう。
Vagrantfile のある場所に移動して以下のコマンドを実行します。

```$ vagrant up```


　4.2.4の変数定義で``receive_dir`` に指定した場所に、結果ファイルが入っていれば成功です。


##おまけ．Vagrantの基本コマンド


 - 起動：　```$ vagrant up```
 - 停止：　```$ vagrant halt```
 - 削除：　```$ vagrant destroy```
 - 再読込:　```$ vagrant reload ```　※--provisonでプロビジョニングも実施
 - プロビジョンだけ実施:　```$ vagrant provision ```