目次
はじめに
こんにちは。クラウド事業部の西川です。
今回は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環境を構築できるテンプレートをご紹介しました。
ご参考になれば幸いです。