インフラの自動テストツール「Infrataster」を試す

インフラの自動テストツール「Infrataster」を試す

今回はインフラに対する自動テストツールであるInfratasterを軽く試してみたいと思います。

Infratasterはインフラの外部から振る舞いをテストするツールで、構築したサーバに対してそのサーバのサービス利用者に近い視点でテストをすることができるのが特徴です。

ちなみにインフラの自動テストと言えばServerspecがよく知られていますが、サーバ内部からテストするServerspecに対してInfratasterは外側からと、ひと味違ったアプローチのテストツールとなっています。

準備

Infratasterの公式サイトにチュートリアルがありますが、今回は最初の一歩としてそれをさらに単純化したものを試していきます。

ローカルのマシンに作業ディレクトリを作り、その中でInfratasterを動かしていきましょう。 MacもしくはLinuxなどで試してみてください。

$ mkdir infrataster-sample
$ cd infrataster-sample

テスト対象のサーバーはVagrantでローカルに作成することとします。

なおこのチュートリアルにはRubyとbundler、Vagrantが必要ですが、それらの導入に関しては省略します。また、Rubyのバージョンは2.1.5を用いて動作を検証しています。

Infratasterを使ったテストの準備

まずローカルマシンにInfratasterを導入しテストを実行する準備を行います。

Infratasterのインストール

bundlerで使ってインストールすることとします。

以下の内容でGemfileを作成してください。

Gemfile

source 'https://rubygems.org'

gem 'infrataster'

Gemfileを作成できたらbundle installでインストールします。

$ bundle install

RSpecの設定

InfratasterはRSpecの上で動きますのでRSpecを実行する準備が必要です。

RSpecの設定ファイルとspec_helper.rbを作成するために以下のコマンドを実行します。

$ rspec --init

このコマンドによって以下のふたつのファイルが生成されます。

  • .rspec
  • spec/spec_helper.rb

.rspec

.rspecは生成されたファイルそのままで問題ありません。

--color
--require spec_helper

spec/spec_helper.rb

spec_helper.rbは生成されたファイルの冒頭に以下を追加したものとなります。

require 'infrataster/rspec'

Infrataster::Server.define(
  :app,
  '192.168.0.0/16',
  vagrant: true,
)

テスト対象としてappという名前のサーバーを定義しています。Vagrantを使う場合IPアドレスはネットマスクで書くことができます。

コメント行を全て削除すると今回の例では以下の通りのファイル内容となります。

require 'infrataster/rspec'

Infrataster::Server.define(
  :app,
  '192.168.0.0/16',
  vagrant: true,
)

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end

  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end
end

テストケースの作成

spec_helper.rbで定義したappに対してhttpdのテストを作成します。

spec/example_spec.rb

require 'spec_helper'

describe server(:app) do
  describe http('http://app') do
    it "responds content including 'Hello Apache'" do
      expect(response.body).to include('Hello Apache')
    end

    it "responds as 'text/html; charset=UTF-8'" do
      expect(response.headers['content-type']).to eq("text/html; charset=UTF-8")
    end
  end
end

これでテストの方は準備ができました。

テスト対象のサーバーの準備

続いてテスト対象のサーバーをセットアップしていきます。

以下の内容でVagrantfileを作成してください。

Vagrantfile

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "hansode/centos-7.1.1503-x86_64"

  config.vm.define :app do |c|
    c.vm.network  "private_network", ip: "192.168.33.10"
  end
end

今回BOXはhansode/centos-7.1.1503-x86_64を使います。

実行

とりあえずこの時点でvagrant upしてテストを実行してみましょう。

$ vagrant up

無事仮想マシンが立ち上がったらローカルでrspecコマンドを実行しテストを走らせます。

$ rspec
FF

Failures:

  1) server 'app' http 'http://app' with {:params=>{}, :method=>:get, :headers=>{}} responds content including 'Hello Apache'
     Failure/Error: expect(response.body).to include('Hello Apache')
     Faraday::ConnectionFailed:
       Connection refused - connect(2) for "192.168.33.10" port 80
     # ./spec/example_spec.rb:8:in `block (3 levels) in <top (required)>'

  2) server 'app' http 'http://app' with {:params=>{}, :method=>:get, :headers=>{}} responds as 'text/html'
     Failure/Error: expect(response.headers['content-type']).to eq("text/html")
     Faraday::ConnectionFailed:
       Connection refused - connect(2) for "192.168.33.10" port 80
     # ./spec/example_spec.rb:11:in `block (3 levels) in <top (required)>'

