インフラ構成管理ツール(Chef,Puppet,Ansible)の複数OS、複数バージョンOSへのテストを自動化する

# はじめに

Chef,Puppet,Ansibleなどの構成管理ツールで作ったスクリプトのテスト自動化について考えてみました。
テストスクリプトについては個人的にはServerspec一択なのですが、複数のOS、同一のOSでも複数バージョンのテストを手動でやり続けるのは限界があります。
そこで今回はAWSとJenkinsとVagrantを使ってテストを自動化してみました。
設定するときに特にはまりやすいポイントについて解説しています。

# 概要

* Vagrant経由でEC2を起動し、ansible,serverspecのテストを流します。テストが成功すればvagrant経由でEC2を停止する。というのが大きな流れになります。
* EC2で起動するインスタンスのAMIは予め選定し、使いやすいようにカスタマイズしておきます。
* 私がカスタマイズした点は一つだけで、ローカルの環境でもVagrantを使っているため、vagrantユーザを追加して、ローカルでもサーバでもvagrantユーザでスクリプトを流せるようにしておきました。あとは何も手を加えていません。

![スクリーンショット 2015-07-27 13.49.04.png](https://qiita-image-store.s3.amazonaws.com/0/57686/d9d37fde-a691-e122-57c2-d5a2c27dc003.png)

* 自動化するためのCIサーバとしてJenkinsを採用しました。定期的にコマンドが叩けることとタスクの依存関係が決められればよかったのでJenkinsで十分でした。cronでもやろうと思えば問題なくできるはずです。
* テスト対象サーバはVagrant経由でAWS EC2を利用しています。当初Jenkinsと同一のサーバ上にDockerを起動させ、そこに対してテストしようと思ったのですが、構成管理スクリプト内でLinuxのカーネルを触るものなどがあったのでDockerはふさわしくないと判断しました。また、CentOSとRedhat両方テストしたかったので手っ取り早くリソースが確保できるAWSを採用しています。

# 設定手順

jenkinsのインストールはyumで一発なので割愛します。ここからは/var/lib/jenkinsにJenkinsがインストールされている前提で説明します。

## AMIの用意

* RHEL6.6のAMIを探します。「Community AMIs」に色々なバージョンのAMIが転がっていますのでAMIからインスタンスを起動します。

![スクリーンショット 2015-07-27 14.13.22.png](https://qiita-image-store.s3.amazonaws.com/0/57686/15bebf2e-1094-c331-8e0f-dc4a44c5761c.png)

* 起動したら、vagrantユーザを作成し、鍵を作ってid_rsaをダウンロードしておきます。rhel66.pemという名前でダウンロードしました。

```
useradd -G wheel vagrant
cd /home/vagrant/
mkdir .ssh
cd .ssh
ssh-keygen -t rsa
mv id_rsa.pub authorized_keys
```

* パスワードなしでsudoできるようにしておきます。ansibleもserverspecもsudo時のパスワードを設定できますので、そちらで設定を行う人は不要です。

```visudo
vagrant ALL=(ALL) NOPASSWD: ALL
```

* ここまでやったらAWSの管理画面からAMIを作成しておきましょう。AMIのIDはあとで使うのでメモっておきます。

## vagrantでEC2を起動するための準備

１． vagrantは[こちら](https://www.vagrantup.com/downloads.html)からダウンロードしてrpmでインストールしておきます。

* ちなみにvagrantのコマンドはJenkinsから起動させますのでjenkinsユーザでインストールさせる必要がありますのでお間違えなく。


２．vagrantからawsを扱うにはvagrant-awsを利用するのでインストールしておきます。

```
$ vagrant plugin install vagrant-aws
```

３．vagrantfileにアクセスキー、シークレットアクセスキーなどの情報が入るのはちょっと嫌なのでdotenvもインストールしておきます。このプラグインを使うことで.envというファイルから変数を取得できるようになります。

```
$ vagrant plugin install dotenv
```

４．インストールされていることを確認しておきましょう。

```
$ sudo -u jenkins vagrant plugin list
dotenv (2.0.2)
vagrant-aws (0.6.0)
vagrant-share (1.1.4, system)
```

５．jenkinsで適当なプロジェクトを作成します。

６．プロジェクトのワークスペース直下に.envファイルVagrantファイルを作成します。

```.env
[ec2-user@ip-10-0-0-238 workspace]$ cat .env 
VAGRANT_DEFAULT_PROVIDER = "aws"
AWS_ACCESS_KEY_ID = "アクセスキー"
AWS_SECRET_ACCESS_KEY = "シークレットアクセスキー"
AWS_KEYPAIR_NAME = "AWSキーペア名"
AWS_SSH_USERNAME = "ec2-user"
AWS_SSH_KEY = "キーペア名に対応するキーファイルへのパス"
```

```
[ec2-user@ip-10-0-0-238 workspace]$ cat Vagrantfile.rhel66 
VAGRANTFILE_API_VERSION = "2"

Dotenv.load

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "dummy"
  config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
  config.vm.synced_folder ".", "/vagrant", disabled: true

  config.vm.provider "aws" do |aws, override|
    aws.access_key_id = ENV['AWS_ACCESS_KEY_ID']
    aws.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
    aws.keypair_name = ENV['AWS_KEYPAIR_NAME']
    override.ssh.username = ENV['AWS_SSH_USERNAME']
    override.ssh.private_key_path = ENV['AWS_SSH_KEY']

    aws.region = "ap-northeast-1"
    aws.instance_type = "t2.micro"
    aws.ami = "先ほど作ったAMIのID（ami-xxxxxxx)"
    aws.subnet_id = "subnet-xxxxxxxx"
    aws.security_groups = "sg-xxxxxxxx"
    aws.private_ip_address = '10.0.0.100'
    aws.elastic_ip = "true or false or 自分の指定したいIP"
    aws.tags = {
      'Name' => 'Lego Test RHEL6.6',
    }
  end
end
```

## jenkinsでansible、Serverspecを動かす準備

### ansible

ansibleで注意したい点は１つだけです。ssh接続用のユーザの設定はInventryファイルに記載が出来ますのでそこに記載しておきます。sudoのパスワードがサーバ側で設定されている場合にはansible_sudo_passも設定しておきましょう。
接続する為の秘密鍵については、ansible-playbookコマンドの引数で指定できるためここでの設定は不要です。

```
[webserver]
10.0.0.100

[all:vars]
ansible_ssh_port=22
ansible_ssh_user=vagrant
#ansible_ssh_pass=password
#ansible_sudo_pass=password
```

### serverspec

serverspecで鍵を使ってSSH接続するにはspec_helper.rbの修正が必要です。修正したバージョンのファイルの内容をそのまま貼っておきますのでコピペしてください。

```spec_helper.rb
require 'serverspec'
require 'net/ssh'

set :backend, :ssh

if ENV['ASK_SUDO_PASSWORD']
  begin
    require 'highline/import'
  rescue LoadError
    fail "highline is not available. Try installing it."
  end
  set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
else
  set :sudo_password, ENV['SUDO_PASSWORD']
end

host = ENV['TARGET_HOST']

options = Net::SSH::Config.for(host)

options[:keys] = ENV['KEY'];
options[:user] = ENV['USER'] || Etc.getLogin
#options[:user] ||= Etc.getlogin

set :host,        options[:host_name] || host
set :ssh_options, options
set :request_pty, true

# 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'
```

## Jenkinsの設定

* といっても、順番に実行していくだけです。コメントを記載しておきましたのでそちらを参照ください。

```
# known_hostのクリア
ssh-keygen -R 10.0.0.100

# Vagrantfileを使ってEC2を起動する
cp Vagrantfile.rhel66 Vagrantfile
vagrant up

# 構成管理スクリプトを取得する
cp -rf /xxxxxxxxxxxxx/webserver .
cd ${WORKSPACE}/webserver
# ansibleを実行する。--private-keyに秘密鍵を設定します。
ansible-playbook -i hosts site.yml --private-key=${WORKSPACE}/rhel66.pem

# serverspecを実行します。jenkinsは/sbin/nologin/ユーザなのでshell内でGEM_PATHを設定しておきます。
export PATH=$PATH:/opt/ruby-2.0.0/bin/
export RUBYLIB=/opt/ruby-2.0.0/lib/
export GEM_HOME=/opt/ruby-2.0.0/lib/ruby/gems/2.0.0/
rake spec USER=vagrant KEY=${WORKSPACE}/rhel66.pem

# EC2を破棄します。
cd ${WORKSPACE}
vagrant destroy -f

# EC2破棄後、terminatedになるまで60秒程度かかるので余裕をみて90秒スリープする。
sleep 90
```

## rspecの結果の可視化

* rspecの結果を可視化するには[rspec_junit_formatter](https://github.com/sj26/rspec_junit_formatter)を使うと幸せになります。
* まずはインストール

```
gem install rspec_junit_formatter
```

* 次にrspecの引数に渡します。.rspecファイルに以下を追記します。

```
--format RspecJunitFormatter
--out ../rspec_result.xml
```

* Jenkinsでjunitの結果を表示できるので、ビルド後の処理でJUnitテスト結果の集計を押します。

![2.lego-test-centos67 Config [Jenkins] 2016-06-13 17-00-51.png](https://qiita-image-store.s3.amazonaws.com/0/57686/d3a0e5b8-1fce-6313-b5b0-6622fd0ee76b.png)

* あとは実行すればできあがり

![2.lego-test-centos67 #351 spec.webserver [Jenkins] 2016-06-13 17-03-17.png](https://qiita-image-store.s3.amazonaws.com/0/57686/3999c498-b8e6-1245-2bad-0ba63fe41f06.png)


## 補足

* jenkinsが/sbin/nologin/ユーザという部分でGEMを読みに行ってくれなくてはまりました。結果shの中で環境変数定義してあげればOKだったというオチでした。上記のパス通りServerspecを利用したい場合は、[こちらのロール](https://github.com/uzresk/ansible-serverspec.git)をお使いください。

# まとめ

* 一度仕組みを作ってしまえば、1OSあたり用意するのにせいぜい２時間程度で出来るようになります。この手間で、デグレードの防止、新バージョンへの対応が速攻できるというのは魅力ですよね。
* 今回はじめてvagrant awsを利用してみましたが、これは想像以上に便利でした。手放せなくなりそうです。これまでAWSでポチポチおすだけでインスタンスができるのは凄い便利だなと思っていたのですが、subnetやセキュリティグループの選択などルーチン化してくるとめんどくさくてしょうがないのです。