Serverspec ハンズオン資料（実践編）


## メニュー

### 本ハンズオンのゴール

- Serverspec で提供される各種バックエンドを試してみます
- Docker バックエンドを利用して Dockerfile のテストを作成し、テストを行います
- Infrataster を利用して Web サイトの振る舞いのテストを行います
- おまけで Jenkins を利用してテスト、Docker Hub へのコンテナイメージの自動化も行います

### 注意

- 資料の見方
- 事前準備について
- 資料内で紹介する各種ツールのバージョンについて
- 後片付けはしっかりと

### 参考資料

- 参考 URL
- 参考書籍
- ハンズオン資料
- ハンズオン教材

### 2. テストコードの書き方

2.1. Rspec とは
2.2. spec_helper.rb
2.3. Resouce Type
2.4. テストを書いてみよう

### 3. Docker バックエンドを利用して Dockerfile をテストする

3.1. バックエンドとは
3.2. 要件
3.3. テストを書く
3.4. 初めてのテスト
3.5. Dockerfile の作成
3.6. テスト

### 4. Infrataster サイトの振る舞いをテストする

4.1. Infrataster とは
4.2. Infrataster のセットアップ
4.3. 要件
4.4. テストを書く
4.5. 初めてのテスト
4.6. テスト

### 5. まとめ

- 今日やったこと
- ハンズオンで利用したリソースの削除

***

## 本ハンズオンのゴール

- Serverspec で提供される各種バックエンドを試してみます
- Docker バックエンドを利用して Dockerfile のテストを作成し、テストを行います
- Infrataster を利用して Web サイトの振る舞いのテストを行います

***

## 注意

### 資料の見方

以下、実行するコマンドの表示です。

~~~sh:command
# コメントです

# ハンズオン内で実行するコマンドです（コピー＆ペーストで貼り付けて利用して下さい）
command "foo" "bar"
~~~

以下、出力例の表示です。

~~~sh:output
# コマンドの実行後の出力例です
foo
bar
~~~

上記に実行例と異なる出力を確認した場合にはお声がけ下さい。

### 事前準備について

- ハンズオンを円滑に進めるにあたって事前準備は出来るだけ行うようにして下さい

### 資料内で紹介する各種ツールのバージョンについて

資料内で紹介する各種ツールのバージョンは以下の通りです。

- OS

~~~sh:output
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.4 LTS
Release:        14.04
Codename:       trusty
~~~

- Ruby

~~~sh:output
$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]
~~~

- Serverspec

~~~sh:output
$ bundle exec gem list | grep serverspec
serverspec (2.36.0)
~~~

- Infrataster

~~~sh:output
$ bundle exec gem list | grep infrataster
infrataster (0.3.2)
~~~

- Docker Engine

~~~sh:output
$ docker version
Client:
 Version:      1.11.2
 API version:  1.23
 Go version:   go1.5.4
 Git commit:   b9f10c9
 Built:        Wed Jun  1 21:47:50 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.11.2
 API version:  1.23
 Go version:   go1.5.4
 Git commit:   b9f10c9
 Built:        Wed Jun  1 21:47:50 2016
 OS/Arch:      linux/amd64
~~~

### 後片付けはしっかりと

本日作成した仮想マシンはハンズオン終了後、不要であれば削除しておきましょう。

***

## 参考資料

### 参考 URL

- http://serverspec.org/
- http://web-k.github.io/blog/2012/09/27/rspec_basic/
- http://qiita.com/jnchito/items/42193d066bd61c740612

### 参考書籍

- http://www.oreilly.co.jp/books/9784873117096/

### ハンズオン資料

