終劇 CPU 100%

ビールは食べ物

Vagrant+Puppet+Serverspec を使って Windows Server をテスト駆動開発する

インフラ構築・運用をコーディングで行うことは珍しい事ではなくなってきていますが、Windows Server の開発記事はあまり見受けられません。しかし、現場では Windows Server を使っている人も多いと思います。私の職場でも Windows Server の案件が多いです。

そこで3連休を利用して、 Vagrant , Puppet , Serverspec を使った Windows Server のテスト駆動開発環境を作ってみましたのでシェアします。

開発マシンは Mac OSX です。開発マシンの内容は以下の通りです。事前に VirtualBoxVagrant ,Serverspec が使えるようにしておいて下さい。

ソフト バージョン
開発OS OS X 10.9.4
ゲストOS Windows Server 2012
Vagrant 1.6.5
Vagrant Plugin puppet(3.7.0) / vagrant-windows (1.6.0)
Serverspec 1.16.0
VirtualBox 4.3.12
Microsoft Remote Desktop Connection for Mac 2.3.6
winrm (gem) 1.2.0

作業の流れ

本稿の流れを以下にまとめます。

順番 内容
1 Vagrant で操作できる Windows Server の Box を作成する
2 Vagrant の設定をする
3 Serverspec でテストを先に実行する
4 Puppet を実行する
5 再びテストを実行する

1.Vagrant で操作できる Windows Server の Box を作成する

開発で使用する Vagrant Box を作成します。manifest 開発中はこの Box を使って何度も Windows Server を壊したりしますので作っておくと楽ですね。

Windows Server の設定をする

設定はこちらのサイトを参考に行いました。

[Vagrant] WindowsServer2012R2のBoxを自作する #Vagrant - NAVER まとめ

まずは Vagrant から Windows Server を操作できるように Windows Server を設定します。VirtualBoxWindows Server 2012 インスタンスを普通にインストールします。

f:id:higaKtoT:20140914220116p:plain

リモートディスプレイのサーバを有効化する

開発マシンからリモートデスクトップ(RDP)できるようにリモートディスプレイのサーバを有効にします。インストールした仮想インスタンスを右クリックし設定を開きます。

[ディスプレイ] を選択し中央ペインの [リモートディスプレイ(R)] を選択します。[サーバを有効化する] にチェックを入れます。

f:id:higaKtoT:20140914221542p:plain

パスワードの複雑性を無効化

インストールした Windows Server にログインし PowerShell を管理者権限で実行します。参考のサイトに習って以降の設定はすべて PowerShell で行います。

パスワードの設定ポリシーをファイルにエクスポートします。エクスポートしたファイルの値をノートパッドで開き以下のように修正し再びポリシーを適用します。(Import LocalSecurityPolicy.cfg after changing)

  MaximumPasswordAge = -1
  PasswordComplexity = 0

vagrant ユーザを作る

Vagrant は 仮想インスタンスへアクセスする際、vagrant ユーザでログインします。なので vagrant Box には予め vagrant ユーザを作成しておきます。

vagrant ユーザを Administrator グループに追加する

リモート管理(WinRM)を有効化する

テストツールの Serverspec は WinRM を使って Windows Server に PowerShell を実行しテストを行います。 WinRM が使えるように設定しておきます。その他、開発マシンから Windows Server にアクセスできるよう設定しておきます。

Firewall のルールを設定する

全ての端末から WinRM ポートにアクセスできるよう設定します。

Puppet のインストール

f:id:higaKtoT:20140915000501p:plain

Puppet Lab から Windows 用の Puppet をダウンロードしインストールします。Windows 用 Puppet はこちらからダウンロードできます。

https://downloads.puppetlabs.com/windows/

今回は puppet-3.7.0-x64.msi をダウンロードしました。

インストールは簡単です。全てデフォルトのままで [ Next ] 連打でOKです。ちなみにコマンドを使えばサイレントインストールも出来ます。サイレントインストールのやり方は公式ページに記載されています。

