CloudFormationでELBの構築

## 概要
[AWS ELBでWebサーバの負荷分散を実現！【触って覚えるAWS入門】](https://qiita.com/zaki-lknr/items/46015aa47fd1e7319e6a)を拝見し、ELBの構築を学びました。
今度はCloudFormationを使い、ELBを構築したので共有します。

## 対象者
- AWSの管理コンソールからELBを構築された経験がある方
- CloudFormationの知見がある方

## 構築されるもの
- VPC
- InternetGateway
- Subnet
- RouteTable
- EC2
- ELB
- SecurityGroup
- TargetGroup

## 構成図
![elb (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502753/92e6c9d2-1fb2-b37d-8028-433b312d5a6b.png)


## セクション説明
|  セクション| 意味  |備考  |
| ---- | ---- | ---- |
|  AWSTemplateFormatVersion  | テンプレートバージョン  | 2010-09-09 であり、現時点で唯一の有効な値 |
|  Description  |  テンプレートを説明するテキスト | ---- |
|  Metadata  | テンプレートバージョン  | ---- |
|  Parameters  |  パラメーターの定義| ---- |
|  Resources  | スタックに含める AWS リソースの宣言 | ---- |
|  Outputs  |  出力値を宣言| 他のスタックでインポートできる|

## 組み込み関数説明
|  組み込み関数| 意味  |備考  |
| ---- | ---- | ---- |
|  Ref  | 指定したパラメータまたはリソースの値| --- |
|  Sub  |  特定した値の入力文字列にある変数の代わりになる| 文字列内に変数を挿入する |
|  GetAtt  |  テンプレートのリソースから属性の値を返す|  --- |

## その他用語説明
### ロードバランサー
アプリケーションへのトラフィックを複数のターゲットに自動的に分散するサービス。

### ターゲット
ELBがトラフィックを転送するEC2インスタンスなどのリソースやエンドポイント。

### ターゲットグループ
登録されているターゲットにリクエストをルーティングするための設定

### ヘルスチェック
登録されたターゲットに定期的にリクエストを送信するステータスのテスト

### リスナー
外部からアクセスするプロトコルやポートの設定

### スティッキーセッション
ELBがサーバにリクエスト振り分ける際、特定のCookieを確認することで、特定のクライアントからのリクエストを特定のサーバに紐付けることが出来る機能

## テンプレートファイル
下記においてあります。ご自由にお使いください
https://github.com/toyoyuto/cloudformation_alb

## テンプレートファイル(解説付き)
``` alb.yml
AWSTemplateFormatVersion: 
  "2010-09-09"
Description:
  ALB construction
  
Metadata:
  # コンソールでパラメータをグループ化およびソートする方法を定義するメタデータキー
  "AWS::CloudFormation::Interface":
    # パラメーターグループとそのグループに含めるパラメーターの定義
    ParameterGroups: 
      # Project名に関するグループ
      - Label: 
          default: "Project Name Prefix"
        Parameters: 
          - PJPrefix
      # ネットワーク設定に関するグループ
      - Label: 
          default: "Network Configuration"
        # 記述された順番に表示される
        Parameters: 
          - KeyName
          
    # パラメーターのラベル
    ParameterLabels: 
      KeyName: 
        default: "Key Name"


# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  PJPrefix:
    Type: String

  KeyName:
    Type: "AWS::EC2::KeyPair::KeyName"
  
Resources: 
# ------------------------------------------------------------#
#  VPC
# ------------------------------------------------------------#
# VPC Create
  VPC: 
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "10.0.0.0/16"
      # VPC に対して DNS 解決がサポートされているか
      EnableDnsSupport: "true"
      # VPC 内に起動されるインスタンスが DNS ホスト名を取得するか
      EnableDnsHostnames: "true"
      # VPC 内に起動されるインスタンスの許可されているテナンシー
      InstanceTenancy: default
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-vpc"

# InternetGateway Create
  InternetGateway: 
    Type: "AWS::EC2::InternetGateway"
    Properties: 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-igw"

# IGW Attach
  InternetGatewayAttachment: 
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC 

# ------------------------------------------------------------#
#  Subnet
# ------------------------------------------------------------#          
# Public1 Subnet Create
  Public1Subnet: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: "10.0.0.0/24"
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public1-subnet"
                    
# Public2 Subnet Create
  Public2Subnet: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: "10.0.1.0/24"
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public2-subnet"
                    
# ------------------------------------------------------------#
#  RouteTable
# ------------------------------------------------------------#          
# Public1 RouteTable Create
  Public1RouteTable: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public1-route"

# Public2 RouteTable Create
  Public2RouteTable: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public2-route"


# ------------------------------------------------------------#
# Routing
# ------------------------------------------------------------# 
# Public1 Route Create
  Public1Route: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref Public1RouteTable 
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway 
# Public2 Route Create
  Public2Route: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref Public2RouteTable 
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway

# ------------------------------------------------------------#
# RouteTable Associate
# ------------------------------------------------------------# 
# Public1RouteTable Associate PublicSubnet
  Public1SubnetRouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref Public1Subnet
      RouteTableId: !Ref Public1RouteTable
# Public2RouteTable Associate PrivateSubnet
  Public2SubnetRouteTableAssociation: 
      Type: "AWS::EC2::SubnetRouteTableAssociation"
      Properties: 
        SubnetId: !Ref Public2Subnet
        RouteTableId: !Ref Public2RouteTable

# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------# 
  # Web1Serverインスタンス
  Web1Server: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: ami-00f045aed21a55240
      KeyName: !Ref KeyName
      InstanceType: t2.micro
      NetworkInterfaces: 
        # IPv4 アドレスを割り当てるか
        - AssociatePublicIpAddress: "true"
          # ------------------------------------------------------
          # アタッチの順序におけるネットワークインターフェイスの位置。
          # ネットワークインターフェイスを指定する場合必須
          # ------------------------------------------------------
          DeviceIndex: "0"
          SubnetId: !Ref Public1Subnet
          GroupSet:
            - !Ref Web1ServerSG

      # インスタンスの作成時に実行するコマンドなどを記述
      UserData: !Base64 |
        #!/bin/bash
        yum update -y
        amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
        yum install -y httpd mariadb-server
        systemctl start httpd
        systemctl enable httpd
        usermod -a -G apache ec2-user
        chown -R ec2-user:apache /var/www
        chmod 2775 /var/www
        find /var/www -type d -exec chmod 2775 {} \;
        find /var/www -type f -exec chmod 0664 {} \;
        echo `hostname` > /var/www/html/index.html

      Tags:
          - Key: Name
            Value: !Sub "${PJPrefix}-web1-server"

  # Web1Serverセキュリティグループ
  Web1ServerSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: web1-sg-cf
      GroupDescription: web1 server sg
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-web1-server-sg"
          
  # Web1ServerSGのインプットルール
  Web1ServerSGIngress: 
    Type: "AWS::EC2::SecurityGroupIngress"
    Properties: 
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      SourceSecurityGroupId: !GetAtt [ ALBSecurityGroup, GroupId ] 
      GroupId: !GetAtt [ Web1ServerSG, GroupId ]

  # Web2Serverインスタンス
  Web2Server: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: ami-00f045aed21a55240
      KeyName: !Ref KeyName
      InstanceType: t2.micro
      NetworkInterfaces: 
        # IPv4 アドレスを割り当てるか
        - AssociatePublicIpAddress: "true"
          # ------------------------------------------------------
          # アタッチの順序におけるネットワークインターフェイスの位置。
          # ネットワークインターフェイスを指定する場合必須
          # ------------------------------------------------------
          DeviceIndex: "0"
          SubnetId: !Ref Public2Subnet
          GroupSet:
            - !Ref Web2ServerSG

      # インスタンスの作成時に実行するコマンドなどを記述
      UserData: !Base64 |
        #!/bin/bash
        yum update -y
        amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
        yum install -y httpd mariadb-server
        systemctl start httpd
        systemctl enable httpd
        usermod -a -G apache ec2-user
        chown -R ec2-user:apache /var/www
        chmod 2775 /var/www
        find /var/www -type d -exec chmod 2775 {} \;
        find /var/www -type f -exec chmod 0664 {} \;
        echo `hostname` > /var/www/html/index.html

      Tags:
          - Key: Name
            Value: !Sub "${PJPrefix}-web2-server"

  # Web2Serverセキュリティグループ
  Web2ServerSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: web2-sg-cf
      GroupDescription: web2 server sg
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-web2-server-sg"

  # Web2ServerSGのインプットルール
  Web2ServerSGIngress: 
    Type: "AWS::EC2::SecurityGroupIngress"
    Properties: 
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      SourceSecurityGroupId: !GetAtt [ ALBSecurityGroup, GroupId ] 
      GroupId: !GetAtt [ Web2ServerSG, GroupId ]

# ------------------------------------------------------------#
#  Target Group
# ------------------------------------------------------------#
  TargetGroup: 
    Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
    Properties: 
      VpcId: !Ref VPC
      Name: !Sub "${PJPrefix}-tg"
      # ターゲットにトラフィックをルーティングするために使用するプロトコル
      Protocol: HTTP
      Port: 80
      # ターゲットでヘルスチェックを実行するときにロードバランサーが使用するプロトコル
      HealthCheckProtocol: HTTP
      # ヘルスチェックのターゲットの送信先である
      HealthCheckPath: "/"
      # ターゲットでヘルスチェックを実行するときにロードバランサーが使用するポート
      HealthCheckPort: "traffic-port"
      # 非正常なインスタンスが正常であると見なすまでに必要なヘルスチェックの連続成功回数
      HealthyThresholdCount: 2
      # ターゲットが異常であると見なされるまでに必要なヘルスチェックの連続失敗回数
      UnhealthyThresholdCount: 2
      # ヘルスチェックを失敗と見なす、ターゲットからレスポンスがない時間
      HealthCheckTimeoutSeconds: 5
      # 個々のターゲットのヘルスチェックの概算間隔 
      HealthCheckIntervalSeconds: 10
      # ターゲットからの正常なレスポンスを確認するために使用する HTTP コード
      Matcher: 
        HttpCode: 200
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-tg"
      # ターゲットグループの属性
      TargetGroupAttributes:
        # 登録解除するターゲットの状態が draining から unused に変わるのをELBが待機する時間
        - Key: "deregistration_delay.timeout_seconds"
          Value: 300
        # スティッキーセッションが有効か¥
        - Key: "stickiness.enabled"
          Value: false
        # スティッキーセッションのタイプ
        - Key: "stickiness.type"
          Value: lb_cookie
        # クライアントからのリクエストを同じターゲットにルーティングする必要がある期間
        - Key: "stickiness.lb_cookie.duration_seconds"
          Value: 86400
      Targets: 
        - Id: !Ref Web1Server
          Port: 80
        - Id: !Ref Web2Server
          Port: 80

# ------------------------------------------------------------#
#  ALB
# ------------------------------------------------------------#
  InternetALB: 
    Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
    Properties: 
      Name: !Sub "${PJPrefix}-alb"
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-alb"
      # 内部向けかインターネット向け
      Scheme: "internet-facing"
      # ロードバランサーの属性
      LoadBalancerAttributes:
        # 削除保護が有効化されているかどうかを示します
        - Key: "deletion_protection.enabled"
          Value: false
          # アイドルタイムアウト値
        - Key: "idle_timeout.timeout_seconds"
          Value: 60
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Subnets: 
        - !Ref Public1Subnet
        - !Ref Public2Subnet

  ALBListener: 
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties: 
      # デフォルトルールのアクション
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup
          # ルールアクションタイプ
          # forwardは指定されたターゲットグループにリクエストを転送
          Type: forward
      LoadBalancerArn: !Ref InternetALB
      Port: 80
      Protocol: HTTP

  # InternetALBのセキュリティグループ
  ALBSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      VpcId: !Ref VPC
      GroupName: !Sub "${PJPrefix}-alb-sg"
      GroupDescription: "-"
      Tags:
        - Key: "Name"
          Value: !Sub "${PJPrefix}-alb-sg"

  # ALBSecurityGroupのインプットルール
  ALBSecurityGroupIngress: 
    Type: "AWS::EC2::SecurityGroupIngress"
    Properties: 
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      CidrIp: "0.0.0.0/0"
      GroupId: !GetAtt [ ALBSecurityGroup, GroupId ]
    
  # ALBSecurityGroupのアウトプットルール
  ALBSecurityGroupEgress1: 
    Type: "AWS::EC2::SecurityGroupEgress"
    Properties: 
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      SourceSecurityGroupId: !GetAtt [ Web1ServerSG, GroupId ] 
      GroupId: !GetAtt [ ALBSecurityGroup, GroupId ]
  ALBSecurityGroupEgress2: 
    Type: "AWS::EC2::SecurityGroupEgress"
    Properties: 
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      SourceSecurityGroupId: !GetAtt [ Web2ServerSG, GroupId ] 
      GroupId: !GetAtt [ ALBSecurityGroup, GroupId ]

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#                
Outputs:
# VPC
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub "${PJPrefix}-vpc"

# Subnet
  Public1Subnet:
    Value: !Ref Public1Subnet
    Export:
      Name: !Sub "${PJPrefix}-public1-subnet"

  Public2Subnet:
    Value: !Ref Public2Subnet
    Export:
      Name: !Sub "${PJPrefix}-public2-subnet"
    
# EC2
  Web1Server:
    Value: !Ref Web1Server
    Export:
      Name: !Sub "${PJPrefix}-web1-server"
  Web2Server:
    Value: !Ref Web2Server
    Export:
      Name: !Sub "${PJPrefix}-web2-server"
# ALB
  InternetALB:
    Value: !Ref InternetALB
    Export:
      Name: !Sub "${PJPrefix}-alb"
```

## 参考
[AWS ELBでWebサーバの負荷分散を実現！【触って覚えるAWS入門】](https://qiita.com/zaki-lknr/items/46015aa47fd1e7319e6a)
[AWS再入門ブログリレー Elastic Load Balancing編](https://dev.classmethod.jp/articles/re-introduction-2020-elb/)
[AWS ALB の設定方法は ? リスナー ? ターゲットグループ ?](https://www.kanzennirikaisita.com/posts/aws-alb-concepts)
[【基礎から学ぶ】ELBのスティッキーセッションについてまとめてみた](https://blog.serverworks.co.jp/tech/2017/01/05/elb-sticky/#ELB%E3%81%AE%E3%82%B9%E3%83%86%E3%82%A3%E3%83%83%E3%82%AD%E3%83%BC%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)