Serverspecテストコード実例の紹介とコード記述の際のポイント
第1回では、Serverspecの概要とテストコードを書くまでの事前準備についてご紹介しました。第2回では、より具体的な環境を例として、実際に即したServerspecのテストコードの書き方をご紹介します。テストコードを記述する際のポイント等をまとめ、テストコードの記述をスムーズに実施できるようになることを目的として解説します。
LAMP構成のシステムのテスト
具体的なシステムとして、LAMP(Linux、Apache HTTP server、MySQL、PHP)構成の環境を想定し、この環境に対するテストコードの一例を紹介します。
LAMPの構成を採るシステムの例として、今回は統合監視ソフトウェアのZabbixを稼働させるための環境を取り上げます。LAMP環境のベースとなるLinuxは、CentOS 6.5を想定しています。
図1: テスト対象環境の概要図
稼働状況のテストとして、大まかに以下の4つの部分に分けて各部分の稼働状況に対するテストコードを見ていきます。
- OS(Linux)部分
- Webサーバ(Apache HTTP server)部分
- DB(MySQL)部分
- アプリケーション(PHP)部分
今回の例で紹介するServerspecのテストコードでは、次の表に掲載したResourceTypeを利用します。
表1: LAMP構成のテストに利用するリソースタイプResourceType
ResourceType種別 | 概要 | 利用可能なMatcherの例 |
---|---|---|
package | パッケージがインストールされているかのテストを実施。 rpmやdeb、rubyのgemパッケージに対応。バージョン確認も可能。 | be_installed等 |
service | サービスの稼働状況のテストを実施。 稼働状態や初期起動の設定状態の確認が可能。 | be_running、be_enabled等 |
file | 指定したファイルの存在確認や権限、所有者の確認、ファイルの中身の確認が可能。 | be_file、be_directory、be_owned_by、be_writable等 |
command | 任意のコマンドの実行結果の確認が可能。 | return_stdout、return_stderr、return_exit_status等 |
selinux | SELinuxの状態の確認が可能。 | be_disabled、be_enforcing、be_permissive |
iptables | iptablesの設定状態の確認が可能。 | have_rule |
host | ホストの名前解決が可能かどうかの確認やホストに到達可能かどうかの確認が可能。 | be_resolvable、be_reachable等 |
port | TCP、UDPのポートの稼働状態の確認が可能。 | be_listening |
php_config | PHPの設定状態の確認が可能。 | - |
【ポイント】
IPアドレス等、実行対象の環境毎に変わる値については変数として外に出し、テストコードの実行処理部分で変数展開するように書くことで、テストコードの使い回しが行いやすくなります。上記の例では、テストコード内で直接変数を定義してテストコード部分で展開していますが、設定情報だけを別ファイルとして管理することも可能です。その方法については第3回で紹介するTipsの中で解説します。
Linuxセキュリティ設定テスト
spec/server-01/security_spec.rb
require 'spec_helper' describe selinux do it { should be_enforcing } end describe command('getsebool httpd_can_network_connect') do it { should return_stdout /on$/ } end ### ポイント: 複数繰り返し処理する際のパラメータ定義 ### accept_ports = [{:protocol => 'tcp', :port => 22 }, {:protocol => 'tcp', :port => 80}, {:protocol => 'tcp', :port => 10051}] accept_ports.each do |p| # ポイント: 同じ処理をループ処理化 describe iptables do it { should have_rule("-A INPUT -p #{p[:protocol]} -m state --state NEW -m #{p[:protocol]} --dport #{p[:port]} -j ACCEPT") } end end
【ポイント】
同じSELinux関連の設定の確認であっても、Matcherとして対応していない部分(SELinuxのルール設定)の確認をしたいケースもあります。そういった場合には別途commandというResourceTypeを活用して、任意のコマンドの処理結果をテストする形の記述方法を採ることができます。また、よく利用するResourceTypeやMatcherは独自にカスタム定義して利用することも可能です。Serverspecのカスタマイズについては下記の記事を参照して下さい。
Tech-Sketch「serverspecを環境にあわせてカスタマイズ」
また、ServerspecのテストコードはRubyのコードとして記述します。そのため、iptablesの設定確認など異なるポート番号毎に同じような確認処理を繰り返し行う場合は、Rubyのループ文で回して記述できます。同じ内容の記述をできる限り減らし、メンテナンスしやすい形でテストコードを管理することをお勧めします。このようにRubyの基本的な書き方を知ることでServerspecのコードもより管理しやすくなります。
DNS設定テスト
spec/server-01/dns_spec.rb
require 'spec_helper' dns_server = 'dns-server01' ### ポイント: 名前解決に関する設定の確認 ### describe file('/etc/resolv.conf') do its(:content) { should match /^nameserver #{dns_server}/ } end describe file('/etc/nsswitch.conf') do its(:content) { should match /^hosts:\s*files\s+dns/ } end ### ポイント: 本当に名前解決できるかの確認 ### describe host('server-01') do it { should be_resolvable } end
【ポイント】
ServerspecにはfileというResourceTypeがあり、ファイルの中にどういった設定が行われているかのテストを書くことができます。設定としてどういった記述が行われているかをテストすることも必要ですが、その設定が正しく反映されてその通りに稼働しているかをテストすることも重要です。そのため、ここではDNSサーバの設定ファイルの中身の確認だけでなく、実際に名前解決ができるかどうかを確認しています。
Webサーバ(Apache HTTP server)部分のテストコード例
ZabbixのWebGUIを動かすために、Webサーバ部分の正常な稼働状態としては例えば以下のような状態をテストします。
正常な稼働状態
- Apacheのパッケージが導入されている
- Apacheのサービスが正常に稼働している
- ApacheのサービスがTCPの80番ポートでListenしている
- ApacheのプロセスがPHPのモジュールを読み込んでいる
- Apacheの設定ファイルにてZabbixのWebGUI用のPHPファイルを参照できる設定がされている
テストコード
spec/server-01/httpd_spec.rb
require 'spec_helper' ### ポイント: OSの種類毎に異なる処理の分岐 ### if os[:family] == 'RedHat' describe package('httpd') do it { should be_installed } end describe service('httpd') do it { should be_enabled } it { should be_running } end describe file('/etc/httpd/conf/httpd.conf') do it { should be_file } its(:content) { should match /Include conf.d\/\*\.conf/ } end describe file('/etc/httpd/conf.d/zabbix.conf') do it { should be_file } its(:content) { should match /Alias \/zabbix \/usr\/share\/zabbix/ } end elsif ['Debian', 'Ubuntu'].include?(os[:family]) describe package('apache2') do it { should be_installed } end describe service('apache2') do it { should be_enabled } it { should be_running } end describe file('/etc/apache2/apache2.conf') do it { should be_file } its(:content) { should match /IncludeOptional conf-enabled \/\*\.conf/ } end describe file('/etc/apache2/conf-enabled/zabbix.conf') do it { should be_linked_to '/etc/apache2/conf-available/zabbix.conf' } its(:content) { should match /Alias \/zabbix \/usr\/share\/zabbix/ } end end describe port(80) do it { should be_listening } end describe command('apachectl -M |grep php5_module') do it { should return_stdout /php5_module/ } end
【ポイント】
上記テストコードの例ではApacheのパッケージの導入確認部分において、OSの種別毎に分岐処理を行っています。第1回の記事でも紹介した通り、Serverspecではある程度OSの違いを吸収してテスト処理が実行できるようになっていますが、OS毎にパッケージ名が異なっていたり、特定のOSでのみ実施したい処理がある場合などは、テスト実行対象サーバのOS情報をもとに条件分岐させて処理を記述することが必要となります。
DB(MySQL)部分のテストコード例
Zabbixの監視結果や監視設定をDBに格納し、正常に監視が実施できるために必要なDBの稼働状態として、以下のような状態をテストします。
正常な稼働状態
- MySQLのパッケージが導入されている
- MySQLのサービスが正常に稼働している
- MySQLのサービスがローカルのネットワークに対してのみTCPの3306番ポートでListenしている
- MySQLにZabbix用のデータベース、テーブルが作成されている
- MySQLが正しいパラメータで稼働している
テストコード
spec/server-01/mysql_spec.rb
require 'spec_helper' describe package('mysql-server') do it { should be_installed } end describe service('mysqld') do it { should be_enabled } it { should be_running } end ### ポイント: ポートが特定のIPアドレスでListenしているか確認(2.0からの新機能) ### describe port(3306) do it { should be_listening.on('127.0.0.1').with('tcp') } end db_user = "zabbix" db_password = "zabbixpassword" describe command("mysqlshow -u#{db_user} -p#{db_password}") do it { should return_stdout /zabbix/ } end describe command("mysqlshow -u#{db_user} -p#{db_password} zabbix") do it { should return_stdout /hosts/ } end describe command("mysqladmin -u#{db_user} -p#{db_password} variables |grep character_set_server") do it { should return_stdout /utf8/ } end
※上記のテストコードには一部Serverspec2.0系からの新機能を利用した部分があります。1系で利用する場合には該当箇所を変更して利用してください。
【ポイント】
セキュリティ面を考慮すると、ただ単に、どのポートでListenしているかの確認だけでなく、より細かくどのIPアドレスでListenしているのかまで確認すべきケースもあります。2014/8/8時点ではまだβ版ではありますが、今後公開される予定のServerspec 2.0系ではportのMatcherがさらに拡張され、上記のテストコードの例のようにListenIP、Listenポート両方を組み合わせたテストが実行可能となります。Serverspecを利用する際にはバージョンに注意し、そのバージョンでどういったResourceTypeやMatcherに対応しているのかを確認することも重要です。
アプリケーション(PHP)部分のテストコード例
ZabbixのWebGUIを正常に稼働させるためは、以下のようなPHPの設定が必要となります。
正常な稼働状態
- PHPの設定値(memory_limitやdate.timezone等)が正しく設定されていること
テストコード
require 'spec_helper' php_values = [{'max_execution_time' => 300}, {'memory_limit' => '128M'}, {'post_max_size' => '16M'}, {'upload_max_filesize' => '2M'}, {'max_input_time' => 300}, {'date.timezone' => 'Asia/Tokyo'}] describe 'PHP config parameters' do php_values.each do |php_value| context php_config(php_value.keys.first) do its(:value) { should eq php_value[php_value.keys.first] } end end end
【ポイント】
php_configでチェックできる項目は、php.iniやphp.d/*.iniのファイルで定義されたパラメータ情報のみです。Apacheの設定ファイル中で設定されたphp_valueの値の確認はできません。こちらの設定項目もチェックしたい場合には、Apache設定ファイルの中身を確認するなど工夫が必要です。また、php_configのテストは実際には以下のようなコマンドをサーバ内部で実行しています。
sudo php -r 'echo get_cfg_var( "max_execution_time" );'
max_execution_timeなどの項目は内部から実行する場合、必ず無制限を示す0を返す仕様となっているため、実際にブラウザからアクセスした時に適用される設定値と異なる値がテスト結果として報告されることがあります。Serverspecはあくまでサーバ内部から見た時のサーバの状態を確認するツールとなっているため、その点は注意が必要です。
まとめ
第2回では、より具体的なシステム例を題材としてServerspecのテストの記述の方法や記述時の要注意ポイントを紹介しました。Serverspecのコードを記述する際には、そのテストコードを書くことで内部的にどういった処理が走るのかを正しく把握しておくことが重要です。また、Serverspecのテストコードもアプリケーションのコードと同じく、より可読性を高めてシンプルに記述することで長期に渡った運用に活用できるものとなります。
次回は、Serverspecのテストコードをより効果的に書くためのTipsをいくつか紹介します。
この記事の著者
池田 大輔
Twitter : @ike_dai
TIS株式会社戦略技術センター所属。社内向けシステムの保守運用業務を経験後、クラウド時代の効率的な統合運用管理をテーマに活動中。特に、OSSを駆使した運用のエコシステム実現を目指し、Zabbix,fluentd,Serverspec,Ansibleなどの導入や検証に取り組む。技術検証成果などを技術ブログ『Tech-Sketch』にて発信中。著書:『Zabbix統合監視徹底活用 - 複雑化・大規模化するインフラの一元管理』