🐭

CloudFormation入門③ ALB

2024/03/17に公開

はじめに

今回はALBを使用して、HTTP/HTTPSトラフィックをプライベートなEC2に接続する構成です。

目標

以下のような構成を作成します。

最終的なコード

先に最終的なコードをお見せします。

AWSTemplateFormatVersion: "2010-09-09"
Description:
    Create ALB and EC2!
Metadata:
    "AWS::CloudFormation::Interface":
        ParameterGroups:
            - Label:
                default: "Product Name"
              Parameters:
                - ProductPrefix
            - Label:
                default: "ALB Name"
              Parameters:
                - ALBName
            - Label:
                default: "EC2Instance"
              Parameters:
                - KeyPairName
                - EC2InstanceName
                - EC2InstanceAMI
                - EC2InstanceInstanceType
                - EC2InstanceVolumeType
                - EC2InstanceVolumeSize
                - SSHAccessSourceIP
      
        ParameterLabels:
            ALBName:
                default: "ALBName"
            KeyPairName:
                default: "KeyPairName"
            EC2InstanceName:
                default: "EC2 Name"
            EC2InstanceAMI:
                default: "EC2 AMI"
            EC2InstanceInstanceType:
                default: "EC2 InstanceType"
            EC2InstanceVolumeType:
                default: "EC2 VolumeType"
            EC2InstanceVolumeSize:
                default: "EC2 VolumeSize"
            SSHAccessSourceIP:
                default: "SSH AccessSourceIP"

################################################################
# Parameters
################################################################
Parameters:
    ProductPrefix:
      Type: String
  
  #ALB
    ALBName:
      Type: String
      Default: "web"
  
  #EC2
    KeyPairName:
      Type: AWS::EC2::KeyPair::KeyName
      Default: ""
    EC2InstanceName:
      Type: String
      Default: "web"
    EC2InstanceAMI:
      Type: String
      Default: ""
    EC2InstanceInstanceType:
      Type: String
      Default: "t2.micro"
    EC2InstanceVolumeType:
      Type: String
      Default: "gp2"
    EC2InstanceVolumeSize:
      Type: String
      Default: "30"
    SSHAccessSourceIP:
      Type: String

################################################################
# Resources
################################################################

