この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
よく訓練されたアップル信者、都元です。
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を意識して、再利用可能なテンプレートを量産していきましょう。