CloudFormationで基本的なシステム構成を作成してみた

以前の記事をバージョンアップさせてみました。
CloudFormationテンプレートサンプルとしてお使いください。

個人的にはMappingsで自動でCIDR範囲を区切ってくれるようにしたところがポイントです。

このテンプレートを使用することで、以下のリソースが作成されます。
- ネットワーク
    - VPC x1
    - PublicSubnet x2
    - PrivateSubnet(ProtectedSubnet) x2
    - NatGateway x2
- コンピューティング
    - EC2(Bastion) x1
    - 起動テンプレート x1
        - 暗号化済みEBS
        - 詳細モニタリング有効化
        - UserData(Apache起動、サンプルページ表示)
    - AutoScaling Group x1
        - Target Tracking Policy
        - オートスケーリンググループ別モニタリング有効化
    - Application Load Balancer
        - TargetGroup
        - リスナールール（パスベースルーティング）
        - AccessLogs（宛先はS3）
    - Security Group
        - ALB用セキュリティグループ
        - サーバー用セキュリティグループ
- IAM
    - インスタンス用ロール
        - AmazonSSMManagedInstanceCore
    - インスタンスプロファイル

イメージはこんな感じ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1610187/b57838ba-76e8-e38e-eb8c-ced7b5d4303c.png)


