【AWS CloudFormation】EC2とRDSの自動構築

##目標
CloudFormationを利用してEC2とRDSを自動構築するテンプレートを作成する。

##前提
・VPC、及びサブネット（パブリック1つ、複数AZにプライベート2つ）が構築済みであること。
・サブネットグループ（RDSを配置する、複数AZにまたがるサブネット集合体）が構築済みであること

##CloudFormationとは
AWSインフラの構成要素一式をテンプレートコードによって自動構築するサービスです。
一度テンプレートを作成すれば、同一構成のAWSインフラを簡易的に作成することが可能です。
テンプレート実行により作成されるAWSリソースの集合体を**スタック**といいます。

##作成するスタックの仕様
・パブリックサブネットに**EC2インスタンスを1台**、プライベートサブネットに**RDSインスタンス（MySQL、シングルAZ）を1台**作成

・**EC2、RDS用のセキュリティグループを2つ新規作成**し、上記EC2及びRDSインスタンスにアタッチする。

・**RDSインスタンスへの通信は、テンプレートによって作成されたEC2のセキュリティグループがアタッチされたEC2インスタンスからの通信のみを許可**

・以下の値はテンプレート実行毎のユーザ入力による可変値とする。
 　-EC2インスタンスタイプ
 　-EC2に接続可能な送信元IPアドレス
 　-EC2接続キーペア
 　-RDSインスタンス識別子
 　-RDSサブネットグループ
 　-RDSで利用するDBポート
 　-RDSで利用する管理者パスワード

##参考サイト
・AWSドキュメント
CloudFormationでのパラメータと、EC2及びRDSリソース作成コードのリファレンスです
[CloudFormationユーザガイド①（パラメータ）](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html)
[CloudFormationユーザガイド②（AWS::EC2::Instance）](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html)
[CloudFormationユーザガイド③（AWS::RDS::DBInstance）](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html)

