Windows 環境で Vagrant + Chef + Serverspec を使って Windows Server マシンのテスト駆動開発を行う #GetChef_ja

以下の記事に影響を受け、Windows 環境上に Vagrant と Chef、Serverspec を使った Windows Server マシンのテスト駆動開発環境を作った際の手順を自分用のメモとしてまとめました。

- [Vagrant+Puppet+Serverspec を使って Windows Server をテスト駆動開発する - 終劇 CPU 100%](http://higaktot.hatenablog.com/entry/2014/09/15/151618)
- [Chef Solo / Vagrant / serverspecでインフラのTDDを行う - Qiita](http://qiita.com/s-kiriki/items/c42fc54df07d7bee1a01)

Vagrant と Chef、Serverspec のインストールと操作は、PowerChef という PowerShell モジュールを作成しましたので、これを使います。

- [PowerChef - https://github.com/kukita/PowerChef](https://github.com/kukita/PowerChef)

## 1: PowerChef モジュールのインストール

まず、PowerChef モジュールをローカルマシンにインストールします。

### 手順 1-1: 'Windows PowerShell' を起動します

```txt:ホストマシン
スタートメニューから `Windows PowerShell` を起動します
```

### 手順 1-2: PowerShellスクリプトの実行セキュリティ・ポリシーを RemoteSigned に変更します

```ps1:ホストマシン
PS> Set-ExecutionPolicy RemoteSigned
PS> Get-ExecutionPolicy

RemoteSigned
```

→ RemoteSigned と表示されることを確かめます。

### 手順 1-3: インストールスクリプト（`install.ps1`）を使って、GitHub上からモジュールをダウンロードします

```ps1:ホストマシン
PS> Invoke-Expression (New-Object System.Net.WebClient).DownloadString("https://raw.github.com/kukita/PowerChef/master/install.ps1")
```

→ `Installation of 'PowerChef' has finished successfully.` と表示されればインストールの成功です。

## 2: ホストマシン環境のセットアップ

次に、ローカルマシンをホストマシン環境としてセットアップします。

### 手順 2-1: PowerChef モジュールをインポートします

```ps1:ホストマシン
PS> Import-Module "PowerChef"
PS> Get-Module
ModuleType Version    Name
---------- -------    ----
Manifest   3.1.0.0    Microsoft.PowerShell.Management
Manifest   3.1.0.0    Microsoft.PowerShell.Utility
Manifest   0.5.2      PowerChef
```

→ `PowerChef` が表示されていることを確かめます。

### 手順 2-2: `SetUp-ChefWorkstation` ファンクションを使って、ローカルマシンを WorkStation 環境としてセットアップします

```txt:ホストマシン
PS> SetUp-ChefWorkstation -PowerChefHomePath "D:\PowerChef"
※30分くらいかかります。最後に WinRM のセキュリティ設定に関して聞かれるので [Y]Yes を選択します。
```

次のソフトウェアがインストールされます。

* Chocolatey [[https://chocolatey.org/]](https://chocolatey.org/)
* Oracle VM VitrualBox [[https://www.virtualbox.org/]](https://www.virtualbox.org/)
* Git for Windows [[http://msysgit.github.io/]](http://msysgit.github.io/)
* posh-git [[https://github.com/dahlbyk/posh-git]](https://github.com/dahlbyk/posh-git)
* Chef [[http://www.getchef.com/chef/]](http://www.getchef.com/chef/)
* Serverspec [[http://serverspec.org/]](http://serverspec.org/)
* Bento [[https://github.com/opscode/bento]](https://github.com/opscode/bento)
* em-winrm [[https://github.com/opscode/em-winrm]](https://github.com/opscode/em-winrm)
* Oracle Java Runtime [[http://java.com/en/download/]](http://java.com/en/download/)
* Vagrant [[http://www.vagrantup.com/]](http://www.vagrantup.com/)
* 7-Zip [[http://www.7-zip.org/]](http://www.7-zip.org/)
* CwRsync [[https://www.itefix.no/i2/cwrsync]](https://www.itefix.no/i2/cwrsync)
* Berkshelf [[http://berkshelf.com/]](http://berkshelf.com/)

### 手順 2-3: `Open-HomeVagrantfile` ファンクションを使って、Vagrant ホームディレクトリの `Vagrantfile` を編集します

```ps1:ホストマシン
PS> Open-HomeVagrantfile
```

→ 仮想化支援機能の有無等ホストマシンのスペックに合わせてファイルを編集しファイルを閉じます。

## 3: Windows Server 2012 R2 の Box を作成する

Vagrant 上で動作する Windows Server の Box を作成します。

### 手順 3-1: 'Oracle VM VirtualBox' を起動します

```txt:ホストマシン
スタートメニューから 'Oracle VM VirtualBox' を起動します
```

### 手順 3-2: `Show-VeeweeDefinitionVboxList` ファンクションを使って、Bento（Veewee）で定義済みの OS の一覧を表示します

```ps1:ホストマシン
PS> Show-VeeweeDefinitionVboxList
windows-2008r2-standard
windows-2012-standard
windows-2012r2-standard
windows-7-enterprise
windows-8-enterprise
```

### 手順 3-3: `Invoke-VeeweeVboxBuild` ファンクションを使って、Bento（Veewee）を実行し仮想マシン（VirtualBox のゲストOS）を作製します

```ps1:ホストマシン
PS> Invoke-VeeweeVboxBuild "windows-2012r2-standard"
```

→ コンソール画面が立ち上がり Windows のインストールがはじまれば成功です。

※ISOイメージのダウンロードで失敗する場合は、手動でダウンロードを行った後、仮想マシンを削除してから再度コマンドを実行してください。

### 手順 3-4: `New-VagrantBox` ファンクションを使って、作成した VirtualBox のゲスト OS をベースに Vagrant の Box を作製します

```ps1:ホストマシン
PS> New-VagrantBox "windows-2012r2-standard"
PS> & vagrant box list

windows-2012r2-standard               (virtualbox, 0)
```

→ `windows-2012r2-standard` が表示されれば成功です。このファンクションは任意のゲストOSをBox化することができます。必要がなければ、ベースとして作成した仮想マシンは削除してください。

## 4: 仮想マシン（VirtualBox のゲスト OS）を作成する

開発/テスト対象となる仮想マシンを作成します。

### 手順 4-1: `New-ChefRepo` ファンクションを使って、"SampleRepo" という名前のリポジトリを作成します

```txt:ホストマシン
PS> New-ChefRepo "SampleRepo"
PS> Show-ChefRepoList

Name        CreationTime        LastWriteTime       LastAccessTime
----        ------------        --------------      -------------
SampleRepo  2014/XX/XX XX:XX:XX 2014/XX/XX XX:XX:XX 2014/XX/XX XX:XX:XX
```

→ `SampleRepo` が表示されることを確かめます。

### 手順 4-2: `Get-ChefRepoPath` ファンクションを使って、作成した chef-repo のパスを取得しカレントディレクトリを移動します

```txt:ホストマシン
PS> Get-ChefRepoPath "SampleRepo" | Set-Location
PS> pwd

Path
----
D:\PowerChef\chef-repositories\SampleRepo\chef-repo
```

→ `D:\PowerChef\chef-repositories\SampleRepo\chef-repo` と表示されることを確かめます。


### 手順 4-3: `New-ChefNode` ファンクションを使って、`Vagrantfile`を作成します

```txt:ホストマシン
PS> New-ChefNode -BoxName "windows-2012r2-standard" -OSType "Windows" -VMNumber 10
PS> Show-ChefNodeList

D:\PowerChef\chef-repositories\SampleRepo\chef-repo> Show-ChefNodeList

Name             Parent      CreationTime        LastWriteTime       LastAccessTime
----             ------      ------------        -------------       --------------
ip-192-168-56-10 development 2014/XX/XX XX:XX:XX 2014/XX/XX XX:XX:XX 2014/XX/XX XX:XX:XX
```

→ `ip-192-168-56-10` という名前の仮想マシンが作成されたことを確かめます。

### 手順 4-4: `Create-ChefNode` ファンクションを使って、仮想マシン（VirtualBox のゲスト OS）を作成します

```txt:ホストマシン
PS> Create-ChefNode "ip-192-168-56-10"
PS> Show-ChefNodeStatus "ip-192-168-56-10"

Current machine states:

ip-192-168-56-10          running (virtualbox)

The VM is running. To stop this VM, you can run `vagrant halt` to
shut it down forcefully, or you can run `vagrant suspend` to simply
suspend the virtual machine. In either case, to restart it again,
simply run `vagrant up`.
```

→ 状態が `running` と表示されていることを確かめます。

# 5: 仮想マシン（VirtualBox のゲスト OS）のテストを行う

### 手順 5-1: `Open-ChefNodeSpecfile` ファンクションを使って、Serverspec の Spec ファイル（`default_spec.rb`）を編集します

```ps1:ホストマシン
PS> Open-ChefNodeSpecfile "ip-192-168-56-10"
```

`Testing for files.` の部分に 3 行追記します。

```rb:default_spec.rb
require 'serverspec'
require 'winrm'

include Serverspec::Helper::WinRM
include Serverspec::Helper::Windows

RSpec.configure do |c|
  user = 'vagrant'
  password = ask("Enter password: ") { |q| q.echo = false }
  endpoint = 'http://192.168.56.10:5985/wsman'

  c.winrm = ::WinRM::WinRMWebService.new(endpoint, :ssl, :user => user, :pass => password, :basic_auth_only => true)
  c.winrm.set_timeout 300 # 5 minutes max timeout for any operation
end

# # Testing for user groups.
# describe group('xxxxxx') do
#   it { should exist }
# end

# # Testing for users.
# describe user('xxxxxx') do
#   it { should exist }
#   it { should belong_to_group 'xxxxxx' }
#   it { should_not belong_to_group 'xxxxxx' }
# end

# # Testing for directories.
# describe file('C:\xxxxxx\xxxxxx') do
#   it { should be_directory }
# end

# # Testing for files.
# describe file('C:\xxxxxx\xxxxxx') do
#   it { should be_file }
#   it { should contain /^xxxxxx/ }
#   it { should be_readable.by('owner') }
#   it { should be_readable.by_user('xxxxxx') }
#   it { should_not be_readable.by('others') }
#   it { should_not be_readable.by_user('xxxxxx') }
#   it { should be_writable.by('owner') }
#   it { should be_writable.by_user('xxxxxx') }
#   it { should_not be_writable.by('others') }
#   it { should_not be_writable.by_user('xxxxxx') }
#   it { should be_executable.by('owner') }
#   it { should be_executable.by_user('xxxxxx') }
#   it { should_not be_executable.by('others') }
#   it { should_not be_executable.by_user('xxxxxx') }
# end
describe file('C:/test.txt') do
  it { should be_file }
end

# # Testing for enabled services.
# describe service('xxxxxx') do
#   it { should be_enabled }
#   it { should be_running }
# end

# # Testing for disabled services.
# describe service('xxxxxx') do
#   it { should_not be_enabled }
#   it { should_not be_running }
# end

# # Testing for open ports.
# describe port(port_number) do
#   it { should be_listening.with('tcp') }
#   it { should be_listening.with('udp') }Pow
# end

# # Testing for close ports.
# describe port(port_number) do
#   it { should_not be_listening }
# end

# # Testing for command returns.
# describe command('xxxxxx') do
#   it { should return_stdout /^xxxxxx/ }
#   it { should return_exit_status 0 }
# end
```

### 手順 5-2: `Test-ChefNode` ファンクションを使って、Serverspec を実行します

```txt:ホストマシン
PS> Test-ChefNode "ip-192-168-56-10"

 :
中略
 :
1 example, 1 failure

Failed examples:
```

→ テストに失敗することを確かめます。

## 6: Chef のレシピを作成して仮想マシン（VirtualBox のゲスト OS）に適用します

### 手順 6-1: `New-Cookbook` ファンクションを使って、"make-testfile" という名前のクックブックを作成します

```txt:ホストマシン
PS> New-Cookbook "make-testfile"

Maintainer: xxxxxxx xxxxxx ←作成者（メンテナー）の名前を入力します。
MainteinerEmail: xxxxxxxx@xxxxxx.xx ←作成者（メンテナー）のメールアドレスを入力します。

PS> Show-CookbookList

Name          Parent         CreationTime        LastWriteTime       LastAccessTime
----          ------         ------------        --------------      -------------
make-testfile site-cookbooks 2014/XX/XX XX:XX:XX 2014/XX/XX XX:XX:XX 2014/XX/XX XX:XX:XX

```

### 手順 6-2: `Open-CookbookRecipe` ファンクションを使って、クックブックのデフォルトレシピ(`default.rb`）を編集します

```ps1:ホストマシン
PS> Open-CookbookRecipe "make-testfile"
```

次のように編集してファイルを閉じます。

```rb:default.rb
file "C:/test.txt" do
  action :create
end
```

### 手順 6-3: `Start-ChefZero` ファンクションを使って、ローカルマシン上にChef Zero（インメモリー版軽量 Chef Server）を起動します。

```ps1:ホストマシン
PS> Start-ChefZero
PS> & NETSTAT.EXE -anb | Select-String "8889"
```

→ ポート 8889 番の状態が LISTENING であることを確かめます。

### 手順 6-4: `New-ChefZeroACL` ファンクションを使って、Windows ファイアウォールの設定を行います（Windows 8、Windows Server 2012 以降の環境のみ）

```ps1
PS> New-ChefZeroACL
```

### 手順 6-5: `Update-Cookbook` ファンクションを使って、依存関係のあるクックブックをダウンロードし Chef Zero にアップロードします

```ps1
PS> Update-Cookbook "make-testfile"
PS> & knife cookbook list
```

→ クックブックの一覧が表示されれば成功です。

### 手順 6-6: `SetUp-ChefNode` ファンクションを使って、作成した仮想マシンを Chef の Node 環境としてセットアップします

```ps1:ホストマシン
PS> SetUp-ChefNode "ip-192-168-56-10"
```

→ パスワード欄に `vagrant` と入力し [OK] ボタンをクリックします。

### 手順 6-7: knife コマンドを使って、Chef Zero サーバー上に Node として登録されたことを確認します

```ps1:ホストマシン
PS> & knife node list

ip-192-168-56-10
```

→ `ip-192-168-56-10` が表示されれば成功です。

### 手順 6-8: knife コマンドを使って run_list に作成したクックブックを追加します

```ps1:ホストマシン
PS> & knife node run_list add "ip-192-168-56-10" "make-testfile::default"
PS> & knife node show "ip-192-168-56-10"
```
### 手順 6-9: Environment の設定ファイル `development.json` を作成します

```ps1:ホストマシン
PS> New-Item -Path ".\environments\development.json" -ItemType "File" -Value @'
{
  "name": "development",
  "json_class": "Chef::Environment",
  "chef_type": "environment",
  "default_attributes": {
  }
}
'@
```

### 手順 6-10: knife コマンドを使って、 Environment を設定します

```ps1:ホストマシン
PS> & knife environment create from file ".\environments\development.json"
```

### 手順 6-11: `Converge-ChefNode` ファンクションを使って、クックブックを仮想マシンに適用します（仮想マシン上で chef-client を実行します）

```ps1:ホストマシン
PS> Converge-ChefNode "ip-192-168-56-10"
```

## 7: 仮想マシン（VirtualBox のゲスト OS）の再テストを行う

### 手順 7-1: `Test-ChefNode` ファンクションを使って、Serverspec を実行します

```txt:ホストマシン
PS> Test-ChefNode "ip-192-168-56-10"

 :
中略
 :
1 example, 0 failures
```

→ テストが通ったことを確認します。

---

Vagrant + Chef + Serverspec を使って、Windows Server 環境も Infrastructure as Code & TDD してしまうという話でした。

今回、使用した PowerShell のモジュール `PowerChef` の詳しい使い方については、以下を参照してください。

- [PowerChef 利用ガイド](https://github.com/kukita/PowerChef/blob/master/Docs/GettingStartedGuide_ja.md#section4) 

以上