「Serverspec」を使ってサーバー環境を自動テストしよう

 ChefやPuppet、Vagrantといったサーバーの設定を自動で行うツールが普及しつつあるが、それと同時にサーバーの自動テストについても注目されるようになっている。今回はサーバーの自動テストを実現するツール「Serverspec」をLinux環境で利用する手順を紹介する。

サーバーの自動テストの必要性とテスト駆動開発

 最近ではサーバーの設定やソフトウェアのインストールといった作業を自動で行えるツールが注目されている。しかし、設定後のテストについてはあまり注目されておらず、各種設定やソフトウェアのインストールが正しく行われているかどうかは手作業で確認することが一般的だった。しかし、手作業での確認にはミスや抜けが発生する可能性があり、また対象とするサーバーの数が増えるとそれに比例して手間も増えてしまう。そこで活用したいのが、サーバーの自動テストツールだ。

 サーバーの自動テストツールは、あらかじめ用意されたテストスクリプトを実行することで、目的の要件を満たすようにサーバーが設定されているかをチェックするというものだ。要件としては、たとえば特定のソフトウェアやパッケージがインストールされているか、特定のサービスは動いているか、指定したポートにアクセスできるか、などがある。これらを自動化することで一貫性を持ったテストが可能になり、また同じテストを容易に複数回実行できるようになる。

 また、別の側面からサーバーの自動テストツールに注目する動きもある。ソフトウェア開発の現場ではテスト駆動開発(Test Driven Development、TDD)という開発スタイルがあるが、これをインフラの分野にも持ち込むというものだ。

 テスト駆動開発では、始めに目的とした機能をテストするテスト環境を作り、そのテストをクリアするようにコードを実装する。サーバーをテストするためのツールと、サーバーの設定を行うツールの2つがあれば、この手法をそのままサーバー運用管理の分野に持ち込んで自動化することができる。サーバーの設定ツールとしてはChefなどがすでに存在するため、あとは実用的なテストツールの登場が期待されていた。

 そういった背景の下で開発されたのが、今回紹介する「Serverspec」だ(図1)。

図1 ServerspecのWebサイト
図1 ServerspecのWebサイト

Serverspecの特徴とインストール

 Serverspecは、サーバー向けのテストフレームワークだ。Rubyで実装されており、RubyやRuby向けのテストフレームワーク「RSpec」の機能を活用して構築されている。LinuxなどのUNIX系サーバーだけでなく、RubyがインストールされていればWindows環境でも利用できる。

 詳しくは後述するが、ServerspecではRubyのコードとしてテストスクリプトを実装する。このテストスクリプトは「specファイル」と呼ばれ、シンプルな文法でテスト内容を記述できるようになっている。このspecファイルやテストの事前準備などを行うためのスクリプトなどをRubyで実装された自動化ツール「Rake」を使って実行する、というのが基本的なServerspecの利用スタイルとなる。

 Serverspecにはspecファイルのひな形や必要なスクリプト、Rake用の設定ファイルであるRakefileなどを自動生成する機能が用意されており、これらを利用することでRubyの文法に関する知識が無くともテストスクリプトの作成や実行が可能だ。また、Rubyの知識があればより柔軟なテストコードの管理や実行なども可能となる。

 Serverspecにはローカル環境(テストスクリプトを実行するマシン)に対するテストだけでなく、ネットワーク経由でほかのマシンに対しテストを実行する機能もある。UNIX系サーバーを対象とする場合はSSHを、Windowsサーバーを対象とする場合はWinRM(Windows Remote Management)と呼ばれるWindows向けのリモート管理ツールを使用し、目的のサーバーに接続して各種コマンドを実行することでテストを行える。Windows環境からLinuxサーバーのテストを実行したり、逆にLinux環境からWindowsサーバーのテストを実行することも可能だ。

Serverspecのインストール

 ServerspecはRuby向けのパッケージ管理システムであるRubyGems向けパッケージとして提供されている。Rubyおよびgemコマンドが利用できる環境であれば、以下のようにしてインストールできる。

$ gem install serverspec

 また、Serverspecに付属するひな形生成ツールで作られたスクリプトの実行にはRakeが必要となる。こちらもgemコマンドでインストールできる。

$ gem install rake

「serverspec-init」コマンドを使ったテストスクリプトの作成

 Serverspecをインストールすると、~/.gem/ruby/gemsディレクトリ以下にライブラリがインストールされ、Rubyスクリプトからserverspecモジュールが利用できるようになる。あとは目的に応じたspecファイルを作成して実行すれば良い。とはいえ、specファイルの作成や実行にはRSpecの知識が必要となる。そのため、Serverspecにはテストスクリプトのひな形を生成する「serverspec-init」コマンドが用意されている。

 serverspec-initコマンドはServerspecのインストール時に、~/binディレクトリにインストールされる。以下ではこのディレクトリにパスが通っている(PATH環境変数の値にこのディレクトリが含まれている)ことを前提として説明するので、適宜設定を行っておこう。

 serverspec-initコマンドでは、対話的な操作で環境に応じたテストスクリプトを作成できる。まずはServerspecに慣れるため、ローカルのLinux環境に対してテストを実行するスクリプトを作成してみよう。この場合、まず「OS type」として「UN*X」を選択し、続いて「backend type」として「Exec (local)」を選択する。

