サーバーレスのススメ AWSCloudFormationでサーバーレス環境を立ち上げる

前回のサーバーレスのススメ S3で静的コンテンツサーバーを立てるでは、AWSのコントロールパネルから環境を立ち上げました。
今回は、この作業をルーチン化してみます。
理解するのにかなり時間がかかってしまいましたが、今まで手動でやっていた作業を処理に書き換えるという作業をやってみると、色々な発見する事が多かったです。

この記事で得られる知識

  • CloudFormationで出来る初歩的な事がわかる
  • CloudFormationを使ってS3バケットの環境を構築できるようになる

イントロダクション

作業の流れ

  1. CloudFormationテンプレート作成
  2. S3バケットを作るリソースを追加
  3. S3バケットのポリシーを追加
  4. 公開URLを出力するように調整
  5. スタック作成

構成(案)

最終的には下記のような構成にすることを考えていますが、今回はS3バケットを立ち上げるところまでです。

f:id:nakahashi_h:20171226222558p:plain

今回作成したソース

githubで公開しています。
github.com

今後随時更新していきますが、今回の作業は、v0.1.1が対象です。 

参考資料

docs.aws.amazon.com

CloudFormationテンプレート作成

AWSで様々なサービスを構成する場合、CloudFormationというサービスを使います。
CloudFormationでは、「Template」と呼ばれる構成ファイルを自作し、それを元にAWSのサービスを構成して「Stack」を作成します。

f:id:nakahashi_h:20171226223050p:plain

まずは、Templateを作成します。
TemplateはJSON形式か、YAML形式から選べます。今回はJSON形式で作成します。
ファイル名も任意ですが、今回はのcloudformation.jsonというファイル名にします。

cloudformation.json

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "Serverless base",
    "Parameters": {
        "EnvType": {
            "Description": "Enviroment type. Default is test.",
            "Default": "test",
            "Type": "String",
            "AllowedValues": [
                "prod",
                "test"
            ],
            "ConstraintDescription": "must specify prod or test."
        },
        "InstanceType": {
            "Description": "Enter t2.micro, m1.small, or m1.large. Default is t2.micro.",
            "Type": "String",
            "Default": "t2.micro",
            "AllowedValues": [
                "t2.micro",
                "m1.small",
                "m1.large"
            ],
            "ConstraintDescription": "must specify t2.micro, m1.small, or m1.large."
        }
    },
    "Conditions" : {
        "CreateProdResources" : {
            "Fn::Equals" : [
                {"Ref" : "EnvType"}, "prod"
            ]
        }
    }
}

構成について簡単に解説します。
詳細は、CloudFormationのドキュメントを参考にしてください。

"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Serverless base",

まず、Templateのバージョンと概要を宣言します。

  • AWSTemplateFormatVersionセクション: テンプレートの規格のバージョンを指定。2010-09-09が最新なので変更不要
  • Descriptionセクション: テンプレートの説明。
"Parameters": {
    "EnvType": {
        "Description": "Enviroment type. Default is test.",
        "Default": "test",
        "Type": "String",
        "AllowedValues": [
            "prod",
            "test"
        ],
        "ConstraintDescription": "must specify prod or test."
    },
    "InstanceType": {
        "Description": "Enter t2.micro, m1.small, or m1.large. Default is t2.micro.",
        "Type": "String",
        "Default": "t2.micro",
        "AllowedValues": [
            "t2.micro",
            "m1.small",
            "m1.large"
        ],
        "ConstraintDescription": "must specify t2.micro, m1.small, or m1.large."
    }
},

次に、Parametersセクションを定義します。
Parametersセクションは、CLIで構築するときに、引数として指定する事でスタック作成時に反映させる事が出来ます。
現在の状態ではまだ組み込めていませんが、次の事をするために使用します。

  • EnvType: 本番とテスト環境の識別
  • InstanceType: EC2インスタンスのサイズ指定
