AWS CloudFormationのリソース依存をWaitConditionの代わりにCreationPolicyで実装する

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

ども、大瀧です。
全国数千人のCloudFormationマークアッパーの皆さんには既に常識かもしれませんが、CloudFormation内で処理待ちを実装するCreationPolicyWaitConditionから移行する機会があったので、まとめてみたいと思います。

CloudFormationのリソース依存

CloudFormationはテンプレートに定義されるリソース同士の依存関係を自動で認識し、作成順序を調節する機能があります。この依存関係はテンプレートに明示でき、明示することでリソース作成のタイミングをユーザーが制御することもできます。例えばEC2同士でクラスタを組む場合に、スレーブノードがマスターノードに依存するので「先にマスターノードを作成してからスレーブノード作成に取りかかる」としたいことがあります。CloudFormationでは、DependsOn属性をリソースに追加することで、以下のように依存関係を明示することができます。

SlaveリソースがMasterリソースに依存する定義

{
  :
  "Resources": {
    "Master": {
      "Type": "AWS::EC2::Instance",
        :
    },
    "Slave": {
      "Type": "AWS::EC2::Instance",
      "DependsOn": "Master",
        :
    }
  }
}    

これにより、リソースMaster作成の完了をもってSlaveの作成が開始します。ところが、一般的なクラスタウェアではマスターノードの起動後に初期処理があり、スレーブノードではその初期処理の完了を待たないとクラスタに参加できない、というのもよくある話です。リソース作成完了ではなく初期処理の完了を待つために、CloudFormationでは以下2通りの方法が用意されています。

  1. CreationPolicy属性
  2. WaitConditionリソース

元々は2. WaitConditionリソースのみだったのが後から1. CreationPolicy属性が追加され、ドキュメントによると、現在はCreationPolicy属性が推奨です。この2通りの方法について比較してみます。

WaitConditionリソースの場合

まずは旧来からあるWaitCondition(待機条件)での処理待ちを先ほどのテンプレートに追加してみます。WaitConditionタイプとWaitConditionHandleタイプの2つのリソースを追加し、SlaveリソースのDependsOn属性をWaitConditionに向けることで、Slaveリソースの作成を待機条件の完了まで待つようになります。

    "Master": {
      "Type": "AWS::EC2::Instance",
        :
    },
    "Slave": {
      "Type": "AWS::EC2::Instance",
      "DependsOn": "MasterWaitCondition",
        :
    },
    "MasterWaitCondition": {
      "Type": "AWS::CloudFormation::WaitCondition",
      "DependsOn": "Master",
      "Properties": {
        "Handle": {"Ref": "MasterWaitHandle"},
        "Timeout": "900"
      }
    },
    "MasterWaitHandle": {
      "Type" : "AWS::CloudFormation::WaitConditionHandle"
    }

待機条件の完了はEC2インスタンスからCloudFormationのSignalResource APIに通知します。APIを簡単にコールするためのツールとしてcfn-signalユーティリティ *1が利用できます。

Linuxでのcfn-signalの実行例

    "Master": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
         :
        "UserData": {"Fn::Base64": {"Fn::Join": ["", [
          "#!/bin/bash\n",
          "/opt/aws/bin/cfn-init -s ", {"Ref": "AWS::StackName"},
          " -r Master ",
          " --region ", {"Ref": "AWS::Region"},
          "\n",
          <ここに初期処理として任意のコマンドを書く>
          "/opt/aws/bin/cfn-signal -e 0 ", {"Ref": "MasterWaitHandle"},
          "\n"
        ]]}}
      }
    }

CloudFormationはこの通知を受けると、待機条件を完了と判断します。通知が来ない場合は、待機条件のTimeout属性の時間(秒数)だけ待った後リソースの作成失敗(CREATE_FAILED)と判断しスタック作成失敗となります *2

CreationPolicy属性の場合

