ECSタスクを定期実行するEventBridge SchedulerをTerraformで実装してみる

はじめに
最近の業務で、ECSタスクを定期実行するEventBridge Schedulerを実装する機会があったので、Terraformでどのように実装できるかハンズオン形式で紹介したいと思います。
ECRイメージ、タスク定義の準備
まず、実行するタスクの元になるコンテナイメージをECRに格納して準備しておきます。
本記事では、以下のリポジトリでビルドしたイメージを使ってタスクを起動する前提のため、実際に試してみたい方は参考にしてみてください。
https://github.com/khomma0312/tf-eventbridge-ecs-run-task-script
※必要なければ、次の「設定ファイルの内容」から本題のコードを見ることができるのでスキップしてください。
ECRをecs-scheduler-example-repository
というリポジトリ名で作成した上で、以下の手順でイメージのビルド、ECRへのプッシュを実施しておきます。
cd tf-eventbridge-run-ecs-task-script
echo "ECR_IMAGE_URI=ECRのイメージURI:latest" > .env
# イメージをプッシュ
scripts/build-and-push-image.sh
次に、以下の手順でタスク定義もプッシュしておきます。S3_BUCKET
で指定する名前のS3バケットは、先に作成しておきます。
また、ECS_ROLE_ARN
やECS_EXECUTION_ROLE_ARN
は今回のサンプルコードに合わせて特定の名前のものにしていますが、
自分で作ったECSタスクロール、ECSタスク実行ロールと同じ名前であればなんでもOKです。
# プロジェクトルートに移動
cd tf-eventbridge-run-ecs-task-script
echo "ECS_ROLE_ARN=arn:aws:iam::アカウントID:role/ecs-scheduler-example-ecs-task-role" >> .env
echo "ECS_EXECUTION_ROLE_ARN=arn:aws:iam::アカウントID:role/ecs-scheduler-example-ecs-task-execution-role" >> .env
echo "S3_BUCKET=結果格納先バケット名" >> .env
# タスク定義をプッシュ
scripts/push-task-definition.sh
設定ファイルの内容
最初に、どのように設定を書けば良いかコードをお見せします。
実際にterraform applyして試したい方は、以下にサンプルリポジトリを用意しているので、こちらで試すこともできます。
https://github.com/khomma0312/tf-eventbridge-run-ecs-task
eventbridge.tf
data "aws_caller_identity" "current" {}
locals {
aws_account_id = data.aws_caller_identity.current.account_id
}
resource "aws_scheduler_schedule" "ecs_run_task_scheduler" {
name = "${local.project_name}-scheduler"
group_name = "default"
flexible_time_window {
mode = "OFF"
}
# 毎月1日の4時に実行する
schedule_expression = "cron(0 4 1 * ? *)"
schedule_expression_timezone = "Asia/Tokyo"
target {
arn = aws_ecs_cluster.main.arn
role_arn = aws_iam_role.ecs_task_scheduler.arn
ecs_parameters {
task_definition_arn = "arn:aws:ecs:ap-northeast-1:${local.aws_account_id}:task-definition/${local.project_name}"
launch_type = "FARGATE"
platform_version = "LATEST"
network_configuration {
subnets = [aws_subnet.private.id]
security_groups = [aws_security_group.ecs.id]
}
}
input = jsonencode({
containerOverrides = [
{
name = local.project_name
environment = [
{
name = "JOB_NAME"
value = "bar"
}
]
}
]
})
}
}
iam.tf
data "aws_iam_policy_document" "ecs_task_assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
# ECSタスク実行Role
resource "aws_iam_role" "ecs_task_execution" {
name = "${local.project_name}-ecs-task-execution-role"
assume_role_policy = data.aws_iam_policy_document.ecs_task_assume_role.json
}
resource "aws_iam_role_policy_attachment" "ecs_task_execution" {
role = aws_iam_role.ecs_task_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
# ECSタスクRole用ポリシー
data "aws_iam_policy_document" "ecs_task" {
statement {
effect = "Allow"
actions = [
"s3:PutObject",
"s3:GetObject",
]
resources = ["*"]
}
}
resource "aws_iam_policy" "ecs_task" {
name = "${local.project_name}-ecs-task-policy"
policy = data.aws_iam_policy_document.ecs_task.json
}
# ECSタスクRole
resource "aws_iam_role" "ecs_task" {
name = "${local.project_name}-ecs-task-role"
assume_role_policy = data.aws_iam_policy_document.ecs_task_assume_role.json
}
resource "aws_iam_role_policy_attachment" "ecs_task" {
role = aws_iam_role.ecs_task.name
policy_arn = aws_iam_policy.ecs_task.arn
}
data "aws_iam_policy_document" "ecs_run_task" {
statement {
effect = "Allow"
actions = [
"ecs:RunTask"
]
resources = ["*"]
}
statement {
actions = [
"iam:PassRole"
]
resources = ["*"]
condition {
test = "StringLike"
variable = "iam:PassedToService"
values = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_policy" "ecs_task_scheduler" {
name = "${local.project_name}-scheduler-policy"
policy = data.aws_iam_policy_document.ecs_run_task.json
}
# EventBridge Scheduler Role
resource "aws_iam_role" "ecs_task_scheduler" {
name = "${local.project_name}-scheduler-role"
assume_role_policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Principal" : {
"Service" : ["scheduler.amazonaws.com"]
},
"Action" : "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_task_scheduler_policy_attachment" {
role = aws_iam_role.ecs_task_scheduler.name
policy_arn = aws_iam_policy.ecs_task_scheduler.arn
}
ecs.tf
resource "aws_ecs_cluster" "main" {
name = "${local.project_name}-cluster"
}
内容の解説
上記のコードを順に解説していきます。
ECSを定期起動するEventBridgeの実装
eventbridge.tf内のaws_scheduler_schedule
にて、ECSを起動するEventBridge Schedulerを作成しています。
ポイントはecs_parameters
の部分で、ここでECSの起動設定を行っています。
特に注意が必要なのがtask_definition_arn
で、常に最新のタスク定義を指すようにするには、「:2」などの後ろのリビジョン番号を何も指定しないでおく必要があります。
task_definition_arn = "arn:aws:ecs:ap-northeast-1:${local.aws_account_id}:task-definition/${local.project_name}"
ecs_parameters
の部分はマネジメントコンソールだと以下のような画面になっており、Terraformでもこれらの項目を指定できるようになっています。

また、コンテナの環境変数やコマンドなどの設定を上書きしたい時には、input
パラメータを使ってcontainerOverrides
で項目を指定すれば任意の値を設定できます。
今回は環境変数(JOB_NAME
)を上書きするケースを想定して実装しています。
target {
arn = aws_ecs_cluster.main.arn
role_arn = aws_iam_role.ecs_task_scheduler.arn
ecs_parameters {
task_definition_arn = "arn:aws:ecs:ap-northeast-1:${local.aws_account_id}:task-definition/${local.project_name}"
launch_type = "FARGATE"
platform_version = "LATEST"
network_configuration {
subnets = [aws_subnet.private.id]
security_groups = [aws_security_group.ecs.id]
}
}
input = jsonencode({
containerOverrides = [
{
name = local.project_name
environment = [
{
name = "JOB_NAME"
value = "bar"
}
]
}
]
})
}
ECSで使用するロールの実装
iam.tfにて、ECSで使用するECSタスクロール、ECSタスク実行ロールを作成しています。
ECSタスクロールでは、タスクで実行する処理内で必要な権限を持ったロールを設定します。
今回は、S3にオブジェクトを追加する処理がある前提で、s3:PutObject
などを指定しています。
ECSタスク実行ロールでは、タスクを実行・起動する際に必要となる権限(CloudWatch Logsへの書き出しなど)を持ったロールを設定します。今回はAWSマネージドのAmazonECSTaskExecutionRolePolicy
をポリシーとして設定しました。
EventBridgeスケジューラー用ロールの実装
iam.tfではその他に、EventBridgeスケジューラー用のロールも実装しています。
EventBridgeスケジューラー用のロールでは、ecs:RunTask
とiam:PassRole
の権限が必要になります。
詳細は以下の公式ドキュメントに記載されていますが、こちらの通り、上記2つの権限が付与されたEventBridgeスケジューラー用のロールも作成しておきます。
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/CWE_IAM_role.html
ECSクラスターの実装
ターゲット先となるECSクラスターも作成しておきます。
こちらで作成したクラスターを、前述のEventBridge Schedulerの定義時に指定しています。
実行結果
以上のコードをterraform apply
で反映すると、以下のようなECSタスクを定期実行するEventBridgeが完成します!


タスク実行後にS3を見ると、オブジェクトのputもできていることが確認できます。

終わりに
ECSタスクを起動するEventBridge SchedulerをTerraformで実装する方法を紹介しました。
シンプルな仕組みではありますが、各ロールに必要な権限やEventBridge SchedulerのECS起動の設定値など、直感的にわからない部分もあったので参考になれば幸いです。