自動ログインを有効にします

インスタンスを起動した際、自動で vagrant ユーザがログインできるようにしておきます。

VirtualBox Guest Addtionsのインストール

こちらも忘れずにインストールしておきましょう。

設定した Windows Server の Box を作成する

ここまでの設定で Vagrant から操作できる Windows Server が出来上がっています。設定した Windows Server を用途に合わせて柔軟に使えるように VirtualBox からエクスポートし Vagrant Box にしたいと思います。

Box 用のディレクトリを作成する

Windows Server をシャットダウンしておきます。VirtualBox から Windows Server をエクスポートしておくためのフォルダを用意します。フォルダは任意です。

$ mkdir -p vagrant-boxfiles/windows2012

VirtualBox からエクスポートする

作成したフォルダに VirtualBox からエクスポートしたファイルを保存します。

$ vagrant package --base "windows-for-vagrantbox_default_1410696984773_58536" --output /Users/[User]/Documents/vagrant-boxfiles/windows-for-vagrant.box

--base windows-for-vagrantbox_default_1410696984773_58536 は先ほど設定した Windows Server のインスタンス名を入れて下さい。 --output にエクスポート用に作成したフォルダパスと box 名を指定します。box ファイルの名前は任意です。分かりやすい名前をつけましょう。

Vagrant に登録する

エクスポートした box を vagrant に登録します。

$vagrant box add "windows2012-puppet" /Users/[User]/Documents/vagrant-boxfiles/windows-for-vagrant.box

add のあとは Box の論理名です。エクスポートしたファイル名と同じじゃなくても OK です。こちらも分かりやすい名前をつけるといいでしょう。ちなみに、私は " puppet が使える Windows Server 2012 " なので " windows2012-puppet "という Box 名前にしました。

2.Vagrant の設定をする

vagrant から Windows Server にアクセスできるように Vagrantfile を作成します。まずは、作業用のフォルダを用意しましょう。任意の場所にフォルダを作成します。

$ mkdir -p /Users/[User]/Documents/vagrant/windows-puppet

Vagrantfile を作成する

先ほど作成したフォルダに移動します。

$ cd /Users/[User]/Documents/vagrant/windows-puppet

移動したフォルダで以下のコマンドで Vagrantfile のひな形を作成します。

$ vagrant init

すると Vagrantfile が生成されます。 Windows Server インスタンスにアクセスできるように Vagrantfile を以下の内容に変更します。

RDP で接続する

ここまで設定が完了すると Vagrant から Windows Server を操作する事が可能になってます。vagrant up コマンドで Windows Server を起動し、RDP でアクセスしてみましょう。

$ vagrant up
WARNING: Could not load IOV methods. Check your GSSAPI C library for an update
WARNING: Could not load AEAD methods. Check your GSSAPI C library for an update
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 3389 => 3389 (adapter 1)
    default: 5985 => 5985 (adapter 1)
    default: 22 => 2222 (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
==> default: Machine booted and ready!
Sorry, don't know how to check guest version of Virtualbox Guest Additions on this platform. Stopping installation.
==> default: Checking for guest additions in VM...
==> default: Mounting shared folders...
    default: /vagrant => /Users/[User]/Documents/Vagrant/windows-for-vagrantbox
==> default: Machine already provisioned. Run `vagrant provision` or use the `--provision`
==> default: to force provisioning. Provisioners marked to run always will still run.

共有フォルダも作成されています。Vagrant から Windows Server をうまく起動出来ました!早速アクセスしてみましょう。

ちなみに Vagrant 1.6 から Windows Guests を正式にサポートしたそうです。vagrant rdp コマンドで直接 rdp クライアントを起動できるようになりました。

$ vagrant rdp

と実行してみてください。RDP が起動します。

f:id:higaKtoT:20140915132821p:plain

ここまで出来たら、puppet も Serverspec も動きます。

3.Serverspec でテストを先に実行する

テスト駆動 Windows をやってみたいと思います。今回は簡単に「 C ドライブの直下にファイルを作成する」お題でテスト駆動してみたいと思います。

要件
Puppet を使って Cドライブ直下に test.txt を配置する

マニフェストを書く前に先にテストを書きます。 C ドライブ直下に test.txt が存在している事をテストに書いて実行し、わざと失敗します。

Serverspec の環境を整える

Vagrantfile を作成したフォルダで serverspec-init を実行しテストのひな形を生成します。

$ serverspec-init

設定内容を対話形式で聞かれるので答えていきます。

Select OS type:

  1) UN*X
  2) Windows

