【AWS】Wordpressサイトをほぼ一撃で作成するCloudFormtionを作成した

# 01.はじめに
マネジメントコンソール作業に煩わしさを感じてきたので
wordpressサイトをほぼ一撃で構築できるCloudFormtionを作成した。
WEBサーバがパブリックサブネットにあるとセキュリティ的に不安が残るので
プライベートサブネットから出来るようにした。
なるべくお金をかけない主義でEC2、RDSともに無料利用枠で利用できるスペックを選定している。

# 02.構成図
下記の通りである。
![dev構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/a62f6dff-4384-b47e-426e-92b7152514a9.png)


EC2のユーザデータを利用してNATGW経由で
・httpdパッケージ等のyumで取得できるものをS3から取得
・wordpressパッケージをwgetでインターネットから取得
する構成にしている。

インストール後はwordpressの初期設定を実施する必要がある。
実施方法はSSHポートフォワーディングを利用してパブリックサブネットに配置してある
EC2経由で接続→設定する流れにしている。

# 03.テンプレート
ネットワークとEC2・RDSに分けてテンプレートを作成したので分けて記載する。

①ネットワーク
```yaml:network.yml
AWSTemplateFormatVersion: 2010-09-09
Description: DEV-Network

# ------------------------------------------------------------#
#  パラメータ
# ------------------------------------------------------------#
Parameters:
  #自宅のグローバルIP(SSHのセキュリティグループで使用する)
  MYip:
    Type: String
    Description: Type of this MYip.

# ------------------------------------------------------------#
#  リソース
# ------------------------------------------------------------#

Resources:
  # ------------------------------------------------------------#
  #  VPC
  # ------------------------------------------------------------#
  CFDEVVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/21
      EnableDnsSupport: true
      Tags:
      - Key: Name
        Value: CFDEVVPC

  # ------------------------------------------------------------#
  #  Internet Gateway
  # ------------------------------------------------------------#
  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: CFDEVigw
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref CFDEVVPC
      InternetGatewayId: !Ref IGW

  #------------------------------------------------------------#
  # Nat GW ElasticIP
  # ------------------------------------------------------------#
  NatGatewayEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
  # ------------------------------------------------------------#
  # Nat GW 
  # ------------------------------------------------------------#
  CFNatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - NatGatewayEIP
          - AllocationId
      SubnetId: !Ref DEVPublicSubnetA
      Tags:
        - Key: Name
          Value: CFNatGateway
  # ------------------------------------------------------------#
  # Public Route Table
  # ------------------------------------------------------------#
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref CFDEVVPC
      Tags:
        - Key: Name
          Value: CFDEV-PublicSubnet_RouteTable
  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW

  # ------------------------------------------------------------#
  # Praivate Route Table
  # ------------------------------------------------------------#
  PraivateRouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref CFDEVVPC
      Tags:
        - Key: Name
          Value: CFDEV-PrivateSubnet_RouteTable
  PraivateRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PraivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref CFNatGateway


  # ------------------------------------------------------------#
  #  DEV Public Sunbet A
  # ------------------------------------------------------------#
  DEVPublicSubnetA:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: 10.0.1.0/24
      VpcId: !Ref CFDEVVPC
      Tags:
        - Key: Name
          Value: CFDEVDEVPublicSubnetA
  PublicRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref DEVPublicSubnetA
      RouteTableId: !Ref PublicRouteTable

  # ------------------------------------------------------------#
  #  DEV Private Sunbet A1
  # ------------------------------------------------------------#
  DEVPrivateSubnetA1:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: 10.0.2.0/24
      VpcId: !Ref CFDEVVPC
      Tags:
        - Key: Name
          Value: CFDEVPrivateSubnetA1
  PrivateRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref DEVPrivateSubnetA1
      RouteTableId: !Ref PraivateRouteTable

  #------------------------------------------------------------#
  #  DEV Private Sunbet A2
  # ------------------------------------------------------------#
  DEVPrivateSubnetC1:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: 10.0.3.0/24
      VpcId: !Ref CFDEVVPC
      Tags:
        - Key: Name
          Value: CFDEVPrivateSubnetC1
  # ------------------------------------------------------------#
  #  DEV Private Sunbet C2
  # ------------------------------------------------------------#
  DEVPrivateSubnetC2:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: 10.0.4.0/24
      VpcId: !Ref CFDEVVPC
      Tags:
        - Key: Name
          Value: CFDEVPrivateSubnetC2
  # ------------------------------------------------------------#
  #  踏み台 Security Group
  # ------------------------------------------------------------#
  BassionSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: CFDEV-Bassion-SG
      GroupDescription: Allow ssh access from Myip
      VpcId: !Ref CFDEVVPC
      SecurityGroupIngress:
        # ssh
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Sub ${MYip}
      Tags:
        - Key: Name
          Value: CFDEV-Bassion-SG

  # ------------------------------------------------------------#
  #  EC2 Security Group
  # ------------------------------------------------------------#
  WebServerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    DependsOn: BassionSecurityGroup
    Properties:
      GroupName: CFDEV-EC2-SG
      GroupDescription: Allow HTTP access from Bassion
      VpcId: !Ref CFDEVVPC
      SecurityGroupIngress:
        # http
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref BassionSecurityGroup
        # ssh
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          SourceSecurityGroupId: !Ref BassionSecurityGroup
      Tags:
        - Key: Name
          Value: CFDEV-EC2-SG
  # ------------------------------------------------------------#
  #  RDS Security Group
  # ------------------------------------------------------------#
  DEVRDSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    DependsOn: WebServerSecurityGroup
    Properties:
      GroupDescription: Allow RDS access from EC2
      VpcId: !Ref CFDEVVPC
      SecurityGroupIngress:
        # DB
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref WebServerSecurityGroup
      Tags:
        - Key: Name
          Value: CFDEV-RDS-SG

Outputs:

#サブネット
  DEVPublicSubnetA:
      Value: !Ref DEVPublicSubnetA
      Export:
        Name: DEVPublicSubnetA-Outputs
  DEVPrivateSubnetA1:
      Value: !Ref DEVPrivateSubnetA1
      Export:
        Name: DEVPrivateSubnetA1-Outputs
  DEVPrivateSubnetC1:
      Value: !Ref DEVPrivateSubnetC1
      Export:
        Name: DEVPrivateSubnetC1-Outputs
  DEVPrivateSubnetC2:
      Value: !Ref DEVPrivateSubnetC2
      Export:
        Name: DEVPrivateSubnetC2-Outputs
#セキュリティグループ
  WebServerSecurityGroup:
      Value: !Ref WebServerSecurityGroup
      Export:
        Name: WebServerSecurityGroup-Outputs
  BassionSecurityGroup:
      Value: !Ref BassionSecurityGroup
      Export:
        Name: BassionSecurityGroup-Outputs
  DEVRDSSecurityGroup:
      Value: !Ref DEVRDSSecurityGroup
      Export:
        Name: DEVRDSSecurityGroup-Outputs
  #VPC
  CFDEVVPC:
      Value: !Ref CFDEVVPC
      Export:
        Name: CFDEVVPC-Outputs
```
パブリックサブネットにあるEC2のSSH接続許可はなるべく接続元を絞るべきなので、
「自宅のグローバルIP/32」を入れる。
Outputs関数は後続のテンプレートで使用する。

