CloudFormation으로 CloudWatch Alarms를 설정해 보기

CloudFormation으로 CloudWatch Alarms를 설정하고, Amazon SNS를 통해 경보를 이메일로 받아보는 과정까지 정리헤 봤습니다.
2022.07.23

안녕하세요 클래스메소드 김재욱(Kim Jaewook) 입니다. 이번에는 CloudFormation으로 CloudWatch Alarms를 설정하고, Amazon SNS를 통해 경보를 이메일로 받아보는 과정까지 정리헤 봤습니다.

CloudFormation 작성

VPC 생성

AWSTemplateFormatVersion: "2010-09-09"
Description: VPC Network set
Metadata: 
  "AWS::CloudFormation::Interface": 
    ParameterGroups: 
      - Label: 
          default: "Network Configuration"
        Parameters: 
          - VPCCIDR
          - PublicSubnetACIDR
          - PublicSubnetCCIDR
          - PrivateSubnetACIDR
          - PrivateSubnetCCIDR
    ParameterLabels: 
      VPCCIDR: 
        default: "VPC CIDR"
      PublicSubnetACIDR: 
        default: "PublicSubnetA CIDR"
      PublicSubnetCCIDR: 
        default: "PublicSubnetC CIDR"
      PrivateSubnetACIDR: 
        default: "PrivateSubnetA CIDR"
      PrivateSubnetCCIDR: 
        default: "PrivateSubnetC CIDR"
#-------------------------------------------------------------------
#Input VPC, Subnet Parameters
#-------------------------------------------------------------------
Parameters:
  VPCCIDR:
    Type: String
    Default: "10.0.0.0/16"

  PublicSubnetACIDR:
    Type: String
    Default: "10.0.0.0/24"

  PublicSubnetCCIDR:
    Type: String
    Default: "10.0.64.0/24"

  PrivateSubnetACIDR:
    Type: String
    Default: "10.0.128.0/24"

  PrivateSubnetCCIDR:
    Type: String
    Default: "10.0.192.0/24"
#-------------------------------------------------------------------
#Set VPC, InternetGateway, Subnet
#-------------------------------------------------------------------
Resources:
  VPC: 
    Type: "AWS::EC2::VPC"
    Properties: 
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: "true"
      EnableDnsHostnames: "true"
      InstanceTenancy: default
      Tags: 
        - Key: Name
          Value: !Sub "test-vpc"
  InternetGateway: 
    Type: "AWS::EC2::InternetGateway"
    Properties: 
      Tags: 
        - Key: Name
          Value: !Sub "test-igw"
  InternetGatewayAttachment: 
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
  PublicSubnetA: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PublicSubnetACIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-front-subnet-1a"
  PublicSubnetC: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PublicSubnetCCIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-front-subnet-1c"
  PrivateSubnetA: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PrivateSubnetACIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-application-subnet-1a"
  PrivateSubnetC: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PrivateSubnetCCIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-application-subnet-1c"
#-------------------------------------------------------------------
#Route Tables
#-------------------------------------------------------------------
  FRONTRTB : 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-front-rtb"
  APPRTB1A: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-application-rtb-1a"
  APPRTB1C: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-application-rtb-1c"
  FRONTRTBroute: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref FRONTRTB
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway
#-------------------------------------------------------------------
#Route Tables Subnet Association
#-------------------------------------------------------------------
  FRONTRTBAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref FRONTRTB
  FRONTRTB2Association: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PublicSubnetC
      RouteTableId: !Ref FRONTRTB
  APPRTB1AAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PrivateSubnetA
      RouteTableId: !Ref APPRTB1A
  APPRTB1CAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PrivateSubnetC
      RouteTableId: !Ref APPRTB1C
#-------------------------------------------------------------------
#OutPut
#-------------------------------------------------------------------
Outputs:
# VPC
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub "test-vpc"

  VPCCIDR:
    Value: !Ref VPCCIDR
    Export:
      Name: !Sub "test-vpc-cidr"
