tkak's tech blog

This is my technological memo.

InSpecではじめるテスト駆動インフラ

最近、新しくChefのCookbookを書く機会があったので、前から気になっていたInSpecを少し触ってみました。

github.com

InSpecとは何か?

InSpecは、Chef社が開発しているオープンソースのサーバーテストフレームワークです。サーバーのテストフレームワークといえば、Serverspecが有名ですが、InSpecはインフラ管理向けというよりコンプライアンスの担保だったりセキュリティ要件を満たしているかどうかのテスト用作られたツールになっています。InSpecのGithubには、

InSpec is inspired by the wonderful Serverspec project. Kudos to mizzy and all contributors!

と記載があるように、Serverspecにインスパイアされているようです。なので、基本的にServerspecで提供されている代表的なリソースはInSpecでも大体用意されていて、今の所(バージョン1.3.0)以下のようなリソースをサポートしています。

apache_conf apt audit_policy auditd_conf auditd_rules bash bond bridge bsd_service command csv directory etc_group etc_passwd etc_shadow file gem group grub_conf host iis_site inetd_conf ini interface iptables json kernel_module kernel_parameter launchd_service limits_conf login_def mount mysql_conf mysql_session npm ntp_conf oneget os os_env package parse_config parse_config_file pip port postgres_conf postgres_session powershell process registry_key runit_service security_policy service ssh_config sshd_config ssl sys_info systemd_service sysv_service upstart_service user users vbscript windows_feature wmi xinetd_conf yaml yum

例えば、指定したパッケージが適切にインストールされているかチェックしたい場合、packageリソースを使って以下のように書きます。InSpecでもServerspecでも大体似たような感じで書くことができ、バージョンチェックのところ以外は全く同じです。

## Serverspec
describe package('nginx') do
  it { should be_installed.with_version('1.9.5') }
end

## InSpec
describe package('nginx') do
  it { should be_installed }
  its('version') { should eq 1.9.5 }
end

ディレクトリとファイルのテストもほとんど一緒で、modeの箇所以外はそのままInSpecでも動かせます。

## Serverspec
describe file('/usr/local/foo') do
  it { should be_directory }
  it { should be_owned_by 'root' }
  it { should be_grouped_into 'root' }
  it { should be_mode '755' }
end

describe file('/usr/local/foo/bar') do
  it { should be_file }
  it { should be_owned_by 'root' }
  it { should be_grouped_into 'root' }
  it { should be_mode '644' }
  its('content') { should match('Hello') }
end

## InSpec
describe file('/usr/local/foo') do
  it { should be_directory }
  it { should be_owned_by 'root' }
  it { should be_grouped_into 'root' }
  its('mode') { should cmp '0755' }
end

describe file('/usr/local/foo/bar') do
  it { should be_file }
  it { should be_owned_by 'root' }
  it { should be_grouped_into 'root' }
  its('mode') { should cmp '0644' }
  its('content') { should match('Hello') }
end

ちなみに、よりInSpec風に書くのであれば、以下のようにテストを書くこともできます。

describe directory('/usr/local/foo') do
  its('owner') { should cmp 'root' }
  its('group') { should cmp 'root' }
  its('mode') { should cmp '755' }
end

describe file('/usr/local/foo/bar') do
  its('owner') { should cmp 'root' }
  its('group') { should cmp 'root' }
  its('mode') { should cmp '644' }
  its('content') { should match('Hello') }
end

機能的にServerspecもInSpecもほとんど大差ない感じなんですが、強いて違う点を挙げるとすれば、InSpecは、Serverspec に比べ、よりRubyRSpec、Rakeを意識する必要がない点かなと思います。というのも、InSpecは、spec_helper.rbやRakefileを準備する必要がありません。以下のようなCLIが用意されていて、とりあえずテストケースだけ書いてコマンドを実行すれば、すぐ使うことができます。

# run test locally
inspec exec test.rb

# run test on remote host on SSH
inspec exec test.rb -t ssh://user@hostname -i /path/to/key

