ansible_specを利用してAnsibleとServerspecを同一ディレクトリで管理してみる

この記事は[リンク情報システム](https://www.lis.co.jp/)([Facebook](https://ja-jp.facebook.com/lis.co.jp/))が主催するイベント「[Tech Connect! Autumn](https://qiita.com/dgkz/items/4c3a449f3168d6c787cf)」のリレー記事です。
「Tech Connect! Autumn」は engineer.hanzomon のグループメンバによってリレーされます。

本記事は7日目、9/16(日)分です。ちょうど折り返しですね。

## きっかけ
Ansible で環境構築しServerspec で自動テスト、みたいなことをやっているのですが、

* Ansible は**Role 単位**、Serverspec は**Host 単位**で管理されているので、管理が大変
* Ansible の設定とServerspec の設定で**二重化**されてしまい、管理が大変

ということで、自前でServerspec のRakefile やspec_helper.rb 修整したりしてなんとかServerspec もRole 単位で管理できないかとごにょごにょやっていたのですが、イマイチしっくりきていませんでした。
そんな時にansible_specなるものの存在を知り、これいいじゃん！となったので、ご紹介したいと思います。

## ansible_spec とは？
**[ansible_spec](https://github.com/volanja/ansible_spec) は、[@volanja](https://qiita.com/volanja) 氏が作成した、Serverspec をAnsible 用の設定ファイルを使って管理できるように作成されたGem です。**
これにより、Ansible とServerspec を同一ディレクトリでいい感じに管理することができるようになります。

## 使い方
実際に使ってみてどんな塩梅か確認してみましょう。
環境は

* Red Hat Enterprise Linux 7.2
* Python 2.7.5
* Ruby 2.5.1
* Ansible 2.6.3
* Serverspec 2.41.3
* ansible_spec 0.2.25

とします。

超簡単な動作確認なので、ここでは**Web サーバにnginx をインストールする**という感じでやってみます。

### 1. Ansible のPlaybook をつくる

ざっくりディレクトリ構造は以下のようになります。

```
ansible_sample
├─ hosts
├─ site.yml
└─ roles
    └─ nginx
        ├─ handlers
        │  └─ main.yml
        ├─ tasks
        │  └─ main.yml
        ├─ vars
        │  └─ main.yml
        └─ templates
           └─ nginx.conf.j2
```

対象ホストをhosts で管理し、
nginx の環境構築に関わる情報をroles/nginx 配下のディレクトリで管理します。

タスクの中身としてはこんな感じで。

```yaml:roles/nginx/tasks/main.yml
---
- name: install epel repository
  yum:
    name: http://ftp-srv2.kddilabs.jp/Linux/distributions/fedora/epel/7/x86_64/Packages/e/epel-release-7-11.noarch.rpm
    state: present

- name: install nginx
  yum:
    name: nginx
    state: present

- name: enable service nginx
  systemd:
    name: nginx
    state: started
    daemon_reload: yes
    enabled: yes

- name: configration nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify:
    - restart nginx
```

ほんとざっくりインストールするだけという感じで。
動作確認もしてみましょう。

```
$ ansible-playbook -i hosts site.yml

PLAY [web] *************************************************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************************
ok: [webserver]

TASK [nginx : install epel repository] *********************************************************************************************************************************
changed: [webserver]

TASK [nginx : install nginx] *******************************************************************************************************************************************
changed: [webserver]

TASK [nginx : enable service nginx] ************************************************************************************************************************************
changed: [webserver]

TASK [nginx : configration nginx] **************************************************************************************************************************************
changed: [webserver]

RUNNING HANDLER [nginx : restart nginx] ********************************************************************************************************************************
changed: [webserver]

PLAY RECAP *************************************************************************************************************************************************************
webserver                  : ok=6    changed=5    unreachable=0    failed=0
```
とりあえずAnsible は動作しました。

### 2. Serverspec でテストを書く

ほんとは先にこっちやるべきなんですが、まぁ動作確認なんで…
素のServerspec ですと、ディレクトリ構造は以下のようになります。

```
serverspec_sample
├── Rakefile
└── spec
    ├── webserver
    │   └── nginx_spec.rb
    └── spec_helper.rb
```

こちらは、対象ホストをspec 配下のサブディレクトリ名で管理し、
nginx のテストコードをspec 配下のnginx_spec.rb で管理します。

テストの中身はこんな感じで。

```ruby:spec/webserver/nginx_spec.rb
require 'spec_helper'

describe package('nginx') do
  it { should be_installed }
end

describe service('nginx') do
  it { should be_enabled }
  it { should be_running }
end

describe port(80) do
  it { should be_listening }
end
```
プロセスが起きてて80番で受け付けてること確認するだけ。
これも動作確認。

```
$ rake spec:webserver
/root/.rbenv/versions/2.5.1/bin/ruby -I/root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-support-3.8.0/lib:/root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.0/lib /root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.0/exe/rspec --pattern spec/webserver/\*_spec.rb

Package "nginx"
  should be installed

Service "nginx"
  should be enabled
  should be running

Port "80"
  should be listening

Finished in 1.34 seconds (files took 0.35733 seconds to load)
4 examples, 0 failures

```
テストは通りました。

いちおうやりたいことはこれでできているわけですが、これがもっとたくさんのRole であったりHost であったりがあるとそれはもう管理がごちゃごちゃになるわけです。
そこで**ansible_spec** に登場していただきましょう。

### 3. ansible_spec をインストール
インストールはgem で行います。

```
$ gem install ansible_spec
```
インストールできたらAnsible の管理ディレクトリで以下のコマンドを実行します。

```
$ ansiblespec-init
                create  spec
                create  spec/spec_helper.rb
                create  Rakefile
                create  .ansiblespec
                create  .rspec
```
Serverspec 用のRakefile とspec/spec_helper.rb が作成されるはずです。
ここまでのディレクトリ構造は以下となっています。

```diff
 ansible_sample
 ├─ hosts
 ├─ site.yml
 ├─ roles
 │   └─ nginx
 │       ├─ handlers
 │       │  └─ main.yml
 │       ├─ tasks
 │       │  └─ main.yml
 │       ├─ vars
 │       │  └─ main.yml
 │       └─ templates
 │          └─ nginx.conf.j2
+├── Rakefile
+└── spec
+    └── spec_helper.rb
```
ここに先程作成したspec ファイルを格納します。
格納先はroles/_rolename_/spec/ 配下となります。つまりこうですね。

```diff
 ansible_sample
 ├─ hosts
 ├─ site.yml
 ├─ roles
 │   └─ nginx
 │       ├─ handlers
 │       │  └─ main.yml
 │       ├─ tasks
 │       │  └─ main.yml
 │       ├─ vars
 │       │  └─ main.yml
 │       ├─ templates
 │       │  └─ nginx.conf.j2
+│       └─ spec
+│          └─ nginx_spec.rb
 ├── Rakefile
 └── spec
     └── spec_helper.rb
```
Serverspec のテストコードをAnsible 管理化のRoles 配下にまとめることができました。
そうそう、これがやりたかったんだよ。

### 4. ansible_spec 導入状態でServerspecのテストを実行する
テストの実行コマンドを確認してみましょう。

```
$ rake -T
rake all             # Run serverspec to all test
rake serverspec:web  # Run serverspec for web
```
コマンド確認できたら実際叩いてみます。

```
$ rake all
rake all
Run serverspec for web to {"name"=>"webserver", "port"=>22, "uri"=>"webserver"}
/root/.rbenv/versions/2.5.1/bin/ruby -I/root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-support-3.8.0/lib:/root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.0/lib /root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.0/exe/rspec --pattern \{roles\}/\{nginx\}/spec/\*_spec.rb

Package "nginx"
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
  should be installed (FAILED - 1)

Service "nginx"
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
  should be enabled (FAILED - 2)
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
  should be running (FAILED - 3)

Port "80"
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
No backend type is specified. Fall back to :exec type.
  should be listening (FAILED - 4)

Failures:

  1) Package "nginx" should be installed
     On host `webserver'
     Failure/Error: it { should be_installed }
       expected Package "nginx" to be installed
       /bin/sh -c rpm\ -q\ nginx
       package nginx is not installed

     # ./roles/nginx/spec/nginx_spec.rb:4:in `block (2 levels) in <top (required)>'

  2) Service "nginx" should be enabled
     On host `webserver'
     Failure/Error: it { should be_enabled }
       expected Service "nginx" to be enabled
       /bin/sh -c systemctl\ --quiet\ is-enabled\ nginx

     # ./roles/nginx/spec/nginx_spec.rb:8:in `block (2 levels) in <top (required)>'

  3) Service "nginx" should be running
     On host `webserver'
     Failure/Error: it { should be_running }
       expected Service "nginx" to be running
       /bin/sh -c systemctl\ is-active\ nginx
       unknown

     # ./roles/nginx/spec/nginx_spec.rb:9:in `block (2 levels) in <top (required)>'

  4) Port "80" should be listening
     On host `webserver'
     Failure/Error: it { should be_listening }
       expected Port "80" to be listening
       /bin/sh -c ss\ -tunl\ \|\ grep\ --\ :80\\\

     # ./roles/nginx/spec/nginx_spec.rb:13:in `block (2 levels) in <top (required)>'

