【さくらのクラウド】TerraformでWindowsの展開を自動化する

f:id:febc_yamamoto:20180130182943p:plain

はじめに

今回はTerraform for さくらのクラウドを用いてWindows Serverの展開を自動化する方法をご紹介します。
さくらのクラウドではWindows Serverのセットアップを行うにはコントロールパネルからコンソール接続を行う必要がありましたが、
この方法を使えばterraformコマンドだけで一気に展開可能です。

なお、今回の方法は残念ながらリソースマネージャーには非対応です。 terraformコマンドを直接使う方向けとなっています。

背景

まずはさくらのクラウドでのWindows Serverプランの扱いについて確認しておきます。

さくらのクラウドWindows Serverプラン

さくらのクラウドではWindows Serverプランが提供されています。

さくらのクラウド マニュアル - Windows Serverプラン

Datacenter EditionやRDS、さらにはOffice付きのものやSQLServerインストール済みのものまで様々なプランがあります。

さくらのクラウドWindows Serverプランの仕様

便利なWindows Serverプランなのですが、その他のLinux系OSなどとは異なる仕様がいくつかあります。
例えばディスクサイズは100GB以上が要求されることなどがあります。

その中でも構築の自動化の際に大きく関わってくる仕様として「ディスクの修正が一部項目のみ対応」というものがあります。

「ディスクの修正」とは

さくらのクラウドでは各サーバ固有の設定(グローバルIPアドレスやホスト名、管理者パスワードなど)はディスクの修正という機能で各サーバ(のディスク)に設定されます。

SSH用の公開鍵の登録やスタートアップスクリプトなどもこのディスクの修正で反映できるようになっています。

設定できる項目は以下の通りです。

しかしWindows Serverプランではこのディスクの修正機能が一部のみしか利用できないという仕様になっています。

Windows Serverプランでのディスクの修正機能

Windows Serverプランの場合、ディスクの修正機能で設定可能な項目は以下のみとなっています。

参考: さくらのクラウド マニュアル - Windows ディスク修正

また、パブリックアーカイブからの新規作成時のみディスク修正が行えるという制限もあります。

つまり、Windows Serverプランを利用する場合はサーバ作成後にコントロールパネルからサーバのコンソールを利用して管理者パスワードの設定などの初期設定を手作業で行う必要があります。
(一応VNCも使えます。usacloudからだとusacloud server vnc <your-server-name>でOK)

これが数台程度であれば良いですが、数十〜数百台となってくるとブラウザから手作業で構築するのは非常に大変な作業となります。

そこで何か自動化する手段は無いか、、、ということで自動化する方法を考えました。

Windows展開の自動化といえばsysprep+応答ファイル

Windows環境構築の自動化といえばsysprep+応答ファイルという方法がありますね。

参考: MSDN - Windows セットアップの自動化の概要

実はさくらのクラウドで提供されているWindowsパブリックアーカイブはISOイメージからのインストール直後の状態ではなく、NIC用のVirtIOドライバなどがインストールされた状態となっており、起動直後はわずかな入力を行うだけで直ぐに利用できる状態となります。 (おそらくさくらのクラウド側であらかじめある程度構築後にsysprepされた状態になっているのでしょう)

なので、応答ファイルもごく一部の項目のみ作成すればOKとなっています。 具体的には最低限以下の設定をすれば直ぐにリモートデスクトップ接続ができる状態に持っていけます。

  • EULA(ソフトウェア仕様許諾)ヘの同意
  • ロケール/キーボードの設定
  • Administratorのパスワードの設定

これにchocolateyなどをインストールするスクリプトを仕込めば一気に環境構築できそうです。

応答ファイルはサーバにどうやって渡す? -> ISOイメージを使う

応答ファイルを作成しておけば構築の自動化はできそうですが、サーバ作成時にどうやって応答ファイルを渡せば良いでしょうか? いくつか方法はありますが、さくらのクラウドではISOイメージのアップロードに対応していますのでこれを利用してあげれば良いでしょう。

以下のドキュメントにも記載がありますが、応答ファイルをUnattend.xmlという名前でリムーバブルメディアのドライブのルートにおいておけば初回起動時に参照してくれます。

参考: Windows セットアップの実行方法

なので、応答ファイルを格納したISOイメージを作成し、サーバ作成時にISOイメージを挿入した状態で起動すれば良さそうですね。

Terraform for さくらのクラウドならISOイメージも一発構築

Terraform for さくらのクラウドISOイメージの作成もサポートしています。
ISOイメージの作成処理はGo言語のみで実装していますのでTerraformを実行するのがWindowsの場合でも問題なくISOイメージの作成が行えます。

また、ISOイメージに格納する応答ファイルUnattend.xmlについてはTerraformの変数やテンプレート機能を活用すれば作成できますね。