$ 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/httpd_spec.rb
 + spec/spec_helper.rb
 + Rakefile

 すると、serverspec-initコマンドを実行したディレクトリ内に「Rakefile」というファイルと「spec」ディレクトリが作成され、さらにspecファイルが「spec/localhost」ディレクトリ以下に生成される。それぞれの内容は以下のようになっている。

  • Rakefile:rakeコマンドを使ってテストを実行するための設定ファイル
  • spec/spec_helper.rb:Serverspecを使ってテストを実行する際のテスト設定などを記述したRubyスクリプト
  • spec/localhost/httpd_spec.rb:テストスクリプト本体。デフォルトではWebサーバーの設定テストのための項目がサンプルとして記述されている

 serverspec-initコマンドで生成されたスクリプトでは、spec/localhostディレクトリ内にある「_spec.rb」で終わるスクリプトを実行するようになっており、あらかじめ生成されているhttpd_spec.rbファイルを修正する、もしくはこのディレクトリ内に「<テスト名>_spec.rb」という名前でテストスクリプトを作成することで、独自のテストを用意する仕組みになっている。

テストスクリプトの文法

 serverspec-initコマンドで作成されたテストスクリプト本体(spec/localhost/httpd_spec.rb)は、以下のようになっている。

require 'spec_helper'  ←spec/spec_helper.rbを実行する

describe package('httpd') do  ←「httpd」パッケージに関するテスト
  it { should be_installed }  ←パッケージがインストールされているか?
end

describe service('httpd') do  ←「httpd」サービスに関するテスト
  it { should be_enabled   }  ←サービスが有効になっているか?
  it { should be_running   }  ←サービスが実行されているか?
end

describe port(80) do  ←80番ポートに関するテスト
  it { should be_listening }  ←ポートが待ち受け状態になっているか?
end

describe file('/etc/httpd/conf/httpd.conf') do  ←「/etc/httpd/conf/httpd.conf」ファイルに関するテスト
  it { should be_file }  ←ファイルが存在するか?
  its(:content) { should match /ServerName localhost/ }  ←ファイル内に「/ServerName localhost/」にマッチするテキストが存在するか?
end

 テストスクリプトでは、以下のような形でテストする項目を記述する。

describe <リソースタイプ>(<テスト対象>) do
  <テスト条件>
  :
  :
end

 「リソースタイプ」ではどのようなリソースに対してテストを行うかを指定する。用意されているリソース一覧はServerspecのWebサイトに掲載されている。たとえば「package」ではRPMパッケージやDEBパッケージといったソフトウェアパッケージを、「service」では各種サービスを、「port」ではネットワークポートを対象にテストを行える。

 また、「テスト対象」には対象とするリソースを指定する。たとえば「package('httpd')」の場合、「httpd」パッケージに関するテストを行うことになる。

 「テスト条件」は対象とするリソースの状態を指定するもので、次のような書式で記述する。

it { should <条件> }
its(<対象>) { should <条件> <値> }

 リソースタイプごとに指定できる条件は異なるが、そのリソースがここで指定された状態になっていない場合、テストに失敗するという挙動になっている点はすべて同じだ。

  たとえば、「package」リソースに対し次のように記述することで、そのパッケージがインストールされている、という条件を指定できる。

if { should be_installed }

 また、特定のファイルに対するテストを行う「file」リソースでは、次のように記述することで『「/ServerName localhost/」という正規表現にマッチするテキストを含んでいる』という条件を指定できる。

its(:content) { should match /ServerName localhost/ }

 なお、テストスクリプトの冒頭にある「require 'spec_helper'」行では、spec/spec_helper.rbスクリプトを実行するよう指定するものだ。このスクリプトではserverspecモジュールのロードのほか、テストの実行に必要な各種パラメータの設定などが行われている。

テストを実行する

 serverspec-initコマンドを実行したディレクトリ(Rakefileが存在するディレクトリ)でrakeコマンドを実行すると、テストが実行される。もしすべてのテストに成功すれば、次のようにエラーメッセージの出力なしにテストが完了した旨が表示される。

$ rake
/usr/bin/ruby -S rspec spec/localhost/httpd_spec.rb
......

Finished in 0.09108 seconds
6 examples, 0 failures

 また、失敗したテスト項目があった場合はその旨が表示される。

$ rake
/usr/bin/ruby -S rspec spec/localhost/httpd_spec.rb
FFFFFF

Failures:

  1) Package "httpd" should be installed
     On host ``
     Failure/Error: it { should be_installed }
       rpm -q httpd
       パッケージ httpd はインストールされていません。
       expected Package "httpd" to be installed
     # ./spec/localhost/httpd_spec.rb:4:in `block (2 levels) in <top (required)>'
  
  

