CloudFormationでAutoScaling+ALB+EC2の構築


## 概要
CloudFormationを用いたAutoScaling+ALB+EC2のWEBサーバーの構築。



AutoScalingの使い方については、下記記事がとても参考になりました😊
[[入門] AWSのネットワークとAutoScalingなWebサーバーを作るハンズオンを作りました](https://dev.classmethod.jp/articles/introduction-to-aws-networking-and-autoscaling-web-server/)

## 作業の細分化
1. ネットワークの構築(VPCとサブネットなど)
2. 起動テンプレート作成
3. AutoScalingとALB構築

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

## 構築されるもの
- VPC
- InternetGateway
- Subnet
- RouteTable
- NatGateway
- Elastic IP
- 起動テンプレート
- EC2
- ALB
- SecurityGroup
- TargetGroup
- Auto Scaling Group

## 構成図
![auto (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502753/67dd56bc-08d4-ee67-83c0-0b7ea2b4a1c4.png)

## 備考
サーバーへのパッケージインストールようにNatを構築。
ALBを経由でインストールできる方法などは検証中です。:sweat_smile:

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

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

## その他用語説明
### AutoScaling
アプリケーションを監視し、安定した予測可能なパフォーマンスを可能な限り低コストで維持するように、自動的に容量を調整するサービス

### Auto Scaling グループ
Auto Scalingの管理単位です。
EC2インスタンスの最少数、最大数、希望する数を指定し、インスタンス数が増減または維持される。


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

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

# ------------------------------------------------------------#
# 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"

# Private1 Subnet Create
  Private1Subnet: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: "10.0.10.0/24"
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-private1-subnet"
                    
# Private2 Subnet Create
  Private2Subnet: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: "10.0.11.0/24"
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-private2-subnet"

# ------------------------------------------------------------#
#  NAT Gateway
# ------------------------------------------------------------#
  NATGateway: 
    Type: "AWS::EC2::NatGateway"
    Properties:
      # NAT ゲートウェイに関連付ける Elastic IPアドレスの割り当て ID
      AllocationId: !GetAtt NATGatewayAEIP.AllocationId 
      SubnetId: !Ref Public1Subnet
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-natgw"
  # NATGateway For Elastic IP Create
  NATGatewayAEIP: 
    Type: "AWS::EC2::EIP"
    Properties:
      # ------------------------------------------------------
      # Elastic IP アドレスが VPC のインスタンスで使用するか、
      # EC2-Classic のインスタンスで使用するか
      # ------------------------------------------------------
      Domain: vpc  

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

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

# ------------------------------------------------------------#
# Routing
# ------------------------------------------------------------# 
# Public Route Create
  PublicRoute: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref PublicRouteTable 
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway 

  PrivateRoute: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref PrivateRouteTable 
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref NATGateway 

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

  Private1SubnetRouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref Private1Subnet
      RouteTableId: !Ref PrivateRouteTable
  Private2SubnetRouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref Private2Subnet
      RouteTableId: !Ref PrivateRouteTable

# ------------------------------------------------------------#
# SecurityGroup
# ------------------------------------------------------------# 
  WebServerSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: web-sg-cf
      GroupDescription: web server sg
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-web-server-sg"
# ------------------------------------------------------------#
# LaunchTemplate
# ------------------------------------------------------------# 
  EC2InstanceLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      # 起動テンプレートの名前
      LaunchTemplateName: !Sub "${PJPrefix}-web-server-template"
      # 起動テンプレートの情報
      LaunchTemplateData:
        # リソースの作成時にリソースに適用するタグを指定
        TagSpecifications:
        # ------------------------------------------------------
        # タグ付けするリソースのタイプ。
        # 現在、作成中のタグ付けをサポートするリソースタイプは、instance および volume 
        # ------------------------------------------------------
        - ResourceType: instance
          Tags:
          - Key: Name
            Value: !Sub "${PJPrefix}-web-server"
        UserData:
          Fn::Base64: |
              #!/bin/bash
              sudo yum -y update
              sudo yum -y install httpd
              sudo systemctl start httpd.service
              sudo systemctl enable httpd.service
        KeyName: !Ref KeyName
        ImageId: ami-01748a72bed07727c
        InstanceType: t2.micro
        NetworkInterfaces: 
        # IPv4 アドレスを割り当てるか
        - AssociatePublicIpAddress: "false"
          # ------------------------------------------------------
          # アタッチの順序におけるネットワークインターフェイスの位置。
          # ネットワークインターフェイスを指定する場合必須
          # ------------------------------------------------------
          DeviceIndex: "0"
          Groups:
            - !Ref WebServerSG
# ------------------------------------------------------------#
#  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"

# ------------------------------------------------------------#
#  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のアウトプットルール
  ALBSecurityGroupEgress: 
    Type: "AWS::EC2::SecurityGroupEgress"
    Properties: 
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      SourceSecurityGroupId: !GetAtt [ WebServerSG, GroupId ] 
      GroupId: !GetAtt [ ALBSecurityGroup, GroupId ] 

# ------------------------------------------------------------#
# AutoScaling Group
# ------------------------------------------------------------#    
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: !Sub "${PJPrefix}-autoscalinggroup"
      # Virtual Private Cloud (VPC) のサブネット ID のリスト
      # 起動させるサブネットを指定
      VPCZoneIdentifier:
        - !Ref Private1Subnet
        - !Ref Private2Subnet
      # 起動テンプレート
      LaunchTemplate:
        # 起動テンプレートのID
        LaunchTemplateId: !Ref 'EC2InstanceLaunchTemplate'
        # 起動テンプレートのバージョン番号
        Version: !GetAtt 'EC2InstanceLaunchTemplate.LatestVersionNumber'

      # ALBの場合紐づけるTargetGroupを指定する
      TargetGroupARNs:
        - !Ref TargetGroup
      # 台数設定
      DesiredCapacity: 2
      MaxSize: 2
      MinSize: 2
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-server"
          PropagateAtLaunch: true

# ------------------------------------------------------------#
# 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"
      
  Public1Subnet:
    Value: !Ref Public1Subnet
    Export:
      Name: !Sub "${PJPrefix}-public1-subnet"

  Public2Subnet:
    Value: !Ref Public2Subnet
    Export:
      Name: !Sub "${PJPrefix}-public2-subnet"
    
# LaunchTemplate
  EC2InstanceLaunchTemplate:
    Value: !Ref EC2InstanceLaunchTemplate
    Export:
      Name: !Sub "${PJPrefix}-web1-server"
# Target Group
  TargetGroup:
    Value: !Ref TargetGroup
    Export:
      Name: !Sub "${PJPrefix}-tg"
# ALB
  InternetALB:
    Value: !Ref InternetALB
    Export:
      Name: !Sub "${PJPrefix}-alb"
```

## 参考
[[入門] AWSのネットワークとAutoScalingなWebサーバーを作るハンズオンを作りました](https://dev.classmethod.jp/articles/introduction-to-aws-networking-and-autoscaling-web-server/)