Serverspec run by ansible-playbook and it use hostvars variables of Ansible.
こちらはAnsible Advent Calendar 2016の25日目の記事です。
0.はじめに
本記事ではServerspecをAnsibleのhostvars変数の値を使ってansible-playbook から実行する方法を紹介します。
- こちらは以前書いた記事を元にアップデートしたものです。
- コード一式は https://github.com/tbuchi888/serverspec-run-by-ansible-playbook.git へ上げています。すぐに試したい場合は以下手順を参照してください。
#Step1
git clone https://github.com/tbuchi888/serverspec-run-by-ansible-playbook.git
cd serverspec-run-by-ansible-playbook
#Step2
#インベントリファイル(hosts.yml)やspec/ロール名/配下等を自環境へ合わせて修正。
#Step3
ansible-plyabook -i hosts.yml spec.yml
その他、参考までにAnsible公式サイトが推奨するテスト方法については以下をご確認ください。
http://docs.ansible.com/ansible/test_strategies.html
1.インベントリホストへのServerspecの実行
Ansibleのインベントリホストに対してServerspecを実施する場合
以下のような方法があると思います。
No. | 方法 | 特徴 |
---|---|---|
1 | Ansibleのcommandやshellモジュールから、rake spec やrake spec:hogerole などServerspecを直接呼び出す方法 |
AnsibleとServerspecで別々にinventoryやhostvarsを管理する必要がある。 |
2 | ansible_specなどAnsibleとServerspecのパーサーを使う方法 | ansible_specはGemでインストール後に、指定の形式(Inventoryファイルやロールなど、ベストプラクティスディレクトリ構成に近い)をとれば、Ansibleのファイルを利用して、容易にAnsibleのロール単位でテスト実行が可能で非常に便利です。ただし、Ansible側でロール構成が必要なことやインベントリファイル内のVariablesが未だ対応していない等の制約もあります。 |
3 | kitchen-ansibleなどを利用する | こちらは未検証です。DockerやVagrantなどと連携して、テスト用のコンテナやVMを構築するイメージでしょうか。 |
2.今回の方法
今回は、上述のNo.1に近い方法で、Ansibleのtemplate
モジュールやServerspecのTipsなど
以下基本機能を利用して「ServerspecをAnsibleのhostvars変数の値を使ってansible-playbookから実行する方法」を検討しました。
- Ansible
- Serverspec
具体的には、力技ですが ansible-playbook を実行時に
- 各ターゲットホストのhostvarsの情報をAnsibleのtemplateモジュールを利用してYAML形式のファイルとしてdumpします。
- 続けてServerspec呼出し時に先ほどdumpしたファイルのパスと
テストしたいロール名(spec_role
)を渡してあげる
ことで実現しています。
3.環境
AnsibleとServerspec実行 HOSTマシン
- OSX Yosemite
- Ansible: 2.3.0 (devel 6c3de3c299) last updated 2016/12/22
- Serverspec: 2.37.2
参考:サンプルのインベントリホスト(ターゲットホスト)マシン
- Windows2012R2 * 1台
- IISインストール済
- CentOS6.8 * 2台
- apacheインストール済
なお、サンプルのマシンはこちらを参考に今回作成しています。
また、CentOSについてはOSXユーザーのSSH公開鍵をvagrantユーザーにコピーしておきます。
例)
ssh-copy-id vagrant@192.168.33.41
ssh-copy-id vagrant@192.168.33.41
4.事前準備
Directory構成は以下のようになります。
├── README.md
├── Rakefile # Sererspec:カスタマイズしたもの
├── hosts.yml # Ansible: サンプルのインベントリファイル
├── spec
│ ├── AAA # Sererspec:サンプルのロール名
│ │ └── sample_spec.rb # Sererspec:サンプルのspecファイル
│ ├── BBB # Sererspec:サンプルのロール名
│ │ └── sample_spec.rb # Sererspec:サンプルのspecファイル
│ └── spec_helper.rb # Sererspec:カスタマイズしたもの
├── spec.yml # Ansible: カスタマイズしたPlaybook
├── spec_vars # Ansible: playbook実行時に作成
│ ├── hostvars_centos6-httpd01.yml
│ ├── hostvars_centos6-httpd02.yml
│ ├── hostvars_win2012-iis01.yml
└── templates
└── dump_variables.j2 # Ansible: hostvarsをダンプするjinjya2テンプレート
4.1.Ansible側の準備
- インベントリホスト(実行対象ホスト)毎のhostvarsをYAML形式でダンプするjinjya2のtemplateファイルを
templates/dump_variables.j2
として作成します。
{{ hostvars[inventory_hostname] | to_yaml }}
- Ansible実行ホストマシンのlocalで以下を実施するplaybookを
spec.yml
として作成します。- templateモジュールを利用して、インベントリホストのhostvarsの情報をYAML形式でカレントディレクトリ上の
spec_vars/[inventory_hostname].yml
として保存する - shellモジュールより、上記ファイルと、serverspecのロール名(
spec_role
)を引数にしてserverspecを実行する - debugモジュールでserverspec実行結果を表示する
- templateモジュールを利用して、インベントリホストのhostvarsの情報をYAML形式でカレントディレクトリ上の
---
- hosts: all
gather_facts: no
vars:
spec_vars_dir: "{{playbook_dir}}/spec_vars"
host_vars_path: "{{spec_vars_dir}}/hostvars_{{inventory_hostname}}.yml"
tasks:
- name: create "{{spec_vars_dir}}" directory
file:
path: "{{spec_vars_dir}}"
state: directory
delegate_to: localhost
- name: "dump_variables hostvars to yml"
template:
src: templates/dump_variables.j2
dest: "{{host_vars_path}}"
delegate_to: localhost
- name: rake serverspec with hostvars
shell: HOST_VARS_PATH={{host_vars_path}} rake serverspec:{{spec_role}}
register: raw_result
delegate_to: localhost
- name: stdout of serverspec
debug: var=raw_result.stdout_lines
- 参考:以下はサンプルのAnsibleのインベントリファイルとなります。
自環境に合わせて適宜変更してください。
なお、spec_role
に合致するserverspecを実施します。
spec_role
を定義しない場合はserverspecのテスト対象外となります。
[win]
win2012-iis01 spec_role=AAA ansible_host=192.168.33.51
[centos]
centos6-httpd01 spec_role=AAA ansible_host=192.168.33.41
centos6-httpd02 spec_role=BBB ansible_host=192.168.33.42
myansible spec_role=BBB ansible_host=localhost
[win:vars]
ansible_connection=winrm
ansible_port=5985
ansible_user=vagrant
ansible_password=vagrant
web_service_name="W3SVC"
[centos:vars]
ansible_user=vagrant
ansible_private_key_file=~/.ssh/id_rsa
web_service_name=httpd
4.2.Serverspec側の準備
-
Rakefile
を以下の通りカスタマイズします。- hostvarsのYAML形式ファイルのパスを指定して、変数として取込ます。
-
spec_role
名をロールとして指定します。
Rakefile
require 'rake'
require 'rspec/core/rake_task'
require 'yaml'
hostvars = YAML.load_file(ENV['HOST_VARS_PATH'])
namespace :serverspec do
desc "Run serverspec for #{hostvars["spec_role"]}"
RSpec::Core::RakeTask.new(hostvars["spec_role"].to_sym) do |t|
t.pattern = "spec/#{hostvars["spec_role"]}/*_spec.rb"
end
end
-
spec_helper.rb
を以下通りカスタマイズします。- hostvarsのYAML形式ファイルのパスを指定して、変数として取込ます。
- マルチOS(windows/un*x)、マルチコネクション(ssh/local/winrm)対応も実施します。
require 'serverspec'
require 'net/ssh'
require 'winrm'
require 'yaml'
hostvars = YAML.load_file(ENV['HOST_VARS_PATH'])
set_property hostvars
if hostvars["ansible_connection"] == 'winrm'
#
# OS type: Windows / Connetion type: winrm
#
set :backend, :winrm
set :os, :family => 'windows'
host = hostvars["ansible_ssh_host"] || hostvars["ansible_host"] || hostvars["inventory_hostname"]
user = hostvars["ansible_ssh_user"] || hostvars["ansible_user"]
port = hostvars["ansible_ssh_port"] || hostvars["ansible_port"]
pass = hostvars["ansible_ssh_pass"] || hostvars["ansible_password"]
endpoint = "http://#{host}:#{port}/wsman"
winrm = ::WinRM::WinRMWebService.new(endpoint, :ssl, :user => user, :pass => pass, :basic_auth_only => true)
winrm.set_timeout 300 # 5 minutes max timeout for any operation
Specinfra.configuration.winrm = winrm
elsif hostvars["ansible_connection"] == 'local' || hostvars["ansible_ssh_host"] == 'localhost' || hostvars["ansible_host"] == 'localhost' || hostvars["inventory_hostname"] == 'localhost'
#
# OS type: UN*X / Connction type: local exec
#
set :backend, :exec
else
#
# OS type: UN*X / Connction type: ssh
#
set :backend, :ssh
set :sudo_password, hostvars["ansible_ssh_pass"] || hostvars["ansible_password"]
host = hostvars["ansible_ssh_host"] || hostvars["ansible_host"] || hostvars["inventory_hostname"]
options = Net::SSH::Config.for(host)
options[:user] ||= hostvars["ansible_ssh_user"] || hostvars["ansible_user"]
options[:port] ||= hostvars["ansible_ssh_port"] || hostvars["ansible_port"]
options[:keys] ||= hostvars["ansible_ssh_private_key_file"] || hostvars["ansible_private_key_file"]
set :host, options[:host_name] || host
set :ssh_options, options
# Disable sudo
# set :disable_sudo, true
# Set environment variables
# set :env, :LANG => 'C', :LC_MESSAGES => 'C'
# Set PATH
# set :path, '/sbin:/usr/local/sbin:$PATH'
end
- テスト用のspecファイルを準備します。
こちらは自環境に合わせて適宜変更してください。
以下サンプルのServersepcのロールとspecファイル
- ROLE:AAA
- spec/AAA/sample_spec.rb
- Ansibleのhostvarsに設定した変数
web_service_name
のサービス状態を確認するサンプル
require 'spec_helper'
describe service( property["web_service_name"] ) do
it { should be_enabled }
it { should be_running }
end
- ROLE:BBB
- spec/BBB/sample_spec.rb
- Ansibleのインベントリファイルの
inventory_hostname
をホスト名として含んでいるか確認するサンプル
require 'spec_helper'
describe command('hostname') do
its(:stdout) { should contain( "#{property['inventory_hostname']}" ) }
end
5.参考:実行結果
以下サンプル環境でのplaybook実行結果です。
WindowsサーバとCentOSサーバの各々のロールに対してServerspecが実行されます。
$ ansible-playbook -i hosts.yml spec.yml
PLAY [all] ******************************************************************************************************************************************************
TASK [create spec_vars directory] *******************************************************************************************************************************
changed: [win2012-iis01 -> localhost]
ok: [centos6-httpd02 -> localhost]
ok: [centos6-httpd01 -> localhost]
TASK [dump_variables hostvars to yml] ***************************************************************************************************************************
changed: [centos6-httpd02 -> localhost]
changed: [centos6-httpd01 -> localhost]
changed: [win2012-iis01 -> localhost]
TASK [rake serverspec with hostvars] ****************************************************************************************************************************
changed: [centos6-httpd02 -> localhost]
changed: [centos6-httpd01 -> localhost]
changed: [win2012-iis01 -> localhost]
TASK [stdout of serverspec] *************************************************************************************************************************************
ok: [centos6-httpd02] => {
"raw_result.stdout_lines": [
"/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/ruby -I/Library/Ruby/Gems/2.0.0/gems/rspec-support-3.5.0/lib:/Library/Ruby/Gems/2.0.0/gems/rspec-core-3.5.4/lib /Library/Ruby/Gems/2.0.0/gems/rspec-core-3.5.4/exe/rspec --pattern spec/BBB/\\*_spec.rb",
"",
"Command \"hostname\"",
" stdout",
" should contain \"centos6-httpd02\"",
"",
"Finished in 5.33 seconds (files took 2.5 seconds to load)",
"1 example, 0 failures"
]
}
ok: [centos6-httpd01] => {
"raw_result.stdout_lines": [
"/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/ruby -I/Library/Ruby/Gems/2.0.0/gems/rspec-support-3.5.0/lib:/Library/Ruby/Gems/2.0.0/gems/rspec-core-3.5.4/lib /Library/Ruby/Gems/2.0.0/gems/rspec-core-3.5.4/exe/rspec --pattern spec/AAA/\\*_spec.rb",
"",
"Service \"httpd\"",
" should be enabled",
" should be running",
"",
"Finished in 5.62 seconds (files took 2.68 seconds to load)",
"2 examples, 0 failures"
]
}
ok: [win2012-iis01] => {
"raw_result.stdout_lines": [
"/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/ruby -I/Library/Ruby/Gems/2.0.0/gems/rspec-support-3.5.0/lib:/Library/Ruby/Gems/2.0.0/gems/rspec-core-3.5.4/lib /Library/Ruby/Gems/2.0.0/gems/rspec-core-3.5.4/exe/rspec --pattern spec/AAA/\\*_spec.rb",
"",
"Service \"W3SVC\"",
" WARN WinRM::WinRMWebService : WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_powershell_script instead",
" should be enabled",
" WARN WinRM::WinRMWebService : WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_powershell_script instead",
" should be running",
"",
"Finished in 9.7 seconds (files took 2.48 seconds to load)",
"2 examples, 0 failures"
]
}
PLAY RECAP ******************************************************************************************************************************************************
centos6-httpd01 : ok=4 changed=2 unreachable=0 failed=0
centos6-httpd02 : ok=4 changed=2 unreachable=0 failed=0
win2012-iis01 : ok=4 changed=3 unreachable=0 failed=0
6.その他
WindowsサーバへServerspecする際のリソースタイプや設定など以下が参考になりました。
https://github.com/mizzy/serverspec/blob/master/WINDOWS_SUPPORT.md