AWS固有のパラメータタイプを学ぶ

CloudFormationでテンプレートを書く際にパラメータは使っていますか?
テンプレートの柔軟性、汎用性を高め、テンプレートの真骨頂である横展開をスムーズに行うためには使わない手はないですね。
今回は、AWS固有のパラメータタイプの紹介と、ハマりやすい落とし穴・対応方法について紹介します。

AWS固有のパラメータタイプとは

AWS固有のパラメータタイプとは、スタック作成時のパラメータ入力画面において「特定のリストをドロップダウン形式で選択できるようにしてくれる仕組み」を実現するためのパラメータタイプです。パラメータタイプにはStringNumberなどがありますが、それの拡張版にあたる存在、と考えると分かりやすいです。

どんなのがあるの?

ホントは先に「どこで使うの?」というところを紹介したいところではあるのですが、どんなものがあるのかを先に紹介したほうがみなさんのイメージが広がると思いますので、自分がよく使っているタイプの上位5傑をチョイスして先にご紹介です。すべてのリストは公式ドキュメントを参照してください。

  • List
  • AWS::EC2::Subnet::Id
  • AWS::EC2::VPC::Id
  • AWS::EC2::AvailabilityZone::Name
  • List

「ああ、こういうものがマネジメントコンソールでのパラメータ指定画面で選べるようになるのか」というのがなんとなーくお分かりいただけるかと思います。

どんなふうに使えるの?

Listで囲まれているものとそうでないものがありますね。Listで囲まれていないものは単一選択、囲まれているものは複数選択としてマネジメントコンソールのページがレンダリングされます。百聞は一見に如かず。

単一選択型の画面イメージ (AWS::EC2::SecurityGroup::Id)

テンプレートパラメータの単一選択

複数選択型の画面イメージ(List)

テンプレートパラメータの複数選択

それぞれを選択した結果

パラメータを選択した状態

落とし穴1:文字列型とリスト型を間違える

リソースプロパティで参照する際も、ドキュメント上でStringとして指定されている場合は文字列型のパラメータを、[ String, ... ]として指定されている場合は文字列型のパラメータを[]で囲むか、リスト型のパラメータをそれぞれ指定する必要があります。慣れないうちはよく間違えるポイントです。

OKパターン

AWS::EC2::SecurityGroupリソースのSourceSecurityGroupIdプロパティに単一文字列型のパラメータを展開しています。

{
  "Parameters": {
    "SecurityGroupId": {
      "Type": "AWS::EC2::SecurityGroup::Id"
    }
  },
  "Resources": {
    "SecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "SecurityGroupIngress": {
          "IpProtocol": "-1",
          "SourceSecurityGroupId": { "Ref": "SecurityGroupId" }
        }
      }
    }
  }
}

今度はAWS::EC2::Instanceリソースです。SecurityGroupIdsプロパティはリスト型のパラメータを取りますが、テンプレート側でリスト指定の[]を補ったうえで、その中に単一文字列型のパラメータを展開しています。

{
  "Parameters": {
    "SecurityGroupId": {
      "Type": "AWS::EC2::SecurityGroup::Id"
    }
  },
  "Resources": {
    "Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "SecurityGroupIds": [
          { "Ref": "SecurityGroupId" }
        ]
      }
    }
  }
}

同じくSecurityGroupIdsプロパティですが、先ほどと違い、パラメータ側ですでにリスト化されていますので、テンプレート側でリスト指定の[]を補う必要はなく、直接リスト型のパラメータを展開しています。

{
  "Parameters": {
    "SecurityGroupIds": {
      "Type": "List<AWS::EC2::SecurityGroup::Id>"
    }
  },
  "Resources": {
    "Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "SecurityGroupIds": { "Ref": "SecurityGroupIds" }
      }
    }
  }
}

NGパターン

AWS::EC2::InstanceリソースのSecurityGroupIdsはリスト型のパラメータを取りますが、単一文字列型のパラメータを渡してしまっています。