"Conditions" : {
    "CreateProdResources" : {
        "Fn::Equals" : [
            {"Ref" : "EnvType"}, "prod"
        ]
    }
}

最後に、リソースの作成有無についての状態用の識別子です。
リソース内で、Conditionという共通の属性があり、Conditionsセクションで指定した結果を挿入するときに使います。

S3バケットを作るリソースを追加

次にResourcesセクションにS3バケットの構成を追加します。    リソース名は「S3Bucket」としています。

cloudformation.json

{
    "Resources": {
        "S3Bucket": {
            "Type": "AWS::S3::Bucket",
            "Properties": {
                "AccessControl": "PublicRead",
                "WebsiteConfiguration": {
                    "IndexDocument": "index.html",
                    "ErrorDocument": "error.html"
                },
                "Tags" : [
                    {
                        "Key" : "name",
                        "Value" : "S3Bucket",
                    }
                ]
            },
        },
    }
}
  • Type: 追加するAWSのサービスを追加します。追加できるプロパティのタイプはAWS リソースプロパティタイプのリファレンスを参照してください。
  • Properties: Typeで選択したタイプに付与する属性を選択してください。この記事S3のリファレンスに順次ます。

スタックを消してもバケットだけ残したい場合

スタックを削除すると、連動してS3のバケットも消えてしまうのですが、バケットだけ残して置きたいという時があります。
その場合は、リソースにDeletionPolicy属性を追加し「Retain」にします。

{
    "Resources": {
        "S3Bucket": {
            "Type": "AWS::S3::Bucket",
            "DeletionPolicy": "Retain"
        }
    }
}

バケットの名前を定義する

PropertiesセクションにBucketNameを追加する事で指定ができるようです。
もし、固有のバケットを作りたいのであれば上記を指定するやり方もあります。

{
    "Resources": {
        "S3Bucket": {
            "Type": "AWS::S3::Bucket",
            "BucketName" : "名前",
        }
    }
}

S3バケットのポリシーを追加

この状態では、インターネットをバケットに引き込めないのでS3のポリシーを追加します。
ポリシーの書き方については、前の記事でも触れましたのでこちらも参考にしてください。

websandbag.hatenablog.com

cloudformation.json

"S3BucketPolicy": {
    "Type": "AWS::S3::BucketPolicy",
    "Properties": {
        "PolicyDocument": {
            "Id": "MyPolicy",
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "PublicReadForGetBucketObjects",
                    "Effect": "Allow",
                    "Principal": "*",
                    "Action": "s3:GetObject",
                    "Resource": {
                        "Fn::Join": [
                            "",
                            [
                                "arn:aws:s3:::",
                                {
                                    "Ref": "S3Bucket"
                                },
                                "/*"
                            ]
                        ]
                    }
                }
            ]
        },
        "Bucket": {
            "Ref": "S3Bucket"
        }
    }
}

特筆すべきなのは、Fn::Joinの内容です。
Fn::Joinの部分については、区切り文字を使わず、第2引数の配列を結合するという意味です。
Refは、指定したリソースの値を返します。
S3の場合は、バケット名を返します。
他のリソースの場合は、リソースの戻り値の例 を参照してください。

"Resource": {
    "Fn::Join": [
        "",
        [
            "arn:aws:s3:::",
            {
                "Ref": "S3Bucket"
            },
            "/*"
        ]
    ]
}

公開URLを出力するように調整

この状態でスタックを作成してもよいのですが、公開URLはAWSのコントロールパネルからしか確認できません。
そこで、CLIでCloudFormationにアクセスしたときにURLを返すように調整します。

cloudformation.json

