🤖

CFnでCloudFrontDistribution+ALB+EC2その他を作成。

2022/10/22に公開

今回はテンプレートが長いので

手順について詳しく書くと読む側もキツイはずなので概要だけ纏める形にしました。

【今回の構成その他】
・VPC内で2AZそれぞれにパブリックサブネット、プライベートサブネットを作成。
・両パブリックサブネットにALB紐付け、同両パプリックサブネットにEIPをアタッチしたNATゲートウェイ配置。
・EC2は両プライベートサブネットに配置。
・VPC外にはCloudFrontDistributionを作成。

【その他】
・カスタムヘッダーを設定し、CloudFrontのDomainからのアクセス時のみ表示に限定【参考にした記事】
・ALBのEgressとEC2のIngressを相互参照するSecurityGroupの書き方に挑戦。【参考にした記事】
・リソースのNameTagのValueには「!Sub ${AWS::StackName}-」を利用。
・EC2にはセッションマネージャーでアクセス出来るようSSMmanagedInstanceCoreのRoleと4つのVPCE(更新用のS3GatewayTypeも含む)、443のSecurityGroupを作成。
・今回はACMなど無しなのでポートは全て80。
・出力(Outputs)にアクセス可否確認用のCloudFrontとALBのDNSを追加。

Yamlテンプレート内容

※長いので開閉式にしました。

sample.yml
AWSTemplateFormatVersion: 2010-09-09
Description: for practice
# ============== パラメータ ==============
# メタデータ
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - 
        Label:
          default: Paramerters
        Parameters:
          - MyCustomHeaderString

# パラメータ
Parameters: 
  MyCustomHeaderString:
    Type: String
    Description: Enter any min4/max16 length characters for custom header value. It must begin with a letter and contain only alphanumeric.
    MinLength: 4
    MaxLength: 16
    AllowedPattern: "[a-zA-Z][a-zA-Z0-9]*"
# ============== リソース ==============
Resources:
# -------------- IAM --------------
# ロール
  MySSMMICRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      RoleName: !Sub ${AWS::StackName}-MySSMMICRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

# インスタンスプロファイル
  MyInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref MySSMMICRole
# -------------- クラウドフロント --------------
  MyCloudFront:
    Type: AWS::CloudFront::Distribution
    Properties: 
      DistributionConfig:
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
          TargetOriginId: MyALB
          ViewerProtocolPolicy: allow-all
        Enabled: True
        HttpVersion: http1.1
        Origins: 
          - CustomOriginConfig:
              HTTPPort: 80
              OriginProtocolPolicy: http-only
            DomainName: !GetAtt MyALB.DNSName
            Id: MyALB
            OriginCustomHeaders: 
            - HeaderName: Custom-Header
              HeaderValue: !Ref MyCustomHeaderString
        PriceClass: PriceClass_200
# -------------- VPC --------------
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyVPC
#-------------- インターネットゲートウェイ --------------
#  IGW
  MyIGW:
    Type: AWS::EC2::InternetGateway
    Properties: 
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyIGW
#  アタッチメント
  MyIGWAttach:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties: 
      InternetGatewayId: !Ref MyIGW
      VpcId: !Ref MyVPC
# -------------- VPCエンドポイント --------------
  MyVPCESSM:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref MyVPCESG
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
      SubnetIds:
      - !Ref MyPriSN1
      - !Ref MyPriSN2
      VpcEndpointType: Interface
      VpcId: !Ref MyVPC

  MyVPCESSMMessages:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref MyVPCESG
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
      SubnetIds:
      - !Ref MyPriSN1
      - !Ref MyPriSN2
      VpcEndpointType: Interface
      VpcId: !Ref MyVPC 

  MyVPCEEC2Messages:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref MyVPCESG
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
      SubnetIds:
      - !Ref MyPriSN1
      - !Ref MyPriSN2
      VpcEndpointType: Interface
      VpcId: !Ref MyVPC

  MyVPCES3:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds:
        - !Ref MyPriRT1
        - !Ref MyPriRT2
      ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
      VpcEndpointType: Gateway
      VpcId: !Ref MyVPC
