AWS EC2のWindowsインスタンスを作業用として利用するにあたり固定の名前で接続できるようする方法を考えてみた

AWS EC2のWindowsインスタンスを使って何かするとき簡単にインスタンスを立ち上げてWinRMで接続できるようにする。CloudFormationでEC2インスタンスを起動してRoute53にパブリックIPを登録する。

  • 送信元のIPアドレスで接続を制限をする(セキュリティグループを利用)
  • そのときで最新のAmazon AMIを使う
  • パブリックIPをRoute53に登録して名前で接続できるようにする

あらかじめ用意しておく

  • VPCとサブネット
  • セキュリティグループ
  • IAMロール
  • Route53 HostedZone*1
AMIの検索
aws ec2 describe-images 
    --owners amazon 
    --filter Name=name,Values="Windows?Server?2012?R2*Japanese*Base*"

ワイルドカード?*が使える。case sensitive

Windowsの設定

EC2インスタンス起動時にUserDataで設定する

Get-NetFirewallPortFilter -Protocol tcp | 
    ?{ $_.LocalPort -eq 5985 } | 
    get-netfirewallrule | 
    Get-NetFirewallAddressFilter | 
    Set-NetFirewallAddressFilter -RemoteAddress Any

Amazon AMIはWinRM(Windows リモート管理 )がファイアウォールでの許可を含めて有効化されている*2。ただし、ローカルサブネットに限定された許可のため変更する必要がある。ここではVPCのセキュリティグループによって接続元IPを制限するのでWindowsファイアウォールではすべて許可とする。

接続元ではWinRMを有効化してTrustedHostsに今回使用するホスト名を追加する*3

スタックの更新

パブリックIPはインスタンスの「停止」「開始」で変わる。スタックリソースのレコードセットをCloudFormationの外で変更すると以後のスタックの更新、削除が失敗する。スタックを削除したらEC2インスタンスとレコードセットがひとセットとして削除されて欲しい。スタックの更新によってレコードセットの値を変更する。

$params = (Get-CFNStack -StackName $stackname).Parameters 
$params | % {$_.UsePreviousValue = $TRUE; $_.Value = $NULL}
$p = $params | ? key -eq "publicIp" 
$p.UsePreviousValue = $FALSE
$p.Value = $ip

Update-CFNStack
    -StackName $stackname
    -Parameters $params
    -UsePreviousTemplate $true
    -Capability "CAPABILITY_IAM"

テンプレートを変更するかパラメータの値を変更しないとCloudFormationは変更があったことを検知しない。ここではEC2インスタンスリソースを変更せずにレコードセットリソースのみを更新したい。レコードセットの値はパラメーターとして指定できるテンプレートとする。

スタックの更新に必要なIAMポリシー

テンプレートの書き方によって変わる。スタックリソースに対しては更新するリソースについてのものだけ許可されていればいい。この場合ではテンプレートにEC2インスタンスリソースが含まれていれも更新されるリソースがレコードセットリソースのみなのでEC2インスタンスに関わる許可は不要。ただし、テンプレートにパラメータの型としてAWS::EC2::Image::IdやAWS::EC2::Subnet::Idなどを指定していると参照するためのIAMポリシーが必要になる。

レコードセットの更新に必要なIAMポリシー

"Effect": "Allow",
"Action": [
    "route53:ListHostedZones"
],
"Resource": [
    "arn:aws:route53:::hostedzone"
]
"Effect": "Allow",
"Action": [
    "route53:GetChange",
    "route53:ChangeResourceRecordSets"
],
"Resource": [
    "arn:aws:route53:::hostedzone/[Hostedzone ID]"
]

スタックの更新に必要なIAMポリシー

"Effect": "Allow",
"Action": [
    "cloudformation:DescribeStacks",
    "cloudformation:UpdateStack"
],
"Resource": [
    "arn:aws:cloudformation:ap-northeast-1:[account ID]:stack/[stack name]/*"
]
スタートアップ時のタスク実行

タスクスケジューラを使ってWindows起動時にスタックの更新を実行する。PowerShellスクリプトをタスクスケジューラに登録するにはPSScheduledJobモジュールを使う。スタートアップ時に実行させるにはさらに追加の設定をしないとエラーによりタスクの実行が失敗する。

タスク スケジューラは、ユーザー "<コンピューター名>\Administrator" の "\Microsoft\Windows\PowerShell\ScheduledJobs\<タスク名>" タスクを開始できませんでした。追加データ: エラー値: 2147943711。*4

LocalSystemアカウントで実行させると成功する。

Register-ScheduledJob
    -ScriptBlock $sb
    -Name $name
    -Trigger @{Frequency = "AtStartup"}

Get-ScheduledTask -TaskName $name | 
    Set-ScheduledTask -User "NT AUTHORITY\SYSTEM"

*1:今回はあらかじめ用意したHostedZoneにCloudFormationでレコードセットを追加する方法にしたが、スタックごとにHostedZoneを追加する方法にすればもうすこし簡単になりそうではある。ちなみに、HostedZoneは1つずつの料金(月額のホストゾーン料)がかかるが追加から12時間以内に消せばタダ。

*2:http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/WindowsGuide/windows-ami-version-history.html#ami-image-changes

*3:WinRm TrustedHosts 追加

*4:タスクスケジューラのログは有効にしないと見られない。