Resources:
    # VPCの作成
    TestVPC: 
        Type: AWS::EC2::VPC
        Properties:
            CidrBlock: 10.0.0.0/16
            Tags:
                - Key: Name
                  Value: TestVPC

    # インターネットゲートウェイ
    TestInternetGateway:
        Type: AWS::EC2::InternetGateway
        Properties:
            Tags:
                - Key: Name
                  Value: TestInternetGateway

    # インターネットゲートウェイをVPCにアタッチ
    AttachInternetGateway:
        Type: AWS::EC2::VPCGatewayAttachment
        Properties:
            VpcId: !Ref TestVPC
            InternetGatewayId: !Ref TestInternetGateway

    #パブリックサブネットの作成
    TestPublicSubnet1:
        Type: AWS::EC2::Subnet
        Properties:
            AvailabilityZone:
                Fn::Select:
                    - 0
                    - Fn::GetAZs: ""
            VpcId: !Ref TestVPC
            CidrBlock: 10.0.10.0/24
            Tags:
                - Key: Name
                  Value: TestPublicSubnet1
    TestPublicSubnet2:
        Type: AWS::EC2::Subnet
        Properties:
            AvailabilityZone:
                Fn::Select:
                    - 1
                    - Fn::GetAZs: ""
            VpcId: !Ref TestVPC
            CidrBlock: 10.0.20.0/24
            Tags:
                - Key: Name
                  Value: TestPublicSubnet2
    TestPrivateSubnet1:
        Type: AWS::EC2::Subnet
        Properties:
            AvailabilityZone:
                Fn::Select:
                    - 0
                    - Fn::GetAZs: ""
            VpcId: !Ref TestVPC
            CidrBlock: 10.0.30.0/24
            Tags:
                - Key: Name
                  Value: TestPrivateSubnet1
    TestPrivateSubnet2:
        Type: AWS::EC2::Subnet
        Properties:
            AvailabilityZone:
                Fn::Select:
                    - 1
                    - Fn::GetAZs: ""
            VpcId: !Ref TestVPC
            CidrBlock: 10.0.40.0/24
            Tags:
                - Key: Name
                  Value: TestPrivateSubnet2

    # ルートテーブルの作成
    TestRouteTableForPublicSubnet:
        Type: AWS::EC2::RouteTable
        Properties:
            VpcId: !Ref TestVPC
            Tags: 
                - Key: Name
                  Value: TestRouteTableForPublicSubnet

    # パブリックサブネットのルーティング
    RoutePublicSubnetToInternetGateway:
        Type: AWS::EC2::Route
        Properties:
            RouteTableId: !Ref TestRouteTableForPublicSubnet
            GatewayId: !Ref TestInternetGateway
            DestinationCidrBlock: 0.0.0.0/0

    # ルートテーブルをサブネットに紐付け
    AssociatePublicSubnetToRouteTable:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
            SubnetId: !Ref TestPublicSubnet1
            RouteTableId: !Ref TestRouteTableForPublicSubnet

    # IAMロール
    TestEC2IAMRole:
        Type: AWS::IAM::Role
        Properties:
            RoleName: !Sub "${ProductPrefix}-${EC2InstanceName}-role"
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    Effect: "Allow"
                    Principal:
                        Service:
                            - "ec2.amazonaws.com"
                    Action:
                        - "sts:AssumeRole"
            Path: "/"
            ManagedPolicyArns:
                - "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"

    # EC2インスタンスプロファイル
    TestEC2InstanceProfile:
        Type: "AWS::IAM::InstanceProfile"
        Properties:
            Path: "/"
            Roles:
                - Ref: TestEC2IAMRole
            InstanceProfileName: !Sub "${ProductPrefix}-${EC2InstanceName}-profile"

    # EC2インスタンス-1:
    TestEC2Instance1:
        Type: "AWS::EC2::Instance"
        Properties:
            Tags:
                - Key: Name
                  Value: !Sub "${ProductPrefix}-${EC2InstanceName}-1"
            ImageId: !Ref EC2InstanceAMI
            InstanceType: !Ref EC2InstanceInstanceType
            KeyName: !Ref KeyPairName
            IamInstanceProfile: !Ref TestEC2InstanceProfile
            DisableApiTermination: false # 削除保護
            BlockDeviceMappings:
                - DeviceName: /dev/xvda
                  Ebs:
                    DeleteOnTermination: true
                    VolumeType: !Ref EC2InstanceVolumeType
                    VolumeSize: !Ref EC2InstanceVolumeSize
            SecurityGroupIds:
                - !Ref TestWebSecurityGroup
            SubnetId: !Ref TestPrivateSubnet1
            UserData: !Base64 |
                #! /bin/bash
                yum update -y

    # EC2セキュリティグループ
    TestWebSecurityGroup:
        Type: AWS::EC2::SecurityGroup
        Properties:
            GroupName: test-ec2-sg 
            GroupDescription: EC2SecurityGroup
            VpcId: !Ref TestVPC
            SecurityGroupIngress:
                # HTTP
                - IpProtocol: tcp
                  FromPort: 80
                  ToPort: 80
                  CidrIp: 111.111.111.111/32
            Tags:
                - Key: Name
                  Value: !Sub "${ProductPrefix}-web-sg"

    # ALB セキュリティグループ
    TestALBSecurityGroup:
        Type: AWS::EC2::SecurityGroup
        Properties:
            GroupName: test-alb-sg
            GroupDescription: TestALBSecurityGroup
            VpcId: !Ref TestVPC
            SecurityGroupIngress:
                - IpProtocol: tcp
                  FromPort: 80
                  ToPort: 80
                  CidrIp: 0.0.0.0/0
                - IpProtocol: tcp
                  FromPort: 443
                  ToPort: 443
                  CidrIp: 0.0.0.0/0
            Tags:
                - Key: Name
                  Value: !Sub "${ProductPrefix}-alb-sg"
    # ALB
    TestALB:
        Type: AWS::ElasticLoadBalancingV2::LoadBalancer
        Properties:
            Type: "application"
            Scheme: "internet-facing"
            Name: !Sub "${ProductPrefix}-alb"
            Tags:
                - Key: Name
                  Value: !Sub "${ProductPrefix}-alb"
            IpAddressType: ipv4
            Subnets:
                - !Ref TestPublicSubnet1
                - !Ref TestPublicSubnet2
            SecurityGroups:
                - !Ref TestALBSecurityGroup

    # ターゲットグループ
    TargetGroup:
        Type: AWS::ElasticLoadBalancingV2::TargetGroup
        Properties:
            Name: "tg"
            Tags:
                - Key: Name
                  Value: !Sub "${TestALB}-tg"
            Port: 80
            Protocol: HTTP
            VpcId: !Ref TestVPC
            TargetType: instance
            Targets:
                - Id: !Ref TestEC2Instance1

    # リスナールール
    ListenerRule:
        Type: AWS::ElasticLoadBalancingV2::Listener
        Properties:
            DefaultActions:
                - Type: forward
                  TargetGroupArn: !Ref TargetGroup
            LoadBalancerArn: !Ref TestALB
            Port: 80
            Protocol: HTTP