# run test on remote windows host on WinRM
inspec exec test.rb -t winrm://Administrator@windowshost --password 'your-password'

# run test on docker container
inspec exec test.rb -t docker://container_id

test-kitchenでInSpecを使ってみる

では、実際にtest-kitchenと一緒に使ってみます。

準備

現時点での最新版のChefDK(バージョン0.19.6)には、test-kichenもInSpec (kitchen-inspec)もすでに含まれてるので、ChefDKをインストールするだけで使えるようになります。Macであれば以下のコマンドだけで環境の準備ができます。(virtualboxVagrantは別途必要)

$ brew cask install chefdk

chef generate cookbookコマンドで適当にcookbookを作って、recipeとテストを用意します。今回触ってみた環境をgithubにあげてますので、よかったら参考にしてください。

https://github.com/tkak/hello-cookbook

git cloneしてきて、kitchen listを実行すると以下のテスト対象を確認できます。

$ chef exec kitchen list
Instance                     Driver   Provisioner  Verifier  Transport  Last Action
with-serverspec-ubuntu-1604  Vagrant  ChefZero     Busser    Ssh        <Not Created>
with-serverspec-centos-72    Vagrant  ChefZero     Busser    Ssh        <Not Created>
with-inspec-ubuntu-1604      Vagrant  ChefZero     Inspec    Ssh        <Not Created>
with-inspec-centos-72        Vagrant  ChefZero     Inspec    Ssh        <Not Created>
with-shell-ubuntu-1604       Vagrant  ChefZero     Shell     Ssh        <Not Created>
with-shell-centos-72         Vagrant  ChefZero     Shell     Ssh        <Not Created>

Serverspecでテスト

まずは、通常の使い方であるServerspec (busser-serverspec)でテストを実行してみます。

$ chef exec kitchen test serverspec
...
...
...
-----> Running serverspec test suite
-----> Installing Serverspec..
Fetching: diff-lcs-1.2.5.gem (100%)
Fetching: rspec-expectations-3.5.0.gem (100%)
Fetching: rspec-mocks-3.5.0.gem (100%)
Fetching: rspec-3.5.0.gem (100%)
Fetching: rspec-its-1.2.0.gem (100%)
Fetching: multi_json-1.12.1.gem (100%)
Fetching: net-ssh-3.2.0.gem (100%)
Fetching: net-scp-1.2.1.gem (100%)
Fetching: net-telnet-0.1.1.gem (100%)
Fetching: sfl-2.3.gem (100%)
Fetching: specinfra-2.64.0.gem (100%)
Fetching: serverspec-2.37.2.gem (100%)
-----> serverspec installed (version 2.37.2)
       /opt/chef/embedded/bin/ruby -I/tmp/verifier/suites/serverspec -I/tmp/verifier/gems/gems/rspec-support-3.5.0/lib:/tmp/verifier/gems/gems/rspec-core-3.5.4/lib /opt/chef/embedded/bin/rspec --pattern /tmp/verifier/suites/serverspec/\*\*/\*_spec.rb --color --format documentation --default-path /tmp/verifier/suites/serverspec

       Package "zsh"
         should be installed

       File "/usr/local/foo"
         should be directory
         should be owned by "root"
         should be grouped into "root"
         should be mode "755"

       File "/usr/local/foo/bar"
         should be file
         should be owned by "root"
         should be grouped into "root"
         should be mode "644"
         content
           should match "Hello"

       Finished in 0.17573 seconds (files took 0.3651 seconds to load)
       10 examples, 0 failures

       Finished verifying <with-serverspec-centos-72> (0m5.60s).
...
...
...

kitchen testコマンドで、以下の処理が一気に動きます。

ちなみに、上記のログにあるように、busser-serverspec 経由でtest-kitchenを使うと、テスト初回時にインスタンス内でgemのインストールが走り、少し待たされます。ちょっとした待ち時間ですが、何度もインスタンスを作って壊してをやってると、この待ち時間がバカにできないので、そんな時はshell-verifierを使うのがオススメです。shell-verifierを使えば、事前にインストールされたホスト側のServerspecを使ってテストを実行するので、gemインストールの待ち時間なくテストができます。shell-verifierでServerspecを使う方法は以下のとおりです。

