こんにちはsekitakaです。
AWSはとても便利ですが管理コンソールの操作が属人的になりやすい面もあります。
よくあるケースとして以下のような状況が考えられます。
- 実験好きのエンジニアがトライアンドエラーで環境を構築してノウハウを獲得します
- 同じような環境が必要になったが、別の担当者が環境構築を担当することになります
- すると1度社内の誰かが経験したハマりや苦労を別の担当者も経験しないと環境を構築することが出来ないという状況になります
しかし環境構築はシステム開発の本質的な部分ではないので、できるだけ時間をかけたくない部分です。
今回はこの問題を解決すべくCloudFormationを使った環境構築の方法を紹介します。
作成したテンプレートはgistに公開してありますので、自由に使用してください。
CloudFormationって?
CloudFormationとはjsonまたはyaml形式の設定ファイルでさまざまなAWSリソースを作成することができる仕組みです。
一度作成して終わりでなく既存のスタックを更新することもできます。
スタックとはCloudFormationで生成されるAWSリソースの集合の事を言います。
例えばS3のバケットを作成し、そのバケット名を使ってCloudFrontのオリジンに設定するといった事が可能です。
例題
この記事ではランディングページを作ることを想定して、次のような条件で静的Webサイトを構築するCloudFormationのテンプレートを作成します。
- ドメイン名を変数として入力できる
- S3にhtmlなどのリソースを保存する
- CloudFrontでキャッシュする
- Route53でドメイン名とCloudFrontをエリアスとして関連付ける
入力したドメインのネームサーバーをRoute53のHosted Zoneに向かせる設定を行えば、サイトにアクセス出来るようになります。
テンプレートを作成する
テンプレートはjsonもしくはyamlで記述することが出来ます。このテンプレートファイルがCloudFormationのキモです。
マネジメントコンソールからデザイナーで編集することもできます。
ただファイルサイズが大きくなってくると使い慣れたエディタで編集するほうが楽だと思います。どうせテンプレートの殆どの部分は手打ちする必要があるので。
この記事ではjsonでテンプレートを作成します。
全体像
先に最終的に出来上がるテンプレートの全体像を説明しておきます。
今回作成するテンプレートは以下の構成になります。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Mappings": {
},
"Parameters": {
},
"Resources": {
}
}
-
Mappings
は定数データのテーブルのようなものになります。Fn::FindInMap
という組み込み関数によって、値をテンプレート内から参照できます。 -
Parameters
は テンプレートからスタックを作成するときに指定できる入力値です。今回の例ではサイトのドメインを入力します。 -
Resources
は使用するAWSリソースの設定を行います。今回の例ではS3やCloudFront,Route53の設定などです。テンプレートの殆どはリソースの定義に費やされます。
その他の使用できる項目についてはこちらで確認できます。
Mappings
今回の例ではMappingsは次のようになります。
これはS3を静的Webサイトホスティングした場合の、リージョンとエンドポイントの対応表です。
例えばリージョンがus-east-1の場合、静的Webサイトホスティングのドメインは<バケット名>.s3-website-us-east-1.amazonaws.com
となります。
CloudFrontのオリジンの設定に必要なのでMappingsに定義しておきます。
{
"RegionMap": {
"us-east-1": {
"S3hostedzoneID": "Z3AQBSTGFYJSTF",
"websiteendpoint": "s3-website-us-east-1.amazonaws.com"
},
"us-west-1": {
"S3hostedzoneID": "Z2F56UZL2M1ACD",
"websiteendpoint": "s3-website-us-west-1.amazonaws.com"
},
"us-west-2": {
"S3hostedzoneID": "Z3BJ6K6RIION7M",
"websiteendpoint": "s3-website-us-west-2.amazonaws.com"
},
"eu-west-1": {
"S3hostedzoneID": "Z1BKCTXD74EZPE",
"websiteendpoint": "s3-website-eu-west-1.amazonaws.com"
},
"ap-southeast-1": {
"S3hostedzoneID": "Z3O0J2DXBE1FTB",
"websiteendpoint": "s3-website-ap-southeast-1.amazonaws.com"
},
"ap-southeast-2": {
"S3hostedzoneID": "Z1WCIGYICN2BYD",
"websiteendpoint": "s3-website-ap-southeast-2.amazonaws.com"
},
"ap-northeast-1": {
"S3hostedzoneID": "Z2M4EHUR26P7ZW",
"websiteendpoint": "s3-website-ap-northeast-1.amazonaws.com"
},
"sa-east-1": {
"S3hostedzoneID": "Z31GFT0UA1I2HV",
"websiteendpoint": "s3-website-sa-east-1.amazonaws.com"
}
}
}
このように定数を定義しておきテンプレートの別の場所から参照することができます。
Parameters
今回の例ではParametersは次のようになります。
作成したい静的サイトのドメインをRootDomainName
という変数名でテンプレート内の別の場所から参照できるように宣言しています。
{
"RootDomainName": {
"Description": "Domain name for your website (example.com)",
"Type": "String"
}
}
このようにスタック作成時に指定できるパラメタがあることによって、1つのテンプレートで幾つもの静的Webサイトを構築することができます。
変数の値はテンプレートの別の場所から組み込み関数であるRef
関数を使用することで参照できます。
Resources
AWSリソースの設定です。S3やCloudFrontなどの設定をここに記述します。
CloudFrontのテンプレートのメインがこの部分になります。
今回の例ではResourcesの設定の構造は次のようになります。
{
"MainBucket": {
},
"MainBucketPolicy": {
},
"CloudFront": {
},
"Route53HostedZone": {
},
"Route53RecordSet": {
}
}
このオブジェクトのキーは任意の名前で付けることができます。別のリソースから参照するときに必要になるので、それなりに識別し易い値がよいでしょう。
簡単に各リソースの説明をします。
-
MainBucket
WebサイトホスティングするS3のバケットの設定です -
MainBukcetPolicy
はそのバケットのバケットポリシーです -
CloudFront
CloudFrontのディストリビューションの設定です -
Route53HostedZone
はHostedZoneの設定です -
Route53RecordSet
はAレコードの設定です(CloudFrontに向けるのでAWSではエリアス設定と言われます)
では順にリソースの設定を見ていきます。
MainBucket
MainBucket
の設定は次のようになります。
全公開で静的Webサイトホスティングの設定をしています。
{
"Type": "AWS::S3::Bucket",
"Properties": {
"AccessControl": "PublicRead",
"WebsiteConfiguration": {
"ErrorDocument": "error.html",
"IndexDocument": "index.html"
}
}
}
バケット名は明示的に指定せず、自動で生成されるバケット名を使用します。
理由はこちらのBucketNameの説明にある通りです。
MainBucketPolicy
バケットポリシーの設定は次の通りです。
{
"Type": "AWS::S3::BucketPolicy",
"Properties": {
"Bucket": {
"Ref": "MainBucket"
},
"PolicyDocument": {
"Statement": [
{
"Action": [
"s3:GetObject"
],
"Effect": "Allow",
"Resource": {
"Fn::Join": [
"",
[
"arn:aws:s3:::",
{
"Ref": "MainBucket"
},
"/*"
]
]
},
"Principal": "*"
},
{
"Sid": "AddCannedAcl",
"Effect": "Allow",
"Principal": {
"AWS": {
"Fn::Join": [
"",
[
"arn:aws:iam::",
{
"Ref": "AWS::AccountId"
},
":root"
]
]
}
},
"Action": [
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": {
"Fn::Join": [
"",
[
"arn:aws:s3:::",
{
"Ref": "MainBucket"
},
"/*"
]
]
},
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "public-read"
}
}
}
]
}
}
}
Ref関数
Properties.Bucket
でRef
関数を使っています。 このように記述することで,MainBucket
のバケット名を参照することができます。
Ref
関数は参照先のリソースタイプによって返される値が異なります。
詳しくはドキュメントを確認しましょう。
Ref関数(疑似パラメータ参照)
Principalでは{"Ref": "AWS::AccountId"}
と組み込みのRef
関数を使用しています。
AWS::AccountId
というのはテンプレート内にでてきませんが、CloudFromation内で事前定義されている疑似パラメータと呼ばれるものです。
使用できる疑似パラメーターの一覧はドキュメントで確認することができます。
設定内容
ここで設定したポリシーは以下の2つになります。
- 全ユーザーにGetを許可
- 同じAWSアカウントのユーザーにPutObjectを許可し、PutされたObjectをpublic-readにする
Fn::Join関数
Fn::Join
関数で文字列の連結を行いResource
やPrincipal
の値を生成しています。
書式などはこちらで確認できます。
CloudFront
CloudFront
の設定は次のようになります。
{
"Type": "AWS::CloudFront::Distribution",
"DependsOn": [
"MainBucket"
],
"Properties": {
"DistributionConfig": {
"Aliases": [
{
"Ref": "RootDomainName"
}
],
"DefaultCacheBehavior": {
"DefaultTTL": 1,
"MaxTTL": 1,
"ForwardedValues": {
"QueryString": true
},
"TargetOriginId": "s3bucket",
"ViewerProtocolPolicy": "allow-all"
},
"Origins": [
{
"CustomOriginConfig": {
"OriginProtocolPolicy": "match-viewer"
},
"DomainName": {
"Fn::Join": [
"",
[
{
"Ref": "MainBucket"
},
".",
{
"Fn::FindInMap": [
"RegionMap",
{
"Ref": "AWS::Region"
},
"websiteendpoint"
]
}
]
]
},
"Id": "s3bucket"
}
],
"Enabled": true
}
}
}
DependsOnプロパティ
MainBucket
作成後でないと設定ができないので、DependsOn
にMainBucket
リソースを指定しています。
このようにして、リソースの依存関係を解決することができます。
Fn::FindInMap関数
DomainNameの指定でFn::FindInMap
関数を使っています。この関数でMappings
に定義したリージョンごとのS3静的WebサイトのエンドポイントのURLを取得しています。
スタックを作成したリージョンにS3バケットも作成され、CloudFrontからはそのS3バケットのリージョンの静的Webサイトエンドポイントをオリジンとして利用するようになっています。
Route53HostedZone
HostedZoneの設定は次のようになります。
HostedZoneが作成されていない場合に作成されます。
{
"Type": "AWS::Route53::HostedZone",
"Properties": {
"Name": {
"Fn::Join": [
"",
[
{
"Ref": "RootDomainName"
},
"."
]
]
}
}
}
Route53RecordSet
Aレコードの設定は次のようになります。
"HostedZoneId": "Z2FDTNDATAQYW2"
のように固定になっているのはCloudFrontのDistributionに向けるなら、この値を使いなさいとドキュメントで指定されていためです。
{
"Type": "AWS::Route53::RecordSet",
"Properties": {
"AliasTarget": {
"DNSName": {
"Fn::GetAtt": [
"CloudFront",
"DomainName"
]
},
"HostedZoneId": "Z2FDTNDATAQYW2"
},
"HostedZoneId": {
"Ref": "Route53HostedZone"
},
"Name": {
"Fn::Join": [
"",
[
{
"Ref": "RootDomainName"
}
]
]
},
"Type": "A"
}
}
まとめ
いかがでしたでしょうか。CloudFrontのテンプレート例として静的Webサイトを構築するテンプレートを作ってみました。
CloudFrontのテンプレートを作成しておくと環境の複製が用意になり、テスト環境の構築などにも使えるのが良いですね。
また設定をテキスト化していく過程で各種AWSリソースの設定値についても改めて理解を深めることが出来たようにも思えます。
今後は積極的にCloudFormationを使った環境構築をしていこうと思います。
作成したテンプレートはgistに公開してありますので、自由に使用してください。