2017年10月9日月曜日

packer + VirtualBox で Windows Server 2016 を自動構築してみた

概要

前回 VirtualBox 上に Windows Server 2016 を構築してみました
その際に OS のインストールは手動で行いました
Windows には OS を自動でインストールする仕組みとして Autounattend.xml を使う方法があります
今回は packer と Autounattend.xml を組み合わせて Windows Server 2016 のインストールの自動化を行ってみました

環境

  • macOS X 10.12.6
  • VirtualBox 5.1.26 r117224 (Qt5.6.2)
  • packer 1.0.4

テンプレートファイルの準備

まずは Json テンプレートを作成します
今回はプロビジョニングはせず OS をインストールの基本設定だけを自動化します
テンプレートの全容は以下の通りです

  • vim win2016.json
{
  "builders": [
    {
      "type": "virtualbox-iso",
      "vboxmanage": [
        [ "modifyvm", "{{.Name}}", "--memory", "2048" ],
        [ "modifyvm", "{{.Name}}", "--vram", "48" ],
        [ "modifyvm", "{{.Name}}", "--cpus", "2" ]
      ],
      "guest_additions_mode": "{{ user `guest_additions_mode` }}",
      "guest_os_type": "Windows2016_64",
      "headless": "{{ user `headless` }}",
      "iso_url": "{{ user `iso_url` }}",
      "iso_checksum": "{{ user `iso_checksum` }}",
      "iso_checksum_type": "md5",
      "communicator": "winrm",
      "winrm_username": "winuser1",
      "winrm_password": "winpass123",
      "winrm_timeout": "12h",
      "floppy_files": [
        "answer_files/Autounattend.xml",
        "scripts/winrm.ps1"
      ]
    }
  ],
  "variables": {
    "guest_additions_mode": "attach",
    "headless": "true",
    "iso_checksum": "18a4f00a675b0338f3c7c93c4f131beb",
    "iso_url": "/path/to/win2016.ISO"
  }
}

ISO はファイルは前回ダウンロードした ISO をそのまま使います
すでにローカルにあるのでローカルのパスを指定しています
もちろんここに URL を指定しても OK です

xml と ps1 ファイルはこのあと説明します

Autounattend.xml の準備

今回の肝になります
この XML ファイルを作成することで OS のインストールを自動化します
Linux で言うところの kickstart ファイルです
かなり長いですが全容は以下の通りです

