Chefで書いたレシピをテストする(serverspec)

こんにちは。小宮です。

今回で最終回です。
前回までにご紹介したレシピのテストするところをご紹介します。Serverspecを用います。

なぜServerspecがいいかというのは以下リンクにも書いてますが、以下の点がよいと思います。
・Chefのテストツールでなく外部のツールなので依存関係がない(puppetでも使える)
・設計思想がシンプル簡単に使えるものということで、手間があんまりなくて簡単につかえた

参考:
Serverspec at hbstudy #45
入門Chef Solo落ち穂拾い
kayac/newbie-training
「入門Puppet」
resource_typeのマニュアル
advanced_tips
ncstudy#05 ハンズオン資料
parallel_tests

・セットアップ
バージョン0.3と0.6だとテストの書き方が若干違う感じだったので、マニュアルに沿ってる新しいほうを推奨します。
[shell]# yum install rubygems

gem install serverspec rake

serverspec-init

Select a backend type:

  1) SSH
  2) Exec (local)

Select number: 1

Vagrant instance y/n: y
Input vagrant instance name: 10.0.0.241
 + spec/10.0.0.241/
 + spec/10.0.0.241/httpd_spec.rb[/shell]
<br>
コマンドの実行が終わると、上記のようにいくつかのファイルが作成されます。<br>

[shell]# serverspec-init ~略~ Input target host name: 10.0.0.240 + spec/10.0.0.240/ + spec/10.0.0.240/httpd_spec.rb[/shell]
SSHを指定し、ターゲットホストを入力すると、ホスト毎のディレクトリが作成されました。

テストを書いていきます。

[shell]# vi spec/10.0.0.240/httpd_spec.rb require ‘spec_helper’

describe 'httpd' do
  it { should be_installed }
  it { should be_enabled   }
  it { should be_running   }
end

describe 'port 80' do
  it { should be_listening }
end

describe '/etc/httpd/conf/httpd.conf' do
  it { should be_file }
  it { should contain &quot;ServerName localhost:80&quot; }
end[/shell]
<br>
DetectOSとなってるのをRedHatに変更(CentOSなので)<br>

[shell]# vi spec/spec_helper.rb include Serverspec::Helper::RedHat[/shell]
sshのconfigを修正して接続情報を渡せるようにします
[shell]# vi .ssh/config Host 10.0.0.240 HostName 10.0.0.240 User root Port 22 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile “/root/.ssh/komi-test.pem” IdentitiesOnly yes LogLevel FATAL Host 10.0.0.241 HostName 10.0.0.241 User root Port 22 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile “/root/.ssh/komi-test.pem” IdentitiesOnly yes[/shell]
db側のテストファイル名を修正して内容も実体に合わせて修正します
[shell]# mv spec/10.0.0.241/httpd_spec.rb spec/10.0.0.241/mysqld_spec.rb

vi spec/10.0.0.241/mysqld_spec.rb

require 'spec_helper'

describe 'mysql-server' do
  it { should be_installed }
end

describe 'mysqld' do
  it { should be_enabled   }
  it { should be_running   }
end

describe 'port 3306' do
  it { should be_listening }
end

describe '/etc/my.cnf' do
  it { should be_file }
  it { should contain &quot;server-id = 103&quot; }
end[/shell]
<br>
ひとまず書いたとおりのテストがとおるか確認します<br>

[shell]# rake spec (in /root) /root/.rbenv/versions/1.9.2-p290/bin/ruby -S rspec spec/10.0.0.240/httpd_spec.rb spec/10.0.0.241/mysqld_spec.rb …………

Finished in 0.74866 seconds
12 examples, 0 failures[/shell]
<br>
書いた分は上手くいった模様。<br>
エラーのときは以下のようにダメだったコマンドとその戻りが出力されるので大変わかりやすいです。<br>

[shell]Failures:

  1) mysql-server
     Failure/Error: it { should be_enabled   }
       chkconfig --list mysql-server | grep 3:on
       error reading information on service mysql-server: No such file or directory

  2) mysql-server
     Failure/Error: it { should be_running   }
       ps aux | grep -w -- mysql-server | grep -qv grep[/shell]