Finished in 0.10702 seconds (files took 0.51083 seconds to load)
4 examples, 4 failures

Failed examples:

rspec ./roles/nginx/spec/nginx_spec.rb:4 # Package "nginx" should be installed
rspec ./roles/nginx/spec/nginx_spec.rb:8 # Service "nginx" should be enabled
rspec ./roles/nginx/spec/nginx_spec.rb:9 # Service "nginx" should be running
rspec ./roles/nginx/spec/nginx_spec.rb:13 # Port "80" should be listening

/root/.rbenv/versions/2.5.1/bin/ruby -I/root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-support-3.8.0/lib:/root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.0/lib /root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.0/exe/rspec --pattern \{roles\}/\{nginx\}/spec/\*_spec.rb failed
```
ありゃ、なんかエラーになりました。
「No backend type is specified. Fall back to :exec type.」言うてるので、どうもssh でターゲットHost に接続しないでローカルに対して動作しているみたいです。
ansible_spec で生成したspec/spec_helper.rb を、Serverspec で生成したspec/spec_helper.rb で置き換えればとりあえずは動作するようです。(なんか設定漏れてる？)

で、改めて実行。

```
$ rake all
Run serverspec for web to {"name"=>"webserver", "port"=>22, "uri"=>"webserver"}
/root/.rbenv/versions/2.5.1/bin/ruby -I/root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-support-3.8.0/lib:/root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.0/lib /root/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.0/exe/rspec --pattern \{roles\}/\{nginx\}/spec/\*_spec.rb