Select number: 2

Select a backend type:

  1) WinRM
  2) Cmd (local)

Select number: 1

 + spec/
 + spec/localhost/
 + spec/localhost/httpd_spec.rb
 + spec/spec_helper.rb
 + Rakefile

最初の質問には、対象は Windows OS なので 2 を。次の質問は WinRM で接続するので 1 を回答します。 すると spec フォルダが生成されます。spec フォルダ直下の spec_helper.rb の編集を行います。spec_helper.rb は接続先の情報が記載されています。

以下の内容で編集して下さい。

require 'serverspec'
require 'winrm'

include SpecInfra::Helper::WinRM
include SpecInfra::Helper::Windows

RSpec.configure do |c|
  user = "vagrant"
  pass = "vagrant"
  endpoint = "http://127.0.0.1:5985/wsman"

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

Serverspec は Windows Server へ WinRM で接続します。 gem ライブラリの winrm をインストールしておいて下さい。

gem install winrm

テストを書く

serverspec-init を実行した時点でテストファイルがすでに生成されています。

spec/localhost/httpd_spec.rb

httpd 用のテストファイルです。必要ありませんので削除しておきましょう。

rm -rf spec/localhost/httpd_spec.rb

代わりに httpd_spec.rb のあった場所に filecheck_spec.rb というテストファイルを作成します。要件に沿って「C:¥test.txt が存在している事」と記述します。

ちなみに、テストファイルの命名規則spec.rb です。ファイル名の語尾に spec.rb とあると Serverspec がテストファイルと認識できます。

require 'spec_helper'

describe file('C:/test.txt') do
  it { should be_file }
end

早速実行してみましょう。

rake spec

当然、結果はエラーになりました。

/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/ruby -S rspec spec/localhost/filecheck_spec.rb
WARNING: Could not load IOV methods. Check your GSSAPI C library for an update
WARNING: Could not load AEAD methods. Check your GSSAPI C library for an update
F

Failures:

  1) File "c:/test.txt" should be file
     On host ``
     Failure/Error: it { should be_file }
       $exitCode = 1
$ProgressPreference = "SilentlyContinue"
try {
  
  $success = (((Get-Item -Path "c:/test.txt" -Force).attributes.ToString() -Split ', ') -contains 'Archive')
  if ($success -is [Boolean] -and $success) { $exitCode = 0 }
} catch {
  Write-Output $_.Exception.Message
}
Write-Output "Exiting with code: $exitCode"
exit $exitCode
       You cannot call a method on a null-valued expression.