- [Serverspec ハンズオン資料（事前準備編）](http://qiita.com/inokappa/private/43bb47745b3565b9b69f)
- [Serverspec ハンズオン資料（実践編）
](http://qiita.com/inokappa/private/f96c0939331b24236bc0)※本資料です
- [Serverspec ハンズオン資料（Jenkins おじさんお届け編）](http://qiita.com/inokappa/private/f9f624ec5ea01937b840)

### ハンズオン教材

- https://github.com/inokappa/handson-serverspec

***

## 2. テストコードの書き方

### 2.1. Rspec（Serverspec を始めるあたって知っておきたい Rspec の入り口の入り口）

#### 2.1.1. Rspec とは

- Ruby で実装されたポピュラーなテストフレームワーク（他には Test::Unit という標準で組み込まれているフレームワークもある）
- 英語っぽくテストを書くことが出来る

#### 2.1.2. Rspec の基本

- テストファイルを spec ファイルと呼ぶ
- it で始める部分が example（テスト）となる
- describe do ~ end で囲まれた部分を example グループと呼ぶ
- 期待値と実際の値を比較した結果をかえすオブジェクトをマッチャ（抹茶ではない）と呼ぶ

#### 2.1.3. spec ファイル例

Serverspec で利用する spec ファイルの例。

~~~ruby:example_spec.rb
# example グループ
describe package('httpd') do
  # example
  # - should = べきである
  # - be_installed = マッチャ
  it { should be_installed }
end

# 日本語に訳すと
# パッケージ "httpd" について
#   それは(it)インストールされている(be_installed)べき(should)
# である

# example グループ
describe command('ls -al /') do
  # example
  # - match = マッチャ
  its(:stdout) { should match /bin/ }
end

# 日本語に訳すと
# コマンド "ls -al /" について
#   その出力(its(:stdout))は "bin" にマッチ(match)でするべき(should)
# である
~~~

※ `should` の部分を `expect`（いくすぺくと）メソッドに置き換えることが出来るが、本資料では `should` メソッドを利用する

### 2.2. spec_helper.rb

Rspec の挙動を制御する為に利用されるファイルで Serverspec でも利用される。Serverspec では `serverspec-init` を実行すると以下のような内容となっている。（バックエンドを `Exec` を選択した場合）

~~~ruby:spec_helper.rb
require 'serverspec'

set :backend, :exec
~~~

### 2.3. リソースタイプ

- リソースタイプ = テスト対象の種類
- リソース = テスト対象

~~~ruby:example_spec.rb
# interface がテスト対象となるリソースタイプ
# interface('eth0') がテスト対象となるリソース
describe interface('eth0') do
  its(:speed) { should eq 1000 }
end
~~~

Serverspec で扱えるリソースタイプについては、以下の URL に掲載されているので必要に応じて確認する。

- http://serverspec.org/resource_types.html

### 2.4. テストを書いてみよう

#### 2.4.1. テスト要件

![2016062502.png](https://qiita-image-store.s3.amazonaws.com/0/87189/7b516679-65ef-1376-ec2d-5daaa2c68df2.png "2016062502.png")

- OS のバージョンは Ubuntu 14.04 であることを確認する
- docker-engine がインストールされていることを確認する
- docker-engine 起動していることを確認する 
- docker のバージョンを確認する
- Ruby や servespec や infrataster がインストールされていることを確認する

#### 2.4.2. serverspec-init

`serverspec-init` を実行するとテストに必要な各種ファイルや spec ファイルの雛形を生成されるので、以下のように `serverspec-init` を実行する。

~~~sh:command
cd ~/handson-serverspec/
mkdir first_test
cd first_test
bundle exec serverspec-init
~~~

以下のように spec ファイルの雛形や spec_helper.rb や Rakefile を生成される。（`OS type` と `backend type` を選択する）

~~~sh:output
Select OS type:

  1) UN*X
  2) Windows

Select number: 1 #=> 1 を選択

Select a backend type:

  1) SSH
  2) Exec (local)

Select number: 2 #=> 2 を選択

 + spec/
 + spec/localhost/
 + spec/localhost/sample_spec.rb
 + spec/spec_helper.rb
 + Rakefile
 + .rspec
~~~

#### 2.4.3. テストを書く

テスト要件に基いて、sample_spec.rb を以下のように書き換える。

~~~ruby:spec/localhost/sample_spec.rb
require 'spec_helper'

describe "OS のバージョンは Ubuntu 14.04 であることを確認する" do
  describe command("lsb_release -a") do
    its(:stdout) { should match /Ubuntu 14.04/ }
  end
end

describe "docker-engine がインストールされていることを確認する" do
  describe package('docker-engine'), :if => os[:family] == 'ubuntu' do
    it { should be_installed }
  end
end

describe "docker-engine 起動していること / docker のバージョンを確認する" do
  describe service('docker'), :if => os[:family] == 'ubuntu' do
    it { should be_running }
  end
  describe command("docker version") do
    its(:stdout) { should match /1.11.2/ }
  end
end

describe "Ruby や servespec / infrataster がインストールされていることを確認する" do
  it "インストールされている Ruby のバージョンは 2.3.1 である" do
    expect(check_ruby_version).to include("2.3.1")
  end

  it "Serverspec がインストールされている" do
    expect(check_packages("serverspec")).to include("serverspec")
  end

  it "Infrataster がインストールされている" do
    expect(check_packages("infrataster")).to include("infrataster")
  end

  def check_ruby_version
    command("ruby -v").stdout
  end

  def check_packages(package)
    command("bundle exec gem list | grep #{package}").stdout
  end

end
~~~

#### 2.4.4. テスト実行

以下のようにテストを実行する。

~~~sh:command
bundle exec rake
~~~

以下のように出力されることを確認する。

~~~sh:output
(snip)

