仮想化やクラウド化が進み、インフラ環境をプログラマブルに構築できるようになってきました。この流れにより、サーバ構築をプログラムにより自動化することも多くなってきています。自動化が進むと、本当に意図した通りに正しくサーバのインストールや設定が実施されているかの確認テストも自動化することが求められるようになってきています。
本記事では、このような場面で有用なサーバ状態のテスト自動化フレームワークであるserverspecを紹介します。
serverspecとはなにか?
既に多くの技術系記事にて、serverspecの紹介がされているためご存知の方も多いかと思いますが、本技術ブログでは初登場のテーマであるためserverspecとはなにか?から順を追って解説します。
serverspecは宮下剛輔氏によって開発されたサーバの状態をテストするためのフレームワークです (Serverspec公式サイト) 。
Ruby実装で、RubyのテストフレームワークであるRSpecの書き方に準拠しています。
2014/3/10時点での最新バージョンは、0.15.4です。
本記事では、0.15.4を対象に解説します。
serverspecの特徴は?
serverspecは、ChefやPuppetもしくは手動などサーバの構築方法が何であれ関係なく、サーバがどのような状態であるかを確認するという非常にシンプルな形になっているのが特徴です。
serverspecの開発者の方が発表されている論文でも記載されていますが、これまでは、システム環境の自動構築ツール(ChefやPuppet)に依存する形のテストフレームワークであったり、OSの種別毎にテストコードを書き換えなければならないなどの課題がありました。こういった課題に対してserverspecが開発されています。
参考: http://mizzy.org/blog/2014/03/11/1/
serverspecのテスト実行は、テスト対象のサーバに対してSSHログインしてコマンドを実行した結果を確認するという非常にシンプルなアーキテクチャであるため、serverspec用に何か特別なエージェントをインストールする必要などがなく、SSHログインさえできる状態であれば良いというのも特徴です。
サーバの状態を確認するためのOSコマンドの違いについても通常はOS毎にコマンド実行方法が異なりますが、serverspecのバックエンド処理用フレームワークのspecinfraにてその違いが吸収されています。
つまり、テスト対象のサーバのOSの種別毎にテストコードを書き分ける必要がありません。
serverspecはどういった用途で使う?
serverspecはしばしば、ChefやPuppet等のサーバ構成管理自動化ツールとセットで活用されます。構築作業を自動化したはいいが、本当に構成通り正しい状態でセットアップされたのかの確認を毎回手作業で実施していては時間がかかります。そこでserverspecが効果を発揮します。構築作業を行ったが、本当に意図した通りにパッケージがインストールされているか?設定情報が適用されているか?などのテストを実施することが可能です。
そのため、以下のようなタイミングで実行します。
- ChefやPuppetにて構築処理を実行した後
- 設定変更作業を実行した後等
これにより、常に正しい状態が維持できているかを確認することができます。
開発でよく採られる手法であるテスト駆動開発の考え方をサーバ環境の構築・運用作業にも適用することもできます。serverspecで記述したテストコードの通りになるようサーバの構築を進めるといったやり方です。
serverspecの使い方
次に、serverspecの使い方について紹介します。
ここでは、CentOS6.4、Ruby2.0.0、rubygems 2.1.10を利用している環境における例になります。
インストール方法
serverspecはRubyのgemパッケージとして公開されています。
そのため、gemコマンドにてインストール可能です。RubyおよびRubygemsがインストール済みの環境の場合、
インストールは以下のコマンドだけで完了です。
$ gem install serverspec
事前設定
冒頭でも少し述べた通り、serverspecは以下の図のような形でテストを実行します。
そのため、テストを実行したい対象サーバ側にserverspecを実行する元のサーバからSSH接続が可能な状態にする必要があります。(テスト対象サーバがWindowsの場合は、WinRMを使ってアクセスします。そのため、WinRMの接続設定が必要となります。)
もしくは、SSH接続(やWinRM)を使わずlocalに対してのみテスト実行をすることも可能です。
その場合には、serverspecのテストコードを各対象サーバに配置してテスト実行できる状態にする必要があります。
ここでは、SSH接続によるテスト実行する場合の設定方法を紹介します。
初期化
serverspecを実行するサーバ上でserverspecの初期化処理を実施します。
$ serverspec-init ←任意のディレクトリで初期設定コマンド実行 Select OS type: 1) UN*X 2) Windows Select number: 1 ←OSのタイプを選択(今回はCentOSに対するテストを書くため1で) Select a backend type: 1) SSH 2) Exec (local) Select number: 1 ←SSH接続をしてリモートのサーバに対してテスト実行する場合は1を Vagrant instance y/n: n Input target host name: server-01 + spec/ + spec/server-01/ + spec/server-01/httpd_spec.rb + spec/spec_helper.rb + Rakefile
これで、最低限必要なファイル群が生成されます。
spec/server-01という形で接続先サーバ毎のテストコード配置用ディレクトリも作成されます。
SSH接続事前設定
自動生成されるファイルの中にspec/spec_helper.rbというファイルがあります。
このファイル内に、テスト実行対象サーバにSSH接続する処理部分が記述されています。
デフォルトで生成される内容の場合、作成されたディレクトリ名(上記例の場合、server-01 )をホスト名とみなし、OpenSSHの設定ファイルを探索して、そのホスト名に対する設定情報を読み取ります。
例えば、~/.ssh/configに以下のような記述があった場合、
Host server-01 HostName 10.0.0.10 Port 22 User ikeda IdentityFile ~/.ssh/serverspec-key
serverspecは、10.0.0.10の22番ポートに対して、ikedaユーザで、serverspec-keyという鍵を使ってSSH接続を試みることになります。
そのため、SSH接続時にインタラクティブにパスワード入力を求められないよう事前にパスフレーズなしの公開鍵認証設定を実施しておくか、spec_helper.rbの該当部分を修正し、パスワードを入力して接続できるように変更するなどの対応が必要になります。
おすすめはしませんが、パスワード認証で実行したい場合には、一例としてspec_helper.rbを以下のように変更することで対応可能となります。
vim spec/spec_helper.rb ・・・略 host = File.basename(Pathname.new(file).dirname) if c.host != host c.ssh.close if c.ssh c.host = host options = Net::SSH::Config.for(c.host) options[:password] = "パスワード" user = options[:user] || Etc.getlogin c.ssh = Net::SSH.start(host, user, options) end ・・・略
net-sshの接続時のオプションについては この辺り を参照してください。
sudo設定
また、serverspecはテスト実行のための各種コマンドをsudoをつけて実行します。
そのため、SSH接続したユーザに対してsudo権限を付与します。
# visudo ikeda ALL=(ALL) ALL
この設定の場合、sudo実行時にパスワード入力が必要になります。
その場合、serverspecの実行時に、SUDO_PASSWORDもしくは、ASK_SUDO_PASSWORD環境変数を設定することでテスト実行が可能となります。
sudoのパスワードを環境変数として設定する場合には、環境変数「SUDO_PASSWORD」に設定します。
パスワードを環境変数に設定したくない場合には、環境変数「ASK_SUDO_PASSWORD」を1に設定することでテスト実行時に対話式でsudoパスワードを入力することが可能です。
また、sudo実行時にパスワードの入力が必要ないように設定するには、sudoersに以下のような設定を行います。
(パスワードなしでルート権限によるコマンド実行ができるようになるため、設定する場合には注意してください。)
# visudo ikeda ALL=(ALL) NOPASSWD:ALL
テストコードの書き方
テストコードは、先ほどの初期化により作成されたディレクトリ以下に作成することになります。
初期化時に、サンプルとして、httpd_spec.rbというファイルが作成されています。
これは、httpdがインストールされているか?httpdのサービスが稼動しているか?80番ポートがリッスンしているか?などをテストするサンプルテストコードです。
このサンプルコードの書き方や公式サイトのドキュメントを参考に記述するとよいでしょう。
serverspecのテストコードで重要なのは、 Resource Type と Matcher です。
Resource Type
serverspecでは何に対するテストを実施するのか?をResourceTypeとして定義します。
serverspecがサポートしているResourceTypeは (こちら) を参照してください。
例えば、サーバの中にあるファイルの状態をテストする場合には、ReourceTypeの "file" を使います。
Matcher
次に、どういったテストを実施するのか?についてはMatcherで定義します。
サポートしているMatcherは各ResourceType毎に異なります。 ResourceTypeの一覧のページ で詳細を確認して下さい。
先ほどのfileというResourceTypeに対しては、以下のようなMatcherが利用可能です。
- ファイルであるか?(be_file)
- ディレクトリであるか?(be_directory)
- ソケットファイルであるか?(be_socket)
- ファイルにある文字列が含まれているか?(contain)
- ファイルの所有者や権限がどうなっているか?(be_mode,be_readable,be_writable,be_executable,be_owned_by)
- などなど
テストコードサンプル
具体的に、テストコードを書いてみます。
ここでは、例として、以下のテストを実行するコードを書いてみます。
- ResourceType
- ファイル /home/ikeda/test.conf
- Matcher
- ikedaユーザの所有であること
- モードが600(UserにのみRead,Write権限がある)であること
- ファイル内の記述内容について、"<Directory "/var/www/html/test">"と"</Directory>"という文字列の間に"Allow from all"という文字列が書かれていること
file_spec.rbというファイルを作成し、そこに以下のテストを記述します。
require 'spec_helper' describe file('/home/ikeda/test.conf') do it { should be_owned_by 'ikeda' } it { should be_mode 600 } it { should contain('Allow from all').from(/^<Directory \"\/var\/www\/html\/test\">/).to(/^<\/Directory>/) } end
このように、serverspecでは、自分で動作確認用のコードを書くと少し手間になりそうな処理部分を非常に簡単にテスト記述できるよう様々なMatcherが用意されています。
ResourceTypeとそれぞれに用意されているMatcherをうまく組み合わせてテストコードを作成することで非常に幅広く活用することができます。
テストの実行方法
これを実行すると以下のような結果が出力されます。
$ cd テストコードディレクトリ $ rake spec /usr/local/rvm/rubies/ruby-2.0.0-p247/bin/ruby -S rspec spec/server-01/file_spec.rb ... Finished in 0.99466 seconds 3 examples, 0 failures
3つ全てのテストが正常に通過したことがわかります。
実際の/home/ikeda/test.confのファイルはテスト仕様を満たす以下のような状態でサーバに存在しています。
$ ls -l /home/ikeda/test.conf -rw------- 1 ikeda ikeda 159 Mar 14 16:08 /home/ikeda/test.conf $ cat /home/ikeda/test.conf # Sample File <Directory "/var/www/html/test"> Options Indexes FollowSymLinks AllowOverride None Order allow,deny Allow from all </Directory>
テスト実行時に実行されているコマンド
では、実際のテスト実行時にserverspecがテスト対象サーバ内で実行しているコマンドはどうなっているのでしょうか?
例えば、上記ファイルの所有者のテストの場合は以下のように実装されています。
$ vim specinfra-0.7.1/lib/specinfra/command/base.rb ・・・略 def check_owner(file, owner) regexp = "^#{owner}$" "stat -c %U #{escape(file)} | grep -- #{escape(regexp)}" end ・・・略
serverspecのバックエンド処理はserverspecのバージョン0.12.0からspecinfraという別パッケージで処理が分けて管理されるようになっています。
そのため、このファイルの所有者が誰であるかのチェックを実際に行う処理はspecinfraに記述されています。
この実装内容を見ると、以下のコマンドが実行されていることがわかります。
(fileが/home/ikeda/test.conf、ownerがikedaの場合)
stat -c %U /home/ikeda/test.conf | grep -- "^ikeda$"
このコマンドを実行した結果をserverspecが評価を行いテスト成功・失敗の判断をしています。
まとめ
簡単ではありますが、ここまでserverspecの基本の紹介やテストコードの記述方法・実行方法について解説しました。
serverspecのアーキテクチャやテストコードは非常にシンプルです。
この基本を抑えておくことで、それぞれの環境にあわせたテストコードを作成することができます。
まずは、試しに実際にテストコードを書いてみてイメージを掴んでください。
次回は、より環境にあわせたテストを書くために、commandというResourceTypeの紹介と、ResourceTypeやMatcherをカスタマイズする方法を紹介します。