```yaml
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  VPCCidrBlock:
    Description: The primary IPv4 CIDR block for the VPC.
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 10.0.0.0/16
    AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'

  SubnetCidrBits:
    Description: Subnet CidrBits, alowed bitween 8 and 30.
    Type: String
    MinLength: '1'
    MaxLength: '2'
    Default: '24'
    AllowedPattern: '(\d{1,2})'

  PJPrefix:
    Type: String
    Default: base

  AMIID:
    Type: String
    Default: ami-0c8e23f950c7725b9

  AccessLogsAccountID:
    Type: String
    Description: This value will be used in the bucket policy for storing access logs. Change it according to the region you are deploying.
    Default: 127311923021

Mappings:
  CidrBits:
    '30':
      bit: 2
    '29':
      bit: 3
    '28':
      bit: 4
    '27':
      bit: 5
    '26':
      bit: 6
    '25':
      bit: 7
    '24':
      bit: 8
    '23':
      bit: 9
    '22':
      bit: 10
    '21':
      bit: 11
    '20':
      bit: 12
    '19':
      bit: 13
    '18':
      bit: 14
    '17':
      bit: 15
    '16':
      bit: 16
    '15':
      bit: 17
    '14':
      bit: 18
    '13':
      bit: 19
    '12':
      bit: 20
    '11':
      bit: 21
    '10':
      bit: 22
    '9':
      bit: 23
    '8':
      bit: 24

Resources:
################ Create Network ################
  VPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      CidrBlock: !Ref VPCCidrBlock
      Tags:
        - Key: Name
          Value: !Sub '${PJPrefix}-VPC'
  PublicSubnet1:
    Type: 'AWS::EC2::Subnet'
    Properties:
      AvailabilityZone: !Select
        - 0
        - 'Fn::GetAZs': !Ref 'AWS::Region'
      CidrBlock:
        Fn::Select:
          - 0
          - Fn::Cidr:
            - Fn::GetAtt:
              - VPC
              - CidrBlock
            - 4
            - Fn::FindInMap:
              - CidrBits
              - !Ref SubnetCidrBits
              - bit
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub '${PJPrefix}-PublicSubnet1'
      VpcId: !Ref VPC
  PublicSubnet2:
    Type: 'AWS::EC2::Subnet'
    Properties:
      CidrBlock:
        Fn::Select:
          - 1
          - Fn::Cidr:
            - Fn::GetAtt:
              - VPC
              - CidrBlock
            - 4
            - Fn::FindInMap:
              - CidrBits
              - !Ref SubnetCidrBits
              - bit
      AvailabilityZone: !Select
        - 1
        - 'Fn::GetAZs': !Ref 'AWS::Region'
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub '${PJPrefix}-PublicSubnet2'
      VpcId: !Ref VPC
  PrivateSubnet1:
    Type: 'AWS::EC2::Subnet'
    Properties:
      CidrBlock:
        Fn::Select:
          - 2
          - Fn::Cidr:
            - Fn::GetAtt:
              - VPC
              - CidrBlock
            - 4
            - Fn::FindInMap:
              - CidrBits
              - !Ref SubnetCidrBits
              - bit
      AvailabilityZone: !Select
        - 0
        - 'Fn::GetAZs': !Ref 'AWS::Region'
      Tags:
        - Key: Name
          Value: !Sub '${PJPrefix}-PrivateSubnet1'
      VpcId: !Ref VPC
  PrivateSubnet2:
    Type: 'AWS::EC2::Subnet'
    Properties:
      CidrBlock:
        Fn::Select:
          - 3
          - Fn::Cidr:
            - Fn::GetAtt:
              - VPC
              - CidrBlock
            - 4
            - Fn::FindInMap:
              - CidrBits
              - !Ref SubnetCidrBits
              - bit
      AvailabilityZone: !Select
        - 1
        - 'Fn::GetAZs': !Ref 'AWS::Region'
      Tags:
        - Key: Name
          Value: !Sub '${PJPrefix}-PrivateSubnet2'
      VpcId: !Ref VPC
  InternetGateway:
    Type: 'AWS::EC2::InternetGateway'
    DependsOn: VPC
    Properties:
      Tags:
        - Key: Name
          Value: InternetGateway
  VPCGatewayAttachment:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    DependsOn:
      - VPC
      - InternetGateway
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  PublicRouteTable:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      Tags:
        - Key: Name
          Value: PublicRouteTable
      VpcId: !Ref VPC
  PublicRoute:
    Type: 'AWS::EC2::Route'
    DependsOn: VPCGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PublicSubnetRouteTableAssociation1:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    DependsOn:
      - PublicSubnet1
      - PublicRouteTable
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable
  PublicSubnetRouteTableAssociation2:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    DependsOn:
      - PublicSubnet2
      - PublicRouteTable
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable
  ElasticIP1:
    Type: 'AWS::EC2::EIP'
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: ElasticIP1
  ElasticIP2:
    Type: 'AWS::EC2::EIP'
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: ElasticIP2
  NATGateway1:
    Type: 'AWS::EC2::NatGateway'
    Properties:
      AllocationId: !GetAtt
        - ElasticIP1
        - AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: NATGateway1
  NATGateway2:
    Type: 'AWS::EC2::NatGateway'
    Properties:
      AllocationId: !GetAtt
        - ElasticIP2
        - AllocationId
      SubnetId: !Ref PublicSubnet2
      Tags:
        - Key: Name
          Value: NATGateway2
  PrivateRoutetable1:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: PrivateRouteTable1
  PrivateRoute1:
    Type: 'AWS::EC2::Route'
    DependsOn:
      - NATGateway1
    Properties:
      RouteTableId: !Ref PrivateRoutetable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway1
  PrivateSubnetRouteTableAssociation1:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      RouteTableId: !Ref PrivateRoutetable1
      SubnetId: !Ref PrivateSubnet1
  PrivateRoutetable2:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: PrivateRouteTable2
  PrivateRoute2:
    Type: 'AWS::EC2::Route'
    Properties:
      RouteTableId: !Ref PrivateRoutetable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway2
  PrivateSubnetRouteTableAssociation2:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRoutetable2
  PrivateRoutetable3:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: PrivateRouteTable3
################ Create BastionServer ################
  EC2Instance:
    Type: 'AWS::EC2::Instance'
    Properties: 
      ImageId: !Ref AMIID
      InstanceType: t3.micro
      IamInstanceProfile: !Ref EC2InstanceProfile
      SubnetId: !Ref PrivateSubnet1
      SecurityGroupIds:
        - !Ref InstanceSecurityGroup
      Tags: 
        - Key: Name
          Value: Bastion
  InstanceRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: InstanceRole
      Roles: 
        - !Ref InstanceRole
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to client host
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref ALBSecurityGroup
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
################ Create ApplicationLoadBalancer ################
  ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: alb
      Scheme: internet-facing
      SecurityGroups: 
        - !Ref ALBSecurityGroup
      Subnets: 
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      LoadBalancerAttributes:
        - Key: "access_logs.s3.enabled"
          Value: true
        - Key: "access_logs.s3.bucket"
          Value: !Ref AccessLogsBucket
  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to client host
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: target
      TargetType: instance
      Protocol: HTTP
      Port: 80
      VpcId: !Ref VPC
  HTTPlistener:
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties:
      DefaultActions:
        - Type: forward
          ForwardConfig:
            TargetGroups: 
              - TargetGroupArn: !Ref TargetGroup
      LoadBalancerArn: !Ref ALB
      Port: 80
      Protocol: "HTTP"
  ListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions: 
        - Type: forward
          ForwardConfig:
            TargetGroups: 
              - TargetGroupArn: !Ref TargetGroup
      Conditions: 
        - Field: path-pattern
          Values:
            - /main
            - /main/*
      ListenerArn: !Ref HTTPlistener
      Priority: 10
  AccessLogsBucket:
    Type: AWS::S3::Bucket
  
  SampleBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref AccessLogsBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 's3:PutObject'
            Effect: Allow
            Resource: !Sub 'arn:aws:s3:::${AccessLogsBucket}/AWSLogs/${AWS::AccountId}/*'
            Principal:
              AWS: !Sub 'arn:aws:iam::${AccessLogsAccountID}:root'

################ Create AutoScalingGroup ################
  LaunchTemplate:
    Type: 'AWS::EC2::LaunchTemplate'
    Properties:
      LaunchTemplateName: ServerTemplate
      LaunchTemplateData:
        IamInstanceProfile:
          Arn: !GetAtt
            - EC2InstanceProfile
            - Arn
        ImageId: !Ref AMIID
        InstanceType: t3.micro
        BlockDeviceMappings:
          - Ebs:
              VolumeSize: 8
              VolumeType: gp2
              DeleteOnTermination: true
              Encrypted: true
            DeviceName: /dev/xvda
        Monitoring: 
          Enabled: true
        MetadataOptions: 
          InstanceMetadataTags: enabled
        SecurityGroupIds:
          - !Ref InstanceSecurityGroup
        TagSpecifications:
          - ResourceType: instance
            Tags:
            - Key: Name
              Value: Server
        UserData:
          Fn::Base64: |
            #!/bin/bash
            yum update -y
            yum install -y httpd
            echo "<h1>Hello World</h1>" > /var/www/html/index.html
            systemctl start httpd
            
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate
        Version: !GetAtt LaunchTemplate.LatestVersionNumber
      MaxSize: '10'
      MinSize: '1'
      MetricsCollection:
        - Granularity: 1Minute
      TargetGroupARNs:
        - !Ref TargetGroup
  CPUUtilicationTrackingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      PolicyType: TargetTrackingScaling
      TargetTrackingConfiguration:
        PredefinedMetricSpecification:
          PredefinedMetricType: ASGAverageCPUUtilization
        TargetValue: 50


```