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

2023.12.12

旧聞ですが、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
2511C3F0-F693-4B4A-9E4B-0FA65B72DBFE
$ 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"
                }
            ],

            ...
}

まとめ

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

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

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

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

参考