<br>
→これらはchkconfigとpsコマンドでmysql-serverとかないと言われてるので、should be_installedとenabledとrunnningのブロックを分けて修正します。<br>
<br>
<br>
つづいて必要なテストを全て追加しテストしていきます<br>
レシピとテストをセットで使いまわすために、テストファイルもレシピと同じように分割して記述したほうがいいように考えられるので、<br>

    とりあえずクックブック単位にテストファイルを分けることにします。
たぶんホストディレクトリ毎に適当にレシピに対応した名前のテストファイルを作ってけば大丈夫です。
base_settingのレシピのテストは重複するのでロール毎に管理したいとかありますが今後の課題となります。

[shell]# vi spec/10.0.0.240/base_spec.rb require ‘spec_helper’

# バックアップディレクトリ作成
describe file('/etc/.backup') do
  it { should be_directory }
end

describe file('/etc/hosts') do
  it { should contain '10.0.0.240       xxx-web03' }
end

# デフォルトゲートウェイの設定
describe default_gateway do
  its(:ipaddress) { should eq '10.0.0.93' }
  its(:interface) { should eq 'eth0' }
end

# selinuxがdisableであること
describe selinux do
  it { should be_disabled }
end

# yum.confでkernelをupdate除外
describe file('/etc/yum.conf') do
  it { should contain 'exclude=kernel*' }
end

# modprobe.confでipv6を無効化
describe file('/etc/modprobe.conf') do
  it { should contain 'options ipv6 disable=1' }
end

