kakts-log

programming について調べたことを整理していきます

Serverspec でサーバの状態をテストする

chefやansibleを使ってサーバの環境構築が主流になってきていて、業務や個人で使っている人も多いと思います。

サーバ構築した後に、そのサーバの状態が正しく設定されているかを確認するとき、
手動でサーバにはいってプロセスの状態を見たりする事もできますが、 管理するサーバ数が多い場合 その作業が非常に大変になっていきます。
その問題を解決するために、サーバの状態を容易にテストできるRspecやServerspecというツールが出現してきて、サーバの状態テストが非常に用意になりました。

今回は、Serverspecというツールをつかってサーバの状態テストをする手順を解説していきます。

Serverspecとは

serverspec.org テスト対象のサーバの状態(特定のサービスが起動しているか、ポートが開いているか)など、サーバのあるべき状態を記述し、対象サーバの状態をテストする事ができます。
テストの記述はrubyで記述し、rubyを知っていればテストコードの追加と設定ファイルの記述を容易に行えます。

webサーバの状態をテストしてみる

前提
対象サーバへはSSH接続を行い、~/.ssh/config ファイルに以下の設定を記述している

Host wap
  HostName ${SERVER_IP_ADDRESS}
  User test_user
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile "~/.ssh/id_rsa"
  IdentitiesOnly yes
  LogLevel FATAL

対象サーバでtest_userというアカウントを作成し、sudoコマンドを実行できる状態

上記の前提条件の上で、ここでは特定のサーバに対して以下の状態になっているかを簡単にテストしてみます。

- httpdがインストールされている  
- httpd serviceが有効になっている  
- httpd プロセスが起動されている  
- 80番portが開いている  

実際にこの4つの状態は、最初にserverspecを初期化した状態でサンプルとして書かれているので、それを使ってみます。

Serverspecのインストール

ローカル環境(mac OS)において、Serverspecをインストールしていきます。
手順としては非常に簡単でServerspec本家のサイトにInstallationとしてまとまっているので、そのとおりに行えば大丈夫です。
serverspec.org

# gemでserverspecインストール
$ gem install serverspec

# serverspec-initコマンドが使えるようになっているのを確認
$ which serverspec-init
/Users/USERNAME/.rbenv/shims/serverspec-init

gemでserverspecをインストールすると、serverspec-initコマンドが使えるようになります。 serverspec-initコマンドを実行すると、テストしたいサーバの状態(OS, 接続方法, host名)を入力して自動でServerspecの設定ファイルを生成してくれます。
ここでは、外部で作成したcentOSサーバに対してsshでアクセスする際の設定を入力していきます

$ serverspec-init
Select OS type:

  1) UN*X
  2) Windows

Select number: 1        # UN*X環境を設定

Select a backend type:

  1) SSH
  2) Exec (local)

Select number: 1        # SSH接続なので、1

Vagrant instance y/n: n      #Vagrant instanceでないため、 n

Input target host name: wap         #  ~/.ssh/config ファイルに設定したhost名: wap を入力
 + spec/
 + spec/wap/
 + spec/wap/sample_spec.rb
 + spec/spec_helper.rb
 + Rakefile
 + .rspec

これを実行すると、実行したカレントディレクトリ配下にServerspec用のディレクトリ・ファイルが生成されます。

.
├── Rakefile                          
└── spec
    ├── wap
    │   └── sample_spec.rb   # wap サーバのテストを記述
    └── spec_helper.rb           # Serverspec実行時の設定ファイル  

Serverspec でのテストを実行する際には、 rakeコマンドを使ってサーバのテストファイルを呼び出し、テストを行います。 rake実行時の処理はRakefileにかかれており、デフォルトで ./spec配下のディレクトリをチェックし、*_spec.rb のrubyファイルを呼び出して
テストを実行するようになっています。
先程作られた自動生成ファイルは sample_spec.rbとなっていますが、 わかりやすく wap_spec.rbと変更しても動く様になっています。

Rakefile
require 'rake'
require 'rspec/core/rake_task'

task :spec    => 'spec:all'
task :default => :spec

namespace :spec do
  targets = []
  Dir.glob('./spec/*').each do |dir|
    next unless File.directory?(dir)
    target = File.basename(dir)
    target = "_#{target}" if target == "default"
    targets << target
  end

  task :all     => targets
  task :default => :all

  targets.each do |target|
    original_target = target == "_default" ? target[1..-1] : target
    desc "Run serverspec tests to #{original_target}"
    RSpec::Core::RakeTask.new(target.to_sym) do |t|
      ENV['TARGET_HOST'] = original_target
      t.pattern = "spec/#{original_target}/*_spec.rb"
    end
  end
end

そして、Serverspecでのテスト実行時のサーバ接続情報は下記のように設定してみます。
Serverspecでのテスト時に、サービスの起動確認など、内容によっては接続先サーバでのsudoコマンドを使ってテストを行うため、 対象ユーザのパスワードなどを入力させる処理を記述します。
初期生成時にある程度書かれていますが、ここでは微妙に変えています

spec_helper.rb
require 'serverspec'
require 'net/ssh'

set :backend, :ssh

