serverspec+ansible+vagrant-vsphereでやるテスト駆動サーバー運用

この記事は[NIFTY Advent Calendar 2016](http://qiita.com/advent-calendar/2016/nifty) 20日目の記事です。
昨日は二つの意味で自分の同僚である@megane42さんの[アイドルに囲まれて仕事がしたかった](http://qiita.com/megane42/items/46656b02b87a50f4e718)という記事でした。
最近@megane42さんからアイドル駆動開発という素晴らしいライフハックを教えていただき、自分も開発で合法的（？）に担当アイドルのダイレクトマーケティングがしたいと思っている今日この頃ですが、今回は全く関係ない話です。謹んでお詫び申し上げます。

# はじめに
自分が所属している部では自前でvCenterを持って運用していて（社内では珍しい（？）脱ニフティクラウドです）、サーバーの構成管理をするために以下のものを使っています。

* serverspec
* ansible
* vagrant-vsphere

本日はこれらをどのように活用してテスト駆動サーバー運用を行なっているかを書きます。

# テスト駆動サーバー運用のメリット
商用の環境も検証の環境もこれから作るサーバーも全てserverspecのテストを満たしている状態が保証されます。
検証も商用のサーバーも毎日定時にserverspecのテストをかけていますので、その状態は常に保証されています。
これによって、「商用と検証の設定違いでメンテ失敗しましたー」 とか 「サーバー構築したんですけど、設定間違ったまま本番投入しちゃいましたーﾃﾍｯ♩」みたいな悲劇が減ります。
あと、副次的な効果として、いちいち設定を確認しにサーバーに入らなくてよくなりました。自分のチームではサーバー数百台管理していますが、1台1台sshで入って目視で設定確認して〜なんてことをやってたらめんどくさいですね。

# テスト駆動サーバー運用概要
やりたいことはサーバーの構成管理でした。
各ツールを以下の用途として使っています。

* vagrant-vsphere、ansible：新規サーバー構築・メンテ時の設定変更
* serverspec：日々の運用におけるサーバーの設定チェック、新規サーバー構築時の設定チェック

構築時のvagrant-vsphere provisioningとansible playbookを洗練するための流れが以下になります。

1. serverspecのテストを書く
2. 構築用vagrant-vsphere provisioning、ansible playbookを書く
3. vagrant upでサーバーコピー
4. ansible playbook実行
5. serverspecのテストを実行
6. vagrant destroyでサーバー削除
7. テスト結果を見てvagrant-vsphere provisioning、ansible playbookを修正
7. 以下テストが全て通るようになるまで繰り返し

テスト駆動というからにはテストファーストです。serverspecのテストを一番初めに書きます。
当然このテストを満たすようにvagrant-vsphere provisioningとansible playbookを書いていきますので一番重要なところです。
serverspecやansibleについては他に良いドキュメントがあると思うのでここでは割愛します。ググってください。
vagrant-vsphereについては下で少し書きます。

現在3〜6まではjenkinsのジョブで全て自動化してあり、最後にテストの結果をメールで送ってくれます（あとslack連携がしたい）。
そのテスト結果を元にplaybook等を修正し、またjenkinsのジョブ実行（以下エンドレス という流れです。

本番環境に新規サーバーをデプロイするときは6のサーバー削除をやらないだけになっており、全く同じ手順で構築され、全く同じ設定であることが保証されています。

メンテ用のansible playbookを書くときも基本的に同じ流れとなります。

1. 設定変更後のserverspecのテストを書く
2. 設定変更・切り戻し用ansible playbook作成
3. ansible playbook実行
4. serverspec（設定変更後用）テスト実行
5. 切り戻し用ansible playbook実行
6. serverspec（設定変更前）テスト実行
7. 設定変更・切り戻し用ansible playbook修正

4で設定変更後のテストを実施し、それを元に設定変更用ansible playbookを修正していきます。
同様に6で設定変更前のテストを実施し、それを元に切り戻し用ansible playbookを修正していきます。
設定変更と切り戻し同時にできるのがミソです。
これで切り戻し準備も万全ですね。切り戻ししないのが一番ですけど（切実）。

# vagrant-vsphereについて
vagrant-vsphereはvagrantのプラグインで、vagrant up、vagrant halt、vagrant destroyといったコマンドからvCenterのサーバーを操作できます。極力全てをコマンドラインから済ませたい自分には嬉しいです。
provisioningについても基本的に通常のvagrant provisioningと同じように実行できます。
使い方は以下の記事が詳しいです。

* [vagrant-vsphereでvCenter+ESXi環境をコントロール](http://qiita.com/kinunori/items/a036167e32a05e53d291)
* [VagrantでVMware vSphere(ESXi)上のVMを操作する「vagrant-vsphere」を試してみた。](http://tkak.hatenablog.com/entry/2014/06/01/144610)

以下はVagrantfileの例です。

```
load "hosts"

Vagrant.configure("2") do |config|
  config.vm.box = 'vsphere'
  config.vm.box_url = './dummy.box'
  config.ssh.pty = true

  config.vm.provision :shell, :path => "/path/to/provisioning/scripts"

  # synced folder
  config.vm.synced_folder ".", "/vagrant", disabled: true, create: false

  # ssh host when vagrant up
  config.ssh.port = '22'
  config.ssh.username = 'username'
  config.ssh.private_key_path = "/path/to/ssh/key"

  # vsphere client setting
  config.vm.provider :vsphere do |vsphere|
    # vCenter
    vsphere.host = 'vcenter address'
    # use cloning
    vsphere.clone_from_vm = true
    # custamization spec
    vsphere.customization_spec_name = 'customization spec'
    # original vm name
    vsphere.template_name = 'path/to/copy_vm'
    # vCenter name
    vsphere.data_center_name = 'datacenter name'
    # vm base path
    vsphere.vm_base_path = "path/to/create_vm"

    # vlan
    vsphere.vlan = "vlan name"
    # cpu
    vsphere.cpu_count = "CPU num"
    # memory
    vsphere.memory_mb = "Mem num"

    vsphere.user = 'username'
    #vsphere.password = ENV["VSPHERE_PASSWORD"]
    vsphere.password = 'password'
    # ignore warning of certificate
    #vsphere.insecure = true

  end

end
```
一行目で、作成するホストの情報が書いてあるhostsという名前のファイルを読み込んでいます。
hostsファイルは以下のようになっています。

```
Vagrant.configure("2") do |config|

  config.vm.define "vm name" do |server|

    server.vm.network 'private_network', ip: 'private ip'
    server.ssh.host = 'private ip'

    # vagrant-vsphere setting
    server.vm.provider :vsphere do |vsphere|
      # vm name
      vsphere.name = 'vm name'
    end
  end

end
```
このhostファイルには複数サーバー書くことができ、vagrant-vsphereのコマンドで同時に複数サーバー操作できます。
vagrant upで一気にサーバー作ってvagrant destroyで全削除とかやったりすると気持ち良いのでオススメです。

packerなど他のデプロイツールもありましたが、vagrantの方が自分が慣れているからという理由で今は使っています。
最近だとTerraformとかottoとか新しいツールもでてきていますし、そちらも触ってみたいですね。

# serverspecの小技
serverspecの小技も一つここでご紹介しておきます。
serverspecではServerspec::Helper::Propertiesというモジュールによってテストの中で変数を使えるようになります。
それについては以下の記事が詳しいですね（他にも探せばいっぱい出てくる）。

* [serverspecの高度なヒント](http://qiita.com/ando-masaki/items/0269daa47fe7ffb4c723)

上記ではpropertyというHashにinventoryファイルの中身を格納してホストごとに変数を設定していますが、例えば、様々な環境に対して同一のテストを使いまわしたいなんてときには、その環境ごとの変数だったり、全ての環境で同じ変数などを設定したいなんてことがあったりします（少なくとも自分はある）。
というわけで、なんとなくそんな感じのができるあれを作りました。ソースは以下です。

https://github.com/licht110/serversepc

以下、簡単な解説です。
lib/extra_properties.rbでSpecinfra::Propertiesを継承してExtraPropertiesというクラスを生成しています。
このクラスは追加で2つの変数を持っています。

```ruby:lib/extra_properties.rb
require 'singleton'

module Specinfra
  class ExtraProperties < Specinfra::Properties
    def initialize
      @global_prop = {}
      @group_prop = {}
    end
    def global_properties(global_prop=nil)
      if ! global_prop.nil?
        @global_prop = global_prop
      end
      @global_prop
    end
    def group_properties(group_prop=nil)
      if ! group_prop.nil?
        @group_prop = group_prop
      end
      @group_prop
    end
  end
end
```

lib/helper/extra_properties.rbには上記クラスの変数に値を格納するメソッドを持ったモジュールがあります。

```ruby:lib/helper/extra_properties.rb
require 'specinfra/properties'

module Specinfra
  module Helper
    module ExtraProperties
      def property
        Specinfra::ExtraProperties.instance.properties
      end
      def group_property
        Specinfra::ExtraProperties.instance.group_properties
      end
      def global_property
        Specinfra::ExtraProperties.instance.global_properties
      end
      def set_property(prop)
        Specinfra::ExtraProperties.instance.properties(prop)
      end
      def set_group_property(prop)
        Specinfra::ExtraProperties.instance.group_properties(prop)
      end
      def set_global_property(prop)
        Specinfra::ExtraProperties.instance.global_properties(prop)
      end
    end
  end
end
```

spec/spec_helper.rbで上記クラスとモジュールを読み込んで、set_group_propertyとset_global_propertyでそれぞれファイルから読み込んだ変数を格納しているだけです。単純です。

```ruby:spec/spec_helper.rb
require 'serverspec'
require 'serverspec_extra'
require 'net/ssh'
require 'yaml'

include Specinfra::Helper::ExtraProperties

set :backend, :ssh

# load vars from yaml file
host_vars = YAML.load_file('inventories/hosts')
group_vars = YAML.load_file('inventories/group_vars')
global_vars = YAML.load_file('inventories/global_vars')

# set vars
set_group_property group_vars
set_global_property global_vars

RSpec.configure do |c|
  c.host = ENV['TARGET_HOST']
  set_property host_vars[c.host][:vars]
end
...
```

これでテストコード上でgroup_propertyとglobal_propertyという変数が使えるようになります。

見ていただくと分かると思いますが、ただ単に使える変数を増やしましたというだけですね。自分はglobal_propertyにinventories/global_varsを読み込ませて全ての環境で同一の値をもつ変数、group_propertyにinventories/group_varsを読み込ませて環境ごとに違う値をもつ変数という風に使っています。

# 最後に
以上、テスト駆動サーバー運用でした。
テスト駆動という特性上、やはりテストがキモです。
serverspecは結構自由度が高く、Rakefileやspec_helper.rbの書き方次第で結構いろいろなことができると思います。
それぞれのベストプラクティスなディレクトリ構成、inventoryファイルの書き方などなどを探ることでより良いテストライフを送ってください。

明日は@hideakihalさんの記事です。お楽しみに。

おわりだよ〜(o・∇・o)