APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

【CloudFormation】Auto Scaling&Multi-AZ DB ClusterのWordPress環境を構築

目次

はじめに

こんにちは。クラウド事業部の西川です。

今回は前回のテンプレートからRDSをMulti-AZ DB Clusterに変更したテンプレートをご紹介します。

↓前回の記事はこちら

techblog.ap-com.co.jp

CloudFormationを初めて触る方は↓の記事にスタックの作成方法も記載してありますので是非ご覧ください。

techblog.ap-com.co.jp

構成図

EC2 Auto Scalingは希望する台数3、最小台数3、最大サイズ4、CPU平均使用率70%を保つ設定のターゲット追跡スケーリングポリシーです。

EC2にはSession Managerで接続できるようにしています。

SSH接続したい場合はセキュリティグループでSSHのインバウンドを許可してください。

また、CloudWatch LogsはEC2インスタンスの/var/log/messagesのログを収集します。

本記事のメインテーマでもあるMulti-AZ DB Clusterには下記の特徴があります。

・3つのAZに跨る冗長構成

・2つの読み取り可能なスタンバイインスタンス

・35 秒未満の自動フェイルオーバー

Multi-AZ DB ClusterとMulti-AZ DB Instanceの違いについては下記の記事が非常にわかりやすいです。

blog.serverworks.co.jp

テンプレート

AWSTemplateFormatVersion: '2010-09-09'

Description:
  Multi-AZ DB Cluster WordPress Blog Site

Parameters:
  DBMasterName:
    Description: Database Master User Name
    Type : String
    Default: admin
  DBMasterPassword:
    Description: Database Master User Password
    Type : String
  DBName:
    Description: Database Name
    Type : String
    Default: wordpress
  EngineVersion:
    Description: Engine Version
    Type : String
    Default: 8.0.35
  DBClusterInstanceClass:
    Type : String
    Default: db.m6gd.large
  AllocatedStorage:
    Type: Number
    Default: 20
  EC2AMIId:
    Description: AMI ID
    Type : String
    Default: ami-0d0150aa305b7226d
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"
  EnvironmentName:
    Type: String
    Default: r-nishikawa-CFn-WP