・Qiita記事
[初学者のためのAWS入門(2) CloudFormation入門-1](https://qiita.com/mshinoda88/items/c5b238212c2de850efdd)

##作業の流れ
| 項番 | タイトル |
|:----:|:-------------|
| 1 | [テンプレートの作成](#1テンプレートの作成) |
| 2 | [テンプレート実行](#2テンプレート実行)|
| 3 | [簡単な動作確認](#3簡単な動作確認)|

##手順
###1.テンプレートの作成
テンプレートファイルの作成を行います。
本記事ではjsonファイルを利用しています。

`<デフォルトのサブネットグループ名>`、`<VPC ID>`、`<AZ名>`、`<サブネットID>`の部分は環境に応じて詰め替えを行ってください。

```json:EC2_RDS_Create.json
{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "Simple Setup for EC2 and RDS. ",

  "Parameters" : {
    "EC2InstanceType" : {
      "Type" : "String",
      "Default" : "t2.micro",
      "AllowedValues" : ["t2.micro", "m1.small", "m1.large"],
      "Description" : "Enter EC2 InstanceType."
    },

    "EC2SSHLocation" : {
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "0.0.0.0/0",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x.",
      "Description" : " Enter the IP address range that is allowed to connect to EC2"
    },

    "EC2KeyName": {
      "Type": "AWS::EC2::KeyPair::KeyName",
      "ConstraintDescription" : "must be the name of an existing EC2 KeyPair.",
      "Description" : "Enter the name of an existing EC2 KeyPair to enable SSH access to the instance"
    },

    "RDSInstanceIdentifier" : {
      "Type" : "String",
      "Default" : "database-1",
      "Description" : "Enter RDS InstanceIdentifier to identify RDS."
    }, 

    "RDSSubnetGroupName": {
      "Type": "String",
      "Default": "<デフォルトのサブネットグループ名>",
      "Description" : "Enter the subnet group where you want to place the RDS."
    },

    "RDSInstanceType" : {
      "Type" : "String",
      "Default" : "db.t2.small",
      "AllowedValues" : ["db.t2.small", "db.t2.medium", "db.t3.small", "db.t3.medium"],
      "Description" : "Enter RDS InstanceType"
    },

    "RDSDBPort" : {
      "Default" : "3306",
      "Type" : "Number",
      "MinValue" : "1150",
      "MaxValue" : "65535",
      "Description" : "Enter TCP/IP port used to connect DB in RDS."
    },

    "RDSDBPwd" : {
      "NoEcho" : "true",
      "Type" : "String",
      "MinLength" : "1",
      "MaxLength" : "41",
      "AllowedPattern" : "^[a-zA-Z0-9]*$",
      "Description" : "Enter the database admin account password in RDS"
    }
  },

  "Resources" : {
    "EC2SecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "VpcId" : "<VPC ID>",
        "GroupDescription" : "Enable SSH access via port 22",
        "SecurityGroupIngress" : [
          {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "EC2SSHLocation"}}
         ]
      }
    },

    "RDSSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "SecurityGroup rds-grp",
        "VpcId" : "<VPC ID>",
        "SecurityGroupIngress" : [
          { "IpProtocol" : "tcp",
            "FromPort" : { "Ref" : "RDSDBPort" },
            "ToPort" : { "Ref" : "RDSDBPort" },
            "SourceSecurityGroupId" : { "Ref" : "EC2SecurityGroup" } }
        ]
      }
    },

    "RDSInstance": {
      "Type": "AWS::RDS::DBInstance",
      "Properties": {
          "Engine": "mysql",
          "DBInstanceClass": { "Ref" : "RDSInstanceType" },
          "AllocatedStorage": "20",
          "StorageType": "gp2",
          "DBInstanceIdentifier": { "Ref" : "RDSInstanceIdentifier" },
          "MasterUsername": "admin",
          "MasterUserPassword": {"Ref": "RDSDBPwd"},
          "DBSubnetGroupName": { "Ref": "RDSSubnetGroupName" },
          "PubliclyAccessible": false,
          "AvailabilityZone": "<AZ名>",
          "VPCSecurityGroups": [
              {
                  "Ref": "RDSSecurityGroup"
              }
          ],
          "CopyTagsToSnapshot": true,
          "BackupRetentionPeriod": 7,
          "Tags" : [ { "Key" : "Application", "Value" : "string" } ]
      },
      "DeletionPolicy": "Snapshot"
    },

    "EC2Instance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "ImageId" : "<AMI ID>",
        "SubnetId" : "<サブネットID>",
        "InstanceType" : { "Ref" : "EC2InstanceType" },
        "KeyName" : { "Ref" : "EC2KeyName" },
        "SecurityGroupIds": [{"Ref": "EC2SecurityGroup"}],
        "Tags" : [ { "Key" : "Application", "Value" : "string" } ]
         }
      }
   }
}
```

テンプレートの書式内容説明です。

```ruby

{
  # テンプレートのバージョンと説明文
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "Simple Setup for EC2 and RDS.",

  "Parameters" : {
      # テンプレート実行時にユーザ入力を求めるパラメータ定義
      # パラメータ名は自由だが、パラメータの属性名（Type、Default等）は書式に沿う必要がある。
      # これを利用することでDBユーザ名やパスワードをテンプレート実行毎の可変値とすることが可能
  },

  "Resources" : {
      # 必須定義。テンプレートによって実際に構築されるAWSリソース群（スタック）を記載する。
      # リソース名は自由だが、リソースの属性名（Type、Propertiesの中身等）は書式に沿う必要がある。
      #  { "Ref" : <パラメータ名やリソース名> }を利用することで、テンプレート内で事前に定義したパラメータやリソース内容を値として利用することが可能
      # 上から順に構築が実施され、途中テンプレートの実行に失敗した場合、途中まで作成済みのAWSリソースもロールバックされ全て削除される。

      # ※なお、RDSSecurityGroupリソース内プロパティである「"SourceSecurityGroupId" : { "Ref" : "EC2SecurityGroup" }」という定義を利用して、
      #   本テンプレートで作成したEC2セキュリティグループがアタッチされたEC2インスタンスのみRDSへの通信を許可するよう設定している。
  }
}
```

###2.テンプレート実行
**①[CloudFormationコンソール](https://ap-northeast-1.console.aws.amazon.com/cloudformation/home)からスタックの作成を開始**
![tempsnip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647616/4cabc97b-ad2e-db43-3de4-03fd0d37c15e.png)

**②作成したテンプレートファイルをアップロード**
![tempsnip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647616/38ad7531-d9b9-f289-6f42-10d3ff7d68b4.png)

**③スタック名と各種パラメータを入力**
任意のスタック名とパラメータ（テンプレート内で`Parameters`によって定義された項目）値を入力していきます。
![tempsnip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647616/02f3f24d-43bc-c062-ce9e-f2e3a9d81240.png)

**④スタックオプションの設定**
作成するリソースに共通のタグだけ付与し、その他はデフォルト値とします。入力が終わったら次へ。
![tempsnip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647616/e05ba535-d48d-25bd-78eb-bffd11d5eb14.png)

次画面でスタック作成開始を実行
![tempsnip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647616/9d7d5faa-ba30-0686-ebec-6c8bb51da075.png)

**⑤スタック作成状態確認**
スタックが作成されるまでしばらく待機し、状態がCREATE_COMPLETEになるまで待ちます。
![tempsnip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647616/5a39ec7f-ca2d-faac-f0cd-887330a56f97.png)

**⑥スタック内リソース確認**
EC2コンソール及び、RDSコンソールから正常にAWSリソースが作成されていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647616/1fa9bf69-eb2d-c273-2369-414635da605a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647616/f1f50da4-0b39-e236-e4ed-61ee21831e7f.png)

###3.簡単な動作確認
作成したスタック内EC2インスタンスにOSログインし、MySQLクライアントをインストールします。

```
sudo yum install mysql
```

その後、スタック内RDSインスタンスへのログインを検証しログイン出来ればOKです。

```bash
# RDSエンドポイントは作成したスタック内のRDSエンドポイントに詰め替える
mysql -h <RDSエンドポイント> -P 3306 -u admin -p

# 以下、正常出力例
[ec2-user@ip-172-31-34-74 ~]$ mysql -h database-4.cgfjapta11py.ap-northeast-1.rds.amazonaws.com -P 3306 -u admin -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 8.0.20 Source distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]>
```

※念のため、他インスタンスからのRDSインスタンス接続が拒否されることも確認しました。

```bash

# スタック内EC2インスタンス以外からの接続は拒否される
[ec2-user@ip-172-31-40-97 ~]$ mysql -h database-4.cgfjapta11py.ap-northeast-1                                                                                .rds.amazonaws.com -P 3306 -u admin -p
Enter password:

ERROR 2003 (HY000): Can't connect to MySQL server on 'database-4.cgfjapta11py.ap-northeast-1.rds.amazonaws.com' (110)
```