Amazon ECS Workshop #4 ~コンテナのスケーリング~

# 概要
今回の投稿における達成目標と構成図を記載します。
##達成目標
本投稿は[#3 ~コンテナのモニタリング~](https://qiita.com/drafts/2ed9b6a4faf1440712ca)編の続きになります。
下記ができることを確認します。

- リソースの高騰に合わせて、タスク起動数を増加できること
- リソースが定常に戻った際に、タスク起動数を定常数に戻せること


## 環境構成図
本投稿では下記構成図の赤字のリソースを新規に作成します。
今回の目的のタスク起動数のスケーリング自体はLBが無くても実現できますが、スケールした際のロードテストをIP単位で実施すると面倒だったため、作成しています。（というか今までサボっていました）

![環境構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640480/a99cc22e-66f8-181d-274f-3823162d662d.png)


# ALBの作成
下記を実施前提で進めていきます。

- [Amazon ECS Workshop #2 ~コンテナのデプロイ~](https://qiita.com/itoumke/items/1755e6bcef0d76dff65b)
- [Amazon ECS Workshop #3 ~コンテナのモニタリング~](https://qiita.com/itoumke/items/2ed9b6a4faf1440712ca)

## Subnetの追加
ALBは2以上のAZが必要となるので、AZ-cのsubnetを追加するため、VPC関連を構築したスタックを更新します。

<details><summary>Subnet追加 更新用テンプレート</summary><div>

```vpc-cfn-template.yml
AWSTemplateFormatVersion: "2010-09-09"
Description:
  VPC and Subnet Create
 
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - PJPrefix
      - Label:
          default: "Network Configuration"
        Parameters:
          - VPCCIDR
          - PublicSubnetACIDR
          - PublicSubnetCCIDR
    ParameterLabels:
      VPCCIDR:
        default: "VPC CIDR"
      PublicSubnetACIDR:
        default: "PublicSubnetA CIDR"
      PublicSubnetCCIDR:
        default: "PublicSubnetC CIDR"
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  PJPrefix:
    Type: String
 
  VPCCIDR:
    Type: String
    Default: "10.65.0.0/16"
 
  PublicSubnetACIDR:
    Type: String
    Default: "10.65.1.0/24"

  PublicSubnetCCIDR:
    Type: String
    Default: "10.65.2.0/24"

Resources:
# ------------------------------------------------------------#
#  VPC
# ------------------------------------------------------------#
# VPC Create
  VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: "true"
      EnableDnsHostnames: "true"
      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
# ------------------------------------------------------------#         
# Public SubnetA Create
  PublicSubnetA:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PublicSubnetACIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-public-subnet-a"

# Public SubnetC Create
  PublicSubnetC:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PublicSubnetCCIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-public-subnet-c"


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

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

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

# PublicRouteC Create
  PublicRouteC:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref PublicRouteTableC
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway

# ------------------------------------------------------------#
# RouteTable Associate
# ------------------------------------------------------------#
# PublicRouteTable Associate SubnetA
  PublicSubnetARouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref PublicRouteTableA

# PublicRouteTable Associate SubnetC
  PublicSubnetCRouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref PublicSubnetC
      RouteTableId: !Ref PublicRouteTableC
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#               
Outputs:
# VPC
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub "${PJPrefix}-vpc"
 
  VPCCIDR:
    Value: !Ref VPCCIDR
    Export:
      Name: !Sub "${PJPrefix}-vpc-cidr"
 
# Subnet
  PublicSubnetA:
    Value: !Ref PublicSubnetA
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-a"
  PublicSubnetC:
    Value: !Ref PublicSubnetC
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-c"

  PublicSubnetACIDR:
    Value: !Ref PublicSubnetACIDR
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-a-cidr"

  PublicSubnetCCIDR:
    Value: !Ref PublicSubnetCCIDR
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-c-cidr"  
# Route
  PublicRouteTableA:
    Value: !Ref PublicRouteTableA
    Export:
      Name: !Sub "${PJPrefix}-public-route-a"
  PublicRouteTableC:
    Value: !Ref PublicRouteTableC
    Export:
      Name: !Sub "${PJPrefix}-public-route-c"

```

</div></details>


## ALB用SecurityGroup作成
ALB用のSecurityGroupを作成します。
「Client→コンテナ」の構成から「Client→ALB→コンテナ」の構成に変更したことで、コンテナのSecurityGroupは外部から通信許可が不要になり、ALBから通信できるように変更する必要があります。
前段ではECS作成のスタックの中で、ECSのSecurityGroupを定義してましたが、SecurityGroup構築用のスタックに切り出しておきます。

下記テンプレートでALB用とECS用のSecurityGroupを構築します。
前段のECSのスタックを削除していない場合、論理名重複で失敗するため、ECSのスタックは一度削除してください。

<details><summary>SG 構築用テンプレート</summary><div>

```
AWSTemplateFormatVersion: "2010-09-09"
Description:
   Create SG
  
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - PJPrefix
  
      - Label:
          default: "ECS Configuration"
        Parameters:
          - HTTPLocation
 
    ParameterLabels:
      HTTPLocation:
        default: "Permit HTTP Src IP Address"
  
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  PJPrefix:
    Type: String

  HTTPLocation:
    Default: 127.0.0.0/32
    Type: String
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
      
# ------------------------------------------------------------#
#  SG Create
# ------------------------------------------------------------#
Resources:
## SecurityGroup Create
  ALBSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${PJPrefix}-alb-http-permit"
      GroupDescription: Allow HTTP access only MyIP
      VpcId:  { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
      SecurityGroupIngress:
        # 
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: !Ref HTTPLocation

  ECSSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${PJPrefix}-ecs-internal-permit"
      GroupDescription: Allow AllProtocol from ALB
      VpcId:  { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
      SecurityGroupIngress:
        # 
        - IpProtocol: -1
          SourceSecurityGroupId: !Ref ALBSG
# ------------------------------------------------------------#
#  Output Parameter
# ------------------------------------------------------------#
Outputs:
  ALBSG:
    Value: !Ref ALBSG
    Description: SG-ID for ALB
    Export:
      Name: !Sub "${PJPrefix}-sgid-alb"
  ECSSG:
    Value: !Ref ECSSG
    Description: SG-ID for ECS
    Export:
      Name: !Sub "${PJPrefix}-sgid-ecs"
```

</div></details>

## ALB(インスタンス＆ターゲットグループ＆リスナー)の作成
適当なLBを作成します。ヘルスチェックの通信コストを小さくするため、ヘルスチェック間隔を大きくしたり、多少設計はしてますが、適当です。
実運用だとルーティングやドレーニングなどなど、業務を意識した設計が必要になります。

1点だけ注意するポイントはターゲットグループのターゲットタイプをipにしておかなければ、後段のターゲットにFargateを使用したServiceの指定ができなくなります。


<details><summary>ALB構築用テンプレート</summary><div>

```alb-cfn-template.yml

AWSTemplateFormatVersion: "2010-09-09"
Description:
   Create ALB
  
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - PJPrefix

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

# ------------------------------------------------------------#
#  ALB Create
# ------------------------------------------------------------#
Resources:
## 
  LBInstance:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
        IpAddressType: ipv4
        Name: !Sub "${PJPrefix}-alb"
        Scheme: internet-facing
        SecurityGroups: 
          - { "Fn::ImportValue": !Sub "${PJPrefix}-sgid-alb" }
        Subnets: 
          - { "Fn::ImportValue": !Sub "${PJPrefix}-public-subnet-a" }
          - { "Fn::ImportValue": !Sub "${PJPrefix}-public-subnet-c" }
        Type: application
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties: 
      HealthCheckIntervalSeconds: 180
      HealthCheckPath: "/"
      HealthCheckTimeoutSeconds: 2
      HealthyThresholdCount: 2
      Name: !Sub "${PJPrefix}-alb-target"
      Port: 80
      Protocol: HTTP
      TargetType: ip
      UnhealthyThresholdCount: 2
      VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
  Listener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties: 
      DefaultActions: 
        - TargetGroupArn: !Ref TargetGroup
          Type: forward  
      LoadBalancerArn: !Ref LBInstance
      Port: 80
      Protocol: HTTP

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#               
Outputs:
# ALB
  TargetGroup:
    Value: !Ref TargetGroup
    Export:
      Name: !Sub "${PJPrefix}-alb-target"

```

</div></details>

## ServiceをLBのターゲットに設定
Service部分の設定に下記を追加し、LB→Serviceの紐づけを行います。
LBをInternetfacingな前段に挟むことでタスクに外部アクセスする際にパブリックIPは不要となります。ただし、現構成ではパブリックIPの払い出しを無効化した場合、Imageのpullができなくなります。実運用ではNATGatewayを構築することでInternet経由でのpullを可能にし、実行コンテナ環境をよりセキュアにできるはずです。

```
      LoadBalancers: 
        - ContainerName: !Sub ${PJPrefix}-ecscontainer
          ContainerPort: 80
          TargetGroupArn: { "Fn::ImportValue": !Sub "${PJPrefix}-alb-target" }

```

上記を追記した下記テンプレートでECSのスタックを更新します。

<details><summary>LB紐付け追加 ECS更新用スタック</summary><div>

```ecs-cfn-template.yml

AWSTemplateFormatVersion: "2010-09-09"
Description:
   Create Container with ECS on Fargate
  
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - PJPrefix
  
      - Label:
          default: "ECS Configuration"
        Parameters:
          - ECSTaskCPUUnit
          - ECSTaskMemory
          - IAMRoleParameter
 
    ParameterLabels:
      ECSTaskCPUUnit:
        default: "ECSTaskCPUUnit"
      ECSTaskMemory:
        default: "ECSTaskMemory"
      IAMRoleParameter:
        default: "IAM Role"
  
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  PJPrefix:
    Type: String

  ECSTaskCPUUnit:
    AllowedValues: [ 256, 512, 1024, 2048, 4096  ]
    Type: String
    Default: "256"

  ECSTaskMemory:
    AllowedValues: [ 512, 1024, 2048, 4096  ]
    Type: String
    Default: "512"
 
  IAMRoleParameter:
    Type: String
     
# ------------------------------------------------------------#
#  ECS Create
# ------------------------------------------------------------#
Resources:
## ECS Cluster
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${PJPrefix}-Cluster
      ### Enable CloudWatch Container Insights
      ClusterSettings: 
        - Name: containerInsights
          Value: enabled
## ECS LogGroup
  ECSLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /ecs/logs/${PJPrefix}
## ECS TaskDefinition
  ECSTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Cpu: !Ref ECSTaskCPUUnit
      ExecutionRoleArn: !Ref IAMRoleParameter
      Family: !Sub ${PJPrefix}-task
      Memory: !Ref ECSTaskMemory
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        - Name: !Sub ${PJPrefix}-ecscontainer
          Image: !Sub ${AWS::AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/myrepository
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ECSLogGroup
              awslogs-region: !Ref "AWS::Region"
              awslogs-stream-prefix: !Ref PJPrefix
          MemoryReservation: 128
          PortMappings:
            - HostPort: 80
              Protocol: tcp
              ContainerPort: 80
## ECS Service
  ECSService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref ECSCluster
      DesiredCount: 1
      LaunchType: FARGATE
      LoadBalancers: 
        - ContainerName: !Sub ${PJPrefix}-ecscontainer
          ContainerPort: 80
          TargetGroupArn: { "Fn::ImportValue": !Sub "${PJPrefix}-alb-target" }
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - { "Fn::ImportValue": !Sub "${PJPrefix}-sgid-ecs" }
          Subnets:
            - { "Fn::ImportValue": !Sub "${PJPrefix}-public-subnet-a" }
      ServiceName: !Sub ${PJPrefix}-ecsservice
      TaskDefinition: !Ref ECSTaskDefinition
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#               
Outputs:
# ECS Cluster
  ECSCluster:
    Value: !Ref ECSCluster
    Export:
      Name: !Sub "${PJPrefix}-ecs-cluster"
# ECS Service
  ECSService:
    Value: !GetAtt ECSService.Name
    Export:
      Name: !Sub "${PJPrefix}-ecs-service"



```

</div></details>

## 疎通確認
構築したALBのDNS名をコンソールから確認し、ブラウザアクセスしてみます。
ECRにプッシュしているイメージがデプロイされたタスクにアクセスできていることがわかります。

![lb疎通確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640480/6916cd8b-74b8-ecb5-7b7f-91102b5c4395.png)

# Application Autoscalingの設定
## AutoscalingターゲットとAutoscalingポリシー作成
ECSのServiceのAutoscalingするにはApplication Autoscalingというサービスを利用します。
Application Autoscalingはターゲットとポリシーに分かれており、下記のような設定ができるコンポーネントです。

- Autoscalingターゲット：  
どのリソース（今回はECSサービス）をどの範囲でスケール（今回は最小タスク数と最大タスク数）させるのか、を設定
- Autoscalingポリシー：  
イベントを検知した際に、どのようにスケール（今回であれば、何タスクずつ起動させる？起動させた後次のスケールまで何分待つ？（起動のオーバヘッド時間に誤検知して大量にリソースを作らないため）など）、などを設定

私がはまったポイントはAutoscalingターゲットに紐づけるIAMRoleについて、
IAMRole側でApplication Autoscalingから呼び出せるよう、エンティティの設定が必要です。

下記が構築用テンプレートとなります。


<details><summary>ApplicationAutoScaling構築用スタック</summary><div>

```appas-cfn-template.yml

AWSTemplateFormatVersion: "2010-09-09"
Description:
   Create ALB
  
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - PJPrefix
      - Label:
          default: "AS Configuration"
        Parameters:
          - MAXCapacity
          - MINCapacity
          - IAMRoleParameter

    ParameterLabels:
      MAXCapacity:
        default: "MAXCapacity"
      MINCapacity:
        default: "MINCapacity"
      IAMRoleParameter:
        default: "IAM Role"


# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  PJPrefix:
    Type: String
  MAXCapacity:
    Type: String
    Default: "4"
  MINCapacity:
    Type: String
    Default: "1"
  IAMRoleParameter:
    Type: String
# ------------------------------------------------------------#
#  Application AutoScaling Create
# ------------------------------------------------------------#
Resources:
## 
  ScalableTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties: 
      MaxCapacity: !Sub ${MAXCapacity}
      MinCapacity: !Sub ${MINCapacity}
      ResourceId: !Join 
        - /
        - - service
          - { "Fn::ImportValue": !Sub "${PJPrefix}-ecs-cluster" }
          - { "Fn::ImportValue": !Sub "${PJPrefix}-ecs-service" }
      RoleARN: !Sub arn:aws:iam::${AWS::AccountId}:role/${IAMRoleParameter}
      ScalableDimension: ecs:service:DesiredCount
      ServiceNamespace: ecs
## 
  ScalingPolicy:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties: 
      PolicyName: ServiceAlarm
      PolicyType: StepScaling 
      ScalableDimension: ecs:service:DesiredCount 
      ScalingTargetId: !Ref ScalableTarget
      ServiceNamespace: ecs 
      StepScalingPolicyConfiguration: 
        AdjustmentType: ChangeInCapacity
        Cooldown: 180
        MetricAggregationType: Average
        StepAdjustments: 
          - MetricIntervalLowerBound: 0
            ScalingAdjustment: 1
          - MetricIntervalUpperBound: 0
            ScalingAdjustment: -1

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#               
Outputs:
# Autoscalingpolicy
  ScalingPolicy:
    Value: !Ref ScalingPolicy
    Export:
      Name: !Sub "${PJPrefix}-as-policy"


```

</div></details>

## Cloudwatch→ApplicationAutoScalingポリシーの紐づけ
次に何のイベントでApplicationAutoScalingを呼び出すかの紐づけです。
イベントとして、サービス毎にプレ定義されているメトリクスかCloudwatchAlarmのアクションで呼び出す2パターンがあります。
ECSでは、下記のメトリクスがプレ定義されています。

- ECSサービスの全体CPU使用率
- ECSサービスの全体メモリ使用率
- ALB→ECSタスクへの1タスクあたりのリクエスト数

今回は前段でCloudwatchAlarmを設定していたので、せっかくなので、そちらのアクションでApplicationAutoScalingを呼び出すような形にしました。

下記のような記載を追記して、CloudwatchAlarmのスタックを更新します。

```
        AlarmActions:
        - Fn::ImportValue: 
            !Sub "${PJPrefix}-topic"
        - Fn::ImportValue: 
            !Sub "${PJPrefix}-as-policy"
        ~~~~
        OKActions:
        - Fn::ImportValue: 
            !Sub "${PJPrefix}-as-policy"
```

<details><summary>CloudwatchAlarm更新用テンプレート</summary><div>

```cwalarm-cfn-template.yml

AWSTemplateFormatVersion: "2010-09-09"
Description:
   Create CloudwatchAlarm of ecs-resource
  
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - PJPrefix
      - Label:
          default: "Alarm Configuration"
        Parameters:
          - Threshold
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  PJPrefix:
    Type: String
  Threshold:
    Type: String
    Default: "10"
# ------------------------------------------------------------#
#  CloudWatchAlarm of ECS-Resource
# ------------------------------------------------------------#
Resources:
## CloudwatchAlarm of ECS Resource
  CloudWatchAlarmECSResource:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      AlarmActions:
        - Fn::ImportValue: 
            !Sub "${PJPrefix}-topic"
        - Fn::ImportValue: 
            !Sub "${PJPrefix}-as-policy"
      AlarmDescription: !Sub ${PJPrefix}-ecsresource-alarm
      AlarmName: !Sub ${PJPrefix}-ecsresource-alarm
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      Dimensions:
        - Name:  ServiceName
          Value: !Sub "${PJPrefix}-ecsservice"
        - Name:  ClusterName
          Value: !Sub "${PJPrefix}-Cluster"
      MetricName: "CpuUtilized"
      Namespace: "ECS/ContainerInsights"
      OKActions:
        - Fn::ImportValue: 
            !Sub "${PJPrefix}-as-policy"
      Period: 300
      Statistic: Average
      Threshold: !Sub ${Threshold}
      TreatMissingData: ignore

```

</div></details>


## 挙動確認
## 負荷掛け
[#3 ~コンテナのモニタリング~](https://qiita.com/drafts/2ed9b6a4faf1440712ca)で構築しているBastionサーバから一定量の負荷を掛け、 
「ECSサービスの負荷高騰→ECSタスクのスケールアウト→ECSサービスの負荷軽減→ECSタスクのスケールイン」を確認してみます。

今回は
・130 Transaction/s程度を50分間
・20 Transaction/s程度を10分間
掛けてみて、確認しました。

「130 Transaction/s程度を50分間」かけた際のコマンドは下記の通りです。
※ rオプションがレートのはずですが、いまいちここを変動させてもトランザクションが変わりません…　詳しい方教えてください…

```
siege --time=3000S -c 3 -r 5 -d 0.2 http://{ALBのDNS名}
```

## コンソール確認
1時間負荷を掛けた後、ALBのメトリクスから状況を確認してみました。

![スケーリング確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640480/f1b684e5-da29-f341-1817-233321817fcf.png)

まず、Requestsのメトリクスから一定量のアクセスが流入し続けられていることが分かります。
負荷を掛け始めより、下記のことが分かります。

- HealthyHostsの台数が5分間隔で1→4までタスクが増えていること  
→時間軸のメモリが見にくく申し訳ありませんが、5分間隔である理由は、CloudwatchAlarmの間隔やAutoscalingポリシーのクールダウンあたりの設定によるものです。
- Request Count Per Targetのメトリクスから、タスクの増加に伴い1台当たりのアクセス量を分散できていること
- 台数が増えた後、スケールイン/スケールアウトを繰り返していること  
→これはcloudwatchalarmで設定した閾値が今回の負荷量だと3 or 4タスクで処理するギリギリであったためです。
- スケールインする際にも500エラー等を返していないこと  
→新規リクエストは振らずに処理中のレスポンスは返却してからタスクをドレーニングする機能が働いているのだと推察しています。
今回の処理はただhtmlを返すだけで処理時間が短すぎたのでキチンと確認はできていません。


# まとめ
cloudwatchのメトリクス状況から、

- リソースの高騰に合わせて、タスク起動数を増加できること
- リソースが定常に戻った際に、タスク起動数を定常数に戻せること

を確認できました。
コンソール上でECSのAutoscalingを設定するにはサービスの設定時にできますが、CFnで構築しようとすると、ApplicationAutoScalingというサービスであり、ECSとは別サービスであることが良く分かりました。

コンソールでは2,3項目で完了する設定もCFnのパラメータを確認すると大量の項目があり、コンソール作業ではデフォルト設定で隠れていることがあります。
試しに感覚をつかむにはコンソール操作も良いですが、サービスを深く知る際にはCFnのドキュメントを見ることが個人的にはおすすめです。

次はイメージのリリースでB/GリリースやカナリアリリースをCode系サービス使いながら、体験したいと思いますが、業務が繁忙してきたので投稿空きそうです。