http://jawsug-cli.doorkeeper.jp/events/13797 でのハンズオン資料です。
複数のテンプレートを利用して、S3上に独自ドメインでの静的webサイトを構築します。
前提条件
- S3へのフルアクセス権限
- http://qiita.com/tcsh/items/2ae34c849322f9cc9584 の手順でCloudFormationのテンプレートを置くためのS3バケットを作成済であること。
- 事前作業
=================
0.1. 公開用バケット名の決定
S3バケット名を決めておいてください。独自ドメインで公開する場合は、公開するホスト名にしておく必要があります。
$ S3_BUCKET_NAME='www.example.jp'
0.2. 共通テンプレート用バケット名の決定
http://qiita.com/tcsh/items/2ae34c849322f9cc9584 の手順で作成した、CloudFormationのテンプレートを置くためのS3バケット名を指定します。
$ CF_BUCKET_NAME='cf-example'
$ CF_BUCKET_URL="s3-`aws s3api get-bucket-location --bucket=${CF_BUCKET_NAME} --query LocationConstraint --output text`.amazonaws.com/${CF_BUCKET_NAME}" \
&& echo ${CF_BUCKET_URL}
s3-us-west-1.amazonaws.com/cf-example
このURLは、個別テンプレートから共有テンプレートを呼び出すときに利用します。
0.3. 作業用ディレクトリの作成
個別テンプレートを置くためのディレクトリを作成します。
$ WORK_DIR="${HOME}/tmp/aws_cf"
$ mkdir -p ${WORK_DIR}
0.4. コンテンツ用ディレクトリの作成
共通テンプレートを置くためのディレクトリを作成します。ここにあるテンプレートをS3バケットに転送します。
$ S3_CONTENTS_DIR="${HOME}/tmp/contents"
$ mkdir -p ${S3_CONTENTS_DIR}
$ cd ${S3_CONTENTS_DIR}
- 共通テンプレート作成
=======================
1.1. web siteバケット用テンプレート
CloudFormationテンプレートを作成します。
$ FILE_CF_TEMPLATE_BUCKET='cf-s3-bucket-website.template'
$ cat << EOF > ${FILE_CF_TEMPLATE_BUCKET}
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Parameters" : {
"BucketName" : {
"Type" : "String",
"Default" : "",
"Description" : ""
}
},
"Resources" : {
"S3Bucket" : {
"Type" : "AWS::S3::Bucket",
"Properties" : {
"BucketName" : { "Ref" : "BucketName" },
"WebsiteConfiguration": {
"IndexDocument": "index.html",
"ErrorDocument": "error.html"
}
}
}
},
"Description" : "website bucket template",
"Outputs" : {
"S3Bucket" : {
"Value" : { "Ref" : "S3Bucket" }
}
}
}
EOF
JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。
$ cat ${FILE_CF_TEMPLATE_BUCKET} | json_verify
JSON is valid
validate-templateサブコマンドで、簡単な検証ができます。
$ aws cloudformation validate-template --template-body file://${FILE_CF_TEMPLATE_BUCKET}
{
"Description": "website bucket template",
"Parameters": [
{
"DefaultValue": null,
"NoEcho": false,
"Description": null,
"ParameterKey": "BucketName"
}
],
"Capabilities": []
}
1.2. web siteバケットポリシー用テンプレート
CloudFormationテンプレートを作成します。
$ FILE_CF_TEMPLATE_BUCKETPOLICY='cf-s3-bucketpolicy-website.template'
$ cat << EOF > ${FILE_CF_TEMPLATE_BUCKETPOLICY}
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "website bucket-policy template",
"Parameters" : {
"BucketName" : {
"Type" : "String",
"Default" : "",
"Description" : ""
}
},
"Resources" : {
"S3BucketPolicy" : {
"Type" : "AWS::S3::BucketPolicy",
"Properties" : {
"Bucket" : { "Ref" : "BucketName" },
"PolicyDocument" : {
"Statement" : [{
"Action" : "s3:GetObject",
"Effect" : "Allow",
"Principal" : { "AWS": "*" },
"Resource" : {
"Fn::Join" : [
"",
["arn:aws:s3:::", { "Ref" : "BucketName" }, "/*" ]
]
},
"Sid":"AddPerm"
}]
}
}
}
},
"Outputs" : {
"S3BucketPolicy" : {
"Value" : { "Ref" : "S3BucketPolicy" }
}
}
}
EOF
JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。
$ cat ${FILE_CF_TEMPLATE_BUCKETPOLICY} | json_verify
JSON is valid
validate-templateサブコマンドで、簡単な検証ができます。
$ aws cloudformation validate-template --template-body file://${FILE_CF_TEMPLATE_BUCKETPOLICY}
{
"Description": "website bucket-policy template",
"Parameters": [
{
"DefaultValue": null,
"NoEcho": false,
"Description": null,
"ParameterKey": "BucketName"
}
],
"Capabilities": []
}
- 共通テンプレート転送
=======================
作成した共通テンプレートを、共通テンプレート用S3バケットに転送します。
2.1. 共通テンプレートのテンプレート用バケットへの転送
テンプレートを、S3バケットに転送します。
$ cd ${S3_CONTENTS_DIR}
$ aws s3 sync . "s3://${CF_BUCKET_NAME}/"
upload: ./cf-s3-bucket-website.template to s3://cf-example/cf-s3-bucket-website.template
upload: ./cf-s3-bucketpolicy-website.template to s3://cf-example/cf-s3-bucketpolicy-website.template
2.2. 共通テンプレートの確認
テンプレートが転送されたことを確認します。
$ aws s3 ls s3://${CF_BUCKET_NAME}/
2014-09-22 16:48:48 671 cf-s3-bucket-website.template
2014-09-22 16:48:48 997 cf-s3-bucketpolicy-website.template
- 個別テンプレート作成
=======================
個別案件用のテンプレートを作成します。
先程作成した2つの共通テンプレートを呼び出して利用します。(チェーン実行)
3.1. 個別テンプレート作成 (S3バケット)
CloudFormationテンプレートを作成します。
$ FILE_CF_TEMPLATE="${S3_BUCKET_NAME}-bucket.template"
$ S3_DESCRIBE="${S3_BUCKET_NAME} website bucket"
$ cat << EOF > ${FILE_CF_TEMPLATE}
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Resources" : {
"Bucket" : {
"Type" : "AWS::CloudFormation::Stack",
"Properties" : {
"TemplateURL" : "https://${CF_BUCKET_URL}/${FILE_CF_TEMPLATE_BUCKET}",
"Parameters" : {
"BucketName" : "${S3_BUCKET_NAME}"
}
}
},
"BucketPolicy" : {
"Type" : "AWS::CloudFormation::Stack",
"Properties" : {
"TemplateURL" : "https://${CF_BUCKET_URL}/${FILE_CF_TEMPLATE_BUCKETPOLICY}",
"Parameters" : {
"BucketName" : "${S3_BUCKET_NAME}"
}
}
}
},
"Outputs" : {
"S3Bucket" : {
"Value" : { "Fn::GetAtt" : ["Bucket", "Outputs.S3Bucket"]}
},
"S3BucketPolicy" : {
"Value" : { "Fn::GetAtt" : ["BucketPolicy", "Outputs.S3BucketPolicy"]}
}
},
"Description": "${S3_DESCRIBE}"
}
EOF
JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。
$ cat ${FILE_CF_TEMPLATE} | json_verify
JSON is valid
- バケットの作成
=================
4.1. S3バケットの作成
スタック名を定義します。
$ CF_STACK_NAME="s3-bucket-`echo ${S3_BUCKET_NAME} | sed 's/\./-/g'`"; echo ${CF_STACK_NAME}
s3-bucket-www-example-jp
S3バケットを作成するスタックを実行します。
$ aws cloudformation create-stack --stack-name ${CF_STACK_NAME} --template-body file://${FILE_CF_TEMPLATE}
{
"StackId": "arn:aws:cloudformation:us-west-1:XXXXXXXXXXXX:stack/s3-bucket-www-example-jp/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
}
4.2. スタックの状況確認
list-stacksコマンドで、スタックの状況を確認してみましょう。
$ aws cloudformation list-stacks \
| jq -r --arg stackname ${CF_STACK_NAME} '.StackSummaries[] | select(.StackName == $stackname) | .StackStatus'
CREATE_COMPLETE
StackStatusが'CREATE_COMPLETE'になっていれば作成は完了です。
それ以外が表示されている場合は、下記コマンドでFailedの文字が出ている前後を見て原因を調べます。
$ aws cloudformation describe-stack-events --stack-name ${CF_STACK_NAME}
4.3. スタックの内容確認
作成されたスタックの内容を確認します。
$ aws cloudformation get-template --stack-name ${CF_STACK_NAME}
{
"TemplateBody": {
"AWSTemplateFormatVersion": "2010-09-09",
"Outputs": {
"S3Bucket": {
"Value": {
"Fn::GetAtt": [
"Bucket",
"Outputs.S3Bucket"
]
}
},
"S3BucketPolicy": {
"Value": {
"Fn::GetAtt": [
"BucketPolicy",
"Outputs.S3BucketPolicy"
]
}
}
},
"Description": "www.example.jp website bucket",
"Resources": {
"BucketPolicy": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "https://s3-us-west-1.amazonaws.com/cf-example/cf-s3-bucketpolicy-website.template",
"Parameters": {
"BucketName": "www.example.jp"
}
}
},
"Bucket": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "https://s3-us-west-1.amazonaws.com/cf-example/cf-s3-bucket-website.template",
"Parameters": {
"BucketName": "www.example.jp"
}
}
}
}
}
}
4.4. S3バケットの確認
実際にS3バケットが作成されていることを確認しましょう。
$ aws s3 ls | grep ${S3_BUCKET_NAME}
2014-09-09 15:19:34 www.example.jp
4.5. S3バケットポリシーの確認
作成されたS3バケットのバケットポリシーを確認しましょう。
$ aws s3api get-bucket-policy --bucket ${S3_BUCKET_NAME}
{
"Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AddPerm\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":\"s3:GetObject\",\"Resource\":\"arn:aws:s3:::www.example.jp/*\"}]}"
}
上記の内容になっていれば、外部からコンテンツに対するアクセスを許可しています。
4.6. webサイトホスティングの設定
作成されたS3バケットのwebサイトホスティング設定を確認しましょう。
$ aws s3api get-bucket-website --bucket ${S3_BUCKET_NAME}
{
"RedirectAllRequestsTo": {},
"IndexDocument": {
"Suffix": "index.html"
},
"ErrorDocument": {
"Key": "error.html"
},
"RoutingRules": []
}
上記の内容になっていれば、外部からのアクセスに対してコンテンツを返すことができます。
4.7. Outputsの確認
describe-stacksサブコマンドでOutputsの出力を確認できます。
$ aws cloudformation describe-stacks --stack-name ${CF_STACK_NAME}
{
"Stacks": [
{
"StackId": "arn:aws:cloudformation:us-west-1:XXXXXXXXXXXX:stack/s3-bucket-www-example-jp/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"Description": "www-example.jp website bucket",
"Tags": [],
"Outputs": [
{
"OutputKey": "S3Bucket",
"OutputValue": "www-example.jp"
},
{
"OutputKey": "S3BucketPolicy",
"OutputValue": "s3-bucket-www-example-jp-BucketP-S3BucketPolicy-SD37C6TE1IZA"
}
],
"StackStatusReason": null,
"CreationTime": "2014-09-22T08:03:04.997Z",
"StackName": "s3-bucket-www-example-jp",
"NotificationARNs": [],
"StackStatus": "CREATE_COMPLETE",
"DisableRollback": false
}
]
}
- コンテンツの転送
===================
サンプルコンテンツをS3バケットに転送します。
5.1. コンテンツの取得
GitHubからコンテンツを取得します。
$ cd ${HOME}/tmp/
$ git clone https://github.com/opelab/jawsug-cli-sample-web.git && cd jawsug-cli-sample-web
5.2. コンテンツの同期
取得したコンテンツをS3バケットに転送します。
$ aws s3 sync . "s3://${S3_BUCKET_NAME}/" --exclude ".git*"
5.3. コンテンツの確認
S3バケットにコンテンツファイルが転送されたことを確認します。
$ aws s3 ls s3://${S3_BUCKET_NAME}/
2014-09-21 19:52:50 78 index.html
- コンテンツへのアクセス
=========================
ブラウザで下記のURLにアクセスして、コンテンツが見えることを確認します。
$ S3_BUCKET_URL="${S3_BUCKET_NAME}.s3-website-`aws s3api get-bucket-location --bucket=${S3_BUCKET_NAME} --query LocationConstraint --output text`.amazonaws.com" \
&& echo ${S3_BUCKET_URL}
http://www01.opelab.jp.s3-website-us-west-1.amazonaws.com/
www01.opelab.jp.s3-website-us-west-1.amazonaws.com
残作業
今回は、以下の作業についてはやってないです。
-
ログ用バケット
-
作成
-
ACL設定
-
ログアーカイブ設定
-
コンテンツ用バケット
-
ログ保存設定
- 後始末 (S3バケットの削除)
============================
後始末としてS3バケットを削除します。
7.1. スタックの一覧
まず、スタック一覧から削除するスタックのスタック名を確認します。
$ aws cloudformation list-stacks \
| jq '.StackSummaries[] | select(.StackStatus != "DELETE_COMPLETE" )'
7.2. 削除対象の確定
次に、削除するスタック名と、対象となるS3バケット名を特定します。
$ S3_BUCKET_NAME='www.example.jp'
$ CF_STACK_NAME='s3-bucket-www-example-jp'
7.3. バケットの確認
バケットは中身が空でないと削除できないので、バケットにファイルがないか確認します。
$ aws s3 ls s3://${S3_BUCKET_NAME}/
2014-09-09 15:19:34 www.example.jp
7.4. バケットの中身削除
S3バケット内にファイルが存在する場合は、削除します。
$ aws s3 rm s3://${S3_BUCKET_NAME}/ --recursive
7.5. スタックの削除
実際に、スタックを削除してみます。
$ aws cloudformation delete-stack --stack-name ${CF_STACK_NAME}
(戻り値なし)
7.6. スタックのステータス確認
list-stacksサブコマンドで削除状況を確認します。
$ aws cloudformation list-stacks \
| jq -r --arg stackname ${CF_STACK_NAME} '.StackSummaries[] | select(.StackName == $stackname) | .StackStatus'
DELETE_COMPLETE
StackStatusが'DELETE_COMPLETE'になっていれば削除は完了です。
それ以外が表示されている場合は、下記コマンドでFailedの文字が出ている前後を見て原因を調べます。
$ aws cloudformation describe-stack-events --stack-name ${CF_STACK_NAME}
7.7. バケットの確認
削除対象のバケットが存在しないことを確認します。
$ aws s3 ls s3://${S3_BUCKET_NAME}/
A client error (NoSuchBucket) occurred when calling the ListObjects operation: The specified bucket does not exist
7.8. スタックの一覧
list-stacksサブコマンドで他に削除するものがないか確認します。
$ aws cloudformation list-stacks \
| jq '.StackSummaries[] | select(.StackStatus != "DELETE_COMPLETE" )'