# Subnet
  PublicSubnetA:
    Value: !Ref PublicSubnetA
    Export:
      Name: !Sub "test-front-subnet-1a"

  PublicSubnetACIDR:
    Value: !Ref PublicSubnetACIDR
    Export:
      Name: !Sub "test-front-subnet-1a-cidr"

  PublicSubnetC:
    Value: !Ref PublicSubnetC
    Export:
      Name: !Sub "test-front-subnet-1c"

  PublicSubnetCCIDR:
    Value: !Ref PublicSubnetCCIDR
    Export:
      Name: !Sub "test-front-subnet-1c-cidr"

  PrivateSubnetA:
    Value: !Ref PrivateSubnetA
    Export:
      Name: !Sub "test-application-subnet-1a"

  PrivateSubnetACIDR:
    Value: !Ref PrivateSubnetACIDR
    Export:
      Name: !Sub "test-application-subnet-1a-cidr"

  PrivateSubnetC:
    Value: !Ref PrivateSubnetC
    Export:
      Name: !Sub "test-application-subnet-1c"

  PrivateSubnetCCIDR:
    Value: !Ref PrivateSubnetCCIDR
    Export:
      Name: !Sub "test-application-subnet-1c-cidr"

# Route
  FRONTRTB:
    Value: !Ref FRONTRTB
    Export:
      Name: !Sub "test-front-rtb"

  APPRTB1A:
    Value: !Ref APPRTB1A
    Export:
      Name: !Sub "test-application-rtb-1a"

  APPRTB1C:
    Value: !Ref APPRTB1C
    Export:
      Name: !Sub "test-application-rtb-1c"

다음 코드를 이용해서 VPC를 생성합니다.

EC2 생성

AWSTemplateFormatVersion: "2010-09-09"
Description:
  Create EC2

Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "EC2 Configuration"
        Parameters:
          - BastionEC2KeyPair
          - AppEC2KeyPair

    ParameterLabels: 
      BastionEC2KeyPair: 
        default: "tokyo-ec2-key"
      AppEC2KeyPair: 
        default: "AppEC2KeyPair"
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  BastionEC2KeyPair:
    Type: String
    Default: "tokyo-ec2-key"
  AppEC2KeyPair:
    Type: String
    Default: "AppEC2KeyPair"
# ------------------------------------------------------------#
#  Create Security Group
# ------------------------------------------------------------#
Resources:
  FrontBastionSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "test-front-bastion-sg"
      GroupName: test-front-bastion-sg
      SecurityGroupIngress:
              IpProtocol: tcp
              FromPort : 22
              ToPort : 22
              CidrIp: 0.0.0.0/0
      VpcId: { "Fn::ImportValue": !Sub "test-vpc" }
      Tags: 
        - Key: "Name"
          Value: test-front-bastion-sg
  APPSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "test-app-sg"
      GroupName: test-app-sg
      SecurityGroupIngress:
            -
              IpProtocol: tcp
              FromPort : 22
              ToPort : 22
              SourceSecurityGroupId: !GetAtt FrontBastionSecurityGroup.GroupId
      VpcId: { "Fn::ImportValue": !Sub "test-vpc" }
      Tags: 
        - Key: "Name"
          Value: test-app-sg
# ------------------------------------------------------------#
#  Create EC2 Instance
# ------------------------------------------------------------#
# Bastion EC2
  BastionEC2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-02c3627b04781eada
      InstanceType: t3.micro
      KeyName: !Ref BastionEC2KeyPair
      DisableApiTermination: true
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"
          SubnetId: { "Fn::ImportValue": !Sub "test-front-subnet-1a" }
          GroupSet:
            - !Ref FrontBastionSecurityGroup
      Tags: 
        - Key: "Name"
          Value: test-front-bastion