# 必要なパッケージが入っていること
%w{ sendmail ntp }.each do |pkg|
  describe package(&quot;#{pkg}&quot;) do
    it { should be_installed }
  end
end

# 不要サービス停止
%w{ ip6tables iptables messagebus kudzu }.each do |services|
  describe service(&quot;services&quot;) do
    it { should_not be_enabled }
    it { should_not be_running }
  end
end

# logの切りまわし設定
describe file('/etc/logrotate.d/syslog') do
  it { should contain 'compress' }
  it { should contain 'rotate 53' }
end

# 不要cronのパーミッションが0であること
%w{ makewhatis.cron mlocate.cron prelink }.each do |files|
  describe file(&quot;/etc/cron.daily/#{files}&quot;) do
    it { should be_mode 0 }
  end
end

describe file('/etc/cron.weekly/makewhatis.cron') do
  it { should be_mode 0 }
end

# cron for ntpdate
describe cron do
  it { should have_entry '0 * * * * /usr/sbin/ntpdate -bs 10.0.0.93' }
end

# timezone
#describe file('/etc/localtime') do
#  it { should be_linked_to '/usr/share/zoneinfo/Japan' }
#end

# kernelparams for webserver
describe 'Linux kernel parameters' do
  context linux_kernel_parameter('net.ipv4.tcp_syncookies') do
    its(:value) { should eq 1 }
  end

  context linux_kernel_parameter('vm.swappiness') do
    its(:value) { should eq 30 }
  end

  context linux_kernel_parameter('net.ipv4.tcp_tw_reuse') do
    its(:value) { should eq 0 }
  end

  context linux_kernel_parameter('net.ipv4.tcp_tw_recycle') do
    its(:value) { should eq 0 }
  end

  context linux_kernel_parameter('net.ipv4.tcp_fin_timeout') do
    its(:value) { should eq 60 }
  end

  context linux_kernel_parameter('net.ipv4.tcp_max_syn_backlog') do
    its(:value) { should eq 4096 }
  end

  context linux_kernel_parameter('net.core.somaxconn') do
    its(:value) { should eq 4096 }
  end
end

# login users のテスト
%w{ xxx-op yyy-op dev }.each do |u|
  describe user(&quot;#{u}&quot;) do
    it { should exist }
    it { should belong_to_group 'wheel' }
  end
end

rake spec

~略~
Finished in 3.13 seconds
40 examples, 0 failures[/shell]
<br>
一応、ここまで動きました。<br>
Serverspec自体を0.6にupgradeしないと一部のlinux_kernel_paramaterだとかcronなどはそんなメソッドはないとかで動きませんでした。<br>
<br>

[shell]# cp -p spec/10.0.0.240/base_spec.rb spec/10.0.0.241/

vi spec/10.0.0.241/base_spec.rb

diff spec/10.0.0.240/base_spec.rb spec/10.0.0.241/base_spec.rb

9c9
&lt;   it { should contain '10.0.0.240     xxx-web03' }
---
&gt;   it { should contain '10.0.0.241     xxx-db03' }
75c75
&lt; # kernelparams for webserver
---
&gt; # kernelparams for dbserver
82c82
&lt;     its(:value) { should eq 30 }
---
&gt;     its(:value) { should eq 0 }
86c86
&lt;     its(:value) { should eq 0 }
---
&gt;     its(:value) { should eq 1 }
90c90
&lt;     its(:value) { should eq 0 }
---
&gt;     its(:value) { should eq 1 }
94c94
&lt;     its(:value) { should eq 60 }
---
&gt;     its(:value) { should eq 10 }
98c98
&lt;     its(:value) { should eq 4096 }
---
&gt;     its(:value) { should eq 8192 }
102c102
&lt;     its(:value) { should eq 4096 }
---
&gt;     its(:value) { should eq 8192 }[/shell]
<br>

・httpdのクックブックのテストを少し書きくわえた
[shell]require ‘spec_helper’

%w{ httpd php php-pecl-ssh2 php-mysql php-common php-devel php-pear php-pdo php-mbstring php-pecl-apc php-mcrypt php-cli mysql-libs }.each do |pkg|
  describe package(&quot;#{pkg}&quot;) do
    it { should be_installed }
  end
end

describe service('httpd') do
  it { should be_enabled   }
  it { should be_running   }
end

describe port(80) do
  it { should be_listening }
end

describe file('/etc/httpd/conf/httpd.conf') do
  it { should be_file }
  it { should contain &quot;ServerName localhost:80&quot; }
end

# basic auth test
describe file('/etc/httpd/conf.d/basic_auth.conf') do
  it { should be_file }
  it { should contain &quot;Require valid-user&quot; }
end

describe file('/etc/httpd/conf/.htpasswd') do
  it { should be_file }
  it { should contain &quot;dev&quot; }
end

# wp contents exists test
describe file('/var/www/html/wp-config.php') do
  it { should be_file }
end

## mount fuse test
#describe file('/var/www/html/assets') do
#  it { should be_mounted.with(:type =&gt; 'fuse') }
#  it { should be_mounted.with(:options =&gt; { :rw =&gt; true } ) }
#end[/shell]
<br>

・mysqldのテストを少し追加
[shell]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

describe port(3306) do
  it { should be_listening }
end

describe file('/etc/my.cnf') do
  it { should be_file }
  it { should contain &quot;server-id = 103&quot; }
end

describe file('/etc/logrotate.d/mysqld') do
  it { should be_file }
  it { should contain &quot;/usr/bin/mysqladmin flush-logs&quot; }
end

describe file('/opt/bin/mysql-back.sh') do
  it { should be_file }
  it { should be_executable }
end[/shell]
<br>
・muninのテストを書く<br>

[shell]# vi spec/10.0.0.240/munin_spec.rb require ‘spec_helper’

%w{ munin-node perl-DBI }.each do |pkg|
  describe package(&quot;#{pkg}&quot;) do
    it { should be_installed }
  end
end

describe service('munin-node') do
  it { should be_enabled }
  it { should be_running }
end

describe port(4949) do
  it { should be_listening }
end

# munin plugins
%w{ cpu memory df load tcp iostat if_eth0 if_err_eth0 }.each do |plg|
  describe file(&quot;/etc/munin/plugins/#{plg}&quot;) do
    it { should be_file }
  end
end

# munin plugins for httpd
%w{ apache_accesses apache_processes }.each do |plg|
  describe file(&quot;/etc/munin/plugins/#{plg}&quot;) do
    it { should be_file }
  end
end

## munin plugins for mysql
#%w{ mysql_slowqueries mysql_queries mysql_threads }.each do |plg|
#  describe file(&quot;/etc/munin/plugins/#{plg}&quot;) do
#    it { should be_file }
#  end
#end

cp -p spec/10.0.0.240/munin_spec.rb spec/10.0.0.241/munin_spec.rb

vi spec/10.0.0.241/munin_spec.rb

diff spec/10.0.0.240/munin_spec.rb spec/10.0.0.241/munin_spec.rb

26,33c26
&lt; %w{ apache_accesses apache_processes }.each do |plg|
&lt;   describe file(&quot;/etc/munin/plugins/#{plg}&quot;) do
&lt;     it { should be_file }
&lt;   end
&lt; end
&lt;
&lt; ## munin plugins for mysql
&lt; #%w{ mysql_slowqueries mysql_queries mysql_threads }.each do |plg|
---
&gt; #%w{ apache_accesses apache_processes }.each do |plg|
38a32,38
&gt; # munin plugins for mysql
&gt; %w{ mysql_slowqueries mysql_queries mysql_threads }.each do |plg|
&gt;   describe file(&quot;/etc/munin/plugins/#{plg}&quot;) do
&gt;     it { should be_file }
&gt;   end
&gt; end
&gt;[/shell]
<br>
・zabbixのテストを書く<br>

[shell]# vi spec/10.0.0.240/zabbix_spec.rb require ‘spec_helper’

%w{ zabbix-agent zabbix zabbix-jp-release }.each do |pkg|
  describe package(&quot;#{pkg}&quot;) do
    it { should be_installed }
  end
end

describe service('zabbix-agent') do
  it { should be_enabled }
  it { should be_running }
end

describe port(10050) do
  it { should be_listening }
end

describe file('/opt/bin/mem_monitor.sh') do
  it { should be_executable }
end

cp -p spec/10.0.0.240/zabbix_spec.rb spec/10.0.0.241/zabbix_spec.rb

rake spec

~略~
Finished in 5.09 seconds
152 examples, 0 failures[/shell]
<br>
問題なくテストとおりました。<br>
テストそれだけでいいのかというのは疑問ののこるところではあります。<br>

ユーザとかreplicationとかのテストはかいてないがcommandリソースタイプで標準出力をチェックするしかなさそう。
コマンドがこれ↓で、
mysql -u root -pcat /path_to_file -s -e “show grants for repl@‘10.0.0.%’;”
戻り値がこれ↓というテストを書くくらいしか思いつかない
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON . TO ‘repl’@‘10.0.0.%’ IDENTIFIED BY PASSWORD ‘*43E209EED080057E35C2630AC06D3296*****’
複雑なやつは戻り値がシンプルなチェックコマンド作っておく感じになるんでしょうか。。

こんな情報も
・rake spec SPEC_OPTS="–format html"でHTML出力ができる。エラーでないとOKしか帰ってこないのでレポートがほしい場合等に視認性がいい感じです。
・gem install ci_reporterでRakefileにrequire ‘ci/reporter/rake/rspec’追記で
 JUnit 形式の XML (Jenkins で利用可能) へ変換できる
parallel_testsつかうと並列実行できて早い。台数多いときよさそうです。
・ロールやattribute的なものを使いたい場合は以下のサイトが参考になりそうです。
 advanced_tips
 serverspec でホスト固有の属性値を扱う方法
 Serverspecでchefのjsonを読み込む
 serverspecでサーバ環境のテストを書いてみよう

長編Chefシリーズはこれでおしまいです。
では長々見ていただいてありがとうございました。

バックナンバーはこちら↓
 Chef-SoloとVagrantの導入(VPC環境)
 Chef-Soloでレシピを書く前の環境について(Vagrantfile,role,node,data_bags)
 Chefで既存手順のレシピを書く1(初期設定)
 Chefで既存手順のレシピを書く2(ユーザ作成)
 Chefで既存手順のレシピを書く3(WEBサーバ)
 Chefで既存手順のレシピを書く4(DBサーバ)
 Chefで既存手順のレシピを書く5(munin、zabbix)
 Chefで書いたレシピをテストする(serverspec)