検証用のWindows Server 2016を一発で起動するAWS CloudFormationテンプレートを作成してみた

設定済みのWindows Server 2016インスタンスをAWS CloudFormationで立ち上げるテンプレートを作ってみました。これを使用することで、必要なときに同じ環境を簡単に用意できるようになりました。
2018.08.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。サービスグループの武田です。

Webアプリケーションを作成していると、さまざまな環境での動作確認が必要になるケースがあります。近年はクロスブラウザ対策などで比較的互換性は保てますが100%ではありません。最近、Windowsのブラウザで動作確認ができる環境を用意する機会がありました。

私は普段の業務ではMacを使用しており、Windowsの環境がないため、EC2でその環境を用意することにしました。その作業の一環として、設定済みのWindows Server 2016インスタンスをAWS CloudFormationで立ち上げるテンプレートを作ってみました。これを使用することで、必要なときに同じ環境を簡単に用意できるようになりました。

なお、AWSで提供しているWindows環境としてはAmazon WorkSpacesもあります。今回は「使用頻度が不定期かつ短時間」ということからEC2を利用することにしました。

環境

次の環境で動作確認をしています。

$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.13.6
BuildVersion:	17G65

$ aws --version
aws-cli/1.15.33 Python/3.6.5 Darwin/17.7.0 botocore/1.10.33

$ jq --version
jq-1.5

また、EC2インスタンスに設定するキーペアは作成済みとします。

CloudFormationを利用するメリット

検証用のインスタンスですが、毎回削除ではなく、Stop/Startで使い続けることも考えられますね。私はCloudFormationを利用して毎回新規作成することで次のようなメリットがあると考えています。

  • EBS費用の節約
    • インスタンスを停止していてもEBSの料金はかかります。今回使用したAMIはデフォルトで30GBのストレージを使います。そのため何もしてなくても$3.6/月(約400円)の費用が発生します。使い終わったらスタック削除をすれば費用を抑えられます
  • 最新のパッチが当たった状態で使える
    • AWSが提供するAMIは定期的に更新されています。毎回新規に作り直すことで常に最新のAMIを使用できます
  • おかしくなったら作り直せる
    • 作り直せるようにしておくことで、何かトラブルがあっても簡単に初期状態に戻せる安心感があります。またバグの再現性にも活用できそうです

また言ってしまえばEC2インスタンスを起動するだけですので、EC2の起動テンプレートを利用する方法も考えられそうです。それについては次のような差異があると考えられます。

  • バージョン管理可能
    • 起動テンプレートはバージョン管理ができるため、複数のバージョンを用意しておくことでインスタンスの構成を切り替えるのが容易です
  • 起動テンプレートはパラメータストアに対応していない
    • 最新のAMI IDの取得に、CloudFormationではパラメータで対応できますが、起動テンプレートではインスタンス起動時に明示的に渡す必要があります
  • セキュリティグループは事前に作成しておく
    • CloudFormationではその場で作れますが、起動テンプレートでは事前に作成しておいたものをアタッチします

それぞれメリットデメリットあると思いますので、環境に合わせてより適したものを使ってください。

作成したCloudFormationテンプレート

それでは今回作成したテンプレートです。ファイル名はwindows-check-server.template.ymlとしました。再利用はご自由にどうぞ。

windows-check-server.template.yml

---
AWSTemplateFormatVersion: '2010-09-09'
Description: This template create a EC2 instance.
Parameters:
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    ConstraintDescription: must be the name of an existing EC2 KeyPair.
  InstanceType:
    Type: String
    Default: t3.small
    AllowedValues:
      - t3.micro
      - t3.small
      - t3.midium
    ConstraintDescription: must be a valid EC2 instance type.
  InstanceImageId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-windows-latest/Windows_Server-2016-Japanese-Full-Base
  SourceCidrForRDP:
    Type: String
    MinLength: '9'
    MaxLength: '18'
    AllowedPattern: '^([0-9]+\.){3}[0-9]+\/[0-9]+$'
  TagName:
    Type: String
Resources:
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Enable RDP
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 3389
        ToPort: 3389
        CidrIp:
          !Ref SourceCidrForRDP
  WindowsServer:
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          files:
            c:\cfn\cfn-hup.conf:
              content: !Sub |
                [main]
                stack=${AWS::StackId}
                region=${AWS::Region}
            c:\cfn\hooks.d\cfn-auto-reloader.conf:
              content: !Sub |
                [cfn-auto-reloader-hook]
                triggers=post.update
                path=Resources.WindowsServer.Metadata.AWS::CloudFormation::Init
                action=cfn-init.exe -v --stack ${AWS::StackName} --resource WindowsServer --region ${AWS::Region}
            c:\cfn\scripts\Setup-config.ps1:
              content: |
                # set JST TimeZone
                tzutil /s "Tokyo Standard Time"
                Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\TimeZoneInformation" -Name "RealTimeIsUniversal" -Value 1

                # disabled firewall
                Get-NetFirewallProfile | Set-NetFirewallProfile -Enabled false

                # show fileext and hidden file
                Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -Name "HideFileExt" -Value 0
                Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -Name "Hidden" -Value 1

                # set high performance
                powercfg.exe -SETACTIVE 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c

                # disabled enhanced security
                # https://stackoverflow.com/questions/44643997/aws-windows-2012-r2-turning-off-ie-enhanced-security-configuration
                $admin = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}"
                $user = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}"

                Set-ItemProperty -Path $admin -Name "IsInstalled" -Value 0 -Force
                Set-ItemProperty -Path $user -Name "IsInstalled" -Value 0 -Force
                Remove-ItemProperty -Path $admin -Name "IsInstalled" -Force
                Remove-ItemProperty -Path $user -Name "IsInstalled" -Force
            c:\cfn\scripts\Install-choco-packages.ps1:
              content: |
                # install package manager and packages
                # suppressed warning. UnicodeEncodeError fails because the output contains Japanese.
                $WarningPreference = "SilentlyContinue"
                iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex
                $WarningPreference = "Continue"
                choco install -y notepadplusplus.install googlechrome firefox | Out-Null
          commands:
            1-setup-config:
              command: 'powershell.exe -File c:\cfn\scripts\Setup-config.ps1'
              waitAfterCompletion: '0'
            2-install-choco-packages:
              command: 'powershell.exe -File c:\cfn\scripts\Install-choco-packages.ps1 -ExecutionPolicy Bypass'
              waitAfterCompletion: '0'
          services:
            windows:
              cfn-hup:
                enabled: 'true'
                ensureRunning: 'true'
                files:
                  - c:\cfn\cfn-hup.conf
                  - c:\cfn\hooks.d\cfn-auto-reloader.conf
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: !Ref InstanceImageId
      SecurityGroupIds:
        - !Ref SecurityGroup
      KeyName: !Ref KeyName
      Tags:
        - Key: Name
          Value: !Ref TagName
      UserData:
        Fn::Base64: !Sub |
          <powershell>
          cfn-init.exe -v --stack ${AWS::StackName} --resource WindowsServer --region ${AWS::Region}
          cfn-signal.exe -e $lastexitcode --stack ${AWS::StackName} --resource WindowsServer --region ${AWS::Region}
          </powershell>
    CreationPolicy:
      ResourceSignal:
        Timeout: PT15M