Finished in 0.01418 seconds (files took 2.53 seconds to load)
2 examples, 2 failures

当然ながら失敗します。まだサーバーのセットアップを行っていないので正しい結果です。そしてこれによりテストがきちんと動いていることを確認できました。

これはテストを先に書く、TDDで言うところのテストファーストになっています。実際は必ずしもテストファーストにこだわる必要はないと思われますが、テストがきちんと働いていることを確認するためにわざと失敗させるステップは踏んでおいた方がいいでしょう。

httpdのインストール

昨今はこれくらいの検証にもプロビジョナーを使ったセットアップをしたいところですが、チュートリアル的にはファイルが多くなり分かりにくくなるので、Infratasterにフォーカスするために今回はあえて手動でhttpdのインストールします。

仮想マシン上で以下のようにインストールして起動してください。

$ sudo yum install httpd
$ sudo chkconfig httpd on
$ sudo service httpd start

今回は実験ですので、起動時にServerNameに関するエラーメッセージが出ますが無視します。実際の運用では無視しないでください。

コンテンツの準備

実際にコンテンツが返ってくることをテストしますので、コンテンツの準備も行います。

/var/www/html/index.html

以下の内容で作成してください。

Hello Apache

再度テストの実行

テスト対象のサーバーの準備ができたので再度テストを実行します。

$ rspec
FF

Failures:

  1) server 'app' http 'http://app' with {:params=>{}, :method=>:get, :headers=>{}} responds content including 'Hello Apache'
     Failure/Error: expect(response.body).to include('Hello Apache')
     Faraday::ConnectionFailed:
       Connection refused - connect(2) for "192.168.33.10" port 80
     # ./spec/example_spec.rb:8:in `block (3 levels) in <top (required)>'

  2) server 'app' http 'http://app' with {:params=>{}, :method=>:get, :headers=>{}} responds as 'text/html'
     Failure/Error: expect(response.headers['content-type']).to eq("text/html")
     Faraday::ConnectionFailed:
       Connection refused - connect(2) for "192.168.33.10" port 80
     # ./spec/example_spec.rb:11:in `block (3 levels) in <top (required)>'

Finished in 0.01194 seconds (files took 2.13 seconds to load)
2 examples, 2 failures

また失敗してしまいました。エラーの原因は、

Connection refused - connect(2) for "192.168.33.10" port 80

とのことなので80番ポートへの接続を拒否されているようです。

仮想マシンのiptablesの設定確認

サーバー設定を確認してみるとiptablesの設定を忘れていました。CentOS 7からfirewalldが使われるようになっていますので、まずiptablesに切り替えます。

$ sudo systemctl stop firewalld
$ sudo systemctl mask firewalld
$ sudo yum install iptables-services
$ sudo systemctl enable iptables
$ sudo service iptables save

/etc/sysconfig/iptables

# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT

ここに以下の行を追加して

-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT

ルールをリロードします。

$ sudo service iptables reload

再度テストを実行

さあこれでどうでしょうか。再度rspecを実行します。

$ rspec
..

Finished in 0.01547 seconds (files took 2.12 seconds to load)
2 examples, 0 failures

今度は無事通りました。これでrspecを使ったサーバ検証ができるようになりました。

終わりに

いかがだったでしょうか?

Infratasterは実際のクライアントの動きを模して外部からテストするため、結合テストにもってこいのツールではないかと思います。

冒頭で少し述べたように、同じインフラのテストツールであるServerspecがインフラの内部からテストするものなのに対し、Infratasterはインフラの外部から振る舞いをテストするツールであるという違いがあります。

つまりServerspecによるテストはホワイトボックステスト、Infratasterによるテストはブラックボックステストをするものと言えます。

従ってこのふたつのツールは競合するものではなく補完関係にあるもので、両者を併用することでより堅牢なインフラを構築することができるでしょう。

またInfratasterは内部にアクセスできないサーバーに対してもテストが可能であるという利点もあり、Serverspecではカバーできないケースでも対応が可能になるという一面もあります。

今回のようにウェブサーバの動作をテストするケースではCapybaraやSeleniumのようなエンドツーエンドテストツールを使うという手もありますが、本格的なそれらのツールは学習コストやセットアップの手間も相応にかかりますので、インフラサイドからシンプルなテストを書く分にはInfratasterに分がありそうです。

なお現在Infratasterでテストに利用できるリソースとして以下のものがあります。

正直まだあまり充実しているとは言えませんが、今後様々な設定のテストに対応したプラグインが増えてくればさらに価値のあるプロダクトになりそうです。ぜひお試しください。

Infrataster