ではMasterの初期処理待ちをCreationPolicy属性で記述してみます。SlaveリソースのDependsOn属性はMasterのままでMasterリソースにCreationPolicy属性を追加します。cfn-signalは構文が変わり、--resourceオプションに自身のリソース名を指定、cfn-initと同じくスタックID、リージョン名を添えます。

    "Master": {
      "Type": "AWS::EC2::Instance",
        :
      "CreationPolicy": {
        "ResourceSignal": {
          "Timeout": "PT15M"
        }
      },
      "Properties": {
         :
        "UserData": {"Fn::Base64": {"Fn::Join": ["", [
          "#!/bin/bash\n",
          "/opt/aws/bin/cfn-init -s ", {"Ref": "AWS::StackName"},
          " -r Master ",
          " --region ", {"Ref": "AWS::Region"},
          "\n",
          <ここに初期処理として任意のコマンドを書く>
          "/opt/aws/bin/cfn-signal -e 0 --stack ", {"Ref": "AWS::StackName"},
          " --resource Master ",
          " --region ", {"Ref": "AWS::Region"},
          "\n"
        ]]}}
      }
    },
    "Slave": {
      "Type": "AWS::EC2::Instance",
      "DependsOn": "Master",
        :
    }

WaitConditionリソースとの違いを以下に示します。

[良い点] DependsOnが他のリソースと同様に書ける

作成を待つリソースのDependsOn属性をWaitConditionリソースに向ける必要が無いため、直感的な記述が可能です。CreationPolicy属性を定義しない一般的なDependsOn属性の指定方法と合わせることができ、学習コストが多少下がります。

[良い点] TimeoutにISO8601形式が使える

WaitConditionリソースのタイムアウト値は秒数のみですが、CreationPolicy属性のTimeoutISO8601のDurationの形式が利用できるため、直感的に時間指定できます。

[悪い点] 複数のチェックポイントは指定できない

処理待ちのタイミングを複数設定することは、CreationPolicy属性では対応できません。例えば、初期処理がDBのセットアップ、アプリケーションがあって、別のリソースでそれぞれ完了を待つという場合です。WaitConditionとWaitConditionHandlerを2つずつ定義し、DependsOnでそれぞれを向けることになります。

[悪い点] エラーメッセージをCloudFormationで取得できない

cfn-signalユーティリティには--dataオプションという、メッセージをCloudFormation APIに送信する機能があります。CreationPolicyでは指定できないため、-e/--exit-codeオプションによる終了値のみ渡すことになります。

応用編 CreationPolicyとWaitConditionリソースの併用

ここまで2つを比較してきましたが、両方を組み合わせることも可能です。記述は複雑になりますが、前述の良い点、悪い点を上手く補完できるのであれば、検討するのも良いかも知れません。

    "Slave": {
      "Type": "AWS::EC2::Instance",
      "DependsOn": "MasterWaitCondition",
        :
    },
    "MasterWaitCondition": {
      "Type": "AWS::CloudFormation::WaitCondition",
      "Properties": {
        "Handle": {"Ref": "MasterWaitHandle"},
      },
      "CreationPolicy": {
        "ResourceSignal": {
          "Timeout": "PT15M"
        }
      }
    },
    "MasterWaitHandle": {
      "Type" : "AWS::CloudFormation::WaitConditionHandle"
    }

まとめ

CloudFormationで処理待ちを明示するための方法2つ、CreationPolicy属性とWaitConditionリソースを比較してみました。 CreationPolicyを使うと、かなりすっきり記述することができるのでお奨めです。願わくば、cfn-init/cfn-signalのコマンドべた書きがもう少しなんとかできると良いのですけどね :P

Enjoy CloudFormation life!!

参考URL

脚注

  1. cfn-signalはWindows/Linux AMIともにaws-cfn-bootsrapプログラム/パッケージとしてプリインストールされています。
  2. よくあるトラブルとしては、EC2からインターネットへの接続ができないときにCloudFormation APIに到達できずスタック作成が成功しない、なんてのがありますね。プライベートなネットワーク構成でもNATインスタンスなどを使ってインターネット接続経路を確保しましょう。CloudFormationのVPC Endpoint対応が待たれます。