この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
よく訓練されたアップル信者、都元です。
CloudFormationで環境を定義する際、作成済み *1のRoute 53のHostedZone名をパラメータとして与えて、各要素(EC2インスタンス, ELB, RDS等)に名前付けを行うのは、常套手段です。【AWS】VPC環境構築ノウハウ社内資料 2014年4月版でもご紹介しましたが、VPC内のインスタンスに対するプライベートIPアドレスは制御しないポリシーです。また、パブリックなIPアドレスやDNS名も、そもそも制御できるものではありません。したがって、参照が必要なインスタンスには、Route 53を用いて適宜名前を付けてやると、運用が楽になります。
オブジェクト指向プログラミングに慣れた人に対しては、オブジェクト(AWSから割り当てられたIPアドレスやDNS名)を直接触るんじゃなくて、インターフェイス(独自ドメインのDNS名)を介して触ることによって、コンポーネント間が疎結合になる、といった表現で伝わるかもしれません。
EC2編
例えば、踏み台 (bastion) インスタンスを立てる場合。以下のようにして、EIPに対するAレコードと、(自動付与の)プライベートIPアドレスに対するAレコードを両方つけたりします。まぁ後者は必要なければ省略しても全く問題ありません。こうすることにより、HostedZoneパラメータとしてexample.comを受け取った場合、このインスタンスにはbastion.example.comという名前が付き、この名前で外からアクセスできるようになります。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters" : {
"HostedZone" : {
"Default" : "",
"Description" : "The alternative domain name of this distribution.",
"Type" : "String"
},
},
"Resources": {
"BastionInstance": {
"Type": "AWS::EC2::Instance",
"Properties": {
...略...
}
},
"BastionInstanceEIP": {
"Type": "AWS::EC2::EIP",
"Properties": {
"Domain": "vpc",
"InstanceId": { "Ref" : "BastionInstance" }
}
},
"BastionDNSRecord" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Comment" : "A record for the Bastion instance.",
"Name" : { "Fn::Join" : [ "", [ "bastion.", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "A",
"TTL" : "300",
"ResourceRecords" : [
{ "Ref" : "BastionInstanceEIP" }
]
}
},
"BastionLocalDNSRecord" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Comment" : "A record for the private IP address of Bastion instance.",
"Name" : { "Fn::Join" : [ "", [ "bastion.local.", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "A",
"TTL" : "300",
"ResourceRecords" : [
{ "Fn::GetAtt" : [ "BastionInstance", "PrivateIp" ] }
]
}
}
}
}
RDS編
これも何も難しい話ではありません。概ねEC2インスタンスと同じです。最も大きな違いとしては、AレコードではなくCNAMEレコードとしている点です。この定義により、db.local.example.comでRDSに接続できるようになります。
"DatabaseInstance" : {
"Type" : "AWS::RDS::DBInstance",
"DeletionPolicy" : "Snapshot",
"Properties" : {
...略...
}
},
"DatabaseDNSRecord" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Comment" : "CNAME record for the db.",
"Name" : { "Fn::Join" : [ "", [ "db.local.", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "CNAME",
"TTL" : "300",
"ResourceRecords" : [
{ "Fn::GetAtt" : [ "DatabaseInstance", "Endpoint.Address" ] }
]
}
},
ELB編
ELBにも名前をつけておきましょう。この定義でelb.example.comでロードバランサにアクセスできます。
これはAレコードでもCNAMEレコードでもなく、ALIASレコードとして定義しているのが特徴です。ALIASレコードについて、詳しくはAmazon Route 53のALIASレコード利用のススメを御覧ください。
"ElasticLoadBalancer" : {
"Type" : "AWS::ElasticLoadBalancing::LoadBalancer",
"Properties" : {
...略...
}
},
"LoadBalancerDNSRecord" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Comment" : "ALIAS targeted to LoadBalancer.",
"Name" : { "Fn::Join" : [ "", [ "elb", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "A",
"AliasTarget" : {
"HostedZoneId" : { "Fn::GetAtt" : [ "ElasticLoadBalancer", "CanonicalHostedZoneNameID" ] },
"DNSName" : { "Fn::GetAtt" : [ "ElasticLoadBalancer","CanonicalHostedZoneName" ] }
}
}
},
CloudFront編
CloudFrontも、Distributionに対してAWSからDNS名が割り当てられます。これも直接使うのではなく、独自ドメインを挟んでおきましょう。この定義でassets.example.comがCloudFrontに繋がります。
ポイントは2つ。CloudFrontで独自ドメインを使う場合は、AliasesプロパティにDNS名を指定する必要があります(5〜7行目)。また、CloudFrontもALIASレコードに対応しているため、AliasTargetを指定するのですが、cloudfront.netのHosted Zone IDは、現状Z2FDTNDATAQYW2で固定です。ハードコーディングしてしまいましょう。
"AssetsDistribution" : {
"Type" : "AWS::CloudFront::Distribution",
"Properties" : {
"DistributionConfig" : {
"Aliases" : [
{ "Fn::Join" : [ "", [ "assets.", { "Ref" : "HostedZone" } ]]}
],
...略...
}
}
},
"AssetsGlobalDNSRecord" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Comment" : "ALIAS record for the assets distribution.",
"Name" : { "Fn::Join" : [ "", ["assets.", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "A",
"AliasTarget" : {
"HostedZoneId" : "Z2FDTNDATAQYW2",
"DNSName" : { "Fn::GetAtt" : [ "AssetsDistribution", "DomainName" ]}
}
}
},
Elastic Beanstalk 妄想編
さて、豆の木ですよ。Elastic BeanstalkのEnvironmentをCloudFormationに依らず普通に作成すると、その環境には*.elasticbeanstalk.comというDNS名が与えられます。本来は CloudFront のケースと同様に、このDNS名に対するALIASレコードを構成できるのがベストな状況です。しかし残念ながら現状、Route 53及びElastic BeanstalkはこのDNS名のエイリアスに対応していません。(対応お待ちしております!)
また、CloudFormationの "AWS::ElasticBeanstalk::Environment" 型のリソースに対し、{ "Fn::GetAtt" : [ "EBEnvironment", "EndpointURL" ]}とすることで、その環境にアクセスするためのDNS名を得ることができます。ここで取得できるDNS名は、本来はElastic BeanstalkのDNS名が返ってくるのがベストな状況です。しかし残念ながら現状、*.elasticbeanstalk.comではなく、Beanstalkの内部的に作られるELBのDNS名(例: awseb-myst-myen-132MQC4KRLAMD-1371280482.us-east-1.elb.amazonaws.com)となっています。(対応お待ちしております!)
というわけで理想は下記のようなものですが、まぁあくまでも妄想なので動きません。
これは妄想
"EBEnvironment" : {
"Type" : "AWS::ElasticBeanstalk::Environment",
"Properties" : {
...略...
}
},
これは妄想
"EBLoadBalancerDNSRecord" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Comment" : "ALIAS record for EB load balancer.",
"Name" : { "Fn::Join" : [ "", [ "eb.", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "A",
"AliasTarget" : {
"HostedZoneId" : { "Fn::GetAtt" : [ "EBEnvironment", "HostedZoneId" ]}, ← ここが妄想! "example.elasticbeanstalk.com" が返る妄想!
"DNSName" : { "Fn::GetAtt" : [ "EBEnvironment", "DNSName" ]} ← ここが妄想! "elasticbeanstalk.com" のHosted Zone IDが返る妄想!
}
}
},
これは妄想
Elastic Beanstalk ワークアラウンド編
ではどうすればいいか。よく訓練されたアップル信者、都元が頑張りました。{ "Fn::GetAtt" : [ "EBEnvironment", "EndpointURL" ]}で取得できるDNS名が、ELBのDNS名であることが幸いでした。
先ほど、「cloudfront.netのHosted Zone IDは、現状Z2FDTNDATAQYW2で固定です」と説明しました。同じように、ap-northeast-1.elb.amazonaws.comのHosted Zone IDは、Z2YN17T5R711GTで固定であることが分かりました。
ただし、CloudFrontはリージョンに対するサービスではないので、グローバルにHosted Zone IDが1つしかありません。一方、ELBはリージョン毎にゾーンが異なるため、それぞれにHosted Zone IDがありました。リージョン毎に違う値を選ぶ場合はMappingsですね! 各リージョンにおけるELBの Hosted Zone ID を調べました!
"Mappings" : {
"ELBDomain": {
"us-east-1": { "HostedZoneId": "Z3DZXE0Q79N41H" },
"us-west-2": { "HostedZoneId": "Z33MTJ483KN6FU" },
"us-west-1": { "HostedZoneId": "Z1M58G0W56PQJA" },
"eu-west-1": { "HostedZoneId": "Z3NF1Z3NOM5OY2" },
"ap-southeast-1": { "HostedZoneId": "Z1WI8VXHPB1R38" },
"ap-southeast-2": { "HostedZoneId": "Z2999QAZ9SRTIC" },
"ap-northeast-1": { "HostedZoneId": "Z2YN17T5R711GT" },
"sa-east-1": { "HostedZoneId": "Z2ES78Y61JGQKS" }
}
}
このようにMappingsを定義し、以下のように書けば、Elastic Beanstalkが内部的に生成するELBに対するALIASレコードが定義できます。うほほい。
"EBEnvironment" : {
"Type" : "AWS::ElasticBeanstalk::Environment",
"Properties" : {
...略...
}
},
"EBLoadBalancerDNSRecord" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]},
"Comment" : "ALIAS record for EB load balancer.",
"Name" : { "Fn::Join" : [ "", [ "eb.", { "Ref" : "HostedZone" }, "." ]]},
"Type" : "A",
"AliasTarget" : {
"HostedZoneId" : { "Fn::FindInMap" : [ "ELBDomain", { "Ref": "AWS::Region" }, "HostedZoneId" ]},
"DNSName" : { "Fn::GetAtt" : [ "EBEnvironment", "EndpointURL" ]}
}
}
},
というわけで、結構ワークアラウンドな感じですが、綺麗に動いています。一つだけ注意点として、この方法を使ったとしてもElastic BeanstalkのSwap Environment URL機能は使えません。残念orz
まとめ
なんだか無駄に見えるかもしれませんが、とにかく名前は付けておきましょう。独自ドメインで。従って、ある程度まとまったシステムのテンプレートには、パラメータとして必ずHostedZoneがある、と思っておくと良いでしょう。
脚注
- そして権威DNSとして上位に登録済み。 ↩