ECS Scheduled Taskで定期実行するバッチを作成する
今回はECS Scheduled Task(スケジュールされたタスク)を使って定期実行するバッチを作成していきます。
AWS公式ページは以下です。
他のソリューション
ECS Scheduled Taskを使って定期実行するバッチを作成する場合、LambdaやAWS Batchなど他の候補も出てくると思います。
Lambdaは実行時間や、実行リソースに制限があったりします。実行時間は15分までと決まっております。そのため、実行時間が15分以上かかる処理は実行することができません。小規模なバッチ処理に有効です。
AWS Batchは大規模なバッチや複数のバッチを連携して実行させる際に有効な手段です。
バッチ処理をスケーラブルかつ、簡単に実行することができるようです。
公式ドキュメントを見ると、計算科学や金融サービスなどでの本当に大規模なバッチ処理で使用するようです。
今回は「中規模なバッチ処理で、実行時間は15分少し超えるくらい」としたいと思います。
なので、LambdaやAWS Batchは使用せずにECS Scheduled Taskを使ってバッチ処理をしたいと思います。
ECSの作成
ECSを作っていきます。
起動タイプはFargateを使い、cpuやメモリについては今回はサンプルなので一番低い値(cpuが256、memoryが512)とします。
ECRはすでに登録済みで、タスク定義にはリポジトリのURIを指定するだけで良いとします。
IAMロール
まずはIAMロールです。
iam.tf
resource "aws_iam_role" "ecs_role" {
name = "EcsTaskExecutionRole-sample"
assume_role_policy = file("policies/ecs-task-assume-role.json")
}
resource "aws_iam_policy" "ecs_task_execution_policy" {
name = "EcsTaskExecutionPolicy-sample"
policy = file("${path.module}/policies/ecs-task-execution-policy.json")
}
resource "aws_iam_role_policy_attachment" "task_execution_policy_attachment" {
role = aws_iam_role.ecs_role.name
policy_arn = aws_iam_policy.ecs_task_execution_policy.arn
}
policies/ecs-task-assume-role.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
policies/ecs-task-execution-policy.json
CloudWatchにログを吐き出す権限と、ECRのFullAccessと同等の権限を入れています。
(FullAccessは不要な権限も入れてしまうのでセキュリティ上あまりよくないです。。。)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents",
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:DescribeImages",
"ecr:BatchGetImage",
"ecr:GetLifecyclePolicy",
"ecr:GetLifecyclePolicyPreview",
"ecr:ListTagsForResource",
"ecr:DescribeImageScanFindings"
],
"Resource": "*"
}
]
}
タスク定義
ここで、実際に実行されるタスクの定義をしていきます。
ここで指定した情報からEventBridgeがタスクを立ち上げて、バッチを実行してくれます。
task_def.tf
resource "aws_ecs_task_definition" "task_def" {
family = "ecs-scheduled-batch-task"
network_mode = "awsvpc"
cpu = 256
memory = 512
requires_compatibilities = ["FARGATE"]
container_definitions = file("./container_def/container_def.json")
execution_role_arn = aws_iam_role.ecs_role.arn
task_role_arn = aws_iam_role.ecs_role.arn
}
container_def/container_def.json
[
{
"name": "<<Task Name>>",
"image": "<<ECR Repository>>",
"cpu": 256,
"memory": 512,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/fargate-task-definition",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
},
"essential": true
}
]
EventBridgeの作成
続いて、EventBridgeの作成をしていきます。
先ほどまでは実際に実行される側の定義をしてきましたが、今度は実行する側の定義をしていきます。
IAMロール
実行する側はどんなことをするか、権限を決めていきます。
基本的にタスクを実行するだけなので、RunTaskのみを権限として指定しています。
iam.tf
最初に作成したiam.tfの続きに書いていきます。
resource "aws_iam_role" "events_role" {
name = "EcsEventsRole-sample"
assume_role_policy = file("./policies/events-assume-role.json")
}
resource "aws_iam_policy" "events_policy" {
name = "EcsRunTaskEventsPolicy"
policy = templatefile("./policies/events-policy.json", { task_definition_arn = aws_ecs_task_definition.task_def.arn })
}
resource "aws_iam_role_policy_attachment" "events_role_attachment" {
role = aws_iam_role.events_role.name
policy_arn = aws_iam_policy.events_policy.arn
}
policies/events-assume-role.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
policies/events-policy.json
templateの記法を使って、tfファイルからtask_definition_arn
という変数を受け取っています。
ここでは先ほど作成したタスク定義のARNを指定しています。
先ほど作成したタスク定義のみをRunTaskで実行できる権限を持っているということになります。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "ecs:RunTask",
"Resource": "${task_definition_arn}"
}
]
}
EventBridge
events.tf
最後にEventBridgeを作っていきます。
task_definition_arnで先ほど作成したタスク定義のARNを指定します。
また、実際にどこで実行されるのか、ネットワークやセキュリティグループの指定もする必要があります。
また、aws_cloudwatch_event_targetにて、inputでcontainerOverridesを指定しています。
ここで指定したコマンドがタスク起動時に実行されます。
resource "aws_cloudwatch_event_target" "scheduled_batch" {
target_id = "hello"
arn = var.ecs_cluster_arn
rule = aws_cloudwatch_event_rule.scheduled_batch.name
role_arn = aws_iam_role.events_role.arn
ecs_target {
task_count = 1
task_definition_arn = aws_ecs_task_definition.task_def.arn
launch_type = "FARGATE"
platform_version = "1.4.0"
enable_execute_command = true
network_configuration {
subnets = "<<Subnet ID>>"
security_groups = ["<<>Security Group ID>>"]
assign_public_ip = true
}
}
input = jsonencode(
{
containerOverrides = [
{
name = "hello",
command = ["echo", "Hello,World!"]
}
]
}
)
}
resource "aws_cloudwatch_event_rule" "scheduled_batch" {
name = "hello"
is_enabled = true
schedule_expression = "cron(*/30 * * * ? *)"
}
動作確認
実際にAWSに反映させてみます。以下のコマンドで反映させます。
terraform apply
EventBridgeのルールを確認すると、作成したイベントがリストに表示されていることがわかります。
中を確認すると、30分ごとにスケジューリングされていることがわかります。
30分ごとに実行されるのを待ちます。
ログはCloudWatch Logsに出力されるように設定しました。CloudWatch Logsに出力されることを確認してみます。
無事実行されました!
Discussion