ということで実際にTerraform for さくらのクラウドで自動化を行なってみます。

構築

Windows Server向け環境構築用のtfファイル

tfファイルは以下のようになります。

### 概要
# Windows Serverの初期設定を行うテンプレート
#
# このテンプレートはWindows Serverの管理者パスワードの設定や任意のスクリプトの実行を行うテンプレートです。
# 初期設定は応答ファイル(Unattend.xml)を格納したISOイメージをサーバにアタッチすることで行なっています。
#

### 変数定義
# サーバ管理者(Administrator)のパスワード
variable password {}

locals {
  #*********************************************
  # プロビジョニング
  #*********************************************
  # サーバ上で実行したいコマンドを指定
  run_commands = [
    # パッケージマネージャー"chocolatey"のインストール
    "powershell -Command \"Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))\"",

    # chocolateyでChromeをインストール
    "C:\\ProgramData\\chocolatey\\bin\\choco.exe install -y googlechrome",
  ]

  #*********************************************
  # サーバ/ディスク
  #*********************************************
  # 利用するアーカイブ種別: 指定できる値についてはTerraform for さくらのクラウドのマニュアルを参照してください
  os_type = "windows2016"

  # サーバ名
  server_name = "windows-server"

  # ホスト名(コンピューター名)
  host_name = "windows-server"

  # サーバ コア数
  server_core = 2

  # サーバ メモリサイズ(GB)
  server_memory = 4

  # ディスクサイズ(100GB以上)
  disk_size = 100
}

### 構成構築
# 利用するアーカイブ
data sakuracloud_archive "windows" {
  os_type = "${local.os_type}"
}

# ディスク
resource "sakuracloud_disk" "disk" {
  name              = "${local.server_name}"
  size              = "${local.disk_size}"
  source_archive_id = "${data.sakuracloud_archive.windows.id}"

  lifecycle {
    ignore_changes = ["source_archive_id"]
  }
}

# サーバ
resource "sakuracloud_server" "server" {
  name     = "${local.server_name}"
  disks    = ["${sakuracloud_disk.disk.id}"]
  core     = "${local.server_core}"
  memory   = "${local.server_memory}"
  cdrom_id = "${sakuracloud_cdrom.settings.id}"
}

# ISOイメージ(応答ファイル用)
resource sakuracloud_cdrom "settings" {
  name = "${local.server_name}"

  # 単一ファイルを内包するISOイメージを作成する
  content           = "${local.unattend_body}"
  content_file_name = "Unattend.xml"
}

# 応答ファイルの組み立て
locals {
  command_elm_format = <<EOL
                <SynchronousCommand wcm:action="add">
                    <Order>$${index}</Order>
                    <CommandLine>$${body}</CommandLine>
                </SynchronousCommand>
EOL

  commands_format = <<EOL
            <FirstLogonCommands>
                %s
            </FirstLogonCommands>
EOL

  commands_body = "${length(local.run_commands) > 0 ? format(local.commands_format, join("", data.template_file.run_commands.*.rendered)) : "" }"
}

data template_file "run_commands" {
  count    = "${length(local.run_commands)}"
  template = "${local.command_elm_format}"

  vars = {
    index = "${count.index + 1}"
    body  = "${local.run_commands[count.index]}"
  }
}

locals {
  unattend_body = <<EOL
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="oobeSystem">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <OOBE>
                <HideEULAPage>true</HideEULAPage>
            </OOBE>
            <UserAccounts>
                <AdministratorPassword>
                    <Value>${var.password}</Value>
                    <PlainText>true</PlainText>
                </AdministratorPassword>
            </UserAccounts>
            <AutoLogon>
                <Password>
                    <Value>${var.password}</Value>
                    <PlainText>true</PlainText>
                </Password>
                <Enabled>true</Enabled>
                <LogonCount>1</LogonCount>
                <Username>Administrator</Username>
            </AutoLogon>
${local.commands_body}
        </component>
        <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <InputLocale>0411:E0010411</InputLocale>
            <SystemLocale>ja-JP</SystemLocale>
            <UILanguage>ja-JP</UILanguage>
            <UILanguageFallback>ja-JP</UILanguageFallback>
            <UserLocale>ja-JP</UserLocale>
        </component>
    </settings>
    <settings pass="specialize">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <ComputerName>${local.host_name}</ComputerName>
        </component>
    </settings>
</unattend>
EOL
}

あとはterraform applyを実行すればプロビジョニング済みのWindows Serverの出来上がりです。

各要素の解説

一応各要素について解説しておきます。

ISOイメージと応答ファイルの作成

ISOイメージは以下のように定義することで応答ファイルを格納したイメージの作成が可能です。

