Home > Advent Calendar 2013 | Vagrant > vagrant-serverspec で TDD ライクにサーバ構築を行う

vagrant-serverspec で TDD ライクにサーバ構築を行う

この記事の所要時間: 933

Shin x blog Advent Calendar 2013 の 24 日目です。

vagrant

先日リリースされた 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 を使って、テストドリブンなサーバ構築を行ってみて下さい。

参考

  • serverspec
  • jvoorhis/vagrant-serverspec
  • AnsibleWorks | Radically simple IT automation
  • Ansible - Provisioning - Vagrant Documentation
  • Pocket

    follow us in feedly

    Home > Advent Calendar 2013 | Vagrant > vagrant-serverspec で TDD ライクにサーバ構築を行う

    検索
    フィード
    メタ情報

    Return to page top