Resources:
  # ------------------------------------------------------------#
  #  VPC
  # ------------------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
      - Key: Name
        Value: !Sub ${EnvironmentName}-VPC

  # ------------------------------------------------------------#
  #  Internet Gateway
  # ------------------------------------------------------------#
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: !Sub ${EnvironmentName}-VPC-IGW
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  # ------------------------------------------------------------#
  #  Route Table
  # ------------------------------------------------------------#
  RouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${EnvironmentName}-VPC-RTB
  Route:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # ------------------------------------------------------------#
  #  Public Sunbet A
  # ------------------------------------------------------------#
  PublicSubnetA:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: 'true'
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${EnvironmentName}-VPC-PublicSubnet-A
  PublicRouteTableAssociationA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref RouteTable

  # ------------------------------------------------------------#
  #  Private Sunbet A
  # ------------------------------------------------------------#
  PrivateSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: 'false'
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${EnvironmentName}-VPC-PrivateSubnet-A

  # ------------------------------------------------------------#
  #  Public Sunbet C
  # ------------------------------------------------------------#
  PublicSubnetC:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: 10.0.3.0/24
      MapPublicIpOnLaunch: 'true'
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${EnvironmentName}-VPC-PublicSubnet-C
  PublicRouteTableAssociationC:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetC
      RouteTableId: !Ref RouteTable

  # ------------------------------------------------------------#
  #  Private Sunbet C
  # ------------------------------------------------------------#
  PrivateSubnetD:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: 10.0.4.0/24
      MapPublicIpOnLaunch: 'false'
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${EnvironmentName}-VPC-PrivateSubnet-C

  # ------------------------------------------------------------#
  #  Public Sunbet D
  # ------------------------------------------------------------#
  PublicSubnetD:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      AvailabilityZone: "ap-northeast-1d"
      CidrBlock: 10.0.5.0/24
      MapPublicIpOnLaunch: 'true'
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${EnvironmentName}-VPC-PublicSubnet-D
  PublicRouteTableAssociationD:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetD
      RouteTableId: !Ref RouteTable

  # ------------------------------------------------------------#
  #  Private Sunbet D
  # ------------------------------------------------------------#
  PrivateSubnetC:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1d"
      CidrBlock: 10.0.6.0/24
      MapPublicIpOnLaunch: 'false'
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${EnvironmentName}-VPC-PrivateSubnet-D

  # ------------------------------------------------------------#
  #  ALB Security Group
  # ------------------------------------------------------------#
  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${EnvironmentName}-ALB-SG
      GroupDescription: Allow HTTP and HTTPS access from internet
      VpcId: !Ref VPC
      SecurityGroupIngress:
        # http
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: "0.0.0.0/0"
        # https
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: "0.0.0.0/0"
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-ALB-SG

  # ------------------------------------------------------------#
  #  ALB
  # ------------------------------------------------------------#
  ApplicationLoadBalancer:
    Type : AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub ${EnvironmentName}-ALB
      Scheme: "internet-facing"
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Subnets:
        - !Ref PublicSubnetA
        - !Ref PublicSubnetC
        - !Ref PublicSubnetD
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-ALB

  # ------------------------------------------------------------#
  #  ALB Target Group
  # ------------------------------------------------------------#
  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckPath: "/healths/wp-check"
      HealthCheckPort: 80
      HealthCheckProtocol: HTTP
      HealthyThresholdCount: 5
      Name: !Sub ${EnvironmentName}-ALB-TG
      Port: 80
      Protocol: HTTP
      HealthCheckTimeoutSeconds: 5
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-ALB-TG
      TargetType: instance
      UnhealthyThresholdCount: 2
      VpcId: !Ref VPC

  # ------------------------------------------------------------#
  #  ALB Listner
  # ------------------------------------------------------------#
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref ALBTargetGroup
          Type: forward
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP

  # ------------------------------------------------------------#
  #  EC2 Auto Scaling Group
  # ------------------------------------------------------------#
  EC2AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: !Sub "${EnvironmentName}-ASG"
      DesiredCapacity: '3'
      MinSize: '3'
      MaxSize: '4'
      VPCZoneIdentifier:
        - !Ref PublicSubnetA
        - !Ref PublicSubnetC
        - !Ref PublicSubnetD
      LaunchTemplate:
        LaunchTemplateId: !Ref EC2LaunchTemplate
        Version: !GetAtt EC2LaunchTemplate.LatestVersionNumber
      TargetGroupARNs:
        - !Ref ALBTargetGroup
      HealthCheckType: ELB
      HealthCheckGracePeriod: 300
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-EC2-ASG
          PropagateAtLaunch: true

  # ------------------------------------------------------------#
  #  EC2 Auto Scaling Policy
  # ------------------------------------------------------------#
  EC2AutoScalingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref EC2AutoScalingGroup
      PolicyType: TargetTrackingScaling
      TargetTrackingConfiguration:
        PredefinedMetricSpecification:
          PredefinedMetricType: ASGAverageCPUUtilization
        TargetValue: 70

  # ------------------------------------------------------------#
  #  EC2 Security Group
  # ------------------------------------------------------------#
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    DependsOn: ALBSecurityGroup
    Properties:
      GroupName: !Sub ${EnvironmentName}-EC2-SG
      GroupDescription: Allow HTTP and HTTPS from ALBSecurityGroup
      VpcId: !Ref VPC
      SecurityGroupIngress:
        # HTTP
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref ALBSecurityGroup
        # HTTPS
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          SourceSecurityGroupId: !Ref ALBSecurityGroup
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-EC2-SG

  # ------------------------------------------------------------#
  #  EC2 Instance Role
  # ------------------------------------------------------------#
  EC2InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${EnvironmentName}-EC2InstanceRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: [ec2.amazonaws.com, ssm.amazonaws.com]
            Action: 'sts:AssumeRole'
      Path: "/"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Policies:
        - PolicyName: !Sub ${EnvironmentName}-EC2-Policy-V1
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - logs:DescribeLogStreams
                Resource: '*'

  # ------------------------------------------------------------#
  #  Instance Profile
  # ------------------------------------------------------------#
  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref EC2InstanceRole

  # ------------------------------------------------------------#
  #  EC2 Launch Template
  # ------------------------------------------------------------#
  EC2LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub ${EnvironmentName}-Launch-Template
      LaunchTemplateData:
        IamInstanceProfile:
          Arn: !GetAtt InstanceProfile.Arn
        ImageId: !Ref EC2AMIId
        KeyName: !Ref KeyName
        InstanceType: t2.micro
        SecurityGroupIds:
          - !Ref EC2SecurityGroup
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            set -e
            yum -y update
            amazon-linux-extras install php7.2 -y
            yum -y install mysql httpd php-mbstring php-xml gd php-gd
            systemctl enable httpd.service
            systemctl start httpd.service
            wget http://ja.wordpress.org/latest-ja.tar.gz
            tar zxvf latest-ja.tar.gz
            cp -r wordpress/* /var/www/html/
            chown apache:apache -R /var/www/html
            cd /var/www/html/
            mkdir healths
            cd healths
            touch wp-check
            # Amazon CloudWatch Agentのインストールと設定
            yum install -y amazon-cloudwatch-agent
            INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id)
            cat <<EOF > /opt/aws/amazon-cloudwatch-agent/bin/config.json
            {
              "agent": {
                "run_as_user": "root"
              },
              "logs": {
                "logs_collected": {
                  "files": {
                    "collect_list": [
                      {
                        "file_path": "/var/log/messages",
                        "log_group_name": "${EnvironmentName}/logs",
                        "log_stream_name": "$INSTANCE_ID",
                        "timezone": "Local"
                      }
                    ]
                  }
                }
              }
            }
            EOF
            /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json -s
        BlockDeviceMappings:
          - DeviceName: /dev/xvda
            Ebs:
              VolumeSize: 8
              VolumeType: gp2
        TagSpecifications:
          - ResourceType: instance
            Tags:
              - Key: Name
                Value: !Sub ${EnvironmentName}-EC2-Instance

  # ------------------------------------------------------------#
  #  Database Subnet Group
  # ------------------------------------------------------------#
  RDSDBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: WP-RDS-SubnetGroup
      DBSubnetGroupName: WP-RDS-SubnetGroup
      SubnetIds:
        - !Ref PrivateSubnetA
        - !Ref PrivateSubnetC
        - !Ref PrivateSubnetD
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-RDS-SubnetGroup

  # ------------------------------------------------------------#
  #  RDS Security Group
  # ------------------------------------------------------------#
  RDSSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: WP-RDS-SG
      GroupDescription: Allow Request from WebServer
      VpcId: !Ref VPC
      SecurityGroupIngress:
        # http
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref EC2SecurityGroup
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-RDS-SG

  # ------------------------------------------------------------#
  #  RDS Multi-AZ DB Cluster
  # ------------------------------------------------------------#
  MultiAZDBCluster:
    Type: 'AWS::RDS::DBCluster'
    Properties:
      AllocatedStorage: !Ref AllocatedStorage
      DatabaseName: !Ref DBName
      DBClusterIdentifier: !Sub ${EnvironmentName}-DBCluster
      DBClusterInstanceClass: !Ref DBClusterInstanceClass
      Engine: mysql
      EngineVersion: !Ref EngineVersion
      MasterUsername: !Ref DBMasterName
      MasterUserPassword: !Ref DBMasterPassword
      DBSubnetGroupName: !Ref RDSDBSubnetGroup
      VpcSecurityGroupIds:
        - !Ref RDSSG
      BackupRetentionPeriod: 1
      PubliclyAccessible: false
      StorageEncrypted: true
      StorageType: gp3

まとめ

Multi-AZ DB ClusterはMulti-AZ DB Instanceよりもコストは高くなりますが、その分メリットもある構成です。

高可用性が求められ、素早いフェイルオーバーが求められる場合には有力な選択肢になります。

ご参考になれば幸いです。