{
  "Parameters": {
    "SecurityGroupId": {
      "Type": "AWS::EC2::SecurityGroup::Id"
    }
  },
  "Resources": {
    "Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "SecurityGroups": { "Ref": "SecurityGroupId" }
      }
    }
  }
}

該当部分は下記のように展開されます。リスト型を求められているにもかかわらず文字列型を渡してしまったのでエラーになります。

"SecurityGroups": "sg-12345678"

同じくSecurityGroupIdsプロパティですが、パラメータ側ですでにリスト化されているにもかかわらず、リスト型を指定する[]を補った中にリスト型のパラメータを渡してしまっています。

{
  "Parameters": {
    "SecurityGroupIds": {
      "Type": "List<AWS::EC2::SecurityGroup::Id>"
    }
  },
  "Resources": {
    "Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "SecurityGroups": [
          { "Ref": "SecurityGroupIds" }
        ]
      }
    }
  }
}

該当部分は下記のように展開されます。リスト型の中にリスト型が入っている、というおかしな状況になっています。

"SecurityGroups": [
  [ "sg-12345678" ]
]

落とし穴2:ユーザが指定しない場合の挙動

便利に見えるパラメータタイプですが、出来上がったテンプレートを使ってスタックを作成する際にもちょっとした落とし穴があります。
「未指定時にはパラメータ変数そのものが定義されない」という点です。せっかくパラメータを選べるようにはしたものの、テンプレートの利用者(=スタックの作成者)がそれを指定してくれない、もしくは指定し忘れた、というケースが多いですね。

次のテンプレートを見てみてください。

{
  "Parameters": {
    "SecurityGroupId": {
      "Type": "AWS::EC2::SecurityGroup::Id"
    }
  },
  "Conditions": {
    "EmptySecurityGroup": {
      "Fn::Equals": [ { "Ref": "SecurityGroupId" }, "" ]
    }
  }
}

一見行けそうに見えますよね?確かにパラメータが未指定であってもスタックの作成リクエストは行えるのですが、実際にはスタック作成後のイベントで下記のようなメッセージが表示され、スタックの作成もしくは更新がロールバックされます。

Parameter validation failed: parameter value for parameter name {パラメータ名} does not exist. Rollback requested by user.

「じゃあデフォルト値を指定すれば大丈夫なんじゃないの?」と考えたそこのあなた。
やってみましょう。

{
  "Parameters": {
    "SecurityGroupId": {
      "Type": "AWS::EC2::SecurityGroup::Id",
      "Default": ""
    }
  },
  "Conditions": {
    "EmptySecurityGroup": {
      "Fn::Equals": [ { "Ref": "SecurityGroupId" }, "" ]
    }
  }
}

いい考えではあるんですが、だめなんです…。同じエラーが出ます。いずれにしても、(本当は利用者側の指定忘れなのに)「このテンプレートうまく動かないんですけどー」って文句を言われて悲しくなるパターンです。

送信前に未指定を阻止するためのアプローチとして適切なのは、AllowedPattern属性を追加で指定して書式制約を設けてしまうことです。

{
  "Parameters": {
    "SecurityGroupId": {
      "Type": "AWS::EC2::SecurityGroup::Id",
      "AllowedPattern": "^sg-[0-9a-z]+$",
      "ConstraintDescription": "Invalid Value"
    }
  },
  "Conditions": {
    "EmptySecurityGroup": {
      "Fn::Equals": [ { "Ref": "SecurityGroupId" }, "" ]
    }
  }
}

この制約を設けた場合であれば、スタックの作成指示が通る前にメッセージを出してくれます。(テンプレート例ではCostraintDescription属性を追加指定していますので、エラー出力にもそこで指定したメッセージが表示されています。)

Parameter SecurityGroupId failed to satisfy constraint: Invalid Value

まとめ

今回はCloudFormationテンプレートのパラメータで指定可能な「AWS固有のパラメータタイプ」について紹介しました。他にもたくさんの便利機能が存在しますので、うまく活用して素敵なテンプレートづくりにいそしんでくださいませ。ではまた!

投稿者プロフィール

hiroo
根っこはインフラ屋な古いおじさん。