Windows Serverもansibleで自動構築してServerspecでテストしよう

# はじめに

* **2016/8/3 Serverspecでのテストを追記**

* ansibleも2.1になってWindows Serverのサポートも強化され、そろそろ手を出しても良さそうな感じになってきました。
* ということで、ansibleを利用する前のWindowsServer側の設定とIISのインストール、Windowsグループ、ユーザの設定を自動構築し、serverspecでテストするところまでやってみたいと思います。
* 記事中で使ったplaybookのサンプルは[Github uzresk/ansible-serverspec-windows-samples](https://github.com/uzresk/ansible-serverspec-windows-samples.git)からどうぞ。

---

# 前提となる環境

* 以下の環境で動作確認をしています。
* 尚、本文中ではansible自体のインストール手順は割愛しています。

## ansible実行ホスト

* CentOS6.7
* ansible2.1.0
* serverspec 2.36.0
* IP:192.168.1.99

## ansible適用ホスト

* WindowsServer2012R2
* opentableのイメージを使ってvagrantで起動しました。
* IP:192.168.1.25

---

# ansibleで環境構築

## ansibleで環境構築する前準備

### winrmを有効化する

* Ansible で Windows の構成管理を行う場合には Windows Remote Management（ WinRM）を有効化する必要があるようです。

### winrmを有効化するスクリプトの取得

* power shellを管理者権限で起動した後に以下のコマンドを使ってansibleが提供しているwinrmを有効化するスクリプトを取得します。

```
Invoke-WebRequest -Uri https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1 -OutFile ConfigureRemotingForAnsible.ps1
```

#### NetworkCategoryの変更

* [Windows System Prep](http://docs.ansible.com/ansible/intro_windows.html#windows-system-prep)によるとNetworkCategoryをprivateに変更しないといけない模様

```
Set-NetConnectionProfile -InterfaceAlias (Get-NetConnectionProfile -IPv4Connectivity Internet).InterfaceAlias -NetworkCategory Private
```

* 確認するにはこのコマンド(NetworkCategoryがprivateになっていることを確認）

```
Get-NetConnectionProfile -IPv4Connectivity Internet
```

#### winrmを有効化

```
PS C:\Users\vagrant> powershell -ExecutionPolicy RemoteSigned .\ConfigureRemotingForAnsible.ps1


wxf                 : http://schemas.xmlsoap.org/ws/2004/09/transfer
a                   : http://schemas.xmlsoap.org/ws/2004/08/addressing
w                   : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
lang                : en-US
Address             : http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
ReferenceParameters : ReferenceParameters

Ok.
```

#### windows serverでwinrmのポートを確認する

* Powershellを起動して以下のコマンドを実行
* 5986と5985ポートが空いているのがわかりますね。ちなみに5986はhttps、5985はhttpです。

```
Get-Item WSMan:\localhost\Listener\*\Port


   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Listener\Listener_1305953032

Type            Name                           SourceOfValue   Value
----            ----                           -------------   -----
System.String   Port                                           5986


   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Listener\Listener_1084132640

Type            Name                           SourceOfValue   Value
----            ----                           -------------   -----
System.String   Port                                           5985

```

### pipをインストール

#### get-pip.pyを取得

```
curl -x http://PROXY_HOST:PROXY_PORT -O https://bootstrap.pypa.io/get-pip.py
```

#### pipをインストール

* プロキシを設定

```
export http_proxy=http://PROXY_HOST:PROXY_PORT
export https_proxy=http://PROXY_HOST:PROXY_PORT
```

* インストール

```
python get-pip.py
```

### winrmをインストール

```
pip install pywinrm
```

### winrm経由でansibleが実行できるかを確認する

#### プロキシの除外設定

* winrmはhttp/https通信であるため、プロキシの設定を行っている場合は、ansible対象ホストを除外設定する必要がある。

```
export no_proxy="192.168.1.25"
```

#### inventryファイル(hosts)の設定

* 余談ですが、ansible2.0からはansible_ssh_userなどはdeprecatedになって、sshを取った変数名が推奨されます。
* [ansible windows inventory](http://docs.ansible.com/ansible/intro_windows.html#inventory)

```
[windows]
192.168.1.25

[windows:vars]
ansible_user=vagrant
ansible_password=vagrant
ansible_port=5985
ansible_connection=winrm
```

* https(5986)で接続するには以下のように証明書の検証をしないようにinventoryファイルを構成すれば接続できます。(pythonの2.7.9から証明書の検証が行われるようになったそうです）

```
[windows]
192.168.1.25

[windows:vars]
ansible_user=vagrant
ansible_password=vagrant
ansible_port=5986
ansible_connection=winrm
ansible_winrm_server_cert_validation=ignore
```

#### win_pingを使って疎通確認

```
[root@52ae470c2708 scripts]# ansible -i hosts windows -m win_ping
192.168.1.25 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
```

---

## WindowsServerの設定をansibleで！

* ansibleでwindows serverを構成するためのモジュール群は以下に記載があります。
* [Windows Modules](http://docs.ansible.com/ansible/list_of_windows_modules.html)

### IISをインストールしてみる

* [win_feature](http://docs.ansible.com/ansible/win_feature_module.html)に記載があるplaybookを使ってインストールしてみます。
* 私の環境ではうまくいかず、include_sub_features: noとして実行したところうまく動きました。
* 参考：[win_feature for iis with sub features: yes fails on 2012 R2 #3640](https://github.com/ansible/ansible-modules-core/issues/3640)

```
[root@52ae470c2708 ansible-scripts]# cat roles/iis/tasks/main.yml
- name: Install IIS
  win_feature:
    name: "Web-Server"
    state: present
    restart: yes
    include_sub_features: no
    include_management_tools: yes
```

```
[root@52ae470c2708 ansible-scripts]# ansible-playbook -i hosts site.yml

PLAY [windows] *****************************************************************

TASK [iis : Install IIS] *******************************************************
changed: [192.168.1.25]

PLAY RECAP *********************************************************************
192.168.1.25               : ok=1    changed=1    unreachable=0    failed=0
```

* [http://192.168.1.25/](http://192.168.1.25/)にアクセスすると無事IISのデフォルトページが表示されました。

### グループとユーザをつくってみよう

#### グループを作成する

* グループを作成するには[win_groupモジュール](http://docs.ansible.com/ansible/win_group_module.html)を使います。

```
[root@52ae470c2708 ansible-scripts]# cat roles/group/tasks/main.yml
---
- name: create test-group
  win_group:
    name: test-group
    descriptsion: test group
    state: present
```

#### ユーザを作成する

* ユーザを作成するには[win_userモジュール](http://docs.ansible.com/ansible/win_user_module.html)を使います。

```
[root@52ae470c2708 ansible-scripts]# cat roles/user/tasks/main.yml
---
- name: Add User
  win_user:
    name: test
    password: "@Password1"
    groups: ["test-group"]
```

* パスワードポリシーに違反した場合はplaybook実行時に以下のようなエラーがでます

```
TASK [user : Add User] *********************************************************
fatal: [192.168.1.25]: FAILED! => {"changed": false, "failed": true, "msg": "Exception calling \"SetInfo\" with \"0\" argument(s): \"The password does not meet the password policy requirements. Check the minimum password length, password complexity and password history requirements.\r\n\""}
```

* 実行後、サーバマネージャ - コンピュータの管理 - ローカルユーザとグループからユーザとグループが追加できていることが確認できると思います。

---

# Serverspec

## Serverspecを使ってwindowsをテストする為の前準備

* Serverspecもansible同様winrmを使って接続します。Linuxをテストする時と比べて必要になるのはwinrmのモジュールのみです。

### winrmをインストールする

```
gem install winrm
```

### 接続の仕方もwinrm経由に変更

* ここでは、TARGET_HOST、USER、PASSWORDを環境変数から取得しwinrmの接続で利用しています。

```
[root@52ae470c2708 serverspec]# cat spec/spec_helper.rb
require 'serverspec'
require 'net/ssh'
require 'winrm'

set :backend, :winrm

RSpec.configure do |c|
  host = ENV['TARGET_HOST']
  user = ENV['USER'].dup
  pass = ENV['PASSWORD'].dup
  puts user
  puts pass
  endpoint = "http://" + host + ":5985/wsman"

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

## テストしてみよう

### IISがインストールされていることをテストする

* ここではインストールされているか否かとポートがListenしているか否かをテストしてみたいと思います。
* インストールされているか否かはServerspecのResourceTypeに[windows_feature](http://serverspec.org/resource_types.html#windows_feature)があるのでこれを利用します。ポートがListendしているか否かについてはLinux同様[port](http://serverspec.org/resource_types.html#port)を利用します。

```iis_spec.rb
require 'spec_helper'

describe windows_feature('Web-Server') do
  it { should be_installed.by("powershell") }
end

describe port(80) do
  it { should be_listening.with('tcp') }
end
```

### グループ、ユーザのテスト

* グループが存在するか否か、ユーザが存在するか否か、ユーザがグループに存在するか否かのテストを行います。
* こちらはLinux同様[group](http://serverspec.org/resource_types.html#group)と[user](http://serverspec.org/resource_types.html#user)を利用します。

```group_spec.rb
require 'spec_helper'

describe group('test-group') do
  it { should exist }
end
```

```user_spec.rb
require 'spec_helper'

describe user('test') do
  it { should exist }
  it { should belong_to_group 'test-group' }
end
```

### 実行

* 全部無事テストが通りましたね。

```
[root@52ae470c2708 windows]# rake spec
(in /scripts/ansible-serverspec-windows-samples/first-step/serverspec)
env TARGET_HOST=192.168.1.25 /root/.rbenv/versions/2.3.1/bin/ruby -I/root/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/rspec-support-3.4.1/lib:/root/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/rspec-core-3.4.4/lib /root/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/rspec-core-3.4.4/exe/rspec --pattern spec/\{windows\}/\*_spec.rb
vagrant
vagrant

Group "test-group"
 WARN  WinRM::WinRMWebService : WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_powershell_script instead
  should exist

Windows feature "Web-Server"
 WARN  WinRM::WinRMWebService : WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_powershell_script instead
  should be installed by "powershell"

Port "80"
 WARN  WinRM::WinRMWebService : WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_powershell_script instead
  should be listening with tcp

User "test"
 WARN  WinRM::WinRMWebService : WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_powershell_script instead
  should exist
 WARN  WinRM::WinRMWebService : WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_powershell_script instead
  should belong to group "test-group"

Finished in 2.35 seconds (files took 0.48295 seconds to load)
5 examples, 0 failures
```

---

# おわりに

* はじめる前はWinRMとか面倒だなーとか色々思いましたが拍子抜けするほど簡単にできましたので二の足を踏んでいる人はまずはやってみるとよさそうです。
* ansibleは1.7でサポート開始されたころよりもwindows系のモジュールが増えたことでこれからますます活躍の場が増えていきそうですね。
* ansibleはwindowsとlinuxでモジュールが別れていますが、serverspecは一つのResourceTypeで複数のOSをカバーできるようなので学習コストが少なく良い感じですね。