EventBridge Scheduler で ECS を平日だけ動かす仕組みを Terraform で作る

## 前書き

 株式会社ゆめみでエンジニアをしていますむろです。
 今回は，EventBridge Scheduler を使って，ECS サービスで動かしている Task を定期的に起動・停止する方法についての記事になります。Task は祝日を考慮せず，平日(月曜日 ~ 金曜日)の08:00 ~ 22:00に動かします。この記事では Terraform を使って ECS サービスや EventBridge Scheduler を管理することを目的としています。
 AWS コンソールでも同様の Scheduler を作成することが可能ですので，用途などに応じて選択すると良いかと思います。

:::note warn
 この記事では主として EventBridge Scheduler を設定することを目的としています。そのため，ECS サービスや Task の設定などは一部割愛します。
:::

 Terraform のディレクトリ構成は以下になります。なお，この記事で使用しないファイルなどは省略しています。

```shell
terraform
├── ecs_cluster.tf
├── ecs_service.tf
├── event_bridge.tf
├── modules
│   ├── ecs_service
│   │   ├── ecs_service.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   └── event_bridge
│       ├── event_bridge_schedule_group.tf
│       ├── event_bridge_scheduler.tf
│       ├── iam_role_for_event_bridge_scheduler.tf
│       └── variables.tf
└── versions.tf
```

 Terraform のバージョンは以下になります。

 ```versions.tf
 terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.70.0"
    }
  }
  required_version = ">= 1.9"
}
```