②サーバ・RDS
```yaml:server-rds.yml
AWSTemplateFormatVersion: 2010-09-09
Description: DEV-EC2-RDS

# ------------------------------------------------------------#
#  パラメータ
# ------------------------------------------------------------#
Parameters:
  #自宅のグローバルIP(SSHのセキュリティグループで使用する)
  MYip:
    Type: String
    Description: Type of this MYip.

  #最新のAmazonLinux2のAMI
  EC2AMI:
    Type: String
    Description: Type of this EC2AMI.

  #RDSのデータベース識別名
  DBInstanceIdentifier:
    Type: String
    Description: Type of this InstanceIdentifier.

  #RDSのユーザ名
  MasterUsername:
    Type: String
    Description: Type of this MasterUsername.
  
  #RDSのパスワード名
  MasterUserPassword:
    Type: String
    Description: Type of this MasterUsername.


# ------------------------------------------------------------#
#  リソース
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  EC2
# ------------------------------------------------------------#

  #踏み台用

  #EC2本体
  DEVmyEC2Instance1:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: <事前に取得してあるキーペア名>
      ImageId: !Sub ${EC2AMI}
      InstanceType: t2.micro
      Monitoring: true
      SecurityGroupIds:
        - !ImportValue BassionSecurityGroup-Outputs
      SubnetId: !ImportValue DEVPublicSubnetA-Outputs
      Tags:
        - Key: Name
          Value: DEVCF-EC2-1
  #EIP作成
  EIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
  #EIP紐づけ
  EIPAsaign:
    Type: AWS::EC2::EIPAssociation
    Properties:
      AllocationId: !GetAtt EIP.AllocationId
      InstanceId: !Ref DEVmyEC2Instance1
  #wordpress用
  DEVmyEC2Instance2:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: <事前に取得してあるキーペア名>
      ImageId: !Sub ${EC2AMI}
      InstanceType: t2.micro
      Monitoring: true
      SecurityGroupIds:
        - !ImportValue WebServerSecurityGroup-Outputs
      SubnetId: !ImportValue DEVPrivateSubnetA1-Outputs
      UserData:
        Fn::Base64: |
          #!/bin/bash
          echo "===========yum -y update==========="
          yum -y update
          echo "===========amazon-linux-extras install php7.2 -y==========="
          amazon-linux-extras install php7.2 -y
          echo "===========yum -y install mysql httpd php-mbstring php-xml gd php-gd==========="
          yum -y install mysql httpd php-mbstring php-xml gd php-gd
          echo "===========systemctl enable/start httpd.service ==========="
          systemctl enable httpd.service
          systemctl start httpd.service
          echo "=========== http://ja.wordpress.org/latest-ja.tar.gz ~/ ===========" 
          wget http://ja.wordpress.org/latest-ja.tar.gz 
          echo "=========== tar zxvf ~/latest-ja.tar.gz ===========" 
          tar zxvf latest-ja.tar.gz 
          echo "=========== cp -r wordpress/* /var/www/html/ ===========" 
          cp -r wordpress/* /var/www/html/ 
          chown apache:apache -R /var/www/html 

      Tags:
        - Key: Name
          Value: DEVCF-EC2-2

  # ------------------------------------------------------------#
  #  RDS SubnetGroup
  # ------------------------------------------------------------#
  CFDBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: DEVCF-DB-SubnetGroup
      SubnetIds:
        - !ImportValue DEVPrivateSubnetC1-Outputs
        - !ImportValue DEVPrivateSubnetC2-Outputs
      Tags:
      - Key: Name
        Value: DEVCFDBSubnetGroup

  # ------------------------------------------------------------#
  #  RDS 
  # ------------------------------------------------------------#
  DEVRDS:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: !Sub ${DBInstanceIdentifier}
      DBInstanceClass: "db.t2.micro"
      Engine: "mysql"
      EngineVersion: "8.0.28"
      MasterUsername: !Sub ${MasterUsername}
      MasterUserPassword: !Sub ${MasterUserPassword}
      DBName: "wordpress"
      BackupRetentionPeriod: 0
      MultiAZ: false
      PubliclyAccessible: false
      StorageType: "gp2"
      AllocatedStorage: 20
      StorageEncrypted: false
      CopyTagsToSnapshot: false
      DeleteAutomatedBackups: true
      DBSubnetGroupName: !Ref CFDBSubnetGroup
      VPCSecurityGroups: 
      - !ImportValue DEVRDSSecurityGroup-Outputs
      Tags: 
      - 
        Key: "Name"
        Value: "DEVCF-RDS"
```
# 04.wordpressの初期設定
`➀マネジメントコンソールで踏み台EC2のパブリックIPを控える`
![スクリーンショット 2023-02-15 012900.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/eea8602e-e9db-267d-01c5-4be49d9334d7.png)

