Help us understand the problem. What is going on with this article?

[JAWS-UG CLI] CloudFormation:#3 S3でwebサイトホスティング (独自ドメイン)

More than 5 years have passed since last update.

http://jawsug-cli.doorkeeper.jp/events/13797 でのハンズオン資料です。

複数のテンプレートを利用して、S3上に独自ドメインでの静的webサイトを構築します。

前提条件

0. 事前作業

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.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": []
   }

2. 共通テンプレート転送

作成した共通テンプレートを、共通テンプレート用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

3. 個別テンプレート作成

個別案件用のテンプレートを作成します。
先程作成した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. バケットの作成

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
        }
    ]
}

5. コンテンツの転送

サンプルコンテンツを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

6. コンテンツへのアクセス

ブラウザで下記の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設定
    • ログアーカイブ設定
  • コンテンツ用バケット

    • ログ保存設定

7. 後始末 (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" )'
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした