Serverspecの効果的活用に向けたTips

第2回ではシンプルな環境を例に、設定記述の際のポイントをいくつか紹介しました。実際の環境では、より複雑で管理するサーバの台数が非常に多いこともあるでしょう。そのような条件下でも、迅速にテストコードを記述しなければならない状況においては、さらに効果的なテストコード管理のための工夫が必要です。そこで最終回の第3回目は、Serverspecでテストコードを書いたりコードを管理したりする際に、より効率良く行うためのTipsを紹介します。
Serverspecの公式サイトにも様々なTipsがまとめられているので大変参考になります。こちらもあわせてご覧ください。
Serverspec Advanced Tips(英語での解説ページですが、非常に参考になります。)
http://serverspec.org/advanced_tips.html
共通のテストコードをまとめて管理
第2回では、1台のサーバに対して複数のテストを実行する例を紹介しました。しかし実際の環境では、WebサーバやDBサーバというように機能毎にサーバを分け、複数台でシステムを構成することが一般的かと思います。第1回で紹介したserverspec-initを実行すると、標準ではサーバのホスト名のディレクトリ配下に、そのホストに対して実行するテストコードが配置されます。そのため、テスト実行対象のサーバが複数台になると、サーバ台数分のディレクトリを作り、各ディレクトリの配下で同じ内容のファイルを管理することに状況になります。これでは、テストコードのメンテナンスにコストがかかってしまいます。そこで、テストコードを役割別にわけて各サーバで共通利用できるようにします。
役割毎に分けて管理するには、以下の対応が必要です。
- specファイルを配置するディレクトリ構成の変更
- ホスト情報をYAMLファイルに記述
- Rakefileの変更
- spec_helper.rbの変更
各項目について、それぞれ解説していきます。
ディレクトリ構成の変更
server-01がWebサーバ、server-02がDBサーバとし、セキュリティやDNS、NTPの設定については共通であると想定します。ホスト毎にディレクトリを作成して管理する際のディレクトリ構成は、以下のようになります。
ホスト毎にディレクトリを作成して管理する場合の例
├ Rakefile └ spec ├ server-01 │ ├ dns_spec.rb │ ├ httpd_spec.rb │ ├ security_spec.rb │ ├ ntp_spec.rb │ └ php_spec.rb ├ server-02 │ ├ dns_spec.rb │ ├ security_spec.rb │ ├ mysql_spec.rb │ └ ntp_spec.rb └ spec_helper.rb
一方、役割毎にspecファイルを分けて管理する場合のディレクトリ構成は次のようになります。ここではサーバの役割を、以下の3種類と想定しています。
- web(Apache HTTP serverの役割およびPHPアプリケーションを稼働させる役割)
- db(MySQLの役割)
- base(セキュリティやDNS、NTP等各サーバ共通の役割)
役割別にディレクトリを作成して管理する場合の例
├ Rakefile └ spec ├ base │ ├ dns_spec.rb │ ├ security_spec.rb │ └ ntp_spec.rb ├ db │ └ mysql_spec.rb ├ spec_helper.rb └ web ├ httpd_spec.rb └ php_spec.rb
ホスト情報をYAMLファイルに記述
ディレクトリ構成を変更したことで、これまではディレクトリ名に設定されていたテスト実行対象のホスト名の情報が損なわれることになります。そこで、ホスト名の情報をYAML形式のファイルにまとめて記述します。
ここでは、Rakefileと同じディレクトリ配下に「hosts.yml」という名前でファイルを作成します。
hosts.yml
server-01:
:roles:
- base
- web
server-02:
:roles:
- base
- db
Rakefileの変更
テスト実行の起点となるRakefileでは、先ほど作成したhosts.yml内の設定情報を取得し、テスト実行対象のホスト名の指定と、そのホストにどの役割に対するテストを実行するかを定義します。
Rakefile
require 'rake'
require 'rspec/core/rake_task'
require 'yaml'
hosts = YAML.load_file('hosts.yml')
desc "Run serverspec to all hosts (=serverspec:all)"
task :spec => 'serverspec:all'
class ServerspecTask < RSpec::Core::RakeTask
attr_accessor :target
def spec_command
cmd = super
"env TARGET_HOST=#{target} #{cmd}"
end
end
namespace :serverspec do
desc "Run serverspec to all hosts (=spec)"
task :all => hosts.keys.map {|host| 'serverspec:' + host }
hosts.keys.each do |host|
desc "Run serverspec to #{host}"
ServerspecTask.new(host.to_sym) do |t|
t.target = host
t.pattern = 'spec/{' + hosts[host][:roles].join(',') + '}/*_spec.rb'
end
end
end
hosts.ymlから取得した各ホスト名をTARGET_HOSTという環境変数に格納し、「spec/指定した役割のディレクトリ/*_spec.rb」のファイルを実行するようにタスクが定義されています。
Rakefileを上記のように変更した後、rakeタスクの一覧を確認すると以下のようになります。
$ rake -T rake serverspec:server-01 # Run serverspec to server-01 rake serverspec:server-02 # Run serverspec to server-02 rake serverspec:all # Run serverspec to all hosts (=spec) rake spec # Run serverspec to all hosts (=serverspec:all)
このRakefileの変更により、各ホストに対して実行すべきspecファイルが決定します。
spec_helper.rbの変更
次に、各テストコードの実行時にどのホストに対して実行すべきかをspec_helper.rbにて設定する必要があります。spec_helper.rbを以下のように変更することで、環境変数TARGET_HOSTに定義されたホストに対してSSH接続しテスト実行することが可能となります。
spec/spec_helper.rb
require 'serverspec'
require 'pathname'
require 'net/ssh'
include SpecInfra::Helper::Ssh
include SpecInfra::Helper::DetectOS
RSpec.configure do |c|
if ENV['ASK_SUDO_PASSWORD']
require 'highline/import'
c.sudo_password = ask("Enter sudo password: ") { |q| q.echo = false }
else
c.sudo_password = ENV['SUDO_PASSWORD']
end
c.host = ENV['TARGET_HOST']
options = Net::SSH::Config.for(c.host)
user = options[:user] || Etc.getlogin
c.ssh = Net::SSH.start(c.host, user, options)
c.os = backend.check_os
end
テスト実行例
spec_helper.rbを上記のように変更した後、テストを実行します。
$ rake serverspec:all ※hosts.ymlに書かれたすべてのホストに対してテスト実行する場合 $ rake serverspec:server-01 ※server-01に対してのみテスト実行する場合 $ rake serverspec:server-02 ※server-02に対してのみテスト実行する場合
こうすることで、各サーバに対してはRakefileで定義した役割に応じたテストのみが実行されるようになります。

