APC 技術ブログ

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

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

【CloudFormation】CloudWatch Logsが有効なAuto Scaling&マルチAZのWordPress環境を構築

目次

はじめに

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

今回はAuto Scaling&マルチAZ構成かつCloudWatch Logsによるログ収集が有効なWordPress環境を構築できるテンプレートをご紹介します。

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

↓前回の記事はこちら techblog.ap-com.co.jp

また、私が構築していた際にAuto Scalingを設定したEC2がヘルスチェック失敗→ターミネート→新規起動→ヘルスチェック失敗という無限ループに陥った事象とその原因もご紹介します。

構成図

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

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

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

RDSはMulti-AZ ClusterではなくMulti-AZ Instanceにしています。

また、便宜上構成図では1aのAZにプライマリが配置されているように記載していますが、実際にはMulti-AZのRDSはプライマリとスタンバイがサブネットグループのどのAZに配置されるかを指定することはできません。

テンプレート

AWSTemplateFormatVersion: '2010-09-09'

Description:
  Multi-AZ WordPress Blog Site

Parameters:
  DatabaseMasterName:
    Description: Database Master User Name
    Type : String
    Default: admin
  DatabaseMasterPassword:
    Description: Database Master User Password
    Type : String
  DatabaseName:
    Description: Database Name
    Type : String
    Default: wordpress
  EngineVersion:
    Description: Engine Version
    Type : String
    Default: 8.0.35
  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
  # ------------------------------------------------------------#
  PrivateSubnetC:
    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

  # ------------------------------------------------------------#
  #  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
      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: '2'
      MinSize: '2'
      MaxSize: '4'
      VPCZoneIdentifier:
        - !Ref PublicSubnetA
        - !Ref PublicSubnetC
      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
      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
  # ------------------------------------------------------------#
  RDSDatabase:
    Type: AWS::RDS::DBInstance
    Properties:
      AllocatedStorage: 20
      AllowMajorVersionUpgrade: false
      AutoMinorVersionUpgrade: false
      BackupRetentionPeriod: 0
      DBInstanceClass: db.t3.micro
      DBInstanceIdentifier: WP-RDS-Database
      DBName: !Ref DatabaseName
      DBSubnetGroupName: !Ref RDSDBSubnetGroup
      DeleteAutomatedBackups: false
      DeletionProtection: false
      Engine: mysql
      EngineVersion: !Ref EngineVersion
      MasterUsername: !Ref DatabaseMasterName
      MasterUserPassword: !Ref DatabaseMasterPassword
      MaxAllocatedStorage: 1000
      MultiAZ: true
      PubliclyAccessible: false
      StorageEncrypted: true
      StorageType: gp2
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-RDS-Database
      VPCSecurityGroups:
        - !Ref RDSSG

トラブルシューティング

上記テンプレートを使用すれば発生しませんが、私がテンプレートに変更を加えながらスタックを作成しているとEC2がヘルスチェック失敗→ターミネート→新規起動→ヘルスチェック失敗という無限ループに陥りました。

2台のインスタンスのうち1台は正常に動作するものの、もう1台は無限ループを繰り返すという状況でした。

当初はAuto Sacalingの設定やELBの設定に問題があるのかと考えていましたが、片方のパブリックサブネットにインターネットゲートウェイへのルートテーブルがアタッチされていなかった(つまりパブリックサブネットになっていなかった)ことが原因でした。

もし同じ症状でお困りの方がいらっしゃればご参考になれば幸いです。

まとめ

Auto Scaling&マルチAZ構成かつCloudWatch Logsによるログ収集が有効なWordPress環境を構築できるテンプレートをご紹介しました。

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