# ISOイメージ(応答ファイル用)
resource sakuracloud_cdrom "settings" {
  name = "${local.server_name}"

  # 単一ファイルを内包するISOイメージを作成する場合
  content           = "${local.unattend_body}"
  content_file_name = "Unattend.xml"
}

ポイントはcontentcontent_file_nameの部分ですね。
Terraform for さくらのクラウドのISOイメージリソースはすでに存在するISOイメージのアップロードも可能ですが、文字列を指定して単一のファイルを内包するISOイメージの作成が行えるようになっています。

contentに作成するファイルの内容を、content_file_nameに作成するファイル名を指定すればOKです。
(なお、content_file_nameに指定した値はボリュームラベルとしても利用されます。)

ここではファイル名にUnattend.xmlを指定してますね。 contentには応答ファイルのXMLを組み立てた結果を格納した変数が指定されています。

応答ファイルには以下の内容を記載しています。

  • EULA(ソフトウェア仕様許諾)ヘの同意
  • ロケール/キーボードの設定
  • Administratorのパスワードの設定
  • コンピューター名の変更
  • 指定のスクリプトを初回ログオン時に実行
  • サーバ作成後に1回だけ自動ログイン

実行したいスクリプトはLocalValuesのrun_commandsに指定しています。
この例ではChocolateyをインストールし、chocolateyを用いてChromeをインストールしています。
好みに応じてこの辺りを編集してみてください。

サーバへのISOイメージの挿入

これはサーバリソースの定義にISOイメージのIDを指定するだけでOKです。 例では以下のように指定しています。

# サーバ
resource "sakuracloud_server" "server" {
  # ...

  cdrom_id = "${sakuracloud_cdrom.settings.id}"
}

注意点

ISOイメージは安価とはいえお金がかかります(現時点では月額108円)。
サーバ構築後はISOイメージは利用しませんので削除してしまってもOKです。
削除する場合はtfファイルからISOイメージ関連の記述をコメントアウト & applyなどしておきましょう。

応用: WinRMを有効化してAnsibleやTerraformのプロビジョナーを利用する

簡単なスクリプト程度なら先程のrun_commandsに実行したいコマンドを書いていけばいいですが、 少し複雑なことをやる場合はやはりAnsibleなどのツールを利用するのが楽だと思います。

そのためにはWinRMを有効にする必要があります。

この場合、以下のようにtfファイルに記載すれば良いでしょう。

  #*********************************************
  # プロビジョニング
  #*********************************************
  # サーバ上で実行したいコマンドを指定
  run_commands = [
    #*****************************************************************
    # WinRMを有効化(5985:httpと5986:httpsポートが解放される)
    #*****************************************************************
    "powershell -Command \"Invoke-WebRequest -Uri https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1 -OutFile ConfigureRemotingForAnsible.ps1\"",
    "powershell -Command \"powershell ConfigureRemotingForAnsible.ps1\"",
    "powershell -Command \"Remove-Item -path ConfigureRemotingForAnsible.ps1 -force\"",
  ]

この記述をしておけば、Ansibleが提供しているスクリプト(powershell)を利用してWinRMを有効化できます。
WinRMさえ有効になってしまえば以下のようにTerraformのプロビジョナーも利用できます。

# サーバ
resource "sakuracloud_server" "server" {
  name     = "${local.server_name}"
  disks    = ["${sakuracloud_disk.disk.id}"]
  core     = "${local.server_core}"
  memory   = "${local.server_memory}"
  cdrom_id = "${sakuracloud_cdrom.settings.id}"

  #  > WinRMを有効化しておけばプロビジョナーも利用可能
  provisioner "remote-exec" {
      # 接続情報の指定
      connection {
        type     = "winrm"
        host     = "${sakuracloud_server.server.ipaddress}"
        port     = 5986
        https    = true
        insecure = true
  
        user     = "Administrator"
        password = "${var.password}"
      }
  
      # サーバ上で実行するコマンド
      inline = [
        "echo foobar",
      ]
  }
}

Ansibleを利用する場合はfileリソースなどで以下のようにインベントリファイルを作成しておくと楽でしょう。

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

resource "local_file" "foo" {
  filename = "${path.module}/hosts"
  content  = <<EOL
[windows]
${sakuracloud_server.server.ipaddress}

[windows:vars]
ansible_user=Administrator
ansible_password=${var.password}
ansible_port=5986
ansible_connection=winrm
ansible_winrm_server_cert_validation=ignore
EOL
}

あとはansible -i hosts windows -m win_pingみたいに実行できるはずです。
(これをlocal_execプロビジョナーで実行してもいいですね)

終わりに

これでWindows環境の展開が楽になりますね!! 是非ご活用ください!!!

以上です。