まず、ホストにServerspec をインストールしておきます。

$ chef gem install serverspec 

そして、.kitchen.ymlファイルに以下のような設定を追記し、

verifier:
  name: shell
  command: chef exec rspec -c -f documentation -I test/integration/${KITCHEN_SUITE}/serverspec --pattern test/integration/${KITCHEN_SUITE}/serverspec/**/*_spec.rb --pattern test/integration/helpers/serverspec/spec_helper.rb

spec_helper.rbを用意します。

require 'serverspec'

set :backend, :ssh

options = Net::SSH::Config.for(host)
options[:host_name] = ENV['KITCHEN_HOSTNAME']
options[:port] = ENV['KITCHEN_PORT']
options[:user] = ENV['KITCHEN_USERNAME']
options[:keys] = ENV['KITCHEN_SSH_KEY']

set :host,        options[:host_name]
set :ssh_options, options
set :env, :LANG => 'C', :LC_ALL => 'C'

これでbusser-serverspecを使うより短時間でテストが実行できるようになります。ただし、.kitchen.ymlにコマンドの記述とspec_helper.rbの用意をする必要があり、若干面倒です。例えば、複数のCookbookを管理する時とか。

InSpecでテスト

次にInSpecでテストを動かしてみます。使い方は簡単で、InSpecを使うには.kitchen.ymlに以下のようなveriferを追記するだけで使えるようになります。

verifier:
  name: inspec

デフォルトだとtest/integration/<test suites>/以下のディレクトリにテストを配置します。また、以下のようにテストの場所を変更したり、他のリポジトリにあるテストを使うことも可能です。

## local
verifier:
  name: inspec
  inspec_tests:
    - test/recipes

## github
verifier:
  name: inspec
  inspec_tests:
    - https://github.com/dev-sec/tests-ssh-hardening

kitchen testのログは以下のようになります。

$ chef exec kitchen test inspec
...
...
...
-----> Verifying <with-inspec-centos-72>...
       Detected alternative framework tests for `inspec`
       Use `/Users/takaaki.furukawa/src/github.com/tkak/hello-cookbook/test/integration/with-inspec/inspec` for testing

Target:  ssh://[email protected]:2201


  System Package
     ✔  zsh should be installed
  File /usr/local/foo
     ✔  should be directory
     ✔  should be owned by "root"
     ✔  should be grouped into "root"
     ✔  mode should cmp == "0755"
  File /usr/local/foo/bar
     ✔  should be file
     ✔  should be owned by "root"
     ✔  should be grouped into "root"
     ✔  mode should cmp == "0644"
     ✔  content should match "Hello"

Test Summary: 10 successful, 0 failures, 0 skipped
       Finished verifying <with-inspec-centos-72> (0m1.13s).
...
...
...

特に問題なく動きます。しかも、busser-serverspec のようにgemインストールが走って待たされるということもなく、shell-verifierを使うときのようにspec_helper.rbを用意する必要もないので、なかなかいい感じです。

まとめ

test-kitchenでInSpecを試してみました。後継のツールだけあって思ってたよりシンプルで使いやすかったです。すでにServerspec で書いてあるテストコードは、Serverspec で十分テストをまわせているのですぐにInSpecに置き換えようとは思いませんが、新規で書くならInSpecも選択肢としてありだなと感じました。また、InSpec本来の使い方であるコンプライアンスチェックとしてChef Automateと組み合わせるといったことができるらしいのですが、今回はインフラのテストとしてしか試していないので、機会があればその辺も触ってみたいです。

参考

DevOps導入指南 Infrastructure as Codeでチーム開発・サービス運用を効率化する (DEV Engineer’s Books)

DevOps導入指南 Infrastructure as Codeでチーム開発・サービス運用を効率化する (DEV Engineer’s Books)

Infrastructure As Code: Managing Servers in the Cloud

Infrastructure As Code: Managing Servers in the Cloud

Serverspec

Serverspec