OS のバージョンは Ubuntu 14.04 であることを確認する
  Command "lsb_release -a"
    stdout
      should match /Ubuntu 14.04/

docker-engine がインストールされていることを確認する
  Package "docker-engine"
    should be installed

docker-engine 起動していること / docker のバージョンを確認する
  Service "docker"
    should be running  Command "docker version"    stdout
      should match /1.11.2/

Ruby や servespec / infrataster がインストールされていることを確認する
  インストールされている Ruby のバージョンは 2.3.1 である
  Serverspec がインストールされている
  Infrataster がインストールされている

Finished in 0.68314 seconds (files took 0.31922 seconds to load)
7 examples, 0 failures
~~~

実際の出力画面は以下のように表示される。

![2016062501.png](https://qiita-image-store.s3.amazonaws.com/0/87189/2a40ad28-46d9-d878-1128-8b1435a3b7df.png "2016062501.png")

***

## 3. Docker バックエンドを利用して Dockerfile をテストする

### 3.1. バックエンドとは

#### 3.1.1. バックエンドとは

- Serverspec（実際には Specinfra）では核となる部分
- ざっくり言うと...実行形式の違いを意識することなくコマンドを実行することが出来るレイヤー
- テスト対象ホスト OS に応じたコマンドをバックエンドに応じた実行形式で実行する

#### 3.1.2. バックエンドの種類

以下のようなバックエンドが利用出来る。

- exec
- ssh
- powershell
- cmd
- docker
- lxc
- winrm
- shell_script
- dockerfile
- telnet

### 3.2. 要件

![2016061905.png](https://qiita-image-store.s3.amazonaws.com/0/87189/87a5ac2f-990c-d42e-11eb-97b9107e0e42.png "2016061905.png")

以下のような要件の Docker コンテナ作ってテストしたい。

- Web アプリケーションコンテナ
- Web サーバーは Apache
- コンテンツは静的な index.html 一枚

### 3.3. Dockerfile の作成

以下のコマンドを実行して作成済みの Dockerfile を確認する。

~~~sh:command
cd ~/handson-serverspec
cat docker/Dockerfile
~~~

以下のように出力されることを確認する。

~~~sh:output
FROM ubuntu:14.04
RUN apt-get update && \
    apt-get install -y apache2 && \
    mkdir /var/run/apache2 && \
    mkdir /var/lock/apache2
ADD index.html /var/www/html/
EXPOSE 80
env APACHE_RUN_USER    www-data
env APACHE_RUN_GROUP   www-data
env APACHE_PID_FILE    /var/run/apache2.pid
env APACHE_RUN_DIR     /var/run/apache2
env APACHE_LOCK_DIR    /var/lock/apache2
env APACHE_LOG_DIR     /var/log/apache2
env LANG               C
CMD ["/usr/sbin/apache2", "-D", "FOREGROUND"]
~~~

### 3.4. テストの作成

以下のコマンドを実行して作成済みの spec ファイルを確認する。

~~~sh:command
cd ~/handson-serverspec
cat spec/docker_dockerfile_spec.rb
~~~

以下のように出力されることを確認する。

~~~ruby:output
require "serverspec"
require "docker"

describe "Dockerfile Check" do
  #
  # 最初に一度だけ実行される
  #
  before(:all) do
    image = Docker::Image.build_from_dir('./docker/')
    set :os, family: :debian
    set :backend, :docker       # Serverspec の Docker バックエンドを利用してテスト
    set :docker_image, image.id
  end

  describe command("lsb_release -a") do
    its(:stdout) { should match /Ubuntu 14/ }
  end

  describe package("apache2") do
    it { should be_installed }
  end

  describe process("apache2") do
    it { should be_running }
  end

  describe port(80) do
    it { should be_listening }
  end

  describe file("/var/www/html/index.html") do
    it { should exist }
    its(:content) { should match /I love mentai.co/ }
  end

end
~~~

### 3.5. テスト

以下のコマンドを実行してテストを実行する。（初めて実行する場合にはコンテナのビルドが走るので時間が掛かるので気長に待つ）

~~~sh:command
cd ~/handson-serverspec
bundle exec rspec spec/docker_dockerfile_spec.rb
~~~

以下のように出力されることを確認する。

~~~sh:output
Dockerfile Check
  Command "lsb_release -a"
    stdout
      should match /Ubuntu 14/
  Package "apache2"
    should be installed
  Process "apache2"
    should be running
  Port "80"
    should be listening
  File "/var/www/html/index.html"
    should exist
    content
      should match /I love mentai.co/

Finished in 0.4776 seconds (files took 0.35406 seconds to load)
6 examples, 0 failures
~~~

***

## 4. Infrataster サイトの振る舞いをテストする

### 4.1. Infrataster とは

- https://github.com/ryotarai/infrataster
- インフラの外部から各種リソースの振る舞いをテストするツール
- Web サイトや DNS クエリ、Redis や memcacned の振る舞いをテストすることが出来る

### 4.2. 要件

![2016061906.png](https://qiita-image-store.s3.amazonaws.com/0/87189/9863aba5-45fe-b5a6-5119-559b819008ad.png "2016061906.png")

前のステップ（3. Docker バックエンドを利用して Dockerfile をテストする）さ作成した Docker コンテナサイトに対して以下ようなテストを行いたい。

- HTTP レスポンスコード 200 が返ってくること
- HTTP レスポンスヘッダ `Content-Type` は `text/html` が含まれていること
- HTTP レスポンスボディ に `I love menti.co` が含まれていること

### 4.3. テストを書く

以下のコマンドを実行して作成済みの spec ファイルを確認する。

~~~sh:command
cd ~/handson-serverspec
cat spec/docker_site_spec.rb
~~~

以下のように出力されることを確認する。

~~~sh:ruby
require "infrataster/rspec"

#
# Infrataster のテストで利用するコンテナを用意する定義
#  - コンテナをビルドする
#  - コンテナを起動する（コンテナを作成して start する）
#  - コンテナの IP アドレスを取得する
#
image = Docker::Image.build_from_dir("./docker/")
container = Docker::Container.create("Image" => image.id)
container.start
ip = container.json["NetworkSettings"]["IPAddress"]

#
# テストの最後に実行される処理を定義
#  - コンテナを削除する
#
RSpec.configure do |config|
  config.after(:suite) do
    container.delete(:force => true)
  end
end

#
# Infrataster のサーバー定義
#
Infrataster::Server.define(:docker) do |s|
  s.address = ip
end

#
# Infrataster によるサイトの振る舞いテスト
#
describe "Site Check" do

  describe server(:docker) do
    describe http("http://docker") do
      it "responds as 'text/html'" do
        expect(response.headers['content-type']).to match(%r{^text/html})
      end
      it "responds as '200'" do
        expect(response.status).to be 200
      end
      it "responds as 'I love mentai.co'" do
        expect(response.body).to include("I love mentai.co")
      end
    end
  end

end
~~~

### 4.4. テスト

以下のコマンドを実行してテストを実行する。

~~~sh:command
cd ~/handson-serverspec
bundle exec rspec spec/docker_site_spec.rb
~~~

以下のように出力されることを確認する。

~~~sh:output
Site Check
  server 'docker'
    http 'http://docker' with {:params=>{}, :method=>:get, :headers=>{}}
      responds as 'text/html'
      responds as '200'
      responds as 'I love mentai.co'

Finished in 0.0607 seconds (files took 0.6372 seconds to load)
3 examples, 0 failures
~~~

### 5.5. Dockerfile のテストと一気通貫でテストする

![2016061907.png](https://qiita-image-store.s3.amazonaws.com/0/87189/ffd9d160-d0e7-ad07-fe82-4f6f0b5881b9.png "2016061907.png")

以下のコマンドを実行してテストを実行する。

~~~sh:command
cd ~/handson-serverspec
bundle exec rspec spec/docker_*_spec.rb
~~~

以下のように出力されることを確認する。

~~~sh:output
Site Check
  server 'docker'
    http 'http://docker' with {:params=>{}, :method=>:get, :headers=>{}}
      responds as 'text/html'
      responds as '200'
      responds as 'I love mentai.co'

Finished in 0.0607 seconds (files took 0.6372 seconds to load)
3 examples, 0 failures

root@(none):~/handson-serverspec# bundle exec rspec spec/docker_*_spec.rb

Dockerfile Check
  Command "lsb_release -a"
    stdout
      should match /Ubuntu 14/
  Package "apache2"
    should be installed
  Process "apache2"
    should be running
  Port "80"
    should be listening
  File "/var/www/html/index.html"
    should exist
    content
      should match /I love mentai.co/

Site Check
  server 'docker'
    http 'http://docker' with {:params=>{}, :method=>:get, :headers=>{}}
      responds as 'text/html'
      responds as '200'
      responds as 'I love mentai.co'

Finished in 0.53204 seconds (files took 0.78807 seconds to load)
9 examples, 0 failures
~~~

***

## 5. まとめ

### 今日やったこと

- 簡単な spec ファイルを作成してテスト
- Docker バックエンドを利用して Dockerfile のテスト
- Infrataster を利用して Web サイトの振る舞いをテスト

### ハンズオンで利用したリソースの削除

- 本日作成した仮想マシンはハンズオン終了後、不要であれば削除しておきましょう

***

「[Serverspec ハンズオン資料（Jenkins おじさんお届け編）](http://qiita.com/inokappa/items/f9f624ec5ea01937b840)」に続く...