【CloudFormation Tips】全リージョン・全アカウント対応型テンプレート
よく訓練されたアップル信者、都元です。
CloudFormationテンプレートの理想 *1は、同じテンプレートを使って、あらゆるリージョン・あらゆるAWSアカウントで同じ構成が展開できることです。しかし、何も考えずにテンプレートを作ってしまうと、特定のリージョンでしか動かない、特定のAWSアカウントでしか動かないテンプレートが出来てしまいがちです。
AMIの参照方法
例えば、AMIというのは特定のリージョン内に存在するエンティティです。なので、テンプレートの中に固定でAMI IDを記述してしまうと、そのAMIがあるリージョンでしか使えないテンプレートとなってしまいます。また、公開状態ではないプライベートなAMIを使った場合、そのAMIが参照できるAWSアカウントでしか使えないテンプレートになってしまいます。
そこで、Amazon LinuxのAMI(2014.03版)であれば、このようなMappingを用意して、テンプレート内部からは下記のような参照を記述することによって、リージョン依存を防ぐことができます。
"Mappings": { "AWSAmazonLinuxAMI": { "us-east-1": { "201403": "ami-2f726546" }, "us-west-2": { "201403": "ami-b8f69f88" }, "us-west-1": { "201403": "ami-84f1cfc1" }, "eu-west-1": { "201403": "ami-a921dfde" }, "ap-southeast-1": { "201403": "ami-787c2c2a" }, "ap-southeast-2": { "201403": "ami-0bc85031" }, "ap-northeast-1": { "201403": "ami-a1bec3a0" }, "sa-east-1": { "201403": "ami-89de7c94" } } }
{ "Fn::FindInMap": [ "AWSAmazonLinuxAMI", { "Ref": "AWS::Region" }, "201403" ]}
あと、身も蓋もないんですが、プライベートなAMIはなるべく使わないようにします。Amazon Linuxスタートで、UserDataやcfn-initを使って、自己組織化に勤しみますw
Availability Zoneの参照
次に、Multi-AZの構成を組む際に、2つのAZを指定しなければならない場合があります。例えばVPC構成の場合は、2つのサブネットを複数のAZに渡って展開したいことがしばしばあります。それを実現するために、従来私は下記のようなMappingを使っていました。("従来" ということは、もっと良い解決策があるんですが、それは後ほど)
"AZ": { "us-east-1": { "primary": "us-east-1d", "secondary": "us-east-1a" }, "us-west-2": { "primary": "us-west-2a", "secondary": "us-west-2b" }, "us-west-1": { "primary": "us-west-1a", "secondary": "us-west-1b" }, "eu-west-1": { "primary": "eu-west-1a", "secondary": "eu-west-1b" }, "ap-southeast-1": { "primary": "ap-southeast-1a", "secondary": "ap-southeast-1b" }, "ap-southeast-2": { "primary": "ap-southeast-2a", "secondary": "ap-southeast-2b" }, "ap-northeast-1": { "primary": "ap-northeast-1a", "secondary": "ap-northeast-1c" }, "sa-east-1": { "primary": "sa-east-1a", "secondary": "sa-east-1b" } },
このMappingを下記のように参照すれば、Multi-AZのサブネットが出来上がります。
"Subnet1": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "VPC" }, "AvailabilityZone": { "Fn::FindInMap": [ "AZ", { "Ref": "AWS::Region" }, "primary" ]}, "CidrBlock": "10.0.0.0/24" } }, "Subnet2": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "VPC" }, "AvailabilityZone": { "Fn::FindInMap": [ "AZ", { "Ref": "AWS::Region" }, "secondary" ]}, "CidrBlock": "10.0.1.0/24" } },
しかしこの方法には問題点があります。例えば、東京リージョンには、ap-northeast-1a, 1b, 1cという3つのAZがあります。しかし、この中の1つ(多くの場合は1b)は新規リソースの割り当てが停止しているようで、新しいサブネットが定義できないことが知られています。ここでは便宜的にこのようなAZを「無効AZ」と呼びましょう。逆に1aと1cは「有効AZ」と呼ぶことにします。
そして、東京にある3つのAZは、アカウントによって実体が異なる、ということも一部で知られています。つまり、AWSアカウントAと、AWSアカウントBがあったとして、それぞれのap-northeast-1aは同じAZを表しているとは限らない *2のです。というわけで、1bが無効AZであることが多いようですが、1bが無効だとは限らないんですね。1aが無効AZであるAWSアカウントも確認されています。
という状況下で、前述のようなMappingを利用してしまうと、"1aが無効AZであるAWSアカウント" では使えないテンプレートになってしまいます。
これを避けるための手法を最近思いつたでのご紹介します。
"Subnet1": { "Type": "AWS::EC2::Subnet", "Properties" : { "VpcId": { "Ref": "VPC" }, "AvailabilityZone" : { "Fn::Select" : [ "0", { "Fn::GetAZs" : { "Ref" : "AWS::Region" }}]}, "CidrBlock": "10.0.0.0/24" } }, "FrontendSubnet2": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "VPC" }, "AvailabilityZone" : { "Fn::Select" : [ "1", { "Fn::GetAZs" : { "Ref" : "AWS::Region" }}]}, "CidrBlock": "10.0.1.0/24" } },
Fn::GetAZsは、私は勝手に各リージョンにおける「全AZのリスト」を返すものだと思い込んでいましたが、実は「有効AZのリスト」を返すようです。そこで、上記のような記述によって、複数の有効AZを取得できる、というわけです。
一応検証のために、私の持っているアカウントにおいて全リージョンでそれぞれ、下記のテンプレートのoutputを確認してみました。
{ "AWSTemplateFormatVersion": "2010-09-09", "Resources": { "DummyWaitHandle" : { "Type" : "AWS::CloudFormation::WaitConditionHandle" } }, "Outputs": { "AvailabilityZones": { "Value": { "Fn::Join" : [ ",", { "Fn::GetAZs" : { "Ref" : "AWS::Region" }}]} } } }
その結果は下記です。
"eu-west-1a,eu-west-1b,eu-west-1c" "sa-east-1a,sa-east-1b" "us-east-1a,us-east-1c,us-east-1d" "ap-northeast-1a,ap-northeast-1c" "us-west-2a,us-west-2b,us-west-2c" "us-west-1a,us-west-1b" "ap-southeast-1a,ap-southeast-1b" "ap-southeast-2a,ap-southeast-2b"
各リージョンにおいて、最低2つのAZが返ってきていますね。
まとめ
というわけで、上記のTipsを意識して、再利用可能なテンプレートを量産していきましょう。