# 対象サーバ内でのsudo passwordの設定処理
# 実行時に 環境変数に ASK_SUDO_PASSWORDが設定されている場合
# コマンドライン上でパスワードを入力させる
if ENV['ASK_SUDO_PASSWORD']
  begin
    require 'highline/import'
  rescue LoadError
    fail "highline is not available. Try installing it."
  end
  # コマンドライン上でパスワードを入力させる
  set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
else
  # ASK_SUDO_PASSWORD設定がオフの場合はここに設定したパスワードを利用する
  set :sudo_password, 'SERVER_USER_PASSWORD'
end
host = ENV['TARGET_HOST']

options = Net::SSH::Config.for(host)

# 接続先サーバのユーザ名
options[:user] ||= 'test_user'

set :host,        options[:host_name] || host
set :ssh_options, options
set :family,      'redhat'

これでサーバ接続時の設定を記述できました。 今回は1つのサーバに対してテストを行いますが、複数のサーバに対してテストを行う場合、設定に合わせてspec_helper.rbファイルの修正も必要になるかと思います。

続いて、対象サーバの状態テストを記述していきます。
./spec/wap/sample_spec.rb にテスト内容を記述していきます。
前述したように、対象サーバのhttpdに対して以下の状態になっていることをテストするための処理を記述します。

- httpdがインストールされている  
- httpd serviceが有効になっている  
- httpd プロセスが起動されている  
- 80番portが開いている  
sample_spec.rb
require 'spec_helper'

# httpdがインストールされている
describe package('httpd') do
  it { should be_installed }
end

# httpdのserviceの状態を記述する
describe service('httpd') do
  it { should be_enabled }     # serviceが有効になっている
  it { should be_running }      # serviceが起動されている
end

# 80番ポートが開いているかテストする
describe port(80) do
  it { should be_listening }
end

テスト実行

ここで、テストするサーバへの接続設定と、テスト内容の記述ができたので、実際にテストを実行してみます。
テスト実行は、rakeコマンドを実行することで可能です。

$ rake spec

rake実行時にカレントディレクトリのRakefileの内容を呼び出してテストを行っていきます。

まずは、対象サーバのhttpdがインストールされているが、サービスが無効・起動していない状態でテストを実行してみます。

$ rake spec

# 最初にsudo用のパスワードを入力する
Enter sudo password: 


# httpdがインストールされている テスト○
Package "httpd"
  should be installed

# 以下のテストが失敗している
Service "httpd"
  should be enabled (FAILED - 1)
  should be running (FAILED - 2)

Port "80"
  should be listening (FAILED - 3)

Failures:

  1) Service "httpd" should be enabled
     On host `wap'
     Failure/Error: it { should be_enabled }
       expected Service "httpd" to be enabled
       sudo -p 'Password: ' /bin/sh -c chkconfig\ --list\ httpd\ \|\ grep\ 3:on
       
     # ./spec/wap/sample_spec.rb:8:in `block (2 levels) in <top (required)>'

  2) Service "httpd" should be running
     On host `wap'
     Failure/Error: it { should be_running }
       expected Service "httpd" to be running
       sudo -p 'Password: ' /bin/sh -c service\ httpd\ status
       httpd is stopped

     # ./spec/wap/sample_spec.rb:9:in `block (2 levels) in <top (required)>'

  3) Port "80" should be listening
     On host `wap'
     Failure/Error: it { should be_listening }
       expected Port "80" to be listening
       sudo -p 'Password: ' /bin/sh -c netstat\ -tunl\ \|\ grep\ --\ :80\\\ 
       
     # ./spec/wap/sample_spec.rb:13:in `block (2 levels) in <top (required)>'

Finished in 0.85183 seconds (files took 13.41 seconds to load)
4 examples, 3 failures

Failed examples:

rspec ./spec/wap/sample_spec.rb:8 # Service "httpd" should be enabled
rspec ./spec/wap/sample_spec.rb:9 # Service "httpd" should be running
rspec ./spec/wap/sample_spec.rb:13 # Port "80" should be listening

テストを実行してみると httpdのパッケージはインストールされているが、httpdのserviceのテストと80番ポートのテストが失敗していることがわかります。
失敗時のログをみるとわかりますが、 サービスが有効かどうかのチェックは 実際にはchkconfigコマンドをつかっていて、 起動の確認はserviceコマンドを実行していることがわかります。 ポートの確認にはnetstatを実行していることがわかります。

ここで、対象サーバのhttpdをchkconfigコマンドを使って有効にし、httpdを起動してみます。

#テスト対象サーバでtest_userとしてログインする

# httpdを有効にする
$ sudo chkconfig httpd on

# httpdを起動する
$ sudo service httpd start
Starting httpd:                                            [  OK  ]

httpdを有効にし、起動した上で再度テストを実行するとすべて成功していることが確認できます。

$ rake spec

# 最初にsudo用のパスワードを入力する
Enter sudo password: 

Package "httpd"
  should be installed

Service "httpd"
  should be enabled
  should be running

Port "80"
  should be listening

Finished in 0.85282 seconds (files took 3.13 seconds to load)
4 examples, 0 failures

参考

Serverspecでサーバの構成をテストする 導入と個人的知見 - Qiita

https://www.ossnews.jp/oss_info/Serverspec

「Serverspec」を使ってサーバー環境を自動テストしよう - さくらのナレッジ

Serverspecでよく使うテストの書き方まとめ - Qiita

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス