この記事は個人ブログと同じ内容です
www.ritolab.com
Amazon ECS には タスクをスケジューリングして動作させることのできる機能があり、これを用いることで毎日走らせたい処理など定期的に行いたい処理を実行する事ができます。
今回は AWS ECS の「タスクのスケジューリング」を使って、タスクを定期的に実行してみます。
ECS タスクのスケジューリング
ECS の画面から設定が可能ですが、内部的には CloudWatch Events Rule を作成してスケジュールを構成しているようですね。
Amazon ECS タスクのスケジューリング
docs.aws.amazon.com
ECS の構築
まずはベースとなるタスク定義やクラスタを作成しておきます。
- Terraform (v0.14.3) で行います。
- 起動タイプは FARGATE です。
- コンテナのイメージは ECR に登録済みの前提です。
IAM の作成
ECS でタスクを実行する IAM Role 作成します。
main.tf
resource "aws_iam_role" "ecs_scheduler_task_execution" {
name = "EcsTaskExecutionRole-sample"
assume_role_policy = file("policies/iam_role/ecs_task_execution.json")
}
resource "aws_iam_policy" "ecs_scheduler_task_execution" {
name = "EcsTaskExecutionPolicy-sample"
description = "Ecs Task Execution"
policy = file("policies/iam_policy/ecs_task_execution.json")
}
resource "aws_iam_role_policy_attachment" "ecs_scheduler_task_execution" {
role = aws_iam_role.ecs_scheduler_task_execution.name
policy_arn = aws_iam_policy.ecs_scheduler_task_execution.arn
}
読み込んでいる各ファイルの内容は以下です
policies/iam_role/ecs_task_execution.json
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
policies/iam_policy/ecs_task_execution.json
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
ECS のタスク定義・クラスタを作成します。
main.tf
resource "aws_cloudwatch_log_group" "ecs_scheduler" {
name = "/ecs-scheduler"
}
resource "aws_ecs_task_definition" "task_scheduler" {
family = "scheduler"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = "256"
memory = "512"
container_definitions = templatefile("task-definitions/scheduler.json", {
log_group_name = aws_cloudwatch_log_group.ecs_scheduler.name
})
execution_role_arn = aws_iam_role.ecs_scheduler_task_execution.arn
}
resource "aws_ecs_cluster" "task_scheduler" {
name = "scheduler-cluster"
}
読み込んでいる各ファイルの内容は以下です
[
{
"name": "<< CONTAINER-NAME >>",
"image": "<< AWS-ID >>.dkr.ecr.<< REGION >>.amazonaws.com/<< CONTAINER-IMAGE-NAME >>:<< CONTAINER-IMAGE-TAG >>",
"cpu": 128,
"memory": null,
"memoryReservation": 128,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "${log_group_name}",
"awslogs-region": "<< REGION >>",
"awslogs-stream-prefix": "scheduler",
"awslogs-datetime-format": "%Y-%m-%d %H:%M:%S"
}
}
}
]
<< ... >> としている部分は各々で必要な値が入ります。
これでベースとなる ECS 環境が構築できました。
タスクのスケジューリング設定
ここから ECS のタスクスケジューリングを設定していきます。
Cloudwatch Events を設定することでタスクスケジューリングを実現します。
IAM Role の作成
CloudWatch Events の IAM Role を作成します。
main.tf
resource "aws_iam_role" "ecs_events" {
name = "EcsEventsRole-sample"
assume_role_policy = file("policies/iam_role/ecs_events.json")
}
resource "aws_iam_policy" "ecs_events" {
name = "EcsEventsPolicy-sample"
policy = templatefile("policies/iam_policy/ecs_events.json", {
// リビジョンは固定しない
task_definition_arn = replace(aws_ecs_task_definition.task_scheduler.arn, "/:\\d+$/", "")
})
}
resource "aws_iam_role_policy_attachment" "ecs_events" {
policy_arn = aws_iam_policy.ecs_events.arn
role = aws_iam_role.ecs_events.name
}
1 点だけ注意するポイントがあります。IAM Policy 作成時のポリシー定義で、ECS タスク定義の ARN を指定しますが、この値はリビジョンを除いたものを渡します。
リビジョンが指定されたままの ARN で ポリシーを作成してしまうと当然ながらそのリビジョンでのみ実行可能なポリシーになってしまうため、例えばアプリケーションの新たなリリースを行ってイメージを更新した(イメージのタグを更新した、など)際にはタスク定義のリビジョンが一つ上がるので、作成したポリシーではスケジューリング実行ができなくなってしまいます。
他、読み込んでいる各ファイルの内容は以下です。
policies/iam_role/ecs_events.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
policies/iam_policy/ecs_events.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "ecs:RunTask",
"Resource": "${task_definition_arn}"
}
]
}
タスクスケジューリング
スケジュールを設定します。
main.tf
resource "aws_cloudwatch_event_rule" "ecs_scheduled_task" {
name = "ecs-scheduled-task"
schedule_expression = "cron(20 2 * * ? *)"
}
resource "aws_cloudwatch_event_target" "ecs_scheduled_task" {
arn = aws_ecs_cluster.task_scheduler.arn
rule = aws_cloudwatch_event_rule.ecs_scheduled_task.name
role_arn = aws_iam_role.ecs_events.arn
target_id = "scheduler-target"
input = file("container-overrides/ecs_scheduled_task.json")
ecs_target {
// リビジョンなしで渡すことで常に最新のバージョンを使用するようにする
task_definition_arn = replace(aws_ecs_task_definition.task_scheduler.arn, "/:\\d+$/", "")
task_count = 1
launch_type = "FARGATE"
platform_version = "1.4.0"
network_configuration {
subnets = [aws_subnet.private_1.id,aws_subnet.private_2.id]
assign_public_ip = false
}
}
}
以下、いくつかポイントがあります。
スケジュールの設定
CloudWatch Event Rule の設定において schedule_expression を指定していますが、ここでどういったスケジュールで動作させるのかを指定します。
上記のような cron 式、または rate 式で記述できます。
ルールのスケジュール式
docs.aws.amazon.com
1 つ注意なのが、 cron 式を使う場合 AWS 上では UTC で実行されるため、日本のタイムゾーンで考えると時刻指定は -9h で行う必要があります。
今回は、毎日 11:20 に実行されるように設定しました。
サブネットの指定
network_configuration の値について、ここではマルチ AZ かつプライベートサブネットに ECS を展開しているため subnets および assign_public_ip は上記のような指定になっています。
サブネットの指定などは自身の環境に合わせて設定を行ってください。
Resource: aws_cloudwatch_event_target
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target
ContainerOverride
読み込んでいる各ファイルの内容は以下です
container-overrides/ecs_scheduled_task.json
{
"containerOverrides": [
{
"name": "<< CONTAINER-NAME >>",
"command": ["php", "artisan", "sample:logger"]
}
]
}
ここでは ContainerOverride の値を設定しています。コンテナ起動時のデフォルトコマンドがここで指定したもので上書きされます。
タスク起動時にここで指定したコマンドが実行されるイメージです。
ContainerOverride
docs.aws.amazon.com
今回はサンプルとして、PHP フレームワークである Laravel のコマンドを動かす想定として、artisan コマンドを記述しています。
コンテナ起動時のデフォルトコマンドを上書きしたことによって動作は以下のようになります。
- スケジューリングによって指定した時間にコンテナが起動する
- コマンド php artisan sample:logger を実行する
- 処理が終了したらタスクが終了する
動作確認
ECS でのタスクスケジューリングの設定が完了したので、AWS コンソール画面から確認してみます。
ECS クラスタの画面からタスクのスケジューリングタブを選択すると、スケジュールが設定されている事が確認できます。
また、CloudWatch Events の Rule を確認すると、指定の通りに毎日 11:20 にトリガーが設定されている事が確認できます。
時間になったらタスクが起動しました
タスクを実行した際にログを出力するようにしておいたのでそちらも確認してみます。
スケジューリングでのタスク実行が動作している事を確認できました。
まとめ
ECS のタスクスケジューリングを使う事で、タスク実行をスケジュール化できました。
定期的に実行するような処理は ECS のタスクのスケジューリングでいい感じに行えそうでした。