【小ネタ】Windowsでcfn-signalを使用する時の注意

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

ウィスキー、シガー、パイプをこよなく愛する大栗です。

Windows ServerのEC2インスタンスをCloudFormationで構成していたのですが、少し嵌った事があったので注意点をまとめてみます。EC2のOSはAmazon Linuxを使用することが多いのですが、Windowsの場合に異なる部分がありました。

CloudFormationでEC2を作成する時のポイント

CloudFormationでEC2インスタンスを構成するときに、UserDataやCloudFormation::Initを使用してミドルウェアをインストールすることで一発で環境を構成することができます。しかし、ここで問題になるのがリソースの作成順序です。
CloudFormationでEC2インスタンスを作成するときにはステータスがrunningになるとリソースとしてはCREATE_COMPLETEになり、作成したEC2に依存しているリソースの作成処理が流れてしまいます。するとUserDataでミドルウェアをインストールする前に後続処理が走ってしまい都合が悪くなります。例えばWebサーバをインストールする前にELBに登録されてしまい、Unhealthyになりなかなかつながらない場合などがあります。

Untitled_1

この場合は、WaitConditionとWaitConditionHandleを利用します。UserDataの処理の最後でcfn-signalコマンドを実行してWaitConditionHandleへシグナルを送ります。WaitConditionHandleにシグナルが送られるとWaitConditionがCREATE_COMPLETEとなります。つまり、UserDataの実行後にリソースを作成したい場合には、作成したいリソースのDependsOn属性でWaitConditionを指定すれば良いのです。

Untitled_2

嵌ったこと

Windowsでcfn-signalコマンドを使用した時に問題が発生しました。
cfn-signalコマンドはドキュメントによると以下の様な形式でWaitConditionHandleへシグナルを送ります。

cfn-signal --success|-s signal.to.send \
        --reason|-r resource.status.reason \
        --data|-d data \
        --id|-i unique.id \
        --exit-code|-e exit.code \
        waitconditionhandle.url

Linuxの場合

Amazon Linux(ami-374db956)で起動するときには、以下の様なテンプレートで起動できます。cfn-signalコマンドは以下のようになります。 "/opt/aws/bin/cfn-signal -e $? -r \"AppServer01 setup complete\" '", { "Ref" : "Srv01WaitHandle" }, "'"

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "Linux Stack",
  "Resources" : {
    "Srv01WaitHandle" : {
      "Type" : "AWS::CloudFormation::WaitConditionHandle"
    },
    "Srv01WaitCondition" : {
      "Type" : "AWS::CloudFormation::WaitCondition",
      "Properties" : {
        "Handle" : {"Ref" : "Srv01WaitHandle"},
        "Timeout" : "900"
      }
    },
    "Server01": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId": "ami-374db956",
        "InstanceType": "c4.large",
        "KeyName": "KeyPairName",
        "UserData": { "Fn::Base64": { "Fn::Join": [ "", [
          "#! /bin/bash -v", "\n",
          "sleep 180", "\n",
          "# All is well so signal success", "\n",
          "/opt/aws/bin/cfn-signal -e $? -r \"AppServer01 setup complete\" '", { "Ref" : "Srv01WaitHandle" }, "'"
        ]]}}
      }
    }
  }
}

EC2インスタンスが起動してから180秒以上経ってからWaitConditionがCREATE_COMPLETEになります。

CloudFormation_Management_Console

Windowsの場合

Windows Server 2012 R2(ami-0f19db6e)で、ImageIdを変更してUserDataを以下のようにcfn-signalコマンドを書き換えてテンプレートを実行してみました。

          "<script>", "\n",
          "powershell.exe -command Start-Sleep -s 180", "\n",
          "", "\n",
          "cfn-signal.exe -e %ERRORLEVEL% -r \"AppServer01 setup complete\" \"", { "Ref" : "Srv01WaitHandle" }, "\"", "\n",
          "</script>"

しかし、WaitConditionがCREATE_COMPLETEにならずに、そのままタイムアウトしてCREATE_FAILEDになってしまいます。

CloudFormation_Management_Console

Windowsの正しいcfn-signalの呼び方

正しい呼び方は、『AWS CloudFormation の Windows スタックのブートストラップ』というドキュメントに記載がありました。

Windows スタックでは、待機条件ハンドル URL を再度 base64 エンコードする必要があります。

つまりFn::Base64関数を使えということでした。書き換えたテンプレートの全体は、以下のようになります。
cfn-signalの部分は"cfn-signal.exe -e %ERRORLEVEL% -r \"AppServer01 setup complete\" \"", { "Fn::Base64" : { "Ref" : "Srv01WaitHandle" }}, "\"", "\n"となります

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "Linux Stack",
  "Resources" : {
    "Srv01WaitHandle" : {
      "Type" : "AWS::CloudFormation::WaitConditionHandle"
    },
    "Srv01WaitCondition" : {
      "Type" : "AWS::CloudFormation::WaitCondition",
      "Properties" : {
        "Handle" : {"Ref" : "Srv01WaitHandle"},
        "Timeout" : "900"
      }
    },
    "Server01": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId": "ami-0f19db6e",
        "InstanceType": "c4.large",
        "KeyName": "KeyPairName",
        "UserData": { "Fn::Base64": { "Fn::Join": [ "", [
          "<script>", "\n",
          "powershell.exe -command Start-Sleep -s 180", "\n",
          "", "\n",
          "cfn-signal.exe -e %ERRORLEVEL% -r \"AppServer01 setup complete\" \"", { "Fn::Base64" : { "Ref" : "Srv01WaitHandle" }}, "\"", "\n",
          "</script>"
        ]]}}
      }
    }
  }
}

上記のテンプレートでは問題なくWaitConditionがCREATE_COMPLETEとなりました。

CloudFormation_Management_Console

さいごに

正直cfn-signalの仕様をLinux版とWindows版で合せて欲しい部分でしたが、LinuxとWindowsではCloudFormationの書き方に細かい違いがあるので、注意して作成する必要があります。