【初心者】AWS CloudFormationでVPC構築～EC2起動

恥ずかしながら **AWS CloudFormation** をまだ使ったことがなかったので、トライしてみました。

## 目指すもの
AWS CloudFormationだけでLinuxサーバー構築
できるだけ設定はパラメーターとして外出しする
作成するサーバーには一般的な監視設定(死活監視・リソース監視)も入れた状態で完成させる

1. VPC作成
1. サブネット作成
1. インターネットゲートウェイ作成
1. ルートテーブル設定 & ルート設定
1. キーペア作成
1. EC2作成(Amazon Linux 2023)
1. CloudWatch設定
1. AWS Backup設定

## 詳細設定
|  項目  |  設定内容  |
| ---- | ---- |
|リージョン|大阪(ap-northeast-3)|
|VPC IPv4 CIDRブロック|192.168.0.0/16|
|サブネット IPv4 CIDRブロック|192.168.1.0/24|
|OS|Amazon Linux 2023 (2023/7/16時点の最新)|
|EC2 インスタンスタイプ|t3.micro|
|セキュリティグループ|ICMPと443ポートへのアクセス許可(0.0.0.0) <br> アウトバウンド前許可|
|CloudWatch|システムチェックエラー(発生時にサーバー再起動させる) <br> CPU使用率80%以下 <br>CPUCreditBalance 50以下|
|AWS Backup|毎日03:00に取得し、7世代保持する|


## 目指す構成
![【CloudFormation01】AWS構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969669/83228012-8f42-d430-d978-1db24af8b656.png)


## 事前準備
アラート通知用のSNSトピックだけは作成しました
（ここもCloudFormationにしたい）

