【AWS】CloudFormtionでブログサービスを構築してみた(02.EC2,RDS,ALB編)

# 01.はじめに
[【AWS】CloudFormtionでブログサービスを構築してみた(概要編)](https://qiita.com/1k5/items/4f6d61373e80f824bd9a)の続きである。
過去に公開した記事は下記の通りである。
・[【AWS】CloudFormtionでブログサービスを構築してみた(概要編)](https://qiita.com/1k5/items/4f6d61373e80f824bd9a)
・[【AWS】CloudFormtionでブログサービスを構築してみた(01.Network編)](https://qiita.com/1k5/items/d268697691ef2ef3dd0e)

`この記事で扱うものはコンポーネントは下記である。`
1.EC2
2.ALB
3.RDS(スナップショットを復元する)
  
`また、テンプレートを投入後、手作業が発生するのでこちらも対応する。`
`SSM経由でEC2にログインして実施する`
1.[こちら](https://qiita.com/1k5/items/32bbe213596aabcf3a59)でスナップショットを復元したRDSの接続先が
http\://localhost:500になっている(SSHポートフォワーディングした影響)のでALBのドメイン名に変更する。

2.wp-config.phpに下記を追記する(https通信するとフォントが崩れてしまうため)
```
if($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
  $_SERVER['HTTPS'] = 'on';
  $_ENV['HTTPS'] = 'on';
}
```


# 02.構成図
下記の通りである。
![構成図２‐EC2,ALB.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/8d5328dc-8f04-26c0-3c0b-ec8d2aae446c.png)





# 03.事前準備
事前準備として下記をしておく
1.Route53で登録したゾーンのNSレコードをお名前ドットコムのネームサーバー情報に記載する。
　公式サイトによると設定変更が反映されるまで数時間～72時間かかるとのことだが、
　筆者の環境では1時間かからず反映された。
　[設定変更方法](https://www.onamae.com/guide/p/70)

2.Wordpressの初期設定済のEC2を構築してAMI化しておく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/bbb4cbb1-b951-042a-5296-728b690875f8.png)
3.RDSのスナップショットを取得しておく。
[こちらで取得したものを復元する](https://qiita.com/1k5/items/32bbe213596aabcf3a59)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/ac5c1383-dece-0345-63af-232bafaa189e.png)



# 04.リスナールールの設計
リスナールールで下記アクセスになるように制御する
1.wp-adminは管理画面のログインURLなので、「自宅のIP/32」以外接続許可しない

2.httpアクセスはhttpsへリダイレクトする

表にすると下記。
◆自宅からのアクセス
|アクセスURL|アクセス結果 |
|:-:|:---------:|
|https:\//＜ALBのドメイン名＞|許可(200)|
|https:\//＜ALBのドメイン名＞/wp-admin|許可(302)(認証画面にリダイレクトされるため)|
|http:\//＜ALBのドメイン名＞|httpsにリダイレクト(301)|
|http:\//＜ALBのドメイン名＞/wp-admin|拒否(403)|

※自分からわざわざwp-adminにhttpアクセスしないので拒否にしている

◆自宅以外からのアクセス
|アクセスURL|アクセス結果 |
|:-:|:---------:|
|https:\//＜ALBのドメイン名＞|許可(200)|
|https:\//＜ALBのドメイン名＞/wp-admin|拒否(403)|
|http:\//＜ALBのドメイン名＞|httpsにリダイレクト(301)|
|http:\//＜ALBのドメイン名＞/wp-admin|拒否(403)|

# 05.テスト方法
下記観点で確認する
1.通常アクセス
　ターゲットグループのテストもかねて１台EC2をシャットダウンした状態でアクセスする。

2.ヘルスチェックが想定通りになっているか確認する

3.リスナールールの機能テスト(curlでレスポンスコードをとるのみにする)
　自宅のコマンドプロンプトと稼働中のEC2からそれぞれ実施する。


# 06.テンプレートファイル
Outputs情報
```yaml:
Outputs:

#サブネット
  PublicSubnetA:
      Value: !Ref PublicSubnetA
      Export:
        Name: PublicSubnetA-Outputs
  PublicSubnetB:
      Value: !Ref PublicSubnetB
      Export:
        Name: PublicSubnetB-Outputs
  PrivateSubnetA1:
      Value: !Ref PrivateSubnetA1
      Export:
        Name: PrivateSubnetA1-Outputs
  PrivateSubnetA2:
      Value: !Ref PrivateSubnetA2
      Export:
        Name: PrivateSubnetA2-Outputs
  PrivateSubnetC1:
      Value: !Ref PrivateSubnetC1
      Export:
        Name: PrivateSubnetC1-Outputs
  PrivateSubnetC2:
      Value: !Ref PrivateSubnetC2
      Export:
        Name: PrivateSubnetC2-Outputs
#セキュリティグループ
  EC2SecurityGroup:
      Value: !Ref EC2SecurityGroup
      Export:
        Name: EC2SecurityGroup-Outputs
  ALBSecurityGroup:
      Value: !Ref ALBSecurityGroup
      Export:
        Name: ALBSecurityGroup-Outputs
  RDSSecurityGroup:
      Value: !Ref RDSSecurityGroup
      Export:
        Name: RDSSecurityGroup-Outputs
  #VPC
  CFVPC:
      Value: !Ref CFVPC
      Export:
        Name: CFVPC-Outputs
  #Route53HostZone(ACMの証明書発行で使う)
  HostZone:
      Value: !Ref Route53HostedZone
      Export:
        Name: CFHostZone-Outputs
```

テンプレートファイル
```yaml:EC2-ALB.yml
AWSTemplateFormatVersion: 2010-09-09
Description: EC2-ALB

# ------------------------------------------------------------#
#  パラメータ
# ------------------------------------------------------------#
Parameters:
#取得する証明書のドメイン
  ACMDomainName:
    Type: String
    Description: Type of this ACM Domain
#事前に取得しているEC2のAMI
  EC2AMI:
    Type: String
    Description: Type of this EC2AMI.
#自宅のグローバルIP(リスナールールで使用する)
  MYip:
    Type: String
    Description: Type of this MYip.
#管理画面のURLパス(リスナールールで使用する)
  URLPath:
    Type: String
    Description: URLPath.
    Default: "/wp-admin/*"

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

# ------------------------------------------------------------#
#  証明書取得
# ------------------------------------------------------------#
  ACM:
    Type: AWS::CertificateManager::Certificate
    Properties: 
      DomainName: !Sub "${ACMDomainName}"
      DomainValidationOptions:
        - DomainName: !Sub "${ACMDomainName}"
          HostedZoneId: !ImportValue "CFHostZone-Outputs"
      Tags: 
        - Key: Name
          Value: CF-ACM
      ValidationMethod: DNS
# ------------------------------------------------------------#
#  EC2
# ------------------------------------------------------------#

  myEC2Instance1:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: <事前に取得しているキーペア名>
      #取得したAMI 
      ImageId: !Sub ${EC2AMI}
      InstanceType: t2.micro
      Monitoring: true
      IamInstanceProfile: !Ref EC2InstanceProfile
      SecurityGroupIds:
        - !ImportValue EC2SecurityGroup-Outputs
      SubnetId: !ImportValue PrivateSubnetA1-Outputs
      Tags:
        - Key: Name
          Value: CF-EC2-1

  myEC2Instance2:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: <事前に取得しているキーペア名>
      #取得したAMI 
      ImageId: !Sub ${EC2AMI}
      InstanceType: t2.micro
      Monitoring: true
      IamInstanceProfile: !Ref EC2InstanceProfile
      SecurityGroupIds:
        - !ImportValue EC2SecurityGroup-Outputs
      SubnetId: !ImportValue PrivateSubnetC1-Outputs
      Tags:
        - Key: Name
          Value: CF-EC2-2
# ------------------------------------------------------------#
#  セッションマネージャー許可のIAMロールを付与
# ------------------------------------------------------------#
  EC2IAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: EC2-SSM-role
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
# ------------------------------------------------------------#
#  セッションマネージャー許可のIAMロールをアタッチ
# ------------------------------------------------------------#
  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - Ref: EC2IAMRole
      InstanceProfileName: EC2InstanceProfile


# ------------------------------------------------------------#
#  ALB
# ------------------------------------------------------------#

# ------------------------------------------------------------#
#  ALB本体
# ------------------------------------------------------------#
  CFALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: ipv4
      Name: CF-ALB
      Scheme: internet-facing
      SecurityGroups:
        - !ImportValue ALBSecurityGroup-Outputs
      Subnets:
        - !ImportValue PublicSubnetA-Outputs
        - !ImportValue PublicSubnetB-Outputs
      Tags:
        - Key: Name
          Value: CF-ALB
      Type: application

# ------------------------------------------------------------#
#  ターゲットグループ
# ------------------------------------------------------------#
  CFTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: CF-TargetGroup
      VpcId: !ImportValue CFVPC-Outputs
      IpAddressType: "ipv4"
      Protocol: HTTP
      ProtocolVersion: HTTP1
      Port: 80
      TargetType: instance
      Targets:
      - Id: !Ref myEC2Instance1
        Port: 80
      - Id: !Ref myEC2Instance2
        Port: 80
      #httpアクセスはリダイレクトされるので301にしている
      Matcher:
        HttpCode: "301"
      HealthCheckEnabled: true
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /index.php
      HealthCheckPort: traffic-port
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 2

# ------------------------------------------------------------#
#  リスナー(https)    
# ------------------------------------------------------------#
#https用
  CFhttpsListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    DependsOn: ACM
    Properties:
      LoadBalancerArn: !Ref CFALB
      Certificates:
        - !Ref ACM
      DefaultActions:
      - TargetGroupArn: !Ref CFTargetGroup
        Type: forward
      Port: 443
      Protocol: HTTPS
      SslPolicy: "ELBSecurityPolicy-2016-08"
    
#リスナールール
#wp-adminのアクセスを特定IPのみ許可する設定
  CFhttpsListenerRule1:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties: 
      Actions: 
        - Type: forward
          TargetGroupArn: !Ref CFTargetGroup
      Conditions: 
        - Field: source-ip
          SourceIpConfig:
            Values:
              - !Sub ${MYip}
        - Field: path-pattern
          PathPatternConfig:
            Values:
              - !Sub ${URLPath}
      ListenerArn: !Ref CFhttpsListener
      Priority: 1

#wp-adminのアクセスを拒否する設定
  CFhttpsListenerRule2:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties: 
      Actions: 
        - Type: fixed-response
          FixedResponseConfig:
              ContentType: text/plain
              MessageBody: "403 Forbideen"
              StatusCode: 403
      Conditions: 
        - Field: path-pattern
          PathPatternConfig:
            Values:
              - !Sub ${URLPath}
      ListenerArn: !Ref CFhttpsListener
      Priority: 2

# ------------------------------------------------------------#
#  リスナー(http)    
# ------------------------------------------------------------#
#http用
  CFhttpListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref CFALB
      DefaultActions:
      - Type: redirect
        RedirectConfig: 
          Protocol: "HTTPS"
          Port: 443
          Host: "#{host}"
          Path: "/#{path}"
          Query: "#{query}"
          StatusCode: "HTTP_301"
      Port: 80
      Protocol: HTTP

#リスナールール
#wp-adminのアクセスを拒否する設定
  CFhttpListenerRule1:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties: 
      Actions: 
        - Type: fixed-response
          FixedResponseConfig:
              ContentType: text/plain
              MessageBody: "403 Forbideen"
              StatusCode: 403
      Conditions: 
        - Field: path-pattern
          PathPatternConfig:
            Values:
              - !Sub ${URLPath}
      ListenerArn: !Ref CFhttpListener
      Priority: 1

# ------------------------------------------------------------#
#  RDS SubnetGroup
# ------------------------------------------------------------#
  CFDBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: CF-DB-SubnetGroup
      SubnetIds:
        - !ImportValue PrivateSubnetA2-Outputs
        - !ImportValue PrivateSubnetC2-Outputs
      Tags:
      - Key: Name
        Value: CFDBSubnetGroup

Outputs:
#ALBのDNS名の定義(Route53で使う)
  ALBDomainName:
      Value: !GetAtt CFALB.DNSName
      Export:
        Name: ALB-DNS-Outputs
#ALBのゾーンIDの定義(Route53で使う)
  ALBZoneId:
      Value: !GetAtt CFALB.CanonicalHostedZoneID
      Export:
        Name: ALB-CanonicalHostedZoneID-Outputs
#ALBのARNの定義(WAFで使う)
  ALBArn:
      Value: !Ref CFALB
      Export:
        Name: ALB-ARN-Outputs 
```
# 07.RDSのスナップショット復元
RDSのスナップショットを復元する。


# 08.修正作業
`1.RDSの接続ポイント修正`
EC2にセッションマネージャーでログイン、RDSに接続して下記コマンドを実行
```
$ mysql -h <データベースのエンドポイント名> -u wordpress -p
MySQL [(none)]> use wordpress
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MySQL [wordpress]> SELECT * FROM wp_options WHERE option_name IN ('siteurl', 'home');
+-----------+-------------+----------------------+----------+
| option_id | option_name | option_value         | autoload |
+-----------+-------------+----------------------+----------+
|         2 | home        | http://localhost:500 | yes      |
|         1 | siteurl     | http://localhost:500 | yes      |
+-----------+-------------+----------------------+----------+
2 rows in set (0.00 sec)

→下記コマンドで修正する

MySQL [wordpress]> UPDATE wp_options SET option_value = 'http://<ALBのドメイン名>' WHERE option_name IN ('siteurl', 'home');
```

`2.EC2のwp-config.phpに下記を追記する(https通信するとフォントが崩れてしまうため)`
```
if($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
  $_SERVER['HTTPS'] = 'on';
  $_ENV['HTTPS'] = 'on';
}
```

# 09.テスト
1.通常アクセス
ターゲットグループのテストもかねて１台EC2をシャットダウンした状態でアクセスする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/30ffa628-2085-3bf6-3d7c-3792a8e93d0d.png)
ALBのDNS名に対してhttpsアクセスする。
証明書はRoute53のドメインに充てているため、警告は出るがhttpsアクセス出来ている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/e8502f58-9fda-bee6-6c0c-4324a2300ef8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/09432e69-de64-150d-f129-a94763fd1599.png)
→想定通り

2.ヘルスチェックが想定通りになっているか確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050566/26644a05-43fb-fb9b-6194-90f190bad9a3.png)
→想定通り

3.リスナールールの機能テスト(curlでレスポンスコードをとるのみにする)
　自宅のコマンドプロンプトと稼働中のEC2からそれぞれ実施する。
　この表通りになるか確認する。

◆自宅からのアクセス
|アクセスURL|アクセス結果 |
|:-:|:---------:|
|https:\//＜ALBのドメイン名＞|許可(200)|
|https:\//＜ALBのドメイン名＞/wp-admin|許可(302)(認証画面にリダイレクトされるため)|
|http:\//＜ALBのドメイン名＞|httpsにリダイレクト(301)|
|http:\//＜ALBのドメイン名＞/wp-admin|拒否(403)|

※自分からわざわざwp-adminにhttpアクセスしないので拒否にしている

◆自宅以外からのアクセス
|アクセスURL|アクセス結果 |
|:-:|:---------:|
|https:\//＜ALBのドメイン名＞|許可(200)|
|https:\//＜ALBのドメイン名＞/wp-admin|拒否(403)|
|http:\//＜ALBのドメイン名＞|httpsにリダイレクト(301)|
|http:\//＜ALBのドメイン名＞/wp-admin|拒否(403)|


・自宅のコマンドプロンプトから
```
>curl -k https://CF-ALB-156526098.ap-northeast-1.elb.amazonaws.com -o /dev/null -w '%{http_code}\n' -s
'200
'
>
→通常アクセスなので想定通り

>curl -k https://CF-ALB-156526098.ap-northeast-1.elb.amazonaws.com/wp-admin/ -o /dev/null -w '%{http_code}\n' -s
'302
'
>
→ログインの認証画面に遷移するので問題なし

>curl http://CF-ALB-156526098.ap-northeast-1.elb.amazonaws.com -o /dev/null -w '%{http_code}\n' -s
'301
'
>
→httpsにリダイレクトされるので問題なし

>curl http://CF-ALB-156526098.ap-northeast-1.elb.amazonaws.com/wp-admin/ -o /dev/null -w '%{http_code}\n' -s
'403
'
>
→wp-adminへのアクセスなので403で問題なし
```
・EC2から
```
$ whoami 
ec2-user
$ 

$ curl -k https://CF-ALB-156526098.ap-northeast-1.elb.amazonaws.com -o /dev/null -w '%{http_code}\n' -s
200
$ 
→通常アクセスなので想定通り

$ curl -k https://CF-ALB-156526098.ap-northeast-1.elb.amazonaws.com/wp-admin/ -o /dev/null -w '%{http_code}\n' -s
403
$ 
→wp-adminへのアクセスなので403で問題なし

$ curl http://CF-ALB-156526098.ap-northeast-1.elb.amazonaws.com -o /dev/null -w '%{http_code}\n' -s
301
$ 
→httpsへリダイレクトされるので問題なし

$ curl http://CF-ALB-156526098.ap-northeast-1.elb.amazonaws.com/wp-admin/ -o /dev/null -w '%{http_code}\n' -s
403
$ 
→wp-adminへのアクセスなので403で問題なし
```
# 参考文献
https://cloud5.jp/cf-alb/

https://blog.denet.co.jp/cloudformation-acm/

https://dev.classmethod.jp/articles/alb-path-and-ip-based-restriction/

https://qiita.com/natsuya-niki/items/bdd6009d3bfc1c7d313b

★次回はこちら
[【AWS】CloudFormtionでブログサービスを構築してみた(03.S3,CloudFront編)](https://qiita.com/1k5/items/a19cb65bb749b985970c)