Exiting with code: 1
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><S S="Error">Get-Item : Cannot find path 'C:\test.txt' because it does not exist._x000D__x000A_</S><S S="Error">At line:5 char:17_x000D__x000A_</S><S S="Error">+   $success = (((Get-Item -Path "c:/test.txt" -Force).attributes.ToString() -S_x000D__x000A_</S><S S="Error">pli ..._x000D__x000A_</S><S S="Error">+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~_x000D__x000A_</S><S S="Error">    + CategoryInfo          : ObjectNotFound: (C:\test.txt:String) [Get-Item], _x000D__x000A_</S><S S="Error">    ItemNotFoundException_x000D__x000A_</S><S S="Error">    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetIt _x000D__x000A_</S><S S="Error">   emCommand_x000D__x000A_</S><S S="Error"> _x000D__x000A_</S></Objs>
       expected file? to return true, got false
     # ./spec/localhost/filecheck_spec.rb:4:in `block (2 levels) in <top (required)>'

Finished in 0.54846 seconds
1 example, 1 failure

Failed examples:

Cドライブ直下に test.txt を配置するマニフェストを書きたいと思います。

4.Puppet を実行する

テストを通すために Puppet を実行できる環境にします。Puppet は Vagrant と同じタイミングで実行されるようにします。例えば、 vagrant up 、vagrant provision した時、仮想インスタンスが起動したあと自動で Puppet が実行されるようにします。

Vagrantfile を修正する

vagrant provision が使えるように Vagrantfile に以下の内容を追記します。「 Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 」セクションの中に書きます。

  config.vm.define :windows do |c| 
    c.vm.provision "puppet" do |puppet|
      puppet.manifest_file = "default.pp"
    end
  end

※ Vagrantfile 全体

# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "Windows2012-puppet"
  config.vm.guest = :windows
  config.vm.communicator = "winrm"
  config.vm.network :forwarded_port, guest: 3389, host: 3389
  config.vm.network :forwarded_port, guest: 5985, host: 5985, id: "winrm", auto_correct: true

  config.vm.define :windows do |c|
    c.vm.provision "puppet" do |puppet|
      puppet.manifest_file = "default.pp"
    end
  end

end

マニフェストを書く

マニフェストを格納するフォルダを作成しマニフェスト( default.pp )を作成します。マニフェストを格納するフォルダは必ず「 manifests 」にします。

mkdir manifests

マニフェストファイルを作成します。マニフェストファイル名は任意ですが Vagrantfile の記述と同じにします。

vi manifests/default.pp

Cドライブ直下に test.txt を生成するマニフェストを書きます。

file { "C:\\text.txt":
  content => "aaaa"
}

マニフェストを実行します。

vagrant provision
WARNING: Could not load IOV methods. Check your GSSAPI C library for an update
WARNING: Could not load AEAD methods. Check your GSSAPI C library for an update
==> windows: Running provisioner: puppet...
Running Puppet with /tmp/vagrant-puppet-2/manifests/default.pp...
Notice: Compiled catalog for win-r2elstmlcu3 in environment production in 0.25 seconds
Notice: Finished catalog run in 0.02 seconds

無事、マニフェストが実行されました。 vagrant rdp で Windws Server にログインし、 test.txt ファイルが生成されている事を確認します。

f:id:higaKtoT:20140915145631p:plain

生成されていますね!!やった!

5.再びテストを実行する

ファイルが生成されたのでもう1度テストを実行します。

rake spec
Finished in 0.62373 seconds
1 example, 1 failure

Failed examples:

失敗しました。なんでだろう。もう1度マニフェストを確認してみます。

file { "C:\\text.txt":
  content => "aaaa"
}

なるほど。text.txt と誤植してしまってました。test.txt に修正してもう1度 vagrant provision します。その後、rake spec します。

/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/ruby -S rspec spec/localhost/filecheck_spec.rb
WARNING: Could not load IOV methods. Check your GSSAPI C library for an update
WARNING: Could not load AEAD methods. Check your GSSAPI C library for an update
.

Finished in 0.41336 seconds
1 example, 0 failures

テスト通りました!やった。

※これ演出ではなく、本当に誤植してました。早速、テスト駆動開発の大切さを実感しました。

最後に

このように Windows Server でもテスト駆動開発ができる事を確認できました。あとは現場でどう運用していくかだと思います。本当に Puppet , Chef を使った Infrastructure as Code が現場のスタイルに合っているのか見極めていきたいと思います。

感謝

この記事を沢山の方がリツートしていただきました。ありがとうございます。