# test-app-1a
  AppEC2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-02c3627b04781eada
      InstanceType: t3.micro
      KeyName: !Ref AppEC2KeyPair
      DisableApiTermination: true
      SecurityGroupIds:
        - !Ref APPSecurityGroup
      SubnetId: { "Fn::ImportValue": !Sub "test-application-subnet-1a" }
      Tags: 
        - Key: "Name"
          Value: test-app-1a

EC2 인스턴스를 생성합니다.

SNS Topic 생성

AWSTemplateFormatVersion: "2010-09-09"
Description:
  Create sns

# ------------------------------------------------------------#
#  Create sns Topic
# ------------------------------------------------------------#
Resources:
  MySNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: my_sns_topic
      Subscription:
        - Endpoint: kim.jaewook@classmethod.jp
          Protocol: email

#-------------------------------------------------------------------
#OutPut
#-------------------------------------------------------------------
Outputs:
# SNS
  SNS:
    Value: !Ref MySNSTopic
    Export:
      Name: !Sub "my-sns-topic"

이메일로 알람을 받기 위해 Amazon SNS도 생성합니다.

Amazon SNS에 대한 이메일 설정은 아래 블로그를 참고해 주세요.

CloudWatch Alarms 생성

AWSTemplateFormatVersion: "2010-09-09"
Description:
  Set CloudWatch Alarms

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  Ec2InstanceId:
    Description: Ec2 InstanceId
    Type: AWS::EC2::Instance::Id

# ------------------------------------------------------------#
#  Create CloudWatch Alarms
# ------------------------------------------------------------#
Resources:
  C2CPUUtilizationAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
        - { "Fn::ImportValue": !Sub "my-sns-topic" }
      AlarmName: test-Alarms
      MetricName: CPUUtilization
      Namespace: AWS/EC2
      Statistic: Average
      Period: 60
      EvaluationPeriods: 2
      Threshold: 40
      TreatMissingData: breaching
      OKActions: 
        - { "Fn::ImportValue": !Sub "my-sns-topic" }
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Dimensions:
        - Name: InstanceId
          Value: !Ref Ec2InstanceId

마지막으로 CloudWatch Alarms를 생성합니다.

간단하게 CPU 사용률이 40%를 넘기면 이메일로 통지가 가는 설정입니다.

CloudWatch Alarms에 관한 상세한 속성은 아래 링크를 참고해 주세요.

CloudWatch Alarms를 생성할 때, EC2 인스턴스를 선택해야 하는데, CPU 사용률을 감시하고 싶은 EC2 인스턴스를 선택합니다.

이메일로 알람 받아보기

sudo amazon-linux-extras install epel -y
sudo yum install stress -y
sudo stress --cpu 1 --timeout 600

stress를 사용해서 강제로 CPU 사용률을 올립니다. 600초 동안 1개의 CPU를 최대한 사용합니다.

CloudWatch Alarms에서 확인해 보면 40%를 넘겼기 때문에 경보 상태가 된 것을 확인할 수 있습니다.

로그를 살펴보면, 정상에서 경보 상태로 변경된 다음, 지정해 둔 SNS Topic가 실행된 것을 확인할 수 있습니다.

이메일로 들어가 보면, 다음과 같이 알람이 온 것을 확인할 수 있습니다.

stress를 중지시키고 기다려보면 다시 CPU가 40% 밑으로 떨어지면서 status가 알람에서 OK 상태로 변경 됐다는 메일이 날아오는 것을 확인할 수 있습니다.

그 외 EC2 인스턴스 CPU 사용률을 Slack으로 받아보는 내용에 대해서는 아래 블로그를 참고해 주세요.

본 블로그 게시글을 보시고 문의 사항이 있으신 분들은 클래스메소드코리아 (info@classmethod.kr)로 연락 주시면 빠른 시일 내 담당자가 회신 드릴 수 있도록 하겠습니다 !