Amazon ECSタスクを冪等に起動できるようになりました

Amazon ECSタスクを冪等に起動できるようになりました

Clock Icon2023.12.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

旧聞ですが、2023/11/13からAmazon ECSが冪等なタスク起動をサポートし、副作用無しに再試行、複数回呼び出せるようになりました。

現時点では、以下のECS APIが冪等性をサポートしています。

  • CreateService
  • CreateTaskSet
  • RunTask

冪等性を実現する場合、主に次の2通りがあります。

  • 複数回呼び出される前提で、アプリを冪等に実装
  • 一度しか呼び出されない前提で、アプリをシンプルに実装

Amazon SQSを例に取ると、at least onceなスタンダードキューが前者で、exactly onceなFIFOが後者です。

今回のECSアップデートは後者です。ECSタスクの冪等起動対応により、ライブラリ・フレームワークの力を借りながら頑張って冪等に実装していたタスクを、シンプルに実装できるようになることが期待できます。

ポイント

重要なポイントを述べます

  • 冪等にするには、起動コマンドにクライアント・トークン引数(--client-token)を渡す
  • クライアント・トークンはECSクラスターごとにユニーク判定
  • クライアント・トークンのTTLは以下の小さい
    • 24時間
    • タスクがSTOPPEDになってから+1時間
  • 2回目以降の呼び出し時には、トークンに対応する既存タスクを返却

やってみた

hello-world イメージECS:RunTask で複数回呼び出し、冪等性オプションの有無によるタスク起動の違いを確認します。

冪等でない呼び出しの場合

まずは、タスク起動に利用する環境変数を定義します。

$ CLUSTER_NAME=hello
$ SUBNET_ID=subnet-123
$ SECURITY_GROUP_ID=sg-123
$ TASK_DEF_ARN=arn:aws:ecs:ap-northeast-1:123:task-definition/hello-world:1

従来方式で同じ引数の RunTask を2回連続で呼び出すと、呼び出しただけのタスクが起動します。

$ aws ecs run-task \
  --cluster $CLUSTER_NAME \
  --task-definition $TASK_DEF_ARN \
  --network-configuration awsvpcConfiguration="{subnets=[\"$SUBNET_ID\"],securityGroups=[\"$SECURITY_GROUP_ID\"],assignPublicIp=ENABLED}"  --launch-type FARGATE