# -------------- EIP --------------
  MyNATGW1EIP: 
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc 

  MyNATGW2EIP: 
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc 
# -------------- サブネット --------------
# パブリック
  MyPubSN1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.0.0/24
      VpcId: !Ref MyVPC
      AvailabilityZone: !Select [ 0, !GetAZs ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyPubSN1

  MyPubSN2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.2.0/24
      VpcId: !Ref MyVPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyPubSN2

# プライベート
  MyPriSN1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.1.0/24
      VpcId: !Ref MyVPC
      AvailabilityZone: !Select [ 0, !GetAZs ]
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyPriSN1

  MyPriSN2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.3.0/24
      VpcId: !Ref MyVPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyPriSN2
# -------------- ルートテーブル --------------
#  ルートテーブル
  MyPubRT:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyPubRT

  MyPriRT1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyPriRT1

  MyPriRT2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyPriRT2
#  ルート
  MyPubRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref MyPubRT
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyIGW

  MyPriRoute1: 
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref MyPriRT1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref MyNATGW1

  MyPriRoute2: 
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref MyPriRT2 
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref MyNATGW2
#  アソシエーション
  MyPubSN1Assoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref MyPubSN1
      RouteTableId: !Ref MyPubRT

  MyPubSN2Assoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref MyPubSN2
      RouteTableId: !Ref MyPubRT

  MyPrivateSubne1Assoc: 
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      SubnetId: !Ref MyPriSN1
      RouteTableId: !Ref MyPriRT1

  MyPriSN2Assoc: 
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      SubnetId: !Ref MyPriSN2
      RouteTableId: !Ref MyPriRT2
# -------------- NATゲートウェイ --------------
  MyNATGW1: 
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt MyNATGW1EIP.AllocationId 
      SubnetId: !Ref MyPubSN1
      Tags: 
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyNATGW1

  MyNATGW2: 
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt MyNATGW2EIP.AllocationId 
      SubnetId: !Ref MyPubSN2
      Tags: 
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyNATGW2
# -------------- ALB --------------
# ALB
  MyALB: 
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties: 
      Name: !Sub ${AWS::StackName}-MyALB
      Scheme: internet-facing
      Type: application
      LoadBalancerAttributes: 
        - Key: deletion_protection.enabled
          Value: false
        - Key: idle_timeout.timeout_seconds
          Value: 4000
      SecurityGroups:
        - !Ref MyALBSG
      Subnets: 
        - !Ref MyPubSN1
        - !Ref MyPubSN2
# リスナー
  MyALBListener: 
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties: 
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - FixedResponseConfig:
            ContentType: text/plain
            MessageBody: Access denied
            StatusCode: 403
          Type: fixed-response
      LoadBalancerArn: !Ref MyALB
# リスナールール
  MyALBListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties: 
      Actions: 
        - TargetGroupArn: !Ref MyTG
          Type: forward
      Conditions: 
        - Field: http-header
          HttpHeaderConfig:
            HttpHeaderName: Custom-Header
            Values: 
              - !Ref MyCustomHeaderString
      ListenerArn: !Ref MyALBListener
      Priority: 1
# ターゲットグループ
  MyTG: 
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties: 
      VpcId: !Ref MyVPC
      Name: !Sub ${AWS::StackName}-MyTG
      Protocol: HTTP
      ProtocolVersion: HTTP1
      Port: 80
      HealthCheckEnabled: true
      HealthCheckProtocol: HTTP
      HealthCheckPath: /
      HealthCheckPort: 80
      HealthyThresholdCount: 5
      UnhealthyThresholdCount: 2
      HealthCheckTimeoutSeconds: 5
      HealthCheckIntervalSeconds: 30
      IpAddressType: ipv4
      Matcher: 
        HttpCode: 200
      TargetType: instance
      Targets: 
        - Id: !Ref MyEC2no1
          Port: 80
        - Id: !Ref MyEC2no2
          Port: 80
      Tags: 
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyTG
# -------------- EC2インスタンス --------------
  MyEC2no1: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: ami-078296f82eb463377
      InstanceType: t2.micro
      IamInstanceProfile: !Ref MyInstanceProfile
      NetworkInterfaces: 
        - AssociatePublicIpAddress: false
          DeviceIndex: 0
          SubnetId: !Ref MyPriSN1
          GroupSet:
            - !Ref MyEC2SG
      UserData: !Base64 |
        #!/bin/bash
        sudo yum -y update
        sudo yum -y upgrade 
        sudo yum -y install httpd
        sudo systemctl start httpd
        sudo systemctl enable httpd
        sudo echo "hello! This is MyEC2no1!" > /var/www/html/index.html 
      Tags:
          - Key: Name
            Value: !Sub ${AWS::StackName}-MyEC2no1

  MyEC2no2: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: ami-078296f82eb463377
      InstanceType: t2.micro
      IamInstanceProfile: !Ref MyInstanceProfile
      NetworkInterfaces: 
        - AssociatePublicIpAddress: false
          DeviceIndex: 0
          SubnetId: !Ref MyPriSN2
          GroupSet:
            - !Ref MyEC2SG
      UserData: !Base64 |
        #!/bin/bash
        sudo yum -y update
        sudo yum -y upgrade 
        sudo yum -y install httpd
        sudo systemctl start httpd
        sudo systemctl enable httpd
        sudo echo "hello! This is MyEC2no2!" > /var/www/html/index.html 
      Tags:
          - Key: Name
            Value: !Sub ${AWS::StackName}-MyEC2no2
# -------------- セキュリティグループ --------------
  MyVPCESG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref MyVPC
      GroupDescription: for VPCE for Session Manager.
      SecurityGroupIngress:
        - SourceSecurityGroupId: !Ref MyEC2SG
          IpProtocol: tcp
          FromPort: 443
          ToPort: 443  
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyVPCESG

  MyALBSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref MyVPC
      GroupName: security group for ALB
      GroupDescription: Allow HTTP access from Internet Only for your Global IP.
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyALBSG
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
          Description: Allow inbound HTTP access from all IPv4 addresses.

  MyALBEgress:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      Description: Allow all protocol for MyEC2SG.
      DestinationSecurityGroupId: !GetAtt MyEC2SG.GroupId
      GroupId: !GetAtt MyALBSG.GroupId
      IpProtocol: -1

  MyEC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref MyVPC
      GroupName: security group for EC2
      GroupDescription: Allow HTTP access from MyALB, and Allow all protocol for all IPv4 addresses.
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-MyEC2SG

  MyEC2Ingress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Allow HTTP access from MyALB
      FromPort: 80
      ToPort: 80
      IpProtocol: tcp
      SourceSecurityGroupId: !GetAtt MyALBSG.GroupId
      GroupId: !GetAtt MyEC2SG.GroupId
# ============== アウトプット ==============
Outputs:
  MyCloudFrontDomain:
    Value: !GetAtt MyCloudFront.DomainName
    Export: 
      Name: !Sub ${AWS::StackName}-MyCloudFrontDomain

  MyALBDomain:
    Value: !GetAtt MyALB.DNSName
    Export: 
      Name: !Sub ${AWS::StackName}-MyALBDNSName

出力タブにALBとDistributionのDNSがあります

両方クリックして、ALB× CloudFront○ である事を確認してみてください。

最後に

今日はあらためて自分以外の方が作られたテンプレートを読むのはとても勉強になるなぁと思いました。
この項目はなんの為に設定してるんだろうとか、どういう意図でこうしたんだろうと、作成された方の思惑を想像するのが好きです。

CloudFrontのPriceClassやCachePolicyなど思いがけず付随する知識に触れられたり、カスタムヘッダーにも馴染みがなかったのですが、可能ならパラメーターストアとかにStringで納めて呼び出したり出来ないかなとか、色々考えるのが面白かったです。

次回はCloudFrontにACM、Route53を紐づけたいと思っています。
クロスリージョンや循環参照など諸々問題がありそうですが楽しみです。

有難う御座いました!

Discussion