Serverspecを複数のサーバに対して実行する
以下の記事で、Serverspecの実行コンテナを作成した。 tk-ch.hatenablog.com
今回、複数のサーバに対してServerspecでテストを実行したい。
しかし、初期状態のServerspecの設定だと、以下の問題がある。
- specディレクトリ配下に、1サーバ毎にディレクトリを用意し、各ディレクトリ内に実行するテストを記載したspecファイルを配置しておく必要がある。
同じ内容のテストを複数のサーバに実行する場合、同じファイルが格納されたディレクトリが複数存在することになり、作成や維持が面倒。
→Serverspecの設定、ディレクトリ構成を変更してこれら問題を解消してみる。
ちなみに本記事で使ったプレイブックはここにある。
環境
- DockerホストのOS:RockyLinux8.6
- 管理対象サーバ(Serverspecの実行先)のOS:RockyLinux8.6
- Serverspec:2.42.2
実施内容
以下を参考に、ディレクトリ構成とファイルの中身を変更した。
- 公式ドキュメントのTipsの「How to share Serverspec tests among hosts」と「How to use host specific properties」
- Serverspecの効果的活用に向けたTips
- 【Serverspec】【yaml】変数を使用してテストコードを使いまわす
変更点は以下の通り。
- specファイルのディレクトリをサーバ単位からロール(共通、DBサーバ、WEBサーバなど)単位に変更。
- 実行対象のサーバとロールについては「hosts.yml」というファイルに記載して、Rakefileをこのファイルを読み込んで処理するように変更。
- サーバやロールごとに値が異なるテスト内容については、変数に置き換えることでspecファイルに記載するテストコードは共通化する。
変数はpropertyという名前の変数を使って参照する。 - サーバごとに異なる変数は「hosts.yml」、ロールごとに異なる変数は「properties.yml」というファイルに記載する。
spec_helper.rbに、これらファイルを読み込んで変数として使えるように設定する処理を追加。
変更前のディレクトリ構成と
serverspec-initコマンドで作成したままのディレクトリ構成。
以下のようになっている。
# tree -a --charset=c /root/serverspec /root/serverspec |-- .rspec |-- Rakefile `-- spec |-- 192.168.10.123 | `-- sample_spec.rb `-- spec_helper.rb 2 directories, 4 files
変更後のディレクトリ構成とファイル
以下のように変更した。
# tree -a --charset=c /root/serverspec /root/serverspec |-- .rspec |-- Rakefile |-- hosts.yml |-- properties.yml `-- spec |-- base | |-- dns_spec.rb | `-- sshd_spec.rb |-- db | `-- postgres_spec.rb `-- spec_helper.rb 3 directories, 8 files
各ファイルの中身は以下の通り。
Rakefile
require 'rake' require 'rspec/core/rake_task' require 'yaml' # YAMLファイルを処理するために必要 # 実行対象が記載されたYAMLファイルを読み込む hosts = YAML.load_file('hosts.yml') desc "Run serverspec to all hosts (=spec:all)" task :spec => 'spec:all' namespace :spec do desc "Run serverspec to all hosts (=spec)" task :all => hosts.keys.map {|host| 'spec:' + host } hosts.keys.each do |host| desc "Run serverspec to #{host}" RSpec::Core::RakeTask.new(host.to_sym) do |t| ENV['TARGET_HOST'] = host role = hosts[host][:roles].join(',') puts "#####################################################" puts " Target host : #{ENV['TARGET_HOST']}" puts " Role : #{role}" puts "#####################################################" t.pattern = 'spec/{' + hosts[host][:roles].join(',') + '}/*_spec.rb' end end end
spec_helper.rb
require 'serverspec' require 'net/ssh' require 'highline/import' require 'yaml' # YAMLファイルを処理するために必要 # サーバごとの変数が記載されたYAMLファイルを読み込む host_properties = YAML.load_file('hosts.yml') # ロールごとの変数が記載されたYAMLファイルを読み込む common_properties = YAML.load_file('properties.yml') set :backend, :ssh 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 set :sudo_password, ENV['SUDO_PASSWORD'] end host = ENV['TARGET_HOST'] # 対象サーバの変数と、実行されるロールの変数をマージして、Property情報に格納 properties = host_properties[host] properties[:roles].each do |r| properties = common_properties[r].merge(host_properties[host]) if common_properties[r] end set_property properties options = Net::SSH::Config.for(host) options[:user] ||= Etc.getlogin if ENV['ASK_LOGIN_PASSWORD'] options[:password] = ask("\nEnter login password: ") { |q| q.echo = false } else options[:password] = ENV['LOGIN_PASSWORD'] end set :host, options[:host_name] || host set :ssh_options, options
hosts.yml
192.168.10.123: :roles: - base 192.168.10.124: :roles: - base - db :db_user: root
properties.yml
base: :dns_server: 10.0.0.2
sshd_spec.rb
require 'spec_helper' describe service('sshd'), :if => os[:family] == 'redhat' do it { should be_running } end
dns_spec.rb
require 'spec_helper' describe file('/etc/resolv.conf') do its(:content) { should match /^nameserver #{property[:dns_server]}/ } end
postgres_spec.rb
require 'spec_helper' describe user("#{property[:db_user]}") do it { should exist } end
実行してみる
まず、定義されたタスクを表示する。
全サーバを対象でも、サーバ単位でも実行できることが分かる。
# rake -T rake spec # Run serverspec to all hosts (=spec:all) rake spec:192.168.10.123 # Run serverspec to 192.168.10.123 rake spec:192.168.10.124 # Run serverspec to 192.168.10.124 rake spec:all # Run serverspec to all hosts (=spec)
全サーバを対象にServerspecを実行する。
# LOGIN_PASSWORD=実行対象サーバへSSHする際の認証パスワード # rake spec LOGIN_PASSWORD=$LOGIN_PASSWORD ##################################################### Target host : 192.168.10.123 Role : base ##################################################### /usr/bin/ruby -I/usr/local/share/gems/gems/rspec-support-3.12.0/lib:/usr/local/share/gems/gems/rspec-core-3.12.1/lib /usr/local/share/gems/gems/rspec-core-3.12.1/exe/rspec --pattern spec/\{base\}/\*_spec.rb /usr/local/share/gems/gems/specinfra-2.85.0/lib/specinfra/backend/ssh.rb:82:in `create_ssh': Passing nil, or [nil] to Net::SSH.start is deprecated for keys: user File "/etc/resolv.conf" content is expected to match /^nameserver 10.0.0.2/ Service "sshd" is expected to be running Finished in 0.16813 seconds (files took 2.38 seconds to load) 2 examples, 0 failures ##################################################### Target host : 192.168.10.124 Role : base,db ##################################################### /usr/bin/ruby -I/usr/local/share/gems/gems/rspec-support-3.12.0/lib:/usr/local/share/gems/gems/rspec-core-3.12.1/lib /usr/local/share/gems/gems/rspec-core-3.12.1/exe/rspec --pattern spec/\{base,db\}/\*_spec.rb /usr/local/share/gems/gems/specinfra-2.85.0/lib/specinfra/backend/ssh.rb:82:in `create_ssh': Passing nil, or [nil] to Net::SSH.start is deprecated for keys: user File "/etc/resolv.conf" content is expected to match /^nameserver 10.0.0.2/ Service "sshd" is expected to be running User "root" is expected to exist Finished in 0.28116 seconds (files took 2.42 seconds to load) 3 examples, 0 failures
→以下のことが確認できる。
- host.ymlに定義した通り、192.168.10.123にはbaseロール、192.168.10.124にはbaseロールとdbロールのテストが実行されている。
- hosts.ymlとproperties.ymlに定義した変数(DNSサーバのIPと、DB接続用ユーザ)がテスト実行の際に展開、使用されている。
まとめ
- 複数サーバを対象に、管理しやすい形でServerspecを実行できた。
- RakeFileやspec_helper.rbを工夫することで、柔軟にServerspecを使用することができる。
Serverspecというより、Rubyの知識が必要。 - Serverspecの対象サーバとロールをAnsibleのファイルから読み込めるansible_specというツールがあるらしい。
次はこれを試してみたい。