`➁Wordpress用EC2のプライベートIPを控える`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/ccc270b8-78eb-ec9d-dfe7-285205a9b18f.png)

`➂Teratermの設定を下記にする`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/f624f57a-4883-b209-3508-f4b5bc7f7477.png)
ローカルホストのポートは任意の(明らかにクライアントで使われてなさそうなやつであれば)ポート番号でOKなはず

`➃ブラウザでhttp://localhost:500/にアクセスする`
そうすると初期画面が表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/ef1b7add-a7e1-d0e2-b36f-7479e1fc44a8.png)

`➄データベースのエンドポイントを控える`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/d225f164-0477-2879-c49d-058096a26f4f.png)

➅[【超初心者向け】やさしいWordPressの始め方！導入から初期設定まで徹底解説 (digitalidentity.co.jp)](https://digitalidentity.co.jp/blog/creative/%ef%bd%97ordpress-introduce.html)
「ブラウザからWordPressにログインしよう」を参考に設定する

※データベースのホスト名はRDSのエンドポイントに変更する

`➆ログインが出来ることを確認する`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/de801ab9-abe2-57e9-fdf0-e7687ad9da55.png)

`⑧➇C2にSSHログインして
/var/www/html/wp-config.phpに下記を追記する(https通信した時の画面崩れ防止のため)`
```
if($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
  $_SERVER['HTTPS'] = 'on';
  $_ENV['HTTPS'] = 'on';
}
```

# 05.所感
パブリックサブネットから実施するのであれば、そこまで構成に悩まなかったと思うが
プライベートサブネットにあるっていうだけで実施難易度が上がった気がした。
たまたまSSHポートフォワーディングの存在を思い出して今回の方法が実施できた。

# 参考文献
https://zenn.dev/soshimiyamoto/articles/2d835cf2c3fb5a

https://digitalidentity.co.jp/blog/creative/%ef%bd%97ordpress-introduce.html

https://pc.oreda.net/tips/sshportfoward#pc%E3%81%AE%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%83%BC%E3%81%A7_%E8%B8%8F%E3%81%BF%E5%8F%B0%E3%82%B5%E3%83%BC%E3%83%90%E3%82%92%E7%B5%8C%E7%94%B1%E3%81%97%E3%81%A6%E7%89%B9%E5%AE%9A%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AEweb%E3%81%B8%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9