解説

ここからいくつかのパートに分けて解説していきます。(過去の記事で解説した内容に関しては省略します)

Metadata

Metadata:
    "AWS::CloudFormation::Interface":
        ParameterGroups:
            - Label:
                default: "Product Name"
              Parameters:
                - ProductPrefix
            - Label:
                default: "ALB Name"
              Parameters:
                - ALBName
            - Label:
                default: "EC2Instance"
              Parameters:
                - KeyPairName
                - EC2InstanceName
                - EC2InstanceAMI
                - EC2InstanceInstanceType
                - EC2InstanceVolumeType
                - EC2InstanceVolumeSize
                - SSHAccessSourceIP
      
        ParameterLabels:
            ALBName:
                default: "ALBName"
            KeyPairName:
                default: "KeyPairName"
            EC2InstanceName:
                default: "EC2 Name"
            EC2InstanceAMI:
                default: "EC2 AMI"
            EC2InstanceInstanceType:
                default: "EC2 InstanceType"
            EC2InstanceVolumeType:
                default: "EC2 VolumeType"
            EC2InstanceVolumeSize:
                default: "EC2 VolumeSize"
            SSHAccessSourceIP:
                default: "SSH AccessSourceIP"

MetadataセクションキーのAWS::CloudFormation::Interfaceを使用することで、入力パラメータのグループ化と順序を指定できます。

このセクションキーを使用しない場合だと、論理IDのアルファベット順にソートされることになり、各パラメータの意味合い的なまとまりがなくなってしまいます。

ALB

    # ALB
    TestALB:
        Type: AWS::ElasticLoadBalancingV2::LoadBalancer
        Properties:
            Type: "application"
            Scheme: "internet-facing"
            Name: !Sub "${ProductPrefix}-alb"
            Tags:
                - Key: Name
                  Value: !Sub "${ProductPrefix}-alb"
            IpAddressType: ipv4
            Subnets:
                - !Ref TestPublicSubnet1
                - !Ref TestPublicSubnet2
            SecurityGroups:
                - !Ref TestALBSecurityGroup

ALBはType:AWS::ElasticLoadBalancingV2::LoadBalancerを使用することでリソースが作成されます。
Properties内のTypeで指定するのはロードバランサーのタイプでapplication,network,gatewayのいずれかを指定します。今回はHTTPおよびHTTPSトラフィック用に使う想定でapplicationを指定します。
Schemeではロードバランサーを内部ロードバランサーにするか、インターネット向けロードバランサーにするかを選択します。今回はリクエストをインターネット経由でターゲットにルーティングしたいのでinternet-facingを指定します。内部ロードバランサーの場合はinternalを指定します。
SubnetsではALBを配置するサブネットのIDを指定します。Application LoadBalancerは少なくとも2つのAZからサブネットを指定する必要があります。

これだけではALBに到達したトラフィックをEC2に向けることができないため、ターゲットグループとリスナールールの設定をします。

ALB ターゲットグループ

    # ターゲットグループ
    TargetGroup:
        Type: AWS::ElasticLoadBalancingV2::TargetGroup
        Properties:
            Name: "test-tg"
            Tags:
                - Key: Name
                  Value: !Sub "${TestALB}-tg"
            Port: 80
            Protocol: HTTP
            VpcId: !Ref TestVPC
            TargetType: instance
            Targets:
                - Id: !Ref TestEC2Instance1

TargetGroupのTypeはAWS::ElasticLoadBalancingV2::TargetGroupで指定します。
TargetType: instanceはこのターゲットグループにターゲットを登録するときに指定する必要があるターゲットのタイプになります。instance,ip,Lambda,ALBのいずれかを指定します。

ALB リスナールール

    # リスナールール
    ListenerRule:
        Type: AWS::ElasticLoadBalancingV2::Listener
        Properties:
            DefaultActions:
                - Type: forward
                  TargetGroupArn: !Ref TargetGroup
            LoadBalancerArn: !Ref TestALB
            Port: 80
            Protocol: HTTP

DefaultActionsでデフォルトアクションのタイプとターゲットグループを指定します。
LoadBalancerArnでリスナールールを付与するALBのARNを指定します。

おわりに

こちらのコードでスタックを作成した結果がこちらです。

VPC/Subnet/Route Table

ALB

ALBリスナールール


想定通りできていそうです!

Discussion