こんにちは! BFT名古屋支店の猫です。
新卒インフラ女子の備忘録 part2を書いていきたいと思います。
今回もインフラ業界でも流行りの "自動化"技術 の一つである、Serverspecについてです。
はじめに
Serverspecでは、hosts.ymlにテスト対象のIPアドレスと実行するテストコードが格納されたディレクトリ名(ロールと呼ばれます) を書くことで、
- 一回の実行で
- 複数のサーバに対して
- 同じテスト
を行うことができます。つまり、テストコードを"使いまわせる"わけです。
これによりコードの煩雑化・冗長化を防ぐことができます。
イメージはこんな感じです↓
個別の値はどうするの?
ここで、
『IPアドレスとか、個別の値をテストしたいときはどうするの?使い回せないんじゃない?』
という疑問が浮かぶと思います。
それを解決するのが、「テストコードに変数を使用し、hostsで値を定義する」という方法です。
これにより、
- 同じテストコードを使って
- テスト対象ごとに個別の値をテストする
ことができます。
イメージはこんな感じです↓
やってみよう
どのようにコードを書けばよいのか、簡単な例でご説明していきます。
今回の対象はLinuxサーバ2台(webサーバとdbサーバ)です。
- websv → commonロール & websvロール
- dbsv → commonロール & dbsvロール
のテストを行う想定となっています。commonロールは使いまわしです。
ディレクトリ構成
今回のディレクトリ構成はこんな感じです↓
コード
では順番にコードを見ていきましょう!
- Rakefile
- hosts.yml
- spec_helper.rb
- テストコード3つ
の順にご説明します。
①Rakefile
まずはRakefileです。ここではhostsの中身をpropertiesという変数に格納します。
yamlファイルを読み込むためには、require 'yaml'
が必要ですので、忘れないようにしましょう。
require 'rake' require 'rspec/core/rake_task' require 'yaml'#propertiesにhosts.ymlの中身を格納(ハッシュ&配列) properties = YAML.load_file('/etc/server/spec//hosts.yml')
desc 'Run serverspec to all hosts' task :spec => 'serverspec:all' task :default => :spec
namespace :serverspec do task :all => properties.keys.map {|key| 'serverspec:' + key.split('.')[0] } properties.keys.each do |key| desc "Run serverspec to #{key}" RSpec::Core::RakeTask.new(key.split('.')[0].to_sym) do |t| ENV['TARGET_HOST'] = key puts "------------------------------------------------------------------------------------------" puts " #{key} START " puts "------------------------------------------------------------------------------------------" t.pattern = 'spec/{' + properties[key][:roles].join(',') + '}/*_spec.rb' t.rspec_opts = "--color --format d" end end end
②hosts.yml
続いてhosts.ymlです。対象ホストのIPアドレスの他にロールと変数、変数に格納する値を記載します。
192.168.XXX.YYY: :roles: - common - websv :host_vars: - :hostname: wanwan_server - :ipaddress: 192.168.XXX.YYY - :php_version: 7.2.11192.168.XXX.ZZZ: :roles: - common - dbsv :host_vars: - :hostname: nyannyan_server - :ipaddress: 192.168.XXX.ZZZ - :mariadb_version: 10.3.17
③spec_helper.rb
spec_helper.rbはこのように書きます。
properties = YAML.load_file('/etc/server/spec//hosts.yml')
がRakefileとかぶっているのがちょっとダサめですが見なかったことにしましょう。
(今回の趣旨からは外れますが) セキュリティのため、パスワードはべた書きせず環境変数を利用しています。
require 'serverspec' require 'net/ssh' require 'yaml'set :backend, :ssh
#propertiesにhosts.ymlの中身を格納(ハッシュ&配列) properties = YAML.load_file('/etc/server/spec//hosts.yml')
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
#TARGETHOSTはRakefileがテスト対象を格納する環境変数 host = ENV['TARGET_HOST']
#propatyに対象ホストの分のハッシュを格納 set_property properties[host]
options = Net::SSH::Config.for(host)
options[:user] ||= Etc.getlogin
set :host, options[:host_name] || host
if ENV['ASK_LOGIN_PASSWORD'] options[:password] = ask("\nEnter login password: ") { |q| q.echo = false } else options[:password] = ENV['LOGIN_PASSWORD'] end
set :ssh_options, options
④テストコード
最後にテストコードたちです。
変数は#{property[:host_vars][:ipaddress]}
とも書けるのですが
#{}
の中身が長くなるのが嫌で、最初にvarという変数に入れ直しています。
require 'spec_helper' require 'yaml'var = property[:host_vars][0]
#IPaddress describe host("#{var[:hostname]}") do its(:ipv4_address) {should eq "#{var[:ipaddress]}" } end
require 'spec_helper' require 'yaml'var = property[:host_vars][0]
#php/php-mysqlnd/php-json install check %w{ php php-mysqlnd php-json }.each do |pkg| describe package(pkg) do it { should be_installed /#{var[:php_version]}/ } end end
require 'spec_helper' require 'yaml'var = property[:host_vars][0]
describe package('mariadb-server') do it { should be_installed.with_version("#{var[:mariadb_version]}") } end
終わりに
今回は「変数を使用して、テストコードを使いまわす」ことについてご紹介しました。
改めて見ると、(これ、逆に分かりにくくなってない…?)と思わないでもないです…。
が、RubyやYAMLの勉強になったのでよしとします!!!
もっとかっこいい(そして実用的な)コードが書けるようになりたい~!
以上、新卒インフラ女子の備忘録 part2でした。