<?xml version="1.0" encoding="UTF-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
   <settings pass="windowsPE">
      <!-- look for drivers on floppy -->
      <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
         <DriverPaths>
            <PathAndCredentials wcm:keyValue="1" wcm:action="add">
               <Path>A:\</Path>
            </PathAndCredentials>
         </DriverPaths>
      </component>
      <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
         <SetupUILanguage>
            <UILanguage>en-US</UILanguage>
         </SetupUILanguage>
         <InputLocale>en-US</InputLocale>
         <SystemLocale>en-US</SystemLocale>
         <UILanguage>en-US</UILanguage>
         <UILanguageFallback>en-US</UILanguageFallback>
         <UserLocale>en-US</UserLocale>
      </component>
      <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
         <DiskConfiguration>
            <Disk wcm:action="add">
               <CreatePartitions>
                  <CreatePartition wcm:action="add">
                     <Type>Primary</Type>
                     <Order>1</Order>
                     <Size>350</Size>
                  </CreatePartition>
                  <CreatePartition wcm:action="add">
                     <Order>2</Order>
                     <Type>Primary</Type>
                     <Extend>true</Extend>
                  </CreatePartition>
               </CreatePartitions>
               <ModifyPartitions>
                  <ModifyPartition wcm:action="add">
                     <Active>true</Active>
                     <Format>NTFS</Format>
                     <Label>boot</Label>
                     <Order>1</Order>
                     <PartitionID>1</PartitionID>
                  </ModifyPartition>
                  <ModifyPartition wcm:action="add">
                     <Format>NTFS</Format>
                     <Label>Windows 2016</Label>
                     <Letter>C</Letter>
                     <Order>2</Order>
                     <PartitionID>2</PartitionID>
                  </ModifyPartition>
               </ModifyPartitions>
               <DiskID>0</DiskID>
               <WillWipeDisk>true</WillWipeDisk>
            </Disk>
         </DiskConfiguration>
         <ImageInstall>
            <OSImage>
               <InstallFrom>
                  <MetaData wcm:action="add">
                     <Key>/IMAGE/NAME</Key>
                     <Value>Windows Server 2016 SERVERDATACENTER</Value>
                  </MetaData>
               </InstallFrom>
               <InstallTo>
                  <DiskID>0</DiskID>
                  <PartitionID>2</PartitionID>
               </InstallTo>
            </OSImage>
         </ImageInstall>
         <UserData>
            <ProductKey>
               <WillShowUI>OnError</WillShowUI>
            </ProductKey>
            <AcceptEula>true</AcceptEula>
            <FullName>winuser1</FullName>
            <Organization>winuser</Organization>
         </UserData>
      </component>
   </settings>
   <settings pass="generalize">
      <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
         <SkipRearm>1</SkipRearm>
      </component>
      <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-PnpSysprep" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
         <PersistAllDeviceInstalls>false</PersistAllDeviceInstalls>
         <DoNotCleanUpNonPresentDevices>false</DoNotCleanUpNonPresentDevices>
      </component>
   </settings>
   <settings pass="oobeSystem">
      <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
         <InputLocale>en-US</InputLocale>
         <SystemLocale>en-US</SystemLocale>
         <UILanguage>en-US</UILanguage>
         <UserLocale>en-US</UserLocale>
      </component>
      <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
         <OOBE>
            <HideEULAPage>true</HideEULAPage>
            <HideLocalAccountScreen>true</HideLocalAccountScreen>
            <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
            <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
            <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
            <NetworkLocation>Home</NetworkLocation>
            <ProtectYourPC>1</ProtectYourPC>
         </OOBE>
         <TimeZone>UTC</TimeZone>
         <UserAccounts>
            <AdministratorPassword>
               <Value>adminpass</Value>
               <PlainText>true</PlainText>
            </AdministratorPassword>
            <LocalAccounts>
               <LocalAccount wcm:action="add">
                  <Password>
                     <Value>winpass123</Value>
                     <PlainText>true</PlainText>
                  </Password>
                  <Group>administrators</Group>
                  <DisplayName>winuser1</DisplayName>
                  <Name>winuser1</Name>
                  <Description>Windows User 1</Description>
               </LocalAccount>
            </LocalAccounts>
         </UserAccounts>
         <AutoLogon>
            <Password>
               <Value>winpass123</Value>
               <PlainText>true</PlainText>
            </Password>
            <Enabled>true</Enabled>
            <Username>winuser1</Username>
         </AutoLogon>
         <FirstLogonCommands>
            <SynchronousCommand wcm:action="add">
               <CommandLine>cmd.exe /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\winrm.ps1</CommandLine>
               <Order>1</Order>
            </SynchronousCommand>
         </FirstLogonCommands>
      </component>
   </settings>
   <settings pass="specialize">
      <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
         <DoNotOpenServerManagerAtLogon>true</DoNotOpenServerManagerAtLogon>
      </component>
      <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-IE-ESC" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
         <IEHardenAdmin>false</IEHardenAdmin>
         <IEHardenUser>false</IEHardenUser>
      </component>
      <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-OutOfBoxExperience" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
         <DoNotOpenInitialConfigurationTasksAtLogon>true</DoNotOpenInitialConfigurationTasksAtLogon>
      </component>
   </settings>
</unattend>

自分もすべてを把握しているわけではないですが主に行っているのは

  • Administrator のパスワードの設定
  • 新規ユーザの作成 (winuser1/winpass123)
  • OS をインストールするパーティションの設定
  • winuser1 での Windows への自動ログイン
  • タイムゾーンの設定
  • 言語の設定
  • OOBE (Out-Of-Box Experience) の設定
  • フロッピーにマウントした WinRM を有効にするスクリプトの実行

になります
詳細はそれぞれの XML のタグ名で検索すれば出ると思います

ちょっと余談ですがこんなクソ長い XML を手動で作成するわけにはいきません
普通は Windows System Image Manager と ISO を使って XML を生成するみたいです
でもその手順はかなり面倒なので、調べてみたら Web 上で XML を生成できるサービスがありました

ただ、すべてを網羅しているわけではないのでちゃんと作成するのであれば結局公式のドキュメントを見ながら頑張るしかないと思います

ちなみに自分は参考サイトにある Github のリポジトリから引っ張ってきました

WinRM を有効にする PowerShell の準備

WinRM を有効にするのは packer でプロビジョニングしない場合でも必須です

  • mkdir scripts
  • vim scripts/winrm.ps1
netsh advfirewall firewall add rule name="WinRM-HTTP" dir=in localport=5985 protocol=TCP action=allow
winrm set winrm/config/service/auth '@{Basic="true"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'

Windows ファイアウォールに WinRM で通信できる穴を開けています
これは先ほども説明したように Autounattend.xml 内で実行しています

