【AWS】 CloudFormationで基本的な構成のEC2とRDSを作る

#ご挨拶
CloudFormationの学習の中で簡単なものを作ってみました。
#やりたいこと
CloudFormationでパブリックサブネットにあるEC2とプライベートサブネットにあるRDSを作成したい。
構成イメージ図は以下になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612471/a355451d-cbad-86eb-2fd2-f635ccc6822b.png)
#前提と最終ゴール
1. VPCとパブリックサブネットは既存のもの(デフォルト)を使用
2.東京リージョンで作成することを想定 
3. CloudFormationを使用できるIAMユーザなどは作成済み
4. EC2インスタンスに使用するキーペアは作成済み
5. 最終ゴールはWordPressのテストページが表示できるようになること

#テンプレート作成
作成したテンプレートは以下になります。
[gitリポジトリ](https://github.com/mattsun129/CloudFormation-study)
※Resourcesセクションだけでごり押しで作成しました。
　今後Parametersセクションを使用して再利用性を高めていきます。

```text:cloudformationstudy.yml
AWSTemplateFormatVersion: "2010-09-09"

Description: cloudformation study

#リソース
Resources:
  privatesubnet1:
    Type: AWS::EC2::Subnet
    Properties: 
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 172.31.48.0/20
      MapPublicIpOnLaunch: true
      Tags:
          - Key: Name
            Value: privatesubnet1
      VpcId: #vpcid入力

  privatesubnet2:
    Type: AWS::EC2::Subnet
    Properties: 
      AvailabilityZone: ap-northeast-1c
      CidrBlock: 172.31.64.0/20
      MapPublicIpOnLaunch: true
      Tags:
          - Key: Name
            Value: privatesubnet2
      VpcId: #vpcid入力

  privateroutetable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: privateroutetable
      VpcId: #vpcid入力
  
  routetableattach1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId: !Ref privateroutetable
      SubnetId: !Ref privatesubnet1

  routetableattach2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId: !Ref privateroutetable
      SubnetId: !Ref privatesubnet2

  ec2securitygroup: 
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupDescription: ec2-securitygroup
      SecurityGroupEgress: 
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: 0.0.0.0/0
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIpv6: ::/0
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIpv6: ::/0
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: #自分のIPアドレス入力
      VpcId: #vpcid入力

  rdssecuritygroup:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupDescription: rds-securitygroup
      SecurityGroupEgress: 
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: 0.0.0.0/0
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref ec2securitygroup
      VpcId: #vpcid入力

  ec2instance: 
    Type: AWS::EC2::Instance
    Properties: 
      BlockDeviceMappings: 
        - DeviceName: #ルートデバイス名入力
          Ebs: 
            DeleteOnTermination: true
            VolumeSize: 16
            VolumeType: gp2
      ImageId: #イメージID入力
      InstanceType: #インスタンスタイプ入力
      KeyName: #キーペア入力
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeleteOnTermination: true
          DeviceIndex: "0"
          GroupSet: 
            - !Ref ec2securitygroup
          SubnetId: #サブネットID入力
      Tags: 
        - Key: Name
          Value: ec2instance

  rdssubnetgroup: 
    Type: AWS::RDS::DBSubnetGroup
    Properties: 
      DBSubnetGroupDescription: rdssubnetgroup
      SubnetIds:  
        - !Ref privatesubnet1
        - !Ref privatesubnet2
      DBSubnetGroupName: #サブネットグループ名入力
      Tags: 
        - Key: Name
          Value: rdssubnetgroup

  rdsinstance:  
    Type: AWS::RDS::DBInstance
    Properties: 
      AllocatedStorage: #ストレージ量入力
      DBInstanceClass: #インスタンスクラス入力
      DBSubnetGroupName: 
        !Ref rdssubnetgroup
      Engine: mysql
      EngineVersion: "5.7.34"
      MasterUsername: #DBユーザ名入力
      MasterUserPassword: #DBパスワード入力
      StorageType: gp2
      Tags:
        - Key: Name
          Value: rdsinstance
      VPCSecurityGroups: 
        - !Ref rdssecuritygroup
```
##各Resourcesセクション解説
###プライベートサブネット作成
プライベートサブネットはRDSのサブネットグループで使うので2つ作成します。(privatesubnet1、privatesubnet2)

```text:privatesubnet1
privatesubnet1:
  Type: AWS::EC2::Subnet
  Properties: 
    AvailabilityZone: ap-northeast-1a
    CidrBlock: 172.31.48.0/20
    MapPublicIpOnLaunch: true
    Tags:
        - Key: Name
          Value: privatesubnet1
    VpcId: #vpcid入力
```
[AWS::EC2::Subnet公式ドキュメント](https://docs.aws.amazon.com/en_us/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html)

1. AvailabilityZone：アベイラビリティゾーンの指定をします。
2. CidrBlock：サブネットのCidrBlockを指定します。
3. MapPublicIpOnLaunch：インスタンスをこのサブネットで起動した場合にパブリックIPアドレスをアタッチする(RDSを置くだけなので今回は必要なかったかも)
4. Tags：タグの作成
5. VpcId：サブネットが置かれるVPCの指定をします。

###ルートテーブル作成
プライベートサブネットのルートテーブルを作成します。

```text:privateroutetable
privateroutetable:
  Type: AWS::EC2::RouteTable
  Properties:
    Tags:
      - Key: Name
        Value: privateroutetable
    VpcId: #vpcid入力
```
[AWS::EC2::RouteTable公式ドキュメント](https://docs.aws.amazon.com/en_us/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html)

1. Tags：タグの作成
2. VpcId：紐づけるVPCの指定

###ルートテーブルのアタッチ
プライベートサブネットにルートテーブルをアタッチします。
プライベートサブネットは2つあるので2つ分実行します。(routetableattach1、routetableattach2)

```text:routetableattach1
routetableattach1:
  Type: AWS::EC2::SubnetRouteTableAssociation
  Properties: 
    RouteTableId: !Ref privateroutetable
    SubnetId: !Ref privatesubnet1
```
[AWS::EC2::SubnetRouteTableAssociation公式ドキュメント](https://docs.aws.amazon.com/en_us/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet-route-table-assoc.html)

1. RouteTableId：ルートテーブルを紐づけます (組み込み関数のRefでルートテーブルIDを参照しています)
2. サブネットを紐づけます (組み込み関数のRefでサブネットIDを参照しています)

###EC2のセキュリティグループ作成
EC2のセキュリティグループを作成します。
今回はHTTPのポート番号とついでにHTTPSのポート番号、SSHを開けてます。(ドメインとか証明書設定しないのでHTTPSは必要無い)

```text:ec2securitygroup
ec2securitygroup: 
  Type: AWS::EC2::SecurityGroup
  Properties: 
    GroupDescription: ec2-securitygroup
    SecurityGroupEgress: 
      - IpProtocol: -1
        FromPort: -1
        ToPort: -1
        CidrIp: 0.0.0.0/0
    SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIpv6: ::/0
      - IpProtocol: tcp
        FromPort: 443
        ToPort: 443
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: 443
        ToPort: 443
        CidrIpv6: ::/0
      - IpProtocol: tcp
        FromPort: 22
        ToPort: 22
        CidrIp: #自分のIPアドレス入力
    VpcId: #vpcid入力
```
[AWS::EC2::SecurityGroup公式ドキュメント](https://docs.aws.amazon.com/en_us/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html)

1. GroupDescription：セキュリティグループの説明
2. SecurityGroupEgress：アウトバウンドの設定
    2. IpProtocol：プロトコルの指定 (-1を指定するとすべて許可する)
    2. FromPort：許可するポート番号の範囲の開始番号 (-1を指定するとすべて許可する)
    2. ToPort：許可するポート番号の範囲の終了番号 (-1を指定するとすべて許可する)
    2. CidrIp：許可するIPアドレス (0.0.0.0/0ですべてのIPアドレスを許可する)
3. SecurityGroupIngress：インバウンドの設定
    3. IpProtocol：プロトコルの指定
    3. FromPort：許可するポート番号の範囲の開始番号
    3. ToPort：許可するポート番号の範囲の終了番号
    3. CidrIp：許可するIPアドレス
4. VpcId：紐づけるVPCの指定

###RDSのセキュリティグループ作成
RDSのセキュリティグループを作成します。
基本的にはEC2のセキュリティグループと同じ作り方になります。
EC2からの通信を許可する設定をします。
MySQLを使用するのでポート番号3306を開けます。

```text:rdssecuritygroup
rdssecuritygroup:
  Type: AWS::EC2::SecurityGroup
  Properties: 
    GroupDescription: rds-securitygroup
    SecurityGroupEgress: 
      - IpProtocol: -1
        FromPort: -1
        ToPort: -1
        CidrIp: 0.0.0.0/0
    SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 3306
        ToPort: 3306
        SourceSecurityGroupId: !Ref ec2securitygroup
    VpcId: #vpcid入力
```
[AWS::EC2::SecurityGroup公式ドキュメント](https://docs.aws.amazon.com/en_us/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html)

1. GroupDescription：セキュリティグループの説明
2. SecurityGroupEgress：アウトバウンドの設定
    2. IpProtocol：プロトコルの指定 (-1を指定するとすべて許可する)
    2. FromPort：許可するポート番号の範囲の開始番号 (-1を指定するとすべて許可する)
    2. ToPort：許可するポート番号の範囲の終了番号 (-1を指定するとすべて許可する)
    2. CidrIp：許可するIPアドレス (0.0.0.0/0ですべてのIPアドレスを許可する)
3. SecurityGroupIngress：インバウンドの設定
    3. IpProtocol：プロトコルの指定
    3. FromPort：許可するポート番号の範囲の開始番号
    3. ToPort：許可するポート番号の範囲の終了番号
    3. SourceSecurityGroupId：許可するセキュリティグループを指定
4. VpcId：紐づけるVPCの指定

###EC2インスタンス作成
EC2インスタンスを作成していきます。
ルートデバイス名は使用するAMIごとに変更してください。

```text:
ec2instance: 
  Type: AWS::EC2::Instance
  Properties: 
    BlockDeviceMappings: 
      - DeviceName: #ルートデバイス名入力
        Ebs: 
          DeleteOnTermination: true
          VolumeSize: 16
          VolumeType: gp2
    ImageId: #イメージID入力
    InstanceType: #インスタンスタイプ入力
    KeyName: #キーペア入力
    NetworkInterfaces:
      - AssociatePublicIpAddress: true
        DeleteOnTermination: true
        DeviceIndex: "0"
        GroupSet: 
          - !Ref ec2securitygroup
        SubnetId: #サブネットID入力
    Tags: 
      - Key: Name
        Value: ec2instance
```
[AWS::EC2::Instance公式ドキュメント](https://docs.aws.amazon.com/en_us/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html)

1. BlockDeviceMappings：インスタンス起動時に使用するEBSの指定
    1. DeviceName：ルートデバイス名 (Ubuntuなら/dev/sda1)
    1. Ebs：EBSの設定
        1. DeleteOnTermination：インスタンスの終了時にEBSボリュームを削除する設定
        1. VolumeSize：ボリュームサイズの指定
        1. VolumeType：ボリュームタイプの指定
2. ImageId：AMIの指定
3. InstanceType：インスタンスタイプの指定
4. KeyName：キーペアの指定
5. NetworkInterfaces：ネットワークインターフェースの設定
    5. AssociatePublicIpAddress：グローバルIPアドレスを起動時に割り当てる設定
    5. DeleteOnTermination：インスタンスの終了時にネットワークインターフェースを削除する設定
    5. DeviceIndex：プライマリネットワークインターフェイスを指定
    5. GroupSet：セキュリティグループを指定
    5. SubnetId：ネットワークインターフェイスに関連付けられているサブネットIDの指定
6. Tags：タグの作成

###RDSサブネットグループ作成
サブネットグループを作成していきます。
作成したプライベートサブネットを使用します。

```text:rdssubnetgroup
rdssubnetgroup: 
  Type: AWS::RDS::DBSubnetGroup
  Properties: 
    DBSubnetGroupDescription: rdssubnetgroup
    SubnetIds:  
      - !Ref privatesubnet1
      - !Ref privatesubnet2
    DBSubnetGroupName: #サブネットグループ名入力
    Tags: 
      - Key: Name
        Value: rdssubnetgroup
```
[AWS::RDS::DBSubnetGroup公式ドキュメント](https://docs.aws.amazon.com/en_us/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbsubnet-group.html)

1. DBSubnetGroupDescription：サブネットグループの説明
2. SubnetIds：サブネットグループに含めるサブネットの指定 (組み込み関数のRefでサブネットIDを参照しています)
3. DBSubnetGroupName：サブネットグループ名
4. Tags：タグの作成

###RDSの作成
RDSを作成していきます。
今回はMySQL5.7系を使用します。

```text:rdsinstance
rdsinstance:  
  Type: AWS::RDS::DBInstance
  Properties: 
    AllocatedStorage: #ストレージ量入力
    DBInstanceClass: #インスタンスクラス入力
    DBSubnetGroupName: 
      !Ref rdssubnetgroup
    Engine: mysql
    EngineVersion: "5.7.34"
    MasterUsername: #DBユーザ名入力
    MasterUserPassword: #DBパスワード入力
    StorageType: gp2
    Tags:
      - Key: Name
        Value: rdsinstance
    VPCSecurityGroups: 
      - !Ref rdssecuritygroup
```
[AWS::RDS::DBInstance公式ドキュメント](https://docs.aws.amazon.com/en_us/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html)

1. AllocatedStorage：RDSのストレージ量指定
2. DBInstanceClass：RDSインスタンスクラスの指定
3. DBSubnetGroupName：サブネットグループの指定 (組み込み関数のRefでサブネットグループを参照しています)
4. Engine：RDSのエンジンの指定
5. EngineVersion：エンジンバージョンの指定
6. MasterUsername：DBの管理者ユーザ名の指定
7. MasterUserPassword：DBの管理者ユーザのパスワード指定
8. StorageType：ストレージタイプの指定
9. タグの作成
10. VPCSecurityGroups：セキュリティグループを紐づける設定 (組み込み関数のRefでセキュリティグループIDを参照しています)

#CloudFormationでスタック作成
マネジメントコンソールから作成していきます。
マネジメントコンソールからCloudFormationのダウンロードへ移動。
移動後スタックの作成 (下記画像赤枠部分)から「新しいリソースを使用 (標準)」を選択
![cloudformationダッシュボード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612471/d9279806-e949-db0f-37b7-a0579dcfa837.png)
下記画像のようにテンプレートの指定からテンプレートファイルをアップロードを選択して作成したymlファイルをアップロードする。
![スタック作成1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612471/e887e2aa-bb40-f71e-a30e-f5fbcddee66a.png)
次の画面ではスタック名を入力する。ymlファイルにパラメータセクションがあるとここで入力することができます。
![スタック作成2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612471/cb598f58-c620-a948-5af9-369784ce2efa.png)
次の画面ではスタックのタグやIAMロールなどの設定ができます。
今回はデフォルトでそのまま次の画面へ行きます。
![スタック作成3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612471/6012473b-dae2-b0c5-255f-45ffca8ebedb.png)
次の画面ではスタック作成前の最終確認画面です。
この画面で設定に問題が無ければスタックの作成をクリックします。
![スタック作成4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612471/eecc3b28-42b7-7f07-7306-09abf8c3ad7e.png)
作成が開始されると以下の画面のようにリソースの作成状況が表示されます。
ymlファイルでOutputsセクションをつけるとこの段階でEC2のIPアドレスなどが確認できるようです。
今後の更新でOutputsセクションも追加していきたいと考えています。
![スタック作成5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612471/48ef7896-2dfc-638a-0672-972087d0c9f8.png)
#EC2インスタンスでの作業
ここからはEC2インスタンスへSSH接続してApacheのインストールとWordPressのダウンロードを行います。
EC2インスタンスのダッシュボードからCloudFormationで作成されたインスタンスを選択してパブリックIPを確認します。
確認ができたらSSh接続を行います。

```text:ターミナル
ssh -i 鍵ファイル名 ubuntu@EC2のIPアドレス
```
今回はubuntuを使用しました。
以下のコマンドを実行してMySQLクライアントとApacheとphpのインストールとWordPressのダウンロードを行います。

```text:Ubuntu20.04
sudo apt update
sudo apt install mysql-client
sudo apt install apache2
sudo apt install php7.4 php7.4-curl php-imagick php7.4-mbstring php7.4-mysql php7.4-xml php7.4-zip
sudo systemctl start apache2
sudo systemctl enable apache2
sudo wget https://ja.wordpress.org/latest-ja.tar.gz
tar -xvzf latest-ja.tar.gz
sudo mv wordpress/ /var/www/
sudo chown -R www-data:www-data /var/www/wordpress
```
コマンドを解説します。
1. sudo apt update：パッケージを最新にします。
2. sudo apt install mysql-client：MySQLクライアントをインストールします。
3. sudo apt install apache2：Apacheをインストールします。
4. sudo apt install php7.4 php7.4-curl php-imagick php7.4-mbstring php7.4-mysql php7.4-xml php7.4-zip：phpとWordPressに必要なモジュールをインストールします。[必要なモジュール](https://make.wordpress.org/hosting/handbook/server-environment/#php-extensions)
5. sudo systemctl start apache2：Apacheを起動します。
6. sudo systemctl enable apache2：Apacheを自動起動させます。
7. sudo wget https://ja.wordpress.org/latest-ja.tar.gz：最新バージョンのWordPressをダウンロードします。
8. tar -xvzf latest-ja.tar.gz：圧縮ファイルを解凍してアーカイブを展開します。
9. sudo mv wordpress/ /var/www/：ドキュメントルート配下にWordPressのファイルを移動します。
10. sudo chown -R www-data:www-data /var/www/wordpress：Apacheの実行ユーザとグループに変更します。

次にデータベースを作成します。

```text:
mysql -h RDSエンドポイント -u root -P 3306 -p rootのパスワード
create database wordpress default character set utf8;
```
本当はWordPress用のユーザも作成したほうがいいらしいのですが今回はrootで設定していきます。

次にドキュメントルートを初期の/var/www/htmlから/var/www/wordpressへ変更します。

```text:Ubuntu20.04
cd /etc/apache2/sites-available/
sudo cp 000-default.conf wordpress.conf
sudo vi wordpress.conf
ドキュメントルートを以下のように変更
DocumentRoot /var/www/wordpress
```
上記のように初期のファイルをコピーして編集しているのは元々の物はひな形として使用するためです。(会社の先輩の受け売り)
変更したら以下のコマンドを実行します。

```text:Ubuntu20.04
sudo a2dissite 000-default
sudo a2ensite wordpress
sudo systemctl reload apache2
```
コマンドの解説をします。
1. sudo a2dissite 000-default：このコマンドでデフォルトの設定を無効化しています。
2. sudo a2ensite wordpress：このコマンドで新規に作成したファイルを読み込ませるように設定しています。
3. sudo systemctl reload apache2：こちらのコマンドでApacheを再起動します。
ここら辺の解説はこちらの方の記事がわかりやすかったです。
[UbuntuのApache設定ファイル、どうやるんだっけ？](https://qiita.com/diconran/items/1eca0e0792f4ce1930c5)

ここまで完了したらブラウザでEC2のIPアドレスでアクセスしてみてください。
アクセスすると以下の画面が出てきます。
出てきたら「さあ、始めましょう！」をクリックして先に進みます。
![wordpress.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612471/3b3a4953-4576-7533-fe03-51463476675a.png)
次の画面ではデータベースへの接続情報を入力します。
データベースのホスト名はRDSのエンドポイントを入力してください。
![wordpress2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612471/d9fa8124-d9a3-941b-180f-75ee3bbbf780.png)
入力したらWordPressをインストールをクリックして次の画面へ移動します。
この画面ではWordPressの管理画面へのログインに使用する管理ユーザを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612471/474408b1-471b-9e80-535f-653ebfe68b5f.png)
入力が完了してログインに成功したら完了です。
#感想
今回はCloudFormationを使用してEC2とRDSを作成しました。
ymlファイルの内容はごり押しで作成しているので今後ParametersセクションやOutputsセクションを利用して使いやすいスタックにしていきたいと考えています。
~~(今回のハマりポイントがWordPressのPHPモジュールインストール関連とか言えない)~~