"Outputs": {
    "WebsiteURL": {
        "Value": {
            "Fn::GetAtt": [
                "S3Bucket",
                "WebsiteURL"
            ]
        },
        "Description": "URL for website hosted on S3"
    },
    "S3BucketSecureURL": {
        "Value": {
            "Fn::Join": [
                "",
                [
                    "https://",
                    {
                        "Fn::GetAtt": [
                            "S3Bucket",
                            "DomainName"
                        ]
                    }
                ]
            ]
        },
        "Description": "Name of S3 bucket to hold website content"
    }
}

「WebsiteURL」がhttpプロトコルの場合のURLで、「S3BucketSecureURL」がhttpsプロトコルの場合のURLです。

また、テンプレートの中でFn::GetAttという関数を使っています。
これは、次のようにリソース名と属性を指定して値を代入する事ができます。

{
    "Fn::GetAtt" : [ "logicalNameOfResource", "attributeName" ]
}

属性については、公式ドキュメントにも記載されていますので必要に応じて取得してください。

スタック作成

作成したテンプレートを元にスタックを作成します。
今回は、スタック名を「myteststack」にしています。

$ cd (プロジェクトフォルダ)
$ aws cloudformation create-stack \
    --stack-name myteststack \
    --template-body file://cloudformation.json

実行すると、s3のバケットが作成され公開まで出来ます。
試しにAWSのコントロールパネルからS3のバケットを確認してみてください。

作成されたstackを確認する方法

Outputsセクションの設定をしていれば下記のコマンドでURLを確認する事が出来ます。

$ aws cloudformation describe-stacks --stack-name myteststack

... 出力結果 ...

"Outputs": [
     {
         "OutputValue": "httpsのURL",
         "Description": "Name of S3 bucket to hold website content",
         "OutputKey": "S3BucketSecureURL"
     },
     {
         "OutputValue": "httpのURL",
         "Description": "URL for website hosted on S3",
         "OutputKey": "WebsiteURL"
     }
 ],

作成されたstackを削除

作成したスタックを削除する場合は次のコマンドを実行します。
リソースを残す設定をしていない場合は、作成されたリソースも一緒に削除されます。

$ aws cloudformation delete-stack --stack-name myteststack

スタックを更新

テンプレートを修正した場合は、次のコマンドでスタックを作り直す事が出来ます。

$ aws cloudformation update-stack \
    --stack-name mystack \
    --template-body file://cloudformation.json

Tips

CloudFormationデザイナーとは?

GUIでパーツを繋げる事でテンプレートを直感的に作るツールです。 簡単な構成であればテンプレートを自作する必要はなく、このツールだけで作る事が可能です。

f:id:nakahashi_h:20171226223723p:plain

テスト用のサーバーを一時的に構築できないか??

ここで言う、「テスト環境」というのは、表示確認ではなくユニットテストを行うためのサーバーになります。

運用環境の理想を言えば、テストケースを通してからデプロイできるのが一番望ましい環境になります。
例えば、PHPの場合は、一度、jenkinsサーバーのワークスペースにソースを展開し、PHPUnit等のテストライブラリでテストをした後、問題なければデプロイするような構成になるかと思います。 (実装経験がないため、自信はありませんが…)

f:id:nakahashi_h:20171226223112p:plain

これを、AWSの環境で行う場合は、CodePipelineに組み込んで、ビルドサーバーを作るという事になるかと思います。
今回は実装しませんが、ウォークスルー: テストおよび本稼働スタック用のパイプラインを構築するというページに

CodePipelineの所感

料金について、CodePipelineの料金についても、1USD/1ヶ月となっています。
実際には、codeCommitやcodeDeployと行った他のツールと組み合わせたり、インスタンスバケットを作ったりするので、増えると思いますし学習コストもかかるので安易に移行できないかもしれません。
ただ、案件数が少ない場合は、jenkinsサーバーをEC2インスタンスで稼働する費用を考えると運用コスト自体は抑えられるかもしれません。

書籍紹介

AWSに関連する書籍です。  

Amazon Web Services実践入門 (WEB+DB PRESS plus)

Amazon Web Services実践入門 (WEB+DB PRESS plus)