リモートサーバーを対象にテストを行う

 serverspec-initコマンドの実行時、「backend type」として次のように「SSH」を指定すると、リモートサーバーに対するテストを実行するためのスクリプト一式が生成される。この場合、対象がVagrant(仮想環境の設定管理ツール)であるかを尋ねられる。ここで「n」を選択するとホスト名の入力を求められるので、ここで対象とするホストを指定する。ここでは、ホストとして「example.com」を指定しているが、IPアドレスでの指定も可能だ。

$ 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: n
Input target host name: example.com
 + spec/
 + spec/example.com/
 + spec/example.com/httpd_spec.rb
 + spec/spec_helper.rb
 + Rakefile

 この場合、specディレクトリ以下に対象とするホスト名と同じ名前のディレクトリが作成され、そこにテスト用のspecファイルが作成される。生成されたスクリプトでは、このディレクトリ名を参照してターゲットとするホストを決定するようになっているため、このディレクトリ名は変更できない点に注意が必要だ。また、生成されるspecファイル自体はserverspec-initコマンドでローカル環境を指定した場合と変わらない。

 なお、Serverspecでは対象とするリモートホストに対しSSHでログインして各種コマンドを実行することでテストを行う。このとき、以下の2点の事前設定が必要となる。

  • 公開鍵認証で対象とするサーバーにログインできるようにする
  • ログインに使用したユーザーがsudoコマンドでroot権限を取得できるようにする

 また、sudoコマンドの実行時にはそのユーザーのパスワード入力が必要となる。rakeの実行時に次のように「ASK_SUDO_PASSWORD」環境変数を設定しておくことで、対話的にパスワードを指定することが可能だ。

$ ASK_SUDO_PASSWORD=1 rake
/usr/bin/ruby -S rspec spec/example.com/httpd_spec.rb
Enter sudo password:  ←ユーザーのパスワードを入力する
Enter passphrase for /home/hylom/.ssh/id_rsa:  ←SSH公開鍵のパスフレーズを入力する
  
  

 また、スクリプトなどからテスト操作を自動実行させたい場合などは、「SUDO_PASSWORD」環境変数にあらかじめパスワードを格納しておくこともできる。

 ちなみにもし公開鍵認証でログインできない場合、rakeコマンドの実行時に次のように「Net::SSH::AuthenticationFailed」というエラーが表示される。

$ rake
/usr/bin/ruby -S rspec spec/153.120.17.48/httpd_spec.rb
FFFFFF

Failures:

  1) Package "httpd"
     On host `153.120.17.48`
     Failure/Error: Unable to find matching line from backtrace
     Net::SSH::AuthenticationFailed: Authentication failed for user hylom@example.com

 また、sudoコマンドの実行に失敗した場合は次のように「SpecInfra::Command::Base::NotImplementedError」エラーが表示される。

/usr/bin/ruby -S rspec spec/example.com/httpd_spec.rb
Enter passphrase for /home/hylom/.ssh/id_rsa:
FFFFFF

Failures:

  1) Package "httpd" should be installed
     On host `example.com`
     Failure/Error: it { should be_installed }
     SpecInfra::Command::Base::NotImplementedError: SpecInfra::Command::Base::NotImplementedError

       SpecInfra::Command::Base::NotImplementedError
     # ./spec/example.com/httpd_spec.rb:4:in `block (2 levels) in <top (required)>'

テスト対象とするサーバーを追加する

 serverspec-initコマンドで生成されたスクリプトでは、specディレクトリ以下にディレクトリを追加することで、対象とするホストを追加できる。たとえば、example.comに加えて「example2.com」というホストに対するテストも追加したい場合は「spec/example2.com」というディレクトリを作成し、そこに「_spec.rb」で終わるテストスクリプトを追加すれば良い。

テストスクリプトのカスタマイズ

 Serverspecでは、前述の通りRSpecおよびRakeを使ってテストを行うようになっている。そのため、これらの知識があればserverspec-initコマンドで作成されたスクリプトを改良したり、独自のテストスクリプトを作成することが可能だ。ServerspecのWebサイトの「Advanced Tipsページ」では、カスタマイズのサンプルがいくつか掲載されている。複数のホストでspecファイルを共有したり、役割(ロール)単位でテスト内容を管理する方法などが紹介されているので、これらを参考にしてみると良いだろう。

手作業でサーバー管理を行う際にも有用

 ServerspecはChefやVagrantといったサーバー管理ツールと組み合わせて使うことが多いが、手作業でサーバー管理を行う際にも有用だ。設定・構築作業とテストの両方を手作業でやってしまうと、問題が発生した場合にそのどちらに問題があったのかを切り分ける作業が必要となってしまうが、テストを自動化していれば、テスト側でエラーが発生している可能性は少なくなる。

 あまり複雑な設定を行わない場合や、逆に自動化ツールでの設定が面倒になるようなケースもあるだろう。こういった場合にもServerspecは有用なので、サーバー運用コストの低減に向けてうまく活用したい。

おしらせ

banner_vps