Webアプリ公開のためのインフラ環境をCloudFormationで構築してみた

#目次
[1.はじめに](#はじめに)
[2.前提](#前提)
[3.独自ドメインの取得](#独自ドメインの取得)
[4.ネットワークの設定](#ネットワークの設定)
[5.セキュリティの設定](#セキュリティの設定)
[6.仮想サーバおよび独自ドメインの設定](#仮想サーバおよび独自ドメインの設定)
[7.通信のhttps化およびデータベースの設定](#通信のhttps化およびデータベースの設定)
[8.稼働確認](#稼働確認)
[9.まとめ](#まとめ)
[10.cloudformationテンプレート](#cloudformationテンプレート)
[11.参考](#参考)

#はじめに
WebアプリをAWSのEC2上へデプロイする場合、インフラ環境には以下のような様々なAWSリソースを使用することになり、毎回手作業で設定をしていくと、時間もかかりますし、設定ミスをする可能性も出てきます。そこで今回、Webアプリをインターネットに公開するためのインフラ環境を、AWSのCloudFormationでコード化し、構築しましたので、大まかな手順を整理します。<br>

■ AWSインフラ環境で利用されるリソースの例

- ネットワーク：VPC、サブネット、ルートテーブル、インターネットゲートウェイなど

- アクセス制御：セキュリティグループ、IAMなど

- 仮想サーバ：EC2

- 負荷分散：ELB

- DNS：Route53

- データベース：RDS、DynamoDB、Redshiftなど

- ストレージ：S3、EFSなど

- 証明書管理：ACM



#前提
今回、CloudFormationでコード化したAWS環境の構成は以下の通りとなります。
![全体.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/f6d6ff91-d261-d683-5ee9-d741b3ed491e.png)

■ EC2へのSSH接続用キーペアを事前に準備
CloudFormationでPublicSubnet 1aにEC2を作成する際、キーペアを指定する必要があります。マネジメントコンソールから、事前にキーペアを作成しておきましょう。

■ 環境の特徴

- コスト重視で可用性は低めの設定とし、EC2およびRDSは冗長化しない。
- Route53で独自ドメインを設定する。
- ACMを利用し、ユーザからの通信をHTTPS化する。
- ALBが所属するSecurityGroup (以降、SGと記載) は任意のIPアドレスからの80番、443番のアクセスを許可
- EC2が所属するSGはALBが所属するSGからの80番、443番のアクセスを許可
- EC2が所属するSGは特定のIPアドレス(スタック作成時に指定)からの22番アクセスのみを許可
- RDSが所属するSGはEC2が所属するSGからの3306番のアクセスを許可
- ALBはユーザからのHTTPリクエストをHTTPSにリダイレクトする
- 作成するEC2にはユーザデータより、nginxをインストールした上で、index.htmlを配置する。
- 作成するEC2にはブラウザから「www.独自ドメイン名」でアクセス可能

■ 使用するCloudFormationテンプレート

上記の環境を構築する上で、CloudFormationのテンプレートを4種類に分割して作成しました。

- 「network.yml」」:  VPC、IG (インターネットゲートウェイ) 、サブネット、ルートテーブルの設定用
- 「security.yml」」:  セキュリティグループの設定用
- 「application-first.yml」」:   EC2、ALB、Route53の設定用
- 「application-second.yml」」:  ACM、RDSの設定用

以降、これらを使用した環境構築の手順を整理していきますが、各テンプレートの内容は長くなってしまいますので、最後にまとめて記載することにします。


#独自ドメインの取得
今回は、無料から利用できるドメインプロバイダ「Freenom」でドメインを取得していきます。

- 取得したい名前を検索し、ヒットした場合は利用可能なドメイン名を選択した上で「Checkout」を押下する。

![取得.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/0f1070a0-69cc-2163-223b-4564ea67c644.png)

- 利用したい期間を選択し、「Continue」を押下する。

![期間.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/31c1715d-d43e-0437-2060-d3907cda2fbd.png)

- 利用条件を確認した上でチェックを入れ、「Complete Order」を押下する。

![オーダ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/736e5316-1a9c-5375-e13e-1edf08e07d8b.png)

- 「Services」→「My Domains」を押下する。

![ドメイン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/b209dc04-9641-c107-fccb-679be02e07fb.png)

- 先ほど作成したドメイン名が存在することを確認する。

![済.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/6f14e5b0-b846-6bad-07dc-c21fa1f124c1.png)

以上、独自ドメインの取得は完了です。次にネットワークの設定をしていきます。

#ネットワークの設定

CloudFormationテンプレート「network.yml」を使用して、ネットワークの設定をしていきます。

- AWSにログインし、CloudFormationの「スタックの作成」画面にて、「テンプレートファイルのアップロード」を選択した上で、「ファイルの選択」を押下する。

![選択f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/beb76087-6fb0-d300-c2dc-c28f8ffc3e50.png)

- 「network.yml」を選択し、「次へ」を押下する。

![networl.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/8676484d-54fb-4f55-338c-d3f46533d332.png)

- 今回はスタックの名前を「network」、導入予定のWebアプリ名を「AppName」に入力し、「次へ」を押下する。<br>
※ 「Network-Config」では、必要に応じて、VPCやサブネットのCIDRをカスタマイズする。 ( 以下、デフォルト値 )

![スタック.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/45663569-7a49-7b1a-94b5-a2a380593904.png)

- 「スタックオプションの設定」画面に遷移するが、今回は特に設定せず「次へ」を押下する。

![オプション.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/8e6c4331-2dff-5449-fb53-fd9d31e8c27e.png)

![右下.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/8a4161a5-1398-09ed-32b2-795a54817cc6.png)

- 確認画面に遷移後、画面右下の「スタックの作成」を押下する。

![作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/663eaef7-25b9-f181-1216-0869f25d978b.png)

- 数分待った後、「network」スタックの状態が「CREATE_COMPLETE」になることを確認する。

![OK.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/b42e8b23-f99d-ae9d-70a3-4d775541acf7.png)

以上、ネットワークの設定は完了です。現在のAWS構成の状態は以下の通りです。

![インフラ構成図 _NW.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/15337de6-8aac-e8fd-f938-b6a387aa6e6c.png)

次にセキュリティの設定をしていきます。

#セキュリティの設定

CloudFormationテンプレート「security.yml」を使用して、セキュリティの設定をしていきます。
※ ネットワーク設定とほとんど手順が変わらないため、途中経過は割愛し、差分となる画面のみ添付します。

- 今回はスタックの名前を「security」、導入予定のWebアプリ名を「AppName」に入力、また、「SourceIP」には以降で作成するWebアプリデプロイ用EC2へのSSH接続を許可させたいIPアドレス を入力し、「次へ」を押下する。<br>

※ Webアプリデプロイ用のEC2は以降でPublicSubnet1a内に作成される。作成したEC2にSSH接続をし、各種設定作業を実施する際を想定し、ここで許可させたいIPアドレスを指定する。 (例) 作業用端末のグローバルIPアドレスを指定 / PublicSubnet1aに踏み台ホスト(EC2)を作成し、そのIPアドレスを指定。<br>
※ CIDR表記で入力すること。(例)10.0.0.5/32　

![セキュリ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/1f11ebeb-d84e-9509-3127-fcfa69ffdf86.png)

- 「スタックの作成」を押下し、数分待った後、「security」スタックの状態が「CREATE_COMPLETE」になることを確認する。

![っっd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/e9e29c0e-3fb9-fb2b-2144-345ed515a375.png)

以上、セキュリティの設定は完了です。現在のAWS構成の状態は以下の通りです。

![インフラ構成図 _seセキュリティ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/265b0aec-fd2f-7ecf-9ff7-439542fc9d3d.png)


次に仮想サーバおよび独自ドメインの設定をしていきます。

#仮想サーバおよび独自ドメインの設定

CloudFormationテンプレート「application-first.yml」を使用して、仮想サーバおよび独自ドメインの設定をしていきます。
※ 途中経過は割愛し、差分となる画面のみ添付します。

- 今回はスタックの名前を「application-first」、導入予定のWebアプリ名を「AppName」に入力、EC2接続に使用するキーペア名を「KeyName」、また、「DomainName」にはFreenomで取得した独自ドメイン名を入力し、「次へ」を押下する。<br>
※ KeyNameには拡張子「.pem」は付けすに入力する

![っっff.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/fadcdf7c-de04-2522-c252-66a1f132446f.png)

- 「スタックの作成」を押下し、数分待った後、「application-first」スタックの状態が「CREATE_COMPLETE」になることを確認する。

![アッp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/18cc3c6f-e687-31e2-133e-1f120df098e4.png)

- 「Route53」→「ホストゾーン」から、上記のスタックにて作成されたホストゾーンが存在することを確認し、ドメイン名を押下する。

![ゾーン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/1de44a79-9e2a-9529-b228-7cafce1d5ae2.png)

- NSレコードの「値/トラフィックのルーティング先」の内容を控える。

![っっq.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/8d1e6073-105d-861a-80a5-3ff525de56ea.png)

- Freenomの「MyDomains」画面に行き、「Manage Domain」を押下する。

![管理.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/8388e51c-5485-be56-4698-c52dd9077d88.png)

- 「Management Tools」→「Nameservers」を押下する。

![んs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/6d5a4623-de27-c576-abd1-a2c7b3a85dc8.png)

- 「Use custom nameservers (enter below) 」を選択し、Nameseverの欄に先ほど控えた値を入力し、「Change Nameservers」を押下する。

![っっqq.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/cddce3ca-af57-fa74-de92-a88ef298da7d.png)

以上、仮想サーバおよび独自ドメインの設定は完了です。現在のAWS構成の状態は以下の通りです。

![インフラ構成図 _ドメイン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/42d5215a-189e-c94a-1507-4afac8c47ffd.png)


最後に通信のHTTPS化およびデータベースの設定をしていきます。

#通信のhttps化およびデータベースの設定

CloudFormationテンプレート「application-second.yml」を使用して、通信のHTTPS化およびデータベースの設定をしていきます。
※ 途中経過は割愛し、差分となる画面のみ添付します。

- 今回はスタックの名前を「application-second」、導入予定のWebアプリ名を「AppName」に入力、また、「DomainName」にはFreenomで取得した独自ドメイン名を入力する。

![secon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/d88201f3-5aa5-c1c7-6970-c8893d7dd06b.png)

- また、「RDS-Config」にてDBのユーザ名、パスワードを入力し、「次へ」を押下する。

※ 「他の項目は必要に応じてカスタマイズする。 ( 以下、デフォルト値 )

![db.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/3c99a446-ce2f-c163-c346-c613f3b65570.png)

- 「スタックの作成」を押下し、数分待った後、「application-second」スタックの状態が「CREATE_COMPLETE」になることを確認する。

![えんd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/b5f696b0-0175-9ce4-7b94-a1398eafa6d5.png)

以上、通信のHTTPS化およびデータベースの設定は完了です。冒頭に記載したAWSのインフラ構成が完成しました。

![全体.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/c76e493e-9516-7e68-e7ea-e6949f4d1b41.png)


#稼働確認
それでは、ブラウザより独自ドメインにてアクセスをしていきます。せっかくなのでhttpでリクエストするとhttpsにリダイレクトされることを確認します。

![www.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/e40b1de6-4b4a-5762-5be4-ec1b6d11c8a2.png)

![s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067765/6520496e-ed09-2b48-2fce-e80feb7dd625.png)

httpsにリダイレクトされ、EC2のユーザデータによって作成されたindex.htmlの内容 (Hello World!) が表示されました。

#まとめ
以上で、Webアプリ公開のためのインフラ環境をCloudFormationを用いて、構築できました。しかし、アプリ公開のためには別途、今回作成したEC2内で各種設定をし、デプロイをしていく必要があります。以降の内容は以下の記事に記載いたしました。<br>[Webアプリデプロイ方法(AWS EC2編)](https://qiita.com/Unimaru/items/66a01ad5dab54e66ff47)

最後になりますが、作成したCloudFormationテンプレートの内容をまとめていきます。

尚、この記事はAWS初学者を導く体系的な動画学習サービス「AWS CloudTech」の課題カリキュラムで作成しました。

#cloudformationテンプレート

```yaml:network.yml
AWSTemplateFormatVersion: 2010-09-09

# ------------------------------------------------------------#
# Description
# ------------------------------------------------------------# 

Description:
  VPC/InternetGateway/Subnet/RouteTable

# ------------------------------------------------------------#
# MetaData
# ------------------------------------------------------------# 

#スタック作成時の画面入力パラメータの表示設定
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "ApplicationName"
        Parameters:
          - AppName
      - Label:
          default: "Network-Config"
        Parameters:
          - VpcCidr
          - Public1aCidr
          - Public1cCidr
          - Private1aCidr
          - Private1cCidr

# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 

#スタック作成時に入力する各種パラメータ
Parameters:
 
 #アプリケーション名
 AppName:
    Type: String
 
 #VPCのサイダー
 VpcCidr:
    Type: String
    Default: "10.0.0.0/16"

 #PublicSubnet-1aのサイダー
 Public1aCidr:
    Type: String
    Default: "10.0.0.0/24"

#PublicSubnet-1cのサイダー
 Public1cCidr:
    Type: String
    Default: "10.0.1.0/24"

#PrivateSubnet-1aのサイダー
 Private1aCidr:
    Type: String
    Default: "10.0.2.0/24"

#PrivateSubnet-1cのサイダー
 Private1cCidr:
    Type: String
    Default: "10.0.3.0/24"

# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------# 

#作成する各種AWSリソース
Resources: 

  #VPC
  MyVpc:
    Type: AWS::EC2::VPC
    #以下、VPCのプロパティ
    Properties:
      #サイダー
      CidrBlock: !Ref VpcCidr
      #DNS解決
      EnableDnsSupport: "true"
      #DNSホスト名
      EnableDnsHostnames: "true"
      #テナンシー
      InstanceTenancy: default
      #タグ
      Tags:
        - Key: Name
          Value: !Sub "Vpc-for-${AppName}"

  #InternetGateway
  InternetGateway: 
    Type: "AWS::EC2::InternetGateway"
    #以下、InternetGatewayのプロパティ
    Properties: 
      #タグ
      Tags: 
        - Key: Name
          Value: !Sub "igw-${AppName}"

  #InternetGatewayのアタッチ
  InternetGatewayAttach: 
    Type: "AWS::EC2::VPCGatewayAttachment"
    #以下、アタッチのプロパティ
    Properties: 
      #InternetGatewayの指定
      InternetGatewayId: !Ref InternetGateway
      #VPCの指定
      VpcId: !Ref MyVpc 

  #AZ1a内のパブリックサブネット
  PublicSubnet1a:
        Type: AWS::EC2::Subnet
        #以下、サブネットのプロパティ
        Properties:
          #AZの指定
          AvailabilityZone: "ap-northeast-1a"
          #VPCの指定
          VpcId: !Ref MyVpc
          #サイダー
          CidrBlock: !Ref Public1aCidr
          #タグ
          Tags:
            - Key: Name
              Value: !Sub "PublicSubnet-1a-${AppName}"
  
   #AZ1c内のパブリックサブネット
  PublicSubnet1c:
        Type: AWS::EC2::Subnet
        #以下、サブネットのプロパティ
        Properties:
          #AZの指定
          AvailabilityZone: "ap-northeast-1c"
          #VPCの指定
          VpcId: !Ref MyVpc
          #サイダー
          CidrBlock: !Ref Public1cCidr
          #タグ
          Tags:
            - Key: Name
              Value: !Sub "PublicSubnet-1c-${AppName}"
  
  #AZ1a内のプライベートサブネット
  PrivateSubnet1a:
        Type: AWS::EC2::Subnet
        #以下、サブネットのプロパティ
        Properties:
          #AZの指定
          AvailabilityZone: "ap-northeast-1a"
          #VPCの指定
          VpcId: !Ref MyVpc
          #サイダー
          CidrBlock: !Ref Private1aCidr
          #タグ
          Tags:
            - Key: Name
              Value: !Sub "PrivateSubnet-1a-${AppName}"
  
  #AZ1c内のプライベートサブネット
  PrivateSubnet1c:
        Type: AWS::EC2::Subnet
        #以下、サブネットのプロパティ
        Properties:
          #AZの指定
          AvailabilityZone: "ap-northeast-1c"
          #VPCの指定
          VpcId: !Ref MyVpc
          #サイダーの指定
          CidrBlock: !Ref Private1cCidr
          #タグ
          Tags:
            - Key: Name
              Value: !Sub "PrivateSubnet-1c-${AppName}"

  #パブリックルートテーブル
  PublicRouteTable: 
    Type: "AWS::EC2::RouteTable"
    #以下、ルートテーブルのプロパティ
    Properties: 
      #VPCの指定
      VpcId: !Ref MyVpc
      #タグ
      Tags: 
        - Key: Name
          Value: !Sub "public-route-${AppName}"
  
  #プライベートルートテーブル
  PrivateRouteTable: 
    Type: "AWS::EC2::RouteTable"
    #以下、ルートテーブルのプロパティ
    Properties: 
      #VPCの指定
      VpcId: !Ref MyVpc
      #タグ
      Tags: 
        - Key: Name
          Value: !Sub "private-route-${AppName}"

  #ルーティング
  PublicRoute: 
    Type: "AWS::EC2::Route"
    #以下、ルーティングのプロパティ
    Properties: 
      #ルートテーブルの指定
      RouteTableId: !Ref PublicRouteTable
      #宛先 
      DestinationCidrBlock: "0.0.0.0/0"
      #ゲートウェイの指定
      GatewayId: !Ref InternetGateway 

  #パブリックルートテーブルの関連付け
  Public1aRouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    #以下、関連づけのプロパティ
    Properties: 
      #サブネットの指定
      SubnetId: !Ref PublicSubnet1a
      #ルートテーブルの指定 
      RouteTableId: !Ref PublicRouteTable
 
   #パブリックルートテーブルの関連付け
  Public1cRouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    #以下、関連づけのプロパティ
    Properties: 
      #サブネットの指定
      SubnetId: !Ref PublicSubnet1c
      #ルートテーブルの指定 
      RouteTableId: !Ref PublicRouteTable

  #プライベートルートテーブルの関連付け
  Private1aRouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    #以下、関連付けのプロパティ
    Properties: 
      #サブネットの指定
      SubnetId: !Ref PrivateSubnet1a
      #ルートテーブルの指定
      RouteTableId: !Ref PrivateRouteTable 
  
  #プライベートルートテーブルの関連付け
  Private1cRouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    #以下、関連付けのプロパティ
    Properties: 
      #サブネットの指定
      SubnetId: !Ref PrivateSubnet1c
      #ルートテーブルの指定
      RouteTableId: !Ref PrivateRouteTable 

# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------# 

#他のテンプレートで使用可能なパラメータを指定
Outputs :

  MyVpc:
    Value: !Ref MyVpc
    Export:
      Name:  "VPC"

  PublicSubnet1a:
    Value: !Ref PublicSubnet1a
    Export:
      Name: "PublicSubnet-1a"
  
  PublicSubnet1c:
    Value: !Ref PublicSubnet1c
    Export:
      Name: "PublicSubnet-1c"
  
  PrivateSubnet1a:
    Value: !Ref PrivateSubnet1a
    Export:
      Name: "PrivateSubnet-1a"
  
  PrivateSubnet1c:
    Value: !Ref PrivateSubnet1c
    Export:
      Name: "PrivateSubnet-1c"
  
  PublicRouteTable:
    Value: !Ref PublicRouteTable
    Export:
      Name: "public-route"

  PrivateRouteTable:
    Value: !Ref PrivateRouteTable
    Export:
      Name: "private-route"



```

```yaml:security.yml

AWSTemplateFormatVersion: 2010-09-09

# ------------------------------------------------------------#
# Description
# ------------------------------------------------------------# 

Description:
  SecurityGroup

# ------------------------------------------------------------#
# MetaData
# ------------------------------------------------------------# 

#スタック作成時の画面入力パラメータの表示設定
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "ApplicationName"
        Parameters:
          - AppName
      - Label:
          default: "IPAddress of BastionHost"
        Parameters:
          - SourceIP

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 

#スタック作成時に入力する各種パラメータ
Parameters:

 #アプリケーション名
 AppName:
    Type: String

 #SSH接続を許可させるIPアドレス
 SourceIP:
    Type: String

# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------# 

#作成する各種AWSリソース
Resources: 

  #ALB用セキュリティグループ
  AlbSG:
      Type: AWS::EC2::SecurityGroup
      #以下、セキュリティグループのプロパティ 
      Properties:
        #セキュリティグループ名
        GroupName: !Sub AlbSG-${AppName}
        #セキュリティグループの説明
        GroupDescription: AlbSGGroup
        #所属するVPCの指定
        VpcId: !ImportValue "VPC"
        #インバウンド設定
        SecurityGroupIngress:
          
          #任意のIPアドレスからの80番ポートアクセスを許可
          - IpProtocol: tcp
            FromPort: 80
            ToPort:  80
            CidrIp : 0.0.0.0/0
          
          #任意のIPアドレスからの443番ポートアクセスを許可
          - IpProtocol: tcp
            FromPort: 443
            ToPort:  443
            CidrIp : 0.0.0.0/0
        #タグ  
        Tags: 
          - Key: Name
            Value: !Sub "alb-sg-${AppName}"

  #EC2インスタンス用セキュリティグループ
  WebSG:
      Type: AWS::EC2::SecurityGroup
      #以下、セキュリティグループのプロパティ
      Properties:
        #セキュリティグループ名
        GroupName: !Sub WebSG-${AppName}
        #セキュリティグループの説明
        GroupDescription: WebSGGroup
        #所属するVPCの指定
        VpcId: !ImportValue "VPC"
        #インバウンド設定
        SecurityGroupIngress:   

          #ALB用セキュリティグループからの80番ポートアクセスを許可      
          - IpProtocol: tcp
            FromPort: 80
            ToPort:  80
            SourceSecurityGroupId : !Ref AlbSG
          
          #ALB用セキュリティグループからの443番アクセスを許可
          - IpProtocol: tcp
            FromPort: 443
            ToPort:  443
            SourceSecurityGroupId : !Ref AlbSG

          #特定のIPアドレスからの22番ポートアクセスを許可
          - IpProtocol: tcp
            FromPort: 22
            ToPort:  22
            CidrIp : !Ref SourceIP

        #タグ
        Tags: 
          - Key: Name
            Value: !Sub "web-sg-${AppName}"
 
 #RDS用セキュリティグループ
  PrivateSG:
      Type: AWS::EC2::SecurityGroup
      #以下、セキュリティグループのプロパティ
      Properties:
        #セキュリティグループ名
        GroupName: !Sub PrivateSG-${AppName}
        #セキュリティグループの説明
        GroupDescription: PrivateSGGroup
        #所属するVPCの指定
        VpcId: !ImportValue "VPC" 
        #インバウンド設定
        SecurityGroupIngress:

          #EC2用セキュリティグループからの3306番ポートアクセスを許可
          - IpProtocol: tcp
            FromPort: 3306
            ToPort:  3306
            SourceSecurityGroupId: !Ref WebSG
        #タグ
        Tags: 
          - Key: Name
            Value: !Sub "private-sg-${AppName}"

# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#    

#他のテンプレートで使用可能なパラメータを指定
Outputs :
  AlbSG:
    Value: !Ref  AlbSG
    Export:
      Name:  "alb-sg"

  WebSG:
    Value: !Ref  WebSG
    Export:
      Name:  "web-sg"
  
  PrivateSG:
    Value: !Ref  PrivateSG
    Export:
      Name:  "private-sg"
     
     
     

```

```yaml:application-first.yml

AWSTemplateFormatVersion: 2010-09-09

# ------------------------------------------------------------#
# Description
# ------------------------------------------------------------# 

Description:
  EC2/ALB/Route53

# ------------------------------------------------------------#
# MetaData
# ------------------------------------------------------------# 

#スタック作成時の画面入力パラメータの表示設定
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "ApplicationName"
        Parameters:
          - AppName
      - Label:
          default: "DomainName"
        Parameters:
          - DomainName
      - Label:
          default: "KeyName"
        Parameters:
          - KeyName

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 

#スタック作成時に入力する各種パラメータ
Parameters:

 #アプリケーション名
 AppName:
    Type: String

 #Route53で設定するドメイン名
 DomainName:
    Type: String
 
 #EC2インスタンス接続に使用するキーの指定
 KeyName:
    Type: String

# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------# 

#作成する各種AWSリソース
Resources:

 #EC2インスタンス
 MyEC2:
   Type: AWS::EC2::Instance
   #以下、EC2インスタンスのプロパティ
   Properties:
     #紐付けるキーペア名
     KeyName: my-key
     #AMI (今回はAmazon Linux 2) 
     ImageId: ami-0ca38c7440de1749a
     #インスタンスタイプ
     InstanceType: t2.micro
     #CloudWatch詳細モニタリングの有無
     Monitoring: false
     #所属するセキュリティグループ
     SecurityGroupIds: [!ImportValue "web-sg"]
     #配置するサブネット
     SubnetId: !ImportValue "PublicSubnet-1a"
     #ユーザデータ (Nginxのインストール・起動設定)
     UserData: 
        Fn::Base64: |
          #!/bin/bash 
          yum update -y
          amazon-linux-extras install nginx1.12 -y
          systemctl start nginx
          systemctl enable nginx
          echo "Hello World!" > /usr/share/nginx/html/index.html
     #タグ
     Tags:
       - Key: Name
         Value: !Sub "EC2-${AppName}"

 #Elastic IPアドレス
 ElasticIp:
     Type: AWS::EC2::EIP
     #以下、Elastic IPアドレスのプロパティ
     Properties:
      #紐づけるリソース (今回は上記で作成したEC2インスタンスに紐付け)
      InstanceId: !Ref MyEC2
      #VPC内で利用する
      Domain: vpc

 #ALB
 ALB: 
    Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
    #以下、ALBのプロパティ
    Properties: 
      #ALB名
      Name: !Sub "ALB-${AppName}"
      #スキーム (インターネット向け/内部向け)
      Scheme: "internet-facing"
      LoadBalancerAttributes: 
        #削除保護の有無
        - Key: "deletion_protection.enabled"
          Value: false
        #アイドルタイムアウト値
        - Key: "idle_timeout.timeout_seconds"
          Value: 60
      #所属するセキュリティグループ
      SecurityGroups:
        - !ImportValue "alb-sg"
      #配置するサブネット
      Subnets: 
        - !ImportValue "PublicSubnet-1a"
        - !ImportValue "PublicSubnet-1c"

 #ALBのターゲットグループ
 TargetGroup: 
    Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
    #以下、ターゲットグループのプロパティ
    Properties: 
      #VPCの指定
      VpcId: !ImportValue "VPC"
      #ターゲットグループ名 
      Name: !Sub "tg-${AppName}"
      #リクエスト送信で利用するプロトコル
      Protocol: HTTP
      #リクエスト送信で利用するポート
      Port: 80
      #ヘルスチェックで利用するプロトコル
      HealthCheckProtocol: HTTP
      #ヘルスチェックをするパス
      HealthCheckPath: "/"
      #ヘルスチェックで使用するポート
      HealthCheckPort: "traffic-port"
      #異常なターゲットが正常であると見なされるまでに必要なヘルスチェックの連続的な成功回数
      HealthyThresholdCount: 5
      #ターゲットが異常であると見なされるまでに必要なヘルスチェックの連続的な失敗回数
      UnhealthyThresholdCount: 2
      #秒単位の時間で、この間に応答がないと、ヘルスチェックが失敗していることになる
      HealthCheckTimeoutSeconds: 5
      #個々のターゲットのヘルスチェックの概算間隔
      HealthCheckIntervalSeconds: 10
      #ターゲットからの正常なレスポンスを確認するために使用する HTTP コード
      Matcher: 
        HttpCode: 200
      #ターゲット
      Targets: 
        - Id: !Ref MyEC2
          Port: 80

 #ALBリスナー(HTTPSリダイレクト)
 ListenerHTTP: 
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    #以下、リスナーのプロパティ
    Properties: 
      #使用するポート
      Port: 80
      #使用するプロトコル
      Protocol: HTTP
      #HTTPSリダイレクト設定
      DefaultActions:
        - Type: redirect
          RedirectConfig:
           Host: '#{host}'
           Path: '/#{path}'
           Port: 443
           Protocol: HTTPS
           Query: '#{query}'
           StatusCode: HTTP_301
      #ロードバランサーの指定
      LoadBalancerArn: !Ref ALB

 # Route53ホストゾーン
 Route53HostedZone:
    Type: AWS::Route53::HostedZone
    #以下、Route53ホストゾーンのプロパティ
    Properties:
      #ドメイン名
      Name: !Sub "${DomainName}"

 # Route53レコード
 Route53Record:
    Type: AWS::Route53::RecordSetGroup
    #以下、Route53レコードのプロパティ
    Properties:
      #Route53ホストゾーンの指定
      HostedZoneId: !Ref Route53HostedZone
      RecordSets:
          #レコード名
        - Name: !Sub "www.${DomainName}."
          #トラフィックのルーティング先
          AliasTarget:
            HostedZoneId: !GetAtt ALB.CanonicalHostedZoneID
            DNSName: !GetAtt ALB.DNSName
          #レコードタイプ
          Type: A

# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#  

#他のテンプレートで使用可能なパラメータを指定
Outputs :
  Route53HostedZone:
    Value: !Ref  Route53HostedZone
    Export:
      Name:  "Route53HostedZone"

  ALB:
    Value: !Ref  ALB
    Export:
      Name:  "ALB"
  
  TargetGroup:
    Value: !Ref  TargetGroup
    Export:
      Name:  "TargetGroup"


```

```yaml:application-second.yml

AWSTemplateFormatVersion: 2010-09-09

# ------------------------------------------------------------#
# Description
# ------------------------------------------------------------# 

Description:
  ACM/RDS

# ------------------------------------------------------------#
# MetaData
# ------------------------------------------------------------# 

#スタック作成時の画面入力パラメータの表示設定
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "ApplicationName"
        Parameters:
          - AppName
      - Label:
          default: "DomainName"
        Parameters:
          - DomainName
      - Label:
          default: "RDS-Config"
        Parameters:
          - DBInstanceName
          - MySQLMajorVersion
          - MySQLMinorVersion
          - DBInstanceClass
          - DBInstanceStorageSize
          - DBInstanceStorageType
          - DBName
          - DBMasterUserName
          - DBPassword
          - MultiAZ
    ParameterLabels:
      DBInstanceName:
        default: "DBInstanceName"
      MySQLMajorVersion:
        default: "MySQLMajorVersion"
      MySQLMinorVersion:
        default: "MySQLMinorVersion"
      DBInstanceClass:
        default: "DBInstanceClass"
      DBInstanceStorageSize:
        default: "DBInstanceStorageSize"
      DBInstanceStorageType:
        default: "DBInstanceStorageType"
      DBName:
        default: "DBName"
      DBMasterUserName:
        default: "DBMasterUserName(min:1,max:20)"
      DBPassword:
        default: "DBPassword(min:8,max:30)"
      MultiAZ:
        default: "MultiAZ"

# ------------------------------------------------------------#
#  Input Parameters
# ------------------------------------------------------------# 

#スタック作成時に入力する各種パラメータ
Parameters:

 #アプリケーション名
 AppName:
    Type: String

 #ACMに紐づけるドメイン名
 DomainName:
    Type: String

 #DBインスタンス名
 DBInstanceName:
    Type: String
    Default: "rds"

 #MySQLメジャーバージョン
 MySQLMajorVersion:
    Type: String
    Default: "5.7"
    AllowedValues: [ "5.5", "5.6", "5.7" ]

 #MySQLマイナーバージョン
 MySQLMinorVersion:
    Type: String
    Default: "26"

 #DBインスタンスクラス
 DBInstanceClass:
    Type: String
    Default: "db.t2.micro" 

 #ストレージサイズ(GiB)
 DBInstanceStorageSize:
    Type: String
    Default: "20"
 
 #ストレージタイプ
 DBInstanceStorageType:
    Type: String
    Default: "gp2"
 
 #DB名
 DBName:
    Type: String
    Default: "db"

 #DBユーザ名
 DBMasterUserName:
    Type: String
    MinLength: 1
    MaxLength: 20

 #DBユーザパスワード
 DBPassword: 
    NoEcho: true
    Type: String
    MinLength: 8
    MaxLength: 30

 #マルチAZ
 MultiAZ: 
    Default: "false"
    Type: String
    AllowedValues: [ "true", "false" ]

# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------# 

#作成する各種AWSリソース
Resources:

#ACM
 ACM:
    Type: AWS::CertificateManager::Certificate
    #以下、ACMのプロパティ
    Properties:
      #ドメイン名
      DomainName: !Sub "${DomainName}"
      DomainValidationOptions:
          #ACMに紐づけるドメイン名
        - DomainName: !Sub "${DomainName}"
          #ACMに紐づけるRoute53ホストゾーン
          HostedZoneId: !ImportValue  "Route53HostedZone"
      #ワイルドカード証明書の取得 
      SubjectAlternativeNames:
        - !Sub "*.${DomainName}"
      #検証方法
      ValidationMethod: DNS

#ALBリスナー(HTTPS)
 ListenerHTTPS:
    Type: AWS::ElasticLoadBalancingV2::Listener
    #以下、リスナーのプロパティ
    Properties:
      #使用するポート
      Port: 443
      #使用するプロトコル
      Protocol: HTTPS
      #使用するSSL証明書
      Certificates:
        - CertificateArn: !Ref ACM
      DefaultActions:
          #ターゲットグループの指定
        - TargetGroupArn: !ImportValue "TargetGroup"
          #指定されたターゲットグループにリクエストを転送
          Type: forward
      #ロードバランサーの指定
      LoadBalancerArn: !ImportValue "ALB"

#RDSサブネットグループ
 DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    #以下、サブネットグループのプロパティ
    Properties:
      #サブネットグループ名
      DBSubnetGroupName: !Sub "${DBInstanceName}-${AppName}"
      #サブネットグループの説明
      DBSubnetGroupDescription: !Sub "${DBInstanceName}-${AppName}"
      #サブネットの指定
      SubnetIds:
        - !ImportValue "PrivateSubnet-1a"
        - !ImportValue "PrivateSubnet-1c"

 #RDSインスタンス
 DBInstance: 
    Type: "AWS::RDS::DBInstance"
    #以下、RDSインスタンスのプロパティ
    Properties: 
      #DBインスタンス識別子
      DBInstanceIdentifier: !Sub "${DBInstanceName}-${AppName}"
      #エンジン
      Engine: MySQL
      #エンジンバージョン
      EngineVersion: !Sub "${MySQLMajorVersion}.${MySQLMinorVersion}"
      #インスタンスクラス
      DBInstanceClass: !Ref DBInstanceClass
      #ストレージサイズ
      AllocatedStorage: !Ref DBInstanceStorageSize
      #ストレージタイプ
      StorageType: !Ref DBInstanceStorageType
      #DB名
      DBName: !Ref DBName
      #DBユーザ名
      MasterUsername: !Ref DBMasterUserName
      #DBユーザパスワード
      MasterUserPassword: !Ref DBPassword
      #RDSサブネットグループ
      DBSubnetGroupName: !Ref DBSubnetGroup
      #パブリックアクセシビリティ
      PubliclyAccessible: false
      #マルチAZ
      MultiAZ: !Ref MultiAZ
      #バックアップウィンドウ
      PreferredBackupWindow: "18:00-18:30"
      #メンテナンスウィンドウ
      PreferredMaintenanceWindow: "sat:19:00-sat:19:30"
      #マイナーバージョンの自動アップグレード
      AutoMinorVersionUpgrade: false
      #DBパラメータグループ
      DBParameterGroupName: !Ref DBParameterGroup
      #所属するセキュリティグループ  
      VPCSecurityGroups: 
         - !ImportValue  "private-sg"
      #スナップショットにタグをコピー
      CopyTagsToSnapshot: true
      #バックアップ保持期間
      BackupRetentionPeriod: 1
      #タグ
      Tags: 
        - Key: "Name"
          Value: !Ref DBInstanceName
    #スタックの削除時にリソースと (該当する場合) そのすべてのコンテンツを削除
    DeletionPolicy: "Delete"

 #RDSパラメータグループ
 DBParameterGroup:
    Type: "AWS::RDS::DBParameterGroup"
    #以下、パラメータグループのプロパティ
    Properties:
      #ファミリー
      Family: !Sub "MySQL${MySQLMajorVersion}"
      #パラメータグループの説明
      Description: !Sub "${DBInstanceName}-parm-${AppName}"

```

#参考

この記事は以下の情報を参考にして執筆しました。

- [CLOUDFORMATION を使って ACM で証明書を取得して ELB に適用したみた](https://blog.denet.co.jp/cloudformation-acm/)
- [CLOUDFORMATIONでROUTE53のホストゾーンを作成してEIPとELBのDNS名をレコードに追加する](https://blog.denet.co.jp/cloudformation-route53-eip-dns/)
- [[備忘録]HTTPS化の作業を一撃で終わらせるCloudFormationテンプレートについて](https://dev.classmethod.jp/articles/cfn-https-template1/)