AnsibleのPlaybook開発環境をパフォーマンスチューニングしてみた


[スクラップ・アンド・ビルド方式](http://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%AF%E3%83%A9%E3%83%83%E3%83%97%E3%82%A2%E3%83%B3%E3%83%89%E3%83%93%E3%83%AB%E3%83%89)で Ansible の Playbook を開発する場合に、Bootstrapping / Provisioning / Testing に掛かる時間を短縮するために試したことのメモ


## 準備

* Ansible + Vagrant 以外でホストにインストールされているもの

    ```ruby:./Gemfile
    source 'https://rubygems.org'

    gem 'rake'
    gem 'serverspec'
    gem 'highline'
    gem 'test-kitchen'
    gem 'kitchen-ansible'
    gem 'kitchen-vagrant', :github => 'test-kitchen/kitchen-vagrant', :branch => 'master'
    gem 'busser-rspec'
    ```

    ```bash:vagrant_plugin_list
    vagrant-share (1.1.3, system)
    vagrant-vbguest (0.10.0)
    ```
    + [dotless-de/vagrant-vbguest](https://github.com/dotless-de/vagrant-vbguest)
        - VM の VirtualBox Guest Additions のインストール状況を確認して、必要があれば自動的にインストールしてくれるプラグイン

       ```bash:インストール
       vagrant plugin install vagrant-vbguest
       ```

* 実行時間を計測するために用意したもの
    + test-kitchen + kitchen-vagrant + kitchen-ansible  で利用するもの
        - VMのベースイメージとして、[hansode/centos-6.6-x86_64](https://atlas.hashicorp.com/hansode/boxes/centos-6.5-x86_64)を利用する
            - CentOS-6.6-x86_64-minimal + VirtualBox Guest Additions (4.3.20) + [その他](https://github.com/hansode/packer-vagrant-templates/blob/master/common/scripts/bootstrap.sh#L16-L22)

    ```yaml:./.kitchen.yml
    ---
    driver:
      name: vagrant
      provision: true
      vagrantfiles:
        - ./Vagrantfile.rb
      box: hansode/centos-6.6-x86_64
      vm_hostname: kitchen-vagrant
      customize:
        cpuexecutioncap: 90
        memory: 2048

    provisioner:
      name: ansible_playbook

    platforms:
      - name: centos6

    suites:
      - name: provisioning
        provisioner:
          hosts: kitchen-vagrant
          playbook: provisioning.yml
    ```

    ```ruby:./Vagrantfile.rb
    Vagrant.configure('2') do |c|

    end

    # ここでは特に設定が追加されていない状態
    ```

    ```yaml:./provisioning.yml
    ---
    - hosts:        127.0.0.1
      connection:   local
      user:         vagrant
      sudo:         yes
      tasks:

      - name: install the 'Development tools'
        yum:  name="@Development tools" state=latest

      - name: install the latest version of Apache
        yum: name=httpd state=latest

      - name: start service httpd, if not running
        service: name=httpd state=started

      - name: enable service httpd, and not touch the running state
        service: name=httpd enabled=yes
    ```

    + test-kitchen + busser-rspec + Serverspec で利用するもの
        - Serverspec のテストコードはある程度の実行時間が必要となるように適当に作成した

    ```bash:./test/integration/provisioning/rspec/でserverspec-initを実行
    serverspec-init

    ----------------------------------
    Select OS type:

      1) UN*X
      2) Windows

    Select number: 1

    Select a backend type:

      1) SSH
      2) Exec (local)

    Select number: 2

     + spec/
     + spec/localhost/
     + spec/localhost/sample_spec.rb
     + spec/spec_helper.rb
     + Rakefile
     + .rspec
    -----------------------------------
    ```

    ```ruby:./test/integration/provisioning/rspec/Gemfile
    source 'https://rubygems.org'

    gem 'serverspec'
    ```

    ```ruby:./test/integration/provisioning/rspec/spec/localhost/provisioning_spec.rb
    require 'spec_helper'

    enable_service = %w(acpid auditd crond ip6tables iptables kdump
                        mdmonitor netfs network nfslock ntpd ntpdate
                        rpcbind rpcgssd rsyslog sshd sysstat udev-post
                        vboxadd vboxadd-service vboxadd-x11)

    unable_service = %w(htcacheclean netconsole nfs postfix rdisc
                        restorecond rpcsvcgssd saslauthd svnserve)

    enable_service.each do |service|
      describe service("#{service}") do
        it { should be_enabled.with_level(3) }
      end
    end

    unable_service.each do |service|
      describe service("#{service}") do
        it { should_not be_enabled.with_level(3) }
      end
    end

    command("rpm -qai |grep Name |grep Relocations |awk '{print $3}'").stdout.each_line do |pkg|
      describe package("#{pkg.chomp}") do
        it { should be_installed }
      end
    end

    describe command('ls -al /') do
      its(:stdout) { should match /bin/ }
    end

    describe file('/etc/passwd') do
      it { should be_file }
    end

    describe file('/var/log/httpd') do
      it { should be_directory }
    end

    describe file('/etc/httpd/conf/httpd.conf') do
      its(:content) { should match /^#ServerName www\.example\.com\:80$/ }
    end

    describe file('/') do
      it { should be_mounted }
    end

    command("awk -F\":\" '{ print $1}' /etc/passwd").stdout.each_line do |user|
      describe user("#{user.chomp}") do
        it { should exist }
      end
    end

    command("awk -F\":\" '{ print $1}' /etc/group").stdout.each_line do |group|
      describe group("#{group.chomp}") do
        it { should exist }
      end
    end

    describe interface('eth0') do
      it { should exist }
    end

    ```


## 実行時間の計測方法

test-kitchen を利用して実行時間を計測する

* VagrantでVMを起動するだけ
    + => kitchen create

    ```bash:kitchen_create
    kitchen create
    ```

* VagrantでVMを起動してAnsibleのPlaybookを実行する
    + => kitchen create => kitchen setup

    ```bash:kitchen_setup
    kitchen setup
    ```


* VagrantでVMを起動してAnsibleのPlaybookを実行してServerspecでテストを実施する
    + => kitchen create => kitchen setup => kitchen verify

    ```bash:kitchen_test
    kitchen test --destroy=never
    ```


## [最初の状態](https://github.com/libero18/IaC-with-Ansible-example/tree/step_01)

* 計測結果

    ```bash:kitchen_create
    1回目: Kitchen is finished. (3m20.09s)
    2回目: Kitchen is finished. (2m5.09s)
    3回目: Kitchen is finished. (1m57.72s)
    ```

    ```bash:kitchen_setup
    1回目: Kitchen is finished. (14m19.28s)
    2回目: Kitchen is finished. (10m6.04s)
    3回目: Kitchen is finished. (10m17.32s)
    ```

    ```bash:kitchen_test
    1回目: Kitchen is finished. (12m41.69s)
          => Finished verifying <provisioning-centos6> (1m45.20s).
    2回目: Kitchen is finished. (12m31.62s)
          => Finished verifying <provisioning-centos6> (1m41.59s).
    3回目: Kitchen is finished. (12m15.51s)
          => Finished verifying <provisioning-centos6> (1m45.45s).

    # kitchen verify によるテストの部分だけに限れば、 1m40s ~ 1m45s くらい
    ```

* 課題 (2015/03/02の時点では)
    1. kitchen_create / kitchen_setup / kitchen_test 共通
        1. vagrant-vbguest プラグインによって、 VirtualBox Guest Additions がアップデート (4.3.22) されている
            1. 依存しているパッケージファイルのアップデート
            2. 旧バージョンをアンインストール
            3. 新バージョンをインストール
    2. kitchen_setup / kitchen_test 共通
        1. kitchen-ansible プラグインによって、 Ansible がインストールされている
            1. yum に epel リポジトリを追加
            2. epel リポジトリのメタデータをキャッシュする
            3. Python などの依存しているパッケージファイルをインストール
            4. Ansible のインストール
        2. test-kitchen によって Chef がインストールされている
            1. [オムニバスインストーラー](https://docs.chef.io/install_omnibus.html)が実行されている
            2. thor がインストールされている
            3. busser がインストールされている
    3. kitchen_test
        1. bundle install が実行されている
            1. ./test/integration/provisioning/rspec/Gemfile によって Serverspec などがインストールされている
    4. kitchen_setup
        1. Ansible が ./provisioning.yml から yum groupinstall -y 'Development tools' を実行している
            1. yum リポジトリからメタデータをダウンロードしている

## [test-kitchen 専用の Vagrant Box Image を作成する](https://github.com/libero18/IaC-with-Ansible-example/tree/step_02)

* Vagrant Box Image に chef をオムニバスインストールする
    + [chef/vagrant-omnibus](https://github.com/chef/vagrant-omnibus)
        - VM の Chef インストール状況を確認し、必要に応じて自動的にインストールしてくれるプラグイン

   ```bash:インストール
   vagrant plugin install vagrant-omnibus
   ```

* Vagrant Box Image を作成するための Vagrantfile を用意する

    ```ruby:./vagrant_package/Vagrantfile
    Vagrant.configure('2') do |c|

      ## Vagrant Box Base Image
      c.vm.box = "hansode/centos-6.6-x86_64"

      ## Plugin
      if Vagrant.has_plugin?("vagrant-omnibus")
        c.omnibus.chef_version = :latest
      end

    ## Provision
        c.vm.provision "shell",inline: "yum clean all && yum update -y && yum install -y libyaml"
        c.vm.provision "shell",inline: "rpm -ivh http://ftp.riken.jp/Linux/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm"
        c.vm.provision "shell",inline: "yum install -y ansible"
        c.vm.provision "shell",inline: "yum clean all"

        c.vm.provision "shell",inline: "sh -c 'BUSSER_ROOT=\"/tmp/busser\" GEM_HOME=\"/tmp/busser/gems\" GEM_PATH=\"/tmp/busser/gems\" GEM_CACHE=\"/tmp/busser/gems/cache\" ; export BUSSER_ROOT GEM_HOME GEM_PATH GEM_CACHE; if ! sudo -E /opt/chef/embedded/bin/gem list busser -i >/dev/null; then sudo -E /opt/chef/embedded/bin/gem install busser serverspec --no-rdoc --no-ri; fi; gem_bindir=`/opt/chef/embedded/bin/ruby -rrubygems -e \"puts Gem.bindir\"`; sudo -E ${gem_bindir}/busser setup; sudo -E /tmp/busser/bin/busser plugin install busser-rspec'"

    end
    ```

* Vagrant Box Image を作成する

    ```bash:boxイメージの作成コマンドと登録コマンドの実行
    cd ./vagrant_package
    vagrant up
    vagrant reload
    vagrant halt
    vagrant package
    vagrant box add -f libero18/test-kitchen ./package.box
    rm -f ./package.box
    vagrant destroy -f
    cd ..

    # libero18/test-kitchen は任意の名前で登録済みのものは、 vagrant box list で確認可能
    ```

* 作成した Vagrant Box Image を test-kitchen から利用する

    ```yaml:./.kitchen.yml
    ---
    driver:
      name: vagrant
      provision: true
      vagrantfiles:
        - ./Vagrantfile.rb
      box: libero18/test-kitchen
      vm_hostname: kitchen-vagrant
      customize:
        cpuexecutioncap: 90
        memory: 2048

    provisioner:
      name: ansible_playbook

    platforms:
      - name: centos6

    suites:
      - name: provisioning
        provisioner:
          hosts: kitchen-vagrant
          playbook: provisioning.yml
    ```

* 計測結果

    ```bash:kitchen_create
    1回目: Kitchen is finished. (0m57.50s)
    2回目: Kitchen is finished. (0m59.78s)
    3回目: Kitchen is finished. (0m53.06s)
    ```

    ```bash:kitchen_setup
    1回目: Kitchen is finished. (2m43.31s)
    2回目: Kitchen is finished. (2m42.10s)
    3回目: Kitchen is finished. (2m58.98s)
    ```

    ```bash:kitchen_test
    1回目: Kitchen is finished. (2m59.55s)
          => Finished verifying <provisioning-centos6> (0m25.64s).
    2回目: Kitchen is finished. (3m11.04s)
          => Finished verifying <provisioning-centos6> (0m25.00s).
    3回目: Kitchen is finished. (3m3.56s)
          => Finished verifying <provisioning-centos6> (0m25.15s).

    # kitchen verify によるテストの部分だけに限れば、 0m25s ~ 0m26s くらい
    ```

* この段階での残課題 (2015/03/02の時点では)
    1. <del>kitchen_create / kitchen_setup / kitchen_test 共通</del>
        1. <del>vagrant-vbguest プラグインによって、 VirtualBox Guest Additions がアップデート (4.3.22) されている</del>
            1. <del>依存しているパッケージファイルのアップデート</del>
            2. <del>旧バージョンをアンインストール</del>
            3. <del>新バージョンをインストール</del>
    2. <del>kitchen_setup / kitchen_test 共通</del>
        1. <del>kitchen-ansible プラグインによって、 Ansible がインストールされている</del>
            1. <del>yum に epel リポジトリを追加</del>
            2. <del>epel リポジトリのメタデータをキャッシュする</del>
            3. <del>Python などの依存しているパッケージファイルをインストール</del>
            4. <del>Ansible のインストール</del>
        2. <del>test-kitchen によって Chef がインストールされている</del>
            1. <del>[オムニバスインストーラー](https://docs.chef.io/install_omnibus.html)が実行されている</del>
            2. <del>thor がインストールされている</del>
            3. <del>busser がインストールされている</del>
    3. <del>kitchen_test</del>
        1. <del>bundle install が実行されている</del>
            1. <del>./test/integration/provisioning/rspec/Gemfile によって Serverspec がインストールされている</del>
    4. kitchen_setup
        1. Ansible が ./provisioning.yml から yum groupinstall -y 'Development tools' を実行している
            1. yum リポジトリからメタデータをダウンロードしている


## [パッケージ管理システムのメタデータなどをホストにキャッシュして VM とディレクトリ共有する](https://github.com/libero18/IaC-with-Ansible-example/tree/step_03)


+ [fgrehm/vagrant-cachier](https://github.com/fgrehm/vagrant-cachier)

    ```bash:インストール
    vagrant plugin install vagrant-cachier
    ```

+ test-kitchen から作成した VM で vagrant-cachier プラグインを利用するように設定を追加する

    ```ruby:./Vagrantfile.rb
    Vagrant.configure('2') do |c|

      ## Plugin
      if Vagrant.has_plugin?("vagrant-cachier")
        c.cache.scope = :box
      end

    end
    ```

* 計測結果

    ```bash:kitchen_create
    1回目: Kitchen is finished. (1m0.70s)
    2回目: Kitchen is finished. (0m58.51s)
    3回目: Kitchen is finished. (0m58.22s)
    ```

    ```bash:kitchen_setup
    1回目: Kitchen is finished. (2m24.12s)
    2回目: Kitchen is finished. (2m22.79s)
    3回目: Kitchen is finished. (2m26.45s)

    # 数値のみで判断すると誤差と言える程度だが、ネットワークやリポジトリの影響でダウンロード時間にバラツキが出ないことが最大のメリット
    ```

    ```bash:kitchen_test
    1回目: Kitchen is finished. (2m49.35s)
          => Finished verifying <provisioning-centos6> (0m25.15s).
    2回目: Kitchen is finished. (2m45.61s)
          => Finished verifying <provisioning-centos6> (0m23.86s).
    3回目: Kitchen is finished. (2m46.16s)
          => Finished verifying <provisioning-centos6> (0m24.27s).

    # テスト時間の短縮化について、 vagrant-cachier プラグインの導入による影響はない
    ```

* この段階での残課題 (2015/03/02の時点では)
    1. <del>kitchen_create / kitchen_setup / kitchen_test 共通</del>
        1. <del>vagrant-vbguest プラグインによって、 VirtualBox Guest Additions がアップデート (4.3.22) されている</del>
            1. <del>依存しているパッケージファイルのアップデート</del>
            2. <del>旧バージョンをアンインストール</del>
            3. <del>新バージョンをインストール</del>
    2. <del>kitchen_setup / kitchen_test 共通</del>
        1. <del>kitchen-ansible プラグインによって、 Ansible がインストールされている</del>
            1. <del>yum に epel リポジトリを追加</del>
            2. <del>epel リポジトリのメタデータをキャッシュする</del>
            3. <del>Python などの依存しているパッケージファイルをインストール</del>
            4. <del>Ansible のインストール</del>
        2. <del>test-kitchen によって Chef がインストールされている</del>
            1. <del>[オムニバスインストーラー](https://docs.chef.io/install_omnibus.html)が実行されている</del>
            2. <del>thor がインストールされている</del>
            3. <del>busser がインストールされている</del>
    3. <del>kitchen_test</del>
        1. <del>bundle install が実行されている</del>
            1. <del>./test/integration/provisioning/rspec/Gemfile によって Serverspec がインストールされている</del>
    4. <del>kitchen_setup</del>
        1. <del>Ansible が ./provisioning.yml から yum groupinstall -y 'Development tools' を実行している</del>
            1. <del>yum リポジトリからメタデータをダウンロードしている</del>


## その他
上記のやり方では Ansible および Serverspec は、ローカルモードで実行されるため SSH 経由で実行する場合には意味がないので、その場合は以下のようなものを試してみると良さそう

ただし、個人的には [test-kitchen/kitchen-vagrant](https://github.com/test-kitchen/kitchen-vagrant) の代わりに [test-kitchen/kitchen-ec2](https://github.com/test-kitchen/kitchen-ec2) や [test-kitchen/kitchen-digitalocean](https://github.com/test-kitchen/kitchen-digitalocean) などの driver を使ってリモートのインスタンスを相手に実行できる仕組みに変えた方が開発環境としては幸せだと思うのでここでの細かい説明などは省略

* Ansible
    + 並列化
        - [forks](http://docs.ansible.com/intro_configuration.html#forks)
    + 転送速度の最適化
        - [pipelining](http://docs.ansible.com/intro_configuration.html#pipelining) => SSH 経由の場合は必須なレベル

* Serverspec
    + 並列化
        - [grosser/parallel_tests](https://github.com/grosser/parallel_tests)
        - [grosser/parallel](https://github.com/grosser/parallel) => [かなり昔に試してみたやつ](http://qiita.com/libero_18/items/04f0a9bbebdfd41d3399)


## 参考
* [Ansible Performance Tuning (for Fun and Profit)](http://www.ansible.com/blog/ansible-performance-tuning)
* [Ansibleパフォーマンスチューニング](http://qiita.com/kiida/items/3594a25b4e2e8695e2cb)
* [test-kitchenのつかいかた](http://qiita.com/sawanoboly/items/9f560bd63ad0712b17ba)