Outputs:
  WindowsServerHostname:
    Value: !GetAtt WindowsServer.PublicDnsName
    Description: WindowsServer Hostname.

いくつかポイントを解説します。

16-18行目はパラメータとして、「起動するインスタンスのイメージIDを格納しているAWS Systems Managerパラメータストアのキー名」を受け取ります。詳しくは中山のエントリを参照してください。社内で共有してもらって知りました。とても便利ですね!

パラメーターストアから最新のWindows AMIのIDを取得する

54行目からはc:\cfn\scripts\Setup-config.ps1を作成しています。このスクリプトでは次の設定を行っています。特に 「IE セキュリティ強化の構成」を無効化 はかなりはまったところで、試行錯誤の末やっと想定した挙動になりました。

  • タイムゾーンをUTCからJSTに変更
  • 「Windows ファイアウォール」を無効化
  • ファイルの拡張子を表示
  • 隠しファイルを表示
  • 電源オプションを「高パフォーマンス」に変更
  • 「IE セキュリティ強化の構成」を無効化

79行目からはc:\cfn\scripts\Install-choco-packages.ps1を作成しています。このスクリプトでは次のソフトウェアをインストールしています。Chocolateyのインストールでは警告メッセージの抑制が必要で、これをしないとCloudFormationの初期化処理に失敗します。

  • Chocolatey
  • Notepad++
  • Google Chrome
  • Firefox

スタック作成スクリプト

次に、先ほど作成したCloudFormationテンプレートからスタックを作成するスクリプトです。ファイル名はcreate-stack-windows-check-server.shとしました。4行目の$your_key_pair_nameは使用するキーペア名に置き換えてください。また5行目は使用するキーファイルのパスに置き換えてください。

create-stack-windows-check-server.sh

#!/bin/bash

stack_name=WindowsCheckServer
key_name=$your_key_pair_name
key_path="/path/to/${key_name}.pem"

aws cloudformation create-stack \
  --template-body file://windows-check-server.template.yml \
  --stack-name "${stack_name}" \
  --parameters \
    ParameterKey=KeyName,ParameterValue="${key_name}" \
    ParameterKey=SourceCidrForRDP,ParameterValue="$(curl -s ifconfig.io)/32" \
    ParameterKey=TagName,ParameterValue="${stack_name}"

aws cloudformation wait stack-create-complete --stack-name "${stack_name}"

hostname=$(aws cloudformation describe-stacks \
  --stack-name "${stack_name}" \
  | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="WindowsServerHostname") | .OutputValue')
instance_id=$(aws cloudformation list-stack-resources \
  --stack-name "${stack_name}" \
  | jq -r '.StackResourceSummaries[] | select(.LogicalResourceId=="WindowsServer") | .PhysicalResourceId')
password=$(aws ec2 get-password-data \
  --instance-id "${instance_id}" \
  --priv-launch-key "${key_path}" \
  | jq -r '.PasswordData')

echo "hostname: ${hostname}"
echo "password: ${password}"

スクリプトの実行とリモートアクセス

実行権限を付与してスクリプトを実行すると次のようになります。

$ ./create-stack-windows-check-server.sh
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/WindowsCheckServer/30f72890-a6ac-11e8-ace0-50a686699abc"
}
hostname: ec2-54-238-142-250.ap-northeast-1.compute.amazonaws.com
password: qsF!l7iBU9*O.r)aw*Fxq;6NHIEVz3Y0

任意のリモートデスクトップクライアントを使用して、表示されたパスワードでホストに接続すれば、無事に初期設定済みのWindows環境が手に入ります。

使い終わったらスタックの削除を忘れずに!

まとめ

CloudFormatonテンプレートを作成する中で、知らなかったこともいろいろ知ることができ、勉強になりました。一度作ってしまえば使い回しは容易ですし、何より便利ですね。

今回のテンプレートでは、先日リリースされたばかりのT3インスタンスをさっそく取り入れてみました。検証エントリも上がっていますので、ぜひご覧ください。

【速報】T3インスタンスがリリースされました!

T3インスタンスの性能をWordPress環境で確認してみた