開発/検証環境はFargateSpotを使いましょう

# 概要
`Fargate Spot`はSpotインスタンスのFargate版です。
予期せぬシャットダウンの可能性があるものの、通常の7割引の料金で使えます。
ほとんどの場合、開発/検証環境ではこれが活用可能ではないでしょうか。

# 実装
## 1. ECSクラスターに`DefaultCapacityProviderStrategy`を設定
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html#aws-resource-ecs-cluster--examples

```ECS-Cluster.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: ECS cluster
Parameters:
  EnvironmentName:
    Type: String
    Default: staging

Resources:
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${EnvironmentName}-ecs-cluster
      CapacityProviders:
        - FARGATE
        - FARGATE_SPOT
      DefaultCapacityProviderStrategy: # schedule only Fargate_Spot
        - CapacityProvider: FARGATE_SPOT
          Weight: 1

Outputs:
  ECSClusterName:
    Description: The Name of the ECS cluster
    Value: !Ref ECSCluster
    Export:
      Name: !Sub ${EnvironmentName}:ECSClusterName
  ECSClusterArn:
    Description: The ARN of the ECS cluster
    Value: !GetAtt ECSCluster.Arn
    Export:
      Name: !Sub ${EnvironmentName}:ECSClusterArn
```

## 2. ECSサービスにオートスケーリング設定を追加
サービスの`CapacityProviderStrategy`およびに`LaunchType`を指定しない場合、クラスターの`DefaultCapacityProviderStrategy`が適用されます。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html

```ECS-Service.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: ECS service and auto scaling

Parameters:
  EnvironmentName:
    Type: String
    Default: staging

  ServiceName:
    Type: String
    Default: my-service
  ImageUrl:
    Type: String
    Default: <your-docker-image-url>
    Description: The url of docker image at ECR

  ContainerPort:
    Type: Number
    Default: 3000
    Description: What port number the application inside the docker container is binding to
  ContainerCpu:
    Type: Number
    Default: 256
    Description: How much CPU to give the container. 1024 is 1 CPU
  ContainerMemory:
    Type: Number
    Default: 512
    Description: How much memory in megabytes to give the container
  DesiredCount:
    Type: Number
    Default: 2
    Description: How many copies of the service task to run

  TaskMaxContainerCount:
    Type: Number
    Description: Maximum number of containers to run for the service when auto scaling out
    Default: 10
    MinValue: 1
    ConstraintDescription: Value must be at least one
  ServiceScaleEvaluationPeriods:
    Description: The number of periods over which data is compared to the specified threshold
    Type: Number
    Default: 2
    MinValue: 2
  ServiceCpuScaleOutThreshold:
    Type: Number
    Description: Average CPU value to trigger auto scaling out
    Default: 50
    MinValue: 0
    MaxValue: 100
    ConstraintDescription: Value must be between 0 and 100
  ServiceCpuScaleInThreshold:
    Type: Number
    Description: Average CPU value to trigger auto scaling in
    Default: 25
    MinValue: 0
    MaxValue: 100
    ConstraintDescription: Value must be between 0 and 100

Resources:
  # Service

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName}

  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: !Ref ServiceName
      Cpu: !Ref ContainerCpu
      Memory: !Ref ContainerMemory
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ExecutionRoleArn: !Ref ECSTaskExecutionRole
      TaskRoleArn: !Ref ECSTaskRole
      ContainerDefinitions:
        - Name: !Ref ServiceName
          Cpu: !Ref ContainerCpu
          Memory: !Ref ContainerMemory
          Essential: true
          Image: !Ref ImageUrl
          PortMappings:
            - ContainerPort: !Ref ContainerPort
          LinuxParameters:
            InitProcessEnabled: true
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName}
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: !Ref ServiceName

  Service:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: !Ref ServiceName
      Cluster:
        Fn::ImportValue: !Sub ${EnvironmentName}:ECSClusterArn
      # LaunchType: FARGATE # omit this to use DefaultCapacityProviderStrategy
      EnableExecuteCommand: true
      DeploymentConfiguration:
        DeploymentCircuitBreaker:
          Enable: true
          Rollback: true
        MaximumPercent: 200
        MinimumHealthyPercent: 75
      DesiredCount: !Ref DesiredCount
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED # no public ip is needed
          SecurityGroups:
            - !Ref EcsTaskSecurityGroupId
          Subnets:
            - !Ref PrivateSubnet1
      TaskDefinition: !Ref TaskDefinition
      ServiceRegistries:
        - RegistryArn: !GetAtt ServiceDiscovery.Arn
          Port: !Ref ContainerPort

  # AutoScaling
  ServiceScalingTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    DependsOn:
      - Service
    Properties:
      MinCapacity: !Ref DesiredCount
      MaxCapacity: !Ref TaskMaxContainerCount
      ResourceId: !Sub
        - service/${ECSClusterName}/${ECSServiceName}
        - ECSClusterName:
            Fn::ImportValue: !Sub ${EnvironmentName}:ECSClusterName
          ECSServiceName: !Ref ServiceName
      RoleARN: !Ref AutoscalingRole
      ScalableDimension: ecs:service:DesiredCount
      ServiceNamespace: ecs

  ServiceScaleOutPolicy:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: !Sub ${EnvironmentName}-${ServiceName}-ScaleOutPolicy
      PolicyType: StepScaling
      ScalingTargetId: !Ref ServiceScalingTarget
      StepScalingPolicyConfiguration:
        AdjustmentType: ChangeInCapacity
        Cooldown: 60
        MetricAggregationType: Average
        StepAdjustments:
          - ScalingAdjustment: 1
            MetricIntervalLowerBound: 0
    DependsOn: ServiceScalingTarget

  ServiceScaleInPolicy:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: !Sub ${EnvironmentName}-${ServiceName}-ScaleInPolicy
      PolicyType: StepScaling
      ScalingTargetId: !Ref ServiceScalingTarget
      StepScalingPolicyConfiguration:
        AdjustmentType: ChangeInCapacity
        Cooldown: 60
        MetricAggregationType: Average
        StepAdjustments:
          - ScalingAdjustment: -1
            MetricIntervalUpperBound: 0
    DependsOn: ServiceScalingTarget

  ServiceScaleOutAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub ${EnvironmentName}-${ServiceName}-ScaleOutAlarm
      EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
      Statistic: Average
      TreatMissingData: notBreaching
      Threshold: !Ref ServiceCpuScaleOutThreshold
      AlarmDescription: Alarm to add capacity if CPU is high
      Period: 60
      AlarmActions:
        - !Ref ServiceScaleOutPolicy
      Namespace: AWS/ECS
      Dimensions:
        - Name: ClusterName
          Value:
            Fn::ImportValue: !Sub ${EnvironmentName}:ECSClusterName
        - Name: ServiceName
          Value: !Ref ServiceName
      ComparisonOperator: GreaterThanThreshold
      MetricName: CPUUtilization
    DependsOn:
      - ServiceScaleOutPolicy

  ServiceScaleInAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub ${EnvironmentName}-${ServiceName}-ScaleInAlarm
      EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
      Statistic: Average
      TreatMissingData: notBreaching
      Threshold: !Ref ServiceCpuScaleInThreshold
      AlarmDescription: Alarm to reduce capacity if container CPU is low
      Period: 300
      AlarmActions:
        - !Ref ServiceScaleInPolicy
      Namespace: AWS/ECS
      Dimensions:
        - Name: ClusterName
          Value:
            Fn::ImportValue: !Sub ${EnvironmentName}:ECSClusterName
        - Name: ServiceName
          Value: !Ref ServiceName
      ComparisonOperator: LessThanThreshold
      MetricName: CPUUtilization
    DependsOn:
      - ServiceScaleInPolicy

```
## 3. 確認
下記コマンドでタスクの`capacityProvider`を確認できます。

```sh
$ aws ecs describe-tasks 
        --cluster <Cluster_name> \
        --tasks <TaskID> \
        --region <Region>
```

# 参考
下記大変参考にさせていただきました。

https://pages.awscloud.com/rs/112-TZM-766/images/00_AWS%E8%90%BD%E6%B0%B4%E3%81%95%E3%82%93_%E8%B3%87%E6%96%99.pdf

https://dev.classmethod.jp/articles/fargate-spot-detail/

https://dev.classmethod.jp/articles/regrwoth-capacity-provider/

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/cluster-capacity-providers.html

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/fargate-capacity-providers.html