実行

準備完了です
実行してみましょう

  • packer build win2016.json

成功すると以下のようになります

virtualbox-iso output will be in this color.

Warnings for build 'virtualbox-iso':

* A shutdown_command was not specified. Without a shutdown command, Packer
will forcibly halt the virtual machine, which may result in data loss.

==> virtualbox-iso: Downloading or copying Guest additions
    virtualbox-iso: Downloading or copying: file:///Applications/VirtualBox.app/Contents/MacOS/VBoxGuestAdditions.iso
==> virtualbox-iso: Downloading or copying ISO
    virtualbox-iso: Downloading or copying: file:///Users/hawksnowlog/Documents/work/packer/windows/win2016.ISO
==> virtualbox-iso: Creating floppy disk...
    virtualbox-iso: Copying files flatly from floppy_files
    virtualbox-iso: Copying file: answer_files/Autounattend.xml
    virtualbox-iso: Copying file: scripts/winrm.ps1
    virtualbox-iso: Done copying files from floppy_files
    virtualbox-iso: Collecting paths from floppy_dirs
    virtualbox-iso: Resulting paths from floppy_dirs : []
    virtualbox-iso: Done copying paths from floppy_dirs
==> virtualbox-iso: Creating virtual machine...
==> virtualbox-iso: Creating hard drive...
==> virtualbox-iso: Attaching floppy disk...
==> virtualbox-iso: Creating forwarded port mapping for communicator (SSH, WinRM, etc) (host port 3950)
==> virtualbox-iso: Executing custom VBoxManage commands...
    virtualbox-iso: Executing: modifyvm packer-virtualbox-iso-1507116947 --memory 2048
    virtualbox-iso: Executing: modifyvm packer-virtualbox-iso-1507116947 --vram 48
    virtualbox-iso: Executing: modifyvm packer-virtualbox-iso-1507116947 --cpus 2
==> virtualbox-iso: Starting the virtual machine...
    virtualbox-iso: The VM will be run headless, without a GUI. If you want to
    virtualbox-iso: view the screen of the VM, connect via VRDP without a password to
    virtualbox-iso: rdp://127.0.0.1:5959
==> virtualbox-iso: Waiting 10s for boot...
==> virtualbox-iso: Typing the boot command...
==> virtualbox-iso: Waiting for WinRM to become available...
==> virtualbox-iso: Connected to WinRM!
==> virtualbox-iso: Uploading VirtualBox version info (5.1.26)
==> virtualbox-iso: Halting the virtual machine...
    virtualbox-iso: Removing floppy drive...
    virtualbox-iso: Removing guest additions drive...
==> virtualbox-iso: Preparing to export machine...
    virtualbox-iso: Deleting forwarded port mapping for the communicator (SSH, WinRM, etc) (host port 3950)
==> virtualbox-iso: Exporting virtual machine...
    virtualbox-iso: Executing: export packer-virtualbox-iso-1507116947 --output output-virtualbox-iso/packer-virtualbox-iso-150711
6947.ovf
==> virtualbox-iso: Unregistering and deleting virtual machine...
Build 'virtualbox-iso' finished.

==> Builds finished. The artifacts of successful builds are:
--> virtualbox-iso: VM files in directory: output-virtualbox-iso

作成される成果物は以下の通りです
ovf と vmdk が出来上がりました

  • ls -ltr output-virtualbox-iso/
total 9132296
-rw-------  1 hawksnowlog  staff        7076 10  4 20:43 packer-virtualbox-iso-1507116947.ovf
-rw-------  1 hawksnowlog  staff  4675725312 10  4 20:50 packer-virtualbox-iso-1507116947-disk001.vmdk

試しに ovf を VirtualBox に展開してみると良いと思います
winuser1/winpass123 でログインして Windows Server 2016 が立ち上がってくるのが確認できると思います
ちなみに今回の Windows Server は「Datacenter Evaluation」として構築されます

最後に

packer + Autounattend.xml で Windows Server 2016 の OS インストールを自動化してみました
本当はこのあと chef なり powershell を使ってミドルウェアや OS 上のプロビジョニングを行います
プロビジョニングは結局 WinRM を使うので今回のビルドが成功すればプロビジョニングまではすぐにいくと思います

Github にあるサンプルは chef-solo を使っています
chef-solo を選択すると Windows サーバ上に chef-solo コマンドがインストールされてしまうので注意が必要です

またプロビジョニング用のスクリプトを作成するときは一旦 Windows を立ててその中で try & error を繰り返したほうが効率が良いと思います

参考サイト

0 件のコメント:

コメントを投稿