{
    "tasks": [
        {
            ...
            "containers": [
                {
                    "containerArn": "arn:aws:ecs:ap-northeast-1:123456789012:container/hello/4ba47aada3084138a0e0422df0d79f4e/752c9669-6dc4-4842-9b91-36842104dd59",
                    "taskArn": "arn:aws:ecs:ap-northeast-1:123456789012:task/hello/4ba47aada3084138a0e0422df0d79f4e",
                    "name": "hello-world",
                    "image": "hello-world",
                    "lastStatus": "PENDING",
                    "networkInterfaces": [],
                    "cpu": "0"
                }
            ],
            ...
}

$ aws ecs run-task \
...


{
    "tasks": [
        {
            ...
            "containers": [
                {
                    "containerArn": "arn:aws:ecs:ap-northeast-1:123456789012:container/hello/1a8ae93a46b14f2f9d31c52494b1687c/3d03b9db-c272-4041-bbb5-8a7024e6f668",
                    "taskArn": "arn:aws:ecs:ap-northeast-1:123456789012:task/hello/1a8ae93a46b14f2f9d31c52494b1687c",
                    "name": "hello-world",
                    "image": "hello-world",
                    "lastStatus": "PENDING",
                    "networkInterfaces": [],
                    "cpu": "0"
                }
            ],
            ...
}

この状態で ECS:ListTaks を実行すると、起動した2つのタスクを確認できます。

$ aws ecs list-tasks --cluster hello
{
    "taskArns": [
        "arn:aws:ecs:ap-northeast-1:123456789012:task/hello/1a8ae93a46b14f2f9d31c52494b1687c",
        "arn:aws:ecs:ap-northeast-1:123456789012:task/hello/4ba47aada3084138a0e0422df0d79f4e"
    ]
}

冪等な呼び出しの場合

次に、クライアント・トークンを渡して、冪等にタスク起動します。

クライアントトークンの生成には、uuidgen コマンドを利用しました。

$ uuidgen
d689009d-c5cc-437e-8ef0-9ce83f03b8a2
$ CLIENT_TOKEN=$(uuidgen)

先程の ECS:RunTask コマンドに、冪等用のオプション(--client-token $CLIENT_TOKEN)を追加します.

$ aws ecs run-task \
  --cluster $CLUSTER_NAME \
  --task-definition $TASK_DEF_ARN \
  --network-configuration awsvpcConfiguration="{subnets=[\"$SUBNET_ID\"],securityGroups=[\"$SECURITY_GROUP_ID\"],assignPublicIp=ENABLED}"  --launch-type FARGATE \
  --client-token $CLIENT_TOKEN

{
    "tasks": [
        {
            ...
            "containers": [
                {
                    "containerArn": "arn:aws:ecs:ap-northeast-1:123456789012:container/hello/a22f917c3cec4aafb2cf20bfa5ade840/bcb83d02-d3dc-465f-8010-a4ac2a999443",
                    "taskArn": "arn:aws:ecs:ap-northeast-1:123456789012:task/hello/a22f917c3cec4aafb2cf20bfa5ade840",
                    "name": "hello-world",
                    "image": "hello-world",
                    "lastStatus": "PENDING",
                    "networkInterfaces": [],
                    "cpu": "0"
                }
            ],
            ...
}

$ aws ecs run-task \
  ...
  --client-token $CLIENT_TOKEN

{
    "tasks": [
        {
            ...
            "containers": [
                {
                    "containerArn": "arn:aws:ecs:ap-northeast-1:123456789012:container/hello/a22f917c3cec4aafb2cf20bfa5ade840/bcb83d02-d3dc-465f-8010-a4ac2a999443",
                    "taskArn": "arn:aws:ecs:ap-northeast-1:123456789012:task/hello/a22f917c3cec4aafb2cf20bfa5ade840",
                    "name": "hello-world",
                    "image": "hello-world",
                    "lastStatus": "PENDING",
                    "networkInterfaces": [
                        {
                            "attachmentId": "d7d1fd18-7ebb-40cf-8f21-7a420db78f31",
                            "privateIpv4Address": "172.31.18.45"
                        }
                    ],
                    "cpu": "0"
                }
            ],
            ...
}

2度目の呼び出しのレスポンスを確認すると、1回目と同じ taskArn/containerArn が返ってきています

ECS:ListTaks コマンドからも、1タスクだけが起動していることを確認します。

$ aws ecs list-tasks --cluster hello
{
    "taskArns": [
        "arn:aws:ecs:ap-northeast-1:123456789012:task/hello/a22f917c3cec4aafb2cf20bfa5ade840"
    ]
}

起動したタスクが STOPPED になった状態で、同じ冪等コマンドをもう一度呼び出すと、トークンのTTLが切れるまでは、新規にタスクが起動することはなく、STOPPED な既存タスクが返ってきます。

$ aws ecs run-task \
  ...
  --client-token $CLIENT_TOKEN

{
    "tasks": [
        {
            ...
            "containers": [
                {
                    "containerArn": "arn:aws:ecs:ap-northeast-1:123456789012:container/hello/a22f917c3cec4aafb2cf20bfa5ade840/bcb83d02-d3dc-465f-8010-a4ac2a999443",
                    "taskArn": "arn:aws:ecs:ap-northeast-1:123456789012:task/hello/a22f917c3cec4aafb2cf20bfa5ade840",
                    "name": "hello-world",
                    "image": "hello-world",
                    "imageDigest": "sha256:c79d06dfdfd3d3eb04cafd0dc2bacab0992ebc243e083cabe208bac4dd7759e0",
                    "runtimeId": "a22f917c3cec4aafb2cf20bfa5ade840-1116541326",
                    "lastStatus": "STOPPED",
                    "exitCode": 0,
                    "networkBindings": [],
                    "networkInterfaces": [
                        {
                            "attachmentId": "d7d1fd18-7ebb-40cf-8f21-7a420db78f31",
                            "privateIpv4Address": "172.31.18.45"
                        }
                    ],
                    "healthStatus": "UNKNOWN",
                    "cpu": "0"
                }
            ],

            ...
}

ecspresso から使ってみる

ECSのデプロイツールである https://github.com/kayac/ecspressov2.3 から冪等実行に対応しています。

$ CLIENT_TOKEN=$(uuidgen)

$ ecspresso run --config config.yaml --client-token $TOKEN       
2024/05/05 13:34:29 [INFO] ecspresso version: v2.3.3-nightly-c0a0d502edc58db5db9acdbed5cc96463c5bd554
...
2024/05/05 13:35:21 hello/hello-task Run task completed!

$ ecspresso run --config config.yaml --client-token $TOKEN
2024/05/05 13:52:58 [INFO] ecspresso version: v2.3.3-nightly-c0a0d502edc58db5db9acdbed5cc96463c5bd554
...
2024/05/05 13:52:58 [ERROR] FAILED. failed to run task: operation error ECS: RunTask, https response error StatusCode: 400, RequestID: cd839ac4-c62a-4fed-a9ed-09f1f36069c4,
  ConflictException: The RunTask request could not be processed due to conflicts.
  The provided clientToken is already in use with a different request.

2度目の実行時には、トークン重複の旨のメッセージが表示されています。

なお、スタンドアロンタスク実行時も、タスクを実行するサブネット・セキュリティグループなどネットワーク情報が必要なため、サービス定義も必要な点にご留意ください *1

まとめ

冪等性の実装は難しく、理想と現実の乖離を埋めるのも大変です。

ECSのタスクを冪等に処理する場合、今回紹介した冪等な起動オプションを利用すると、呼び出し側や呼び出される側で考慮すべき範囲がグッと減るのではないでしょうか?

冪等性のありがたみは、障害対応や原因調査で痛い目にあわないと実感しづらいかも知れません。AWS上の冪等性入門としては、次のAWSジャパンさまによる builders.flash の連載がオススメです。

サーバーレスが気になる開発者に捧ぐ「べき等性」ことはじめ 第一回〜べき等性 (冪等性/idempotency) ってなんだ!? - builders.flash☆ - 変化を求めるデベロッパーを応援するウェブマガジン | AWS

参考

脚注

  1. https://github.com/kayac/ecspresso/issues/405

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.