Package "nginx"
  should be installed

Service "nginx"
  should be enabled
  should be running

Port "80"
  should be listening

Finished in 1.05 seconds (files took 0.35596 seconds to load)
4 examples, 0 failures
```
うまくいきました。とりあえずはこれで動作確認完了になります。

## 結局何がうれしいの？
Serverspec をAnsible のplaybook 定義に従いRole を割り当て、Role 配下のspec でテストすることができるようになります。
ansible_spec を使用しない場合はServerspec のspec をHost 単位で割り振っていたので、Role の定義変更をした際は各Host 配下のspec もすべて変更する必要がありましたが、spec をRole 配下で管理できればそのようなこともする必要がなくなり、管理が容易になります。

また、Ansible の変数をServerspec で使用することもできます。
Ansible とServerspec で定義の二重管理が不要になるということです。
同名変数の読込順序は[Ansible の読込順序](https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable)に従います。
ただし、2018/09/16現在、一部変数についてはサポートされていないようです。

## まとめ
ansible_spec 、オススメです。
Ansible / Serverspec を使用している方なら導入して損はないと思います。
管理を簡便化して効率よく自動化していけるようにしましょう。

お読みいただきありがとうございました。
他のTechConnect!Autumn の記事も楽しみにしています。
明日は[@hura](https://qiita.com/hura) さんです。

---
#### 関連リンク

- [Ansible is Simple IT Automation](http://www.ansible.com/)
- [Serverspec](https://serverspec.org/)
- [GitHub - ansible_spec](https://github.com/volanja/ansible_spec)