## 実際に作ったコード
```yaml:CloudFormation-VPC-EC2.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: VPC-EC2-tamplate

#####################################################################
# Parameters Setting
#####################################################################
Parameters:
  Region:
    Description: Region
    Type: String
    Default: ap-northeast-3
  SystemName:
    Description: ServerName
    Type: String
    Default: hogehoge
  VpcCidrBlock:
    Description: CidrBlock
    Type: String
    Default: '192.168.0.0/16'
  SubnetCidrBlock:
    Description: CidrBlock
    Type: String
    Default: '192.168.1.0/24'
  Ec2ImageId:
    Description: Amazon Linux 2023 AMI
    Type: String
    Default: ami-0ca0742afa9ee482f
  Ec2InstanceType:
    Description: Ec2 InstanceType
    Type: String
    Default: t3.micro
  SNSTopicName:
    Description: SNS Topic Name
    Type: String
    Default: arn:aws:sns:ap-northeast-3:{アカウントID}:hogehoge-topic
  IAMRoleName:
    Description: IAM Role Name
    Type: String
    Default: SSM-Policy

#####################################################################
# Resources
#####################################################################
Resources:
# Network Setting
  Vpc:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: !Sub ${VpcCidrBlock}
      Tags:
        - Key: 'Name'
          Value: !Sub ${SystemName}-vpc

  Subnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      CidrBlock: !Sub ${SubnetCidrBlock}
      AvailabilityZone: !Sub ${Region}a
      MapPublicIpOnLaunch: true
      Tags:
        - Key: 'Name'
          Value: !Sub ${SystemName}-subnet
      VpcId: !Ref Vpc

  InternetGateway:
    Type: 'AWS::EC2::InternetGateway'
    Properties:
      Tags:
      - Key: 'Name'
        Value: !Sub ${SystemName}-igw

  AttachGateway:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    Properties:
      VpcId: !Ref Vpc
      InternetGatewayId: !Ref InternetGateway

  RouteTable:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      Tags:
        - Key: 'Name'
          Value: !Sub ${SystemName}-rt
      VpcId: !Ref Vpc
  Route:
    Type: 'AWS::EC2::Route'
    DependsOn: InternetGateway
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  SubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref Subnet
      RouteTableId: !Ref RouteTable

# IamRole Setting
  IamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - sts:AssumeRole
            Principal:
              Service:
                - ec2.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      RoleName: !Sub ${SystemName}-role

# EC2 Setting
  NewKeyPair:
    Type: 'AWS::EC2::KeyPair'
    Properties: 
      KeyName: !Sub ${SystemName}-key-pair

  SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: !Sub ${SystemName}-SecurityGroup
      VpcId: !Ref Vpc
      Tags:
        - Key: 'Name'
          Value: !Sub ${SystemName}-sg
      SecurityGroupIngress:
        # http
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
        # ICMP
        - IpProtocol: icmp
          FromPort: -1
          ToPort: -1
          CidrIp: 0.0.0.0/0

  ElasticIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/"
      Roles:
      - !Ref IamRole

  EC2Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: !Ref Ec2ImageId
      InstanceType: !Ref Ec2InstanceType
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          SubnetId: !Ref Subnet
          GroupSet:
            - !Ref SecurityGroup
      IamInstanceProfile: !Ref InstanceProfile
      BlockDeviceMappings:
        - DeviceName: '/dev/xvda'
          Ebs:
            VolumeType: 'gp2'
            VolumeSize: 8
      Tags:
        - Key: 'Name'
          Value: !Sub ${SystemName}-ec2-instance'
      KeyName: !Ref NewKeyPair

  IPAssoc:
    Type: AWS::EC2::EIPAssociation
    Properties:
      InstanceId: !Ref EC2Instance
      EIP: !Ref ElasticIP

# CloudWatch Setting
  StatusCheckFailedActionReboot:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
        - !Ref SNSTopicName
        - !Sub "arn:aws:automate:${Region}:ec2:recover"
      AlarmName: !Sub ${SystemName}_StatusCheckFailed_Action-Reboot
      MetricName: StatusCheckFailed_System
      Namespace: AWS/EC2
      Statistic: Average
      Period: 60
      EvaluationPeriods: 1
      Threshold: 0 
      TreatMissingData: missing
      OKActions:
        - !Ref SNSTopicName
      ComparisonOperator: GreaterThanThreshold
      Dimensions:
        - Name: InstanceId
          Value: !Ref EC2Instance
  CPUCreditBalanceUnder50:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
        - !Ref SNSTopicName
      AlarmName: !Sub ${SystemName}_CPUCreditBalance_Under50
      MetricName: CPUCreditBalance
      Namespace: AWS/EC2
      Statistic: Average
      Period: 60  #[s]
      EvaluationPeriods: 2
      Threshold: 50
      TreatMissingData: missing 
      OKActions:
        - !Ref SNSTopicName
      ComparisonOperator: LessThanOrEqualToThreshold
      Dimensions:
        - Name: InstanceId
          Value: !Ref EC2Instance
  CPU80PercentOver:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
        - !Ref SNSTopicName
      AlarmName: !Sub ${SystemName}_CPU_80%Over
      MetricName: CPUUtilization
      Namespace: AWS/EC2
      Statistic: Average
      Period: 300  #[s]
      EvaluationPeriods: 2 
      Threshold: 80
      TreatMissingData: missing
      OKActions:
        - !Ref SNSTopicName
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Dimensions:
        - Name: InstanceId
          Value: !Ref EC2Instance

# AWS Backup Setting
  BackupVaultforEC2:
    Type: "AWS::Backup::BackupVault"
    Properties:
      BackupVaultName: !Sub ${SystemName}-BackupVault

  BackupPlan:
    Type: "AWS::Backup::BackupPlan"
    Properties:
      BackupPlan:
        BackupPlanName: !Sub ${SystemName}-BackupPlan
        AdvancedBackupSettings:
          -
            ResourceType: EC2
            BackupOptions:
              WindowsVSS: disabled
        BackupPlanRule:
          -
            RuleName: RuleForDailyBackups
            TargetBackupVault: !Ref BackupVaultforEC2
            ScheduleExpression: "cron(00 12 * * ? *)"
            Lifecycle:
              DeleteAfterDays: 7
    DependsOn: BackupVaultforEC2

  TagBasedBackupSelection:
    Type: AWS::Backup::BackupSelection
    Properties:
      BackupSelection:
        SelectionName: !Sub ${SystemName}
        IamRoleArn: !Sub "arn:aws:iam::{アカウントID}:role/service-role/AWSBackupDefaultServiceRole"
        Resources:
          - "*"
        Conditions:
          StringEquals:
            - ConditionKey: aws:ResourceTag/Name
              ConditionValue: !Sub ${SystemName}
      BackupPlanId: !Ref BackupPlan
    DependsOn: BackupPlan
```

## 実際に適応
1. 以下の手順でアクセス
AWSポータル　→ 「CloudFormation」 → 「スタックの作成」　→　「テンプレートファイルのアップロード」

1. 作成したyamlファイルのアップロードをして「次へ」をクリック
![【CloudFormatoin01】アップロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969669/f1c21bce-2f05-c34a-8a82-8a2daa5bd893.png)


※エラーが有る場合はここで引っかかるのでファイルを見直す！

1. 特に設定無しで「次へ」をクリックし続ける

1. 最後にiAMに関するチェックボックスが出るので、チェックして「送信」をクリック
![【CloudFormatoin01】スタック04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969669/720261b5-13ff-cb5f-5f9c-c4c73130bc0f.png)

1. 進捗が表示されるので、完了するまで待つ
![【CloudFormatoin01】スタック06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969669/0a685ad6-55f8-1dce-f96e-f5c717371163.png)

1. 完成！
構築自体は5分もかかりませんでした。かなり早い！

## 感想
ものすごく簡単にサーバーが出来て感動しました。
コードを読み取らせた時点でエラー出るので、エラーハンドリングも楽チンでした。
構築の進捗が表示され、引っかかった部分もわかりやすくエラーメッセージがでるのも丁寧でわかりやすいです。
今まで避けてたのもったいないぐらい便利でしたので、今後はどんどん利用していきたいです。