開発環境のECS Serviceを夜間に自動で停止させる

コスト削減のため、開発環境のECS Serviceを夜間と休日は停止する際に行った内容のメモです

AuroraやEC2を夜間に停止するというのは、以下の SSM Automation を EventBridge 経由で実行することで可能ですが、ECS Serviceは対応するAutomationがないため SSM Document を自作してみました

https://docs.aws.amazon.com/ja_jp/systems-manager-automation-runbooks/latest/userguide/start-stop-aurora-cluster.html

https://docs.aws.amazon.com/ja_jp/systems-manager-automation-runbooks/latest/userguide/automation-aws-startec2instance.html

https://docs.aws.amazon.com/ja_jp/systems-manager-automation-runbooks/latest/userguide/automation-aws-stopec2instance.html

## SSM Documentの作成

SSM Document でAWS APIを実行するため以下の Automation Action を使用します

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/automation-action-executeAwsApi.html

ECS Service を停止するためにはDesired Countを 0 にすることで停止できるため、UpdateService でDesiredCount だけを変更するようにします  
parameters で受け取る引数と mainSteps で実際の処理を書きます  

```
resource "aws_ssm_document" "start_stop_ecs_service" {
  name            = "StartStopECSService"
  document_format = "YAML"
  document_type   = "Automation"

  content = <<EOT
description: ecs service start and stop automation runbook
schemaVersion: '0.3'
parameters:
  EcsClusterName:
    type: String
  EcsServiceName:
    type: String
  DesiredCount:
    type: Integer
    default: 1
mainSteps:
  - name: ECS
    action: 'aws:executeAwsApi'
    inputs:
      Service: ecs
      Api: UpdateService
      cluster: '{{ EcsClusterName }}'
      service: '{{ EcsServiceName }}'
      desiredCount: '{{ DesiredCount }}'
EOT
}
```

## SSM Automationを実行するIAMロールを作成

EventBridge から実行させるためのIAMロールを作成します

```
data "aws_iam_policy_document" "events" {
  statement {
    actions = ["sts:AssumeRole"]
    effect  = "Allow"
    principals {
      type        = "Service"
      identifiers = [
        "events.amazonaws.com"
      ]
    }
  }
}

resource "aws_iam_role" "ecs_automation" {
  name               = "ecs-automation"
  path               = "/"
  assume_role_policy = data.aws_iam_policy_document.events.json
}
```

上記のIAMロールに SSM Document を実行する権限とECSの UpdateService の権限を付与します

```
data "aws_iam_policy_document" "ecs_stop_start" {
  statement {
    actions   = [
      "ecs:Describe*",
      "ssm:*"
    ]
    effect    = "Allow"
    resources = ["*"]
  }
  statement {
    actions   = [
      "ecs:UpdateService"
    ]
    effect    = "Allow"
    resources = [
      (停止させたいECS ServiceのARN）
    ]
  }
}

resource "aws_iam_role_policy" "ecs_stop_start" {
  name   = "ecs-stop-start"
  role   = aws_iam_role.ecs_automation.name
  policy = data.aws_iam_policy_document.ecs_stop_start.json
}
```

## EventBridgeの作成

月曜～金曜の22時に ECS Service を停止します
aws_ecs_service の cluster はARNなので split でCluster名を取得しています

```
resource "aws_cloudwatch_event_rule" "ecs_service_stop" {
  name                = "ecs-service-stop"
  schedule_expression = "cron(0 13 * * MON-FRI *)"
  is_enabled          = true
}

resource "aws_cloudwatch_event_target" "ecs_service_stop" {
  target_id = aws_ecs_service.api.name
  rule      = aws_cloudwatch_event_rule.ecs_service_stop.name
  arn       = "${aws_ssm_document.ecs_service_stop_start.arn}:$DEFAULT"
  role_arn  = aws_iam_role.ecs_automation.arn
  input     = <<EOT
{
  "EcsClusterName": ["${split("/", aws_ecs_service.api.cluster)[-1]}"],
  "EcsServiceName": ["${aws_ecs_service.api.name}"],
  "DesiredCount": ["0"]
}
EOT
}
```

月曜～金曜の9時30分に ECS Service を起動させます

```
resource "aws_cloudwatch_event_rule" "ecs_service_start" {
  name                = "ecs-service-start"
  schedule_expression = "cron(30 0 ? * * *)"
  is_enabled          = true


resource "aws_cloudwatch_event_target" "ecs_service_start" {
  target_id = aws_ecs_service.api.name
  rule      = aws_cloudwatch_event_rule.ecs_service_start.name
  arn       = "${aws_ssm_document.ecs_service_stop_start.arn}:$DEFAULT"
  role_arn  = aws_iam_role.ecs_automation.arn
  input     = <<EOT
{
  "EcsClusterName": ["${split("/", aws_ecs_service.api.cluster)[-1]}"],
  "EcsServiceName": ["${aws_ecs_service.api.name}"],
  "DesiredCount": ["1"]
}
EOT
}
```

## 応用として

今回は開発環境を停止するという用途でしたが、本番環境で深夜のようなほぼアクセスが来ない状況では desiredCount を 1 にするということや、特定の時間帯だけ増やすということが可能になりそうです    
スケジュールで desiredCount を増減するというのは AotoScaling で可能でありますが、この手順のほうがお手軽かなと個人的には思います