- 2013-12-24 (火) 23:53
- Advent Calendar 2013 | Vagrant
Shin x blog Advent Calendar 2013 の 24 日目です。

先日リリースされた vagrant-serverspec を使って、テストドリブンなサーバ構築を行ってみました。
vagrant-serverspec
vagrant-serverspec は、サーバ、インフラの状態をテストするツール serverspec を Vagrant のプロビジョナとして実行できるプラグインです。これを使うことで、vagrant コマンドから、serverspec のテストを実行することができます。
詳しくは、@ryuzee さんの下記エントリを参照して下さい。
vagrant-serverspecを使ってプロビジョニング結果をテストする | Ryuzee.com
仕様
今回構築するサーバの仕様は下記です。PHP 5.5.x をインストールして、ビルトインサーバを起動するというものです。(※ちなみにビルトインサーバは開発用途でのみ利用して下さい。)
* PHP 5.5.x を yum でインストール(remi-php55)
* date.timezone を Asia/Tokyo に設定
* ビルトインサーバを Port 8000 で起動
また、環境は下記で動作確認しています。必要であれば、それぞれについてインストールを行なって下さい。
* Vagrant 1.4.1
* VirtualBox 4.3.0
* serverspec 0.13.7
* ansible 1.2.2
vagrant-serverspec インストール
vagrant plugin コマンドで、vagrant-serverspec をインストールします。
$ vagrant plugin install vagrant-serverspec
Vagrantfile 作成
Vagrant で仮想サーバを構築して、このサーバを仕様を満たす形に進めていきます。まず、vagrant init コマンドで Vagrantfile を作成します。
$ vagrant init centos64_ja
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.
serverspec-init でデフォルトのテストケースを作成
テストドリブンということで、先に serverspec のテストを作っていきます。
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: 1 Vagrant instance y/n: y Auto-configure Vagrant from Vagrantfile? y/n: y + spec/ + spec/default/ + spec/default/httpd_spec.rb + spec/spec_helper.rb + Rakefile
作成されたスペックファイルを元にテストケースを作ります。今回は、PHP に関するテストとなるので、spec/default/httpd_spec.rb を spec/default/php_spec.rb というファイルにリネームします。そして、このファイルにテストケースを書いていきます。
まずは、下記のように、PHP パッケージがインストールされているかだけを確認するテストを書きました。
$ mv spec/default/httpd_spec.rb spec/default/php_spec.rb
$ vim spec/default/php_spec.rb
require 'spec_helper'
describe package('php') do
it { should be_installed }
end
作成したテストケースが実行されるように Vagrantfile に serverspec のプロビジョニング設定を記述します。spce/default/ 以下の全てのスペックファイルを対象としています。
config.vm.provision :serverspec do |spec|
spec.pattern = "spec/default/*_spec.rb"
end
これで、vagrant up を実行すると、serverspec のテストが実行されます。では、実際にvagrant upを実行してみましょう。下記のように、serverspec プロビジョナが実行され、テストが失敗しました。まだ、PHP はインストールしていないので、これは当然の結果です。
$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
(snip)
[default] Running provisioner: serverspec...
F
Failures:
1) Package "php" should be installed
Failure/Error: it { should be_installed }
sudo rpm -q php
パッケージ php はインストールされていません。
expected Package "php" to be installed
# ./spec/default/php_spec.rb:4:in `block (2 levels) in '
Finished in 5.07 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/default/php_spec.rb:4 # Package "php" should be installed
では、PHP をインストールするプロビジョニングをシェルで記述してみます。このプロビジョニングは、serverspec よりも前に実行する必要があるので、serverspec プロビジョニングより上に記述します。
config.vm.provision :shell, :inline => <<-EOT
#
# yum repository
#
rpm -ivh http://ftp.riken.jp/Linux/fedora/epel/6/i386/epel-release-6-8.noarch.rpm
rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
yum -y install php --enablerepo=remi-php55
EOT
config.vm.provision :serverspec do |spec|
(snip)
これで、PHP をインストールが行われ、その後に serverspec が実行されるので、テストが通るはずです。vagrant provision コマンドを実行して、プロビジョニングを行うと、今後はちゃんとテストが通りました。
$ vagrant provision (snip) [default] Running provisioner: serverspec... . Finished in 4.31 seconds 1 example, 0 failures
次に PHP のバージョンが、5.5.x であることを確認するためにテストを追加します。spec/default/php_spec.rb に下記を追加します。
describe command('php -v') do
it { should return_stdout /^PHP 5\.5\./ }
end
テストを再実行します。次は、--provision オプションで、serverspec のみを実行するようにします。ちゃんとテストが通りました。これで、サーバには PHP パッケージがインストールされており、バージョンが 5.5.x であることが確認できました。
$ vagrant provision --provision=serverspec [default] Running provisioner: serverspec... .. Finished in 4.23 seconds 2 examples, 0 failures
date.timezone を Asia/Tokyo に変更する
次に PHP ではお馴染みの date.timezone 設定が Asia/Tokyo であることを確認します。
serverspec では、php_config という、そのままずばり PHP の設定を確認するためのリソースタイプが用意されているので、これを spec/default/php_spec.rb に追加します。
describe 'PHP config parameters' do
context php_config('date.timezone') do
its(:value) { should eq 'Asia/Tokyo' }
end
end
プロビジョニングを再実行して、今回追加したテストが失敗することを確認します。
$ vagrant provision
(snip)
[default] Running provisioner: serverspec...
..F
Failures:
1) PHP config parameters Php config "date.timezone" value should eq "Asia/Tokyo"
Failure/Error: its(:value) { should eq 'Asia/Tokyo' }
sudo php -r 'echo get_cfg_var( "date.timezone" );'
expected: "Asia/Tokyo"
got: ""
(compared using ==)
# ./spec/default/php_spec.rb:13:in `block (3 levels) in '
Finished in 5.23 seconds
3 examples, 1 failure
Failed examples:
rspec ./spec/default/php_spec.rb:13 # PHP config parameters Php config "date.timezone" value should eq "Asia/Tokyo"
では、このテストが通るように date.timezone を設定するプロビジョニングを書きましょう。
今回は、php.ini の値を上書きするために zzmyphp.ini というファイルを作成し、ここに date.timezone の設定を記述することにします。
$ mkdir provision $ vi provision/zzmyphp.ini date.timezone="Asia/Tokyo"
このファイルをシェルプロビジョニングで、仮想サーバの /etc/php.d/ にコピーします。
config.vm.provision :shell, :inline => <<-EOT
#
# yum repository
#
rpm -ivh http://ftp.riken.jp/Linux/fedora/epel/6/i386/epel-release-6-8.noarch.rpm
rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
yum -y install php --enablerepo=remi-php55
cp -a /vagrant/provision/zzmyphp.ini /etc/php.d/ # <--- 追加
EOT
これでテストが通るはずです。vagrant provision を再実行します。下記のとおり、テストが通ったので、date.timezone の値が Asia/Tokyo となっていることが確認できました。
$ vagrant provision (snip) [default] Running provisioner: serverspec... ... Finished in 5.12 seconds 3 examples, 0 failures
ビルトインサーバを起動
最後に PHP のビルトインサーバを 8000 ポートで起動します。DocumentRoot は、/vagrant にします。
まずテストから書きます。ここでは簡易的にポート 8000 で Listen しているかを確認します。
describe port(8000) do
it { should be_listening }
end
プロビジョニングを行なって、追加したテストが失敗することを確認しておきます。
$ vagrant provision
(snip)
[default] Running provisioner: serverspec...
...F
Failures:
1) Port "8000" should be listening
Failure/Error: it { should be_listening }
sudo netstat -tunl | grep -- :8000\
expected Port "8000" to be listening
# ./spec/default/php_spec.rb:18:in `block (2 levels) in '
Finished in 6.07 seconds
4 examples, 1 failure
Failed examples:
rspec ./spec/default/php_spec.rb:18 # Port "8000" should be listening
では、シェルプロビジョニングでビルトインサーバを起動するように設定します。ここでは supervisor を使って、ビルトインサーバを起動するようにします。
config.vm.provision :shell, :inline => <<-EOT
rpm -ivh http://ftp.riken.jp/Linux/fedora/epel/6/i386/epel-release-6-8.noarch.rpm
rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
yum -y install php --enablerepo=remi-php55
cp -a /vagrant/provision/zzmyphp.ini /etc/php.d/
# 追加
yum -y install supervisor
cp -a /vagrant/provision/supervisord.conf /etc/
chkconfig supervisord on
service supervisord start
EOT
vagrant provision を実行すると、ビルトインサーバが起動され、テストが通りました。これで、ビルトインサーバがポート 8000 で起動していることが確認できました。
$ vagrant provision (snip) [default] Running provisioner: serverspec... .... Finished in 6.07 seconds 4 examples, 0 failures
プロビジョナを ansible に変更
これで仕様どおりに稼働するサーバを構築することができました。すでに仕様を検証する serverspec のテストがあるので、構築するためのプロビジョナを変更しても、要求された仕様を満たしているかを検証することができます。ではプロビジョナを ansible に変えて再度サーバを構築し直してみましょう。
Vagrantfile のシェルプロビジョニングの設定を削除して、代わりに ansible の設定を下記のように書きます。
config.vm.provision :ansible do |ansible|
ansible.playbook = "provision/playbook/main.yml"
end
provision/playbook 以下に main.yml というプレイブックを配置しています。やっている内容は、シェルプロビジョニングとほぼ同じです。
---
- hosts: all
sudo: yes
tasks:
- name: add yum repositories
copy: src=files/{{ item }} dest=/etc/yum.repos.d/
with_items:
- epel.repo
- epel-testing.repo
- remi.repo
- name: add rpm-gpg-keys
copy: src=files/{{ item }} dest=/etc/pki/rpm-gpg/
with_items:
- RPM-GPG-KEY-EPEL-6
- RPM-GPG-KEY-remi
- name: install PHP 5.5.x
yum: name=php enablerepo=remi-php55
- name: put zzmyphp.ini
copy: src=../zzmyphp.ini dest=/etc/php.d/ backup=yes
- name: install supervisor
yum: name=supervisor
- name: put supervisord.conf
copy: src=../supervisord.conf dest=/etc/supervisord.conf backup=yes
- name: enable supervisor
service: name=supervisord enabled=yes state=started
では、この ansible プロビジョニングでもテストが通るかを確かめてみます。前回のシェルプロビジョニングで構築した内容が残っているので、仮想マシンを破棄してから、再構築します。
下記のとおり、ansible によるプロビジョニングが実行され、テストが無事に通りました。これにより、要求された仕様が満たされていることが確認できました。
$ vagrant destory -f $ vagrant up (snip) [default] Running provisioner: ansible... PLAY [all] ******************************************************************** GATHERING FACTS *************************************************************** [98/3036] ok: [default] TASK: [add yum repositories] ************************************************** changed: [default] => (item=epel.repo) changed: [default] => (item=epel-testing.repo) changed: [default] => (item=remi.repo) TASK: [add rpm-gpg-keys] ****************************************************** changed: [default] => (item=RPM-GPG-KEY-EPEL-6) changed: [default] => (item=RPM-GPG-KEY-remi) TASK: [install PHP 5.5.x] ***************************************************** changed: [default] TASK: [put zzmyphp.ini] ******************************************************* changed: [default] TASK: [install supervisor] **************************************************** changed: [default] TASK: [put supervisord.conf] ************************************************** changed: [default] TASK: [enable supervisor] ***************************************************** changed: [default] PLAY RECAP ******************************************************************** default : ok=8 changed=7 unreachable=0 failed=0 [default] Running provisioner: serverspec... .... Finished in 10.62 seconds 4 examples, 0 failures
さいごに
Vagrant と serverspec の相性が良いのは分かっていたのですが、vagrant up の後にいちいち rake spec を実行するのが面倒だなと感じていました。
vagrant-serverspec を使えば、プロビジョニングの後に自動で serverspec が実行されるので、構築とテストを一息で行うことができます。
仕様を決める、テスト書く、vagrant provision 、プロビジョニング書く、vagrant provision の流れはなかなか気持ち良いです。こうして検証済のプロビジョニングは、本番環境など Vagrant 以外の環境でも利用できますし、当然ながらそのテストも serverspec で行うことができます。
サーバ設定はわりと仕様が決まりやすく、テストが書きやすいと思うので、vagrant-serverspec を使って、テストドリブンなサーバ構築を行ってみて下さい。