## 記事で使用する ECS の作成
 Scheduler の設定を読みたい方は[こちら](#EventBridge-Scheduler-で使用する-IAM-を設定)まで移動してください。
 ではまず，ECS クラスターを作成します。

``` ecs_cluster.tf
resource "aws_ecs_cluster" "ecs_cluster" {
  name = "ecs-cluster-for-eventbridge-scheduler"
}
```

 次に，ECS サービスを作成していきます。今回はモジュールを使用していますので，変数の設定をしていきます。

``` ecs_service.tf
module "ecs_service" {
  source = "./modules/ecs_service"

  app_name            = "app"
  name                = "ecs-service-for-eventbridge-scheduler"
  desired_count       = 1
  cluster_id          = aws_ecs_cluster.ecs_cluster.id
}
```

```modules/ecs_service/variables.tf
variable "app_name" {
  type        = string
  description = "Name of this application"
}

variable "name" {
  type        = string
  description = "Name of this resource"
}

variable "desired_count" {
  type        = number
  description = "Initial desired count"
  default     = 1
}

variable "cluster_id" {
  type        = string
  description = "ECS Cluster ID"
}
```

 モジュールの実態の方を用意します。なお，提示するパラメータは全てではありませんので，実際に ECS サービスを作成する際には[公式ドキュメント](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service)などを参照した上で作成する方が良いかと思います。

```modules/ecs_service/ecs_service.tf
resource "aws_ecs_service" "default" {
  name                = var.name
  cluster             = var.cluster_id
  desired_count       = var.desired_count
  launch_type         = "FARGATE"
  scheduling_strategy = "REPLICA"
  propagate_tags      = "SERVICE"

  # ~~ 省略 ~~
}
```

 また，IAM の設定や EventBridge Scheduler で用いるため output を設定します。

```modules/ecs_service/output.tf
output "service_name" {
  value       = aws_ecs_service.default.name
  description = "Name of ECS service"
}

output "service_arn" {
  value      = aws_ecs_service.default.id
  descripion = "Arn of ECS service"
}
```

 ## EventBridge Scheduler で使用する IAM を設定

 それでは，ここから実際に EventBridge Scheduler で ECS サービスを操作するために必要な IAM を作成していきます。今回の記事では1ファイルにまとめていますが，適宜ファイルを分割するなどして，ご自身の管理都合が良いものにすると良いかと思います。

 IAM の設定の際に，ECS サービスのモジュールや変数として受け取りたい値定義しておきます。

```event_bridge.tf
module "default_scheduler" {
  source          = "./modules/event_bridge"
  cluster_name    = aws_ecs_cluster.ecs_cluster.name
  service         = "default"
  start_schedule  = "cron(0 8 ? * MON-FRI *)"
  stop_schedule   = "cron(0 22 ? * MON-FRI *)"
  ecs_service_arn = module.ecs_service_api.service_arn
  desired_count   = 1
}
```

```modules/event_bridge/variables.tf
variable "cluster_name" {
  description = "Name of target for EventBridge Scheduler"
  type        = string
}

variable "ecs_service_arn" {
  description = "Arn of target for EventBridge Scheduler"
  type        = string
}

variable "service" {
  description = "Service of target for EventBridge Scheduler"
  type        = string
}

variable "start_schedule" {
  description = "Starting schedule for ECS service. Type: Cron"
  type        = string
}

variable "stop_schedule" {
  description = "Stopping schedule for ECS service. Type: Cron"
  type        = string
}

variable "desired_count" {
  description = "Desired count for ECS service"
  type        = number
}
```

 ```modules/event_bridge/iam_role_for_event_bridge_scheduler.tf
data "aws_iam_policy_document" "eventbridge_for_ecs_assume_policy_doc" {
  statement {
    principals {
      type        = "Service"
      identifiers = ["scheduler.amazonaws.com"]
    }
    actions = ["sts:AssumeRole"]
    effect  = "Allow"
  }
}

data "aws_iam_policy_document" "eventbridge_for_ecs_policy_doc" {
  version = "2012-10-17"
  statement {
    resources = [
      "${var.ecs_service_arn}",
    ]
    actions = ["ecs:UpdateService"]
    effect  = "Allow"
  }
}

resource "aws_iam_policy" "eventbridge_for_ecs_policy" {
  name        = "eventbridge_for_${var.stage_name}_${var.service}_policy"
  description = "Policy for Eventbridge using ecs scheduler"
  policy      = data.aws_iam_policy_document.eventbridge_for_ecs_policy_doc.json
}

resource "aws_iam_role" "eventbridge_for_ecs_role" {
  name               = "${var.stage_name}_${var.service}_eventbridge_for_ecs_role"
  assume_role_policy = data.aws_iam_policy_document.eventbridge_for_ecs_assume_policy_doc.json
}

resource "aws_iam_role_policy_attachment" "eventbridge_for_ecs_role_policy_attachment" {
  role       = aws_iam_role.eventbridge_for_ecs_role.name
  policy_arn = aws_iam_policy.eventbridge_for_ecs_policy.arn
}

 ```

## EventBridge Scheduler で ECS を操作する

 `event_bridge.tf` にもある通り，起動時刻と停止時刻はモジュールの引数として渡しています。 EventBridge Scheduler のリソースを作成していきます。　UpdateService に対し，必要な引数をjson形式で渡してあげるものになります。

 ```modules/event_bridge/event_bridge_scheduler.tf
esource "aws_scheduler_schedule" "stopper" {
  name                         = "${var.service}_stopper"
  state                        = "ENABLED"
  schedule_expression          = var.stop_schedule
  schedule_expression_timezone = "Asia/Tokyo"
  flexible_time_window {
    mode = "OFF"
  }

  target {
    arn      = "arn:aws:scheduler:::aws-sdk:ecs:updateService"
    role_arn = aws_iam_role.eventbridge_for_ecs_role.arn
    input = jsonencode({
      "Cluster" : "${var.stage_name}"
      "Service" : "${var.service}",
      "DesiredCount" : 0
    })
  }
}

resource "aws_scheduler_schedule" "starter" {
  name                         = "${var.service}_starter"
  state                        = "ENABLED"
  schedule_expression          = var.start_schedule
  schedule_expression_timezone = "Asia/Tokyo"
  flexible_time_window {
    mode = "OFF"
  }

  target {
    arn      = "arn:aws:scheduler:::aws-sdk:ecs:updateService"
    role_arn = aws_iam_role.eventbridge_for_ecs_role.arn
    input = jsonencode({
      "Cluster" : "${var.stage_name}"
      "Service" : "${var.service}",
      "DesiredCount" : "${var.desired_count}"
    })
  }
}
```

 以上で必要なファイルの作成が完了したかと思います。表題を十分実現可能な状態を作成できました。

 ## おわりに

  AWSコンソールを操作することでも今回の内容と同様のことは可能ですが，Terraformで管理できることにメリットはあると思います。今回の記事が誰かの一助になると嬉しいです。
  誤りなどありましたらご指摘いただけますと嬉しいです。