【Fargate】タスクのスケジューリング【CloudFormation】

## タスクをスケジュールで増減させたい！
本番でもstagingでも開発環境でもECS Fargateのタスクの増減をスケジュールを決めて実行したいということがあると思います。
EC2の場合はAutoScaling Groupの設定でスケジュールが合ったのですが、Fargateの場合はどうもなさそう。

https://dev.classmethod.jp/articles/amazon-eventbridge-scheduler-ecs-stop-start/

このような記事が作成されていることからも、直接的な機能は提供されていなさそうです。
今回は上記のクラスメソッドさんの記事を参考に作成しました。

## CloudFormationで作成
EventBridgeでタスクのDesiredCountを操作します。
start時は1、stopの際は0としていますが、これは状況によって変えて良さそうです。

このymlファイルをCloudformationに渡すとAWSコンソール上で
- クラスター名
- サービス名
- 止める時間のcron
- 開始時間のcron
- DesiredCount（デフォルト1）

が聞かれます。これに回答するだけで自動での起動・停止のスケジュールが作成されます。
ちなみにcronは以下の形式で書きます。cron()が必要です。
```
cron(0 20 ? * MON-FRI *)
```

```yml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'ECS Fargate Auto Start/Stop Scheduler'

Parameters:
  ClusterName:
    Type: String
    Description: 'Name of the ECS cluster'
  ServiceName:
    Type: String
    Description: 'Name of the ECS service'
  StopCron:
    Type: String
    Description: 'Cron expression for stopping (e.g., cron(0 20 ? * MON-FRI *))'
  StartCron:
    Type: String
    Description: 'Cron expression for starting (e.g., cron(0 8 ? * MON-FRI *))'
  DesiredCount:
    Type: Number
    Default: 1
    Description: 'Number of tasks when starting'

Resources:
  EcsAutoStopSchedule:
    Type: AWS::Scheduler::Schedule
    Properties:
      GroupName: default
      ScheduleExpression: !Ref StopCron
      Target:
        Input: !Sub '{ "Cluster": "${ClusterName}", "Service": "${ServiceName}", "DesiredCount": 0 }'
        Arn: arn:aws:scheduler:::aws-sdk:ecs:updateService
        RetryPolicy:
          MaximumEventAgeInSeconds: 86400
          MaximumRetryAttempts: 3
        RoleArn: !GetAtt SchedulerExecutionRole.Arn
      State: ENABLED
      FlexibleTimeWindow:
        Mode: 'OFF'
      ScheduleExpressionTimezone: Asia/Tokyo
      Name: !Sub '${ClusterName}-${ServiceName}-Stop'

  SchedulerExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: scheduler.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: ECSUpdateServicePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: 'ecs:UpdateService'
                Resource: !Sub 'arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:service/${ClusterName}/${ServiceName}'

  EcsAutoStartSchedule:
    Type: AWS::Scheduler::Schedule
    Properties:
      GroupName: default
      ScheduleExpression: !Ref StartCron
      Target:
        Input: !Sub |-
          {
              "Cluster": "${ClusterName}",
              "Service": "${ServiceName}",
              "DesiredCount": ${DesiredCount}
          }
        Arn: arn:aws:scheduler:::aws-sdk:ecs:updateService
        RetryPolicy:
          MaximumEventAgeInSeconds: 86400
          MaximumRetryAttempts: 3
        RoleArn: !GetAtt SchedulerExecutionRole.Arn
      State: ENABLED
      FlexibleTimeWindow:
        Mode: 'OFF'
      ScheduleExpressionTimezone: Asia/Tokyo
      Name: !Sub '${ClusterName}-${ServiceName}-Start'

```