Amazon ECS タスクの停止理由 (エラー内容) を CloudWatch Logs に保存する方法とその分析をしてみた
はじめに
AWS Fargateのタスクの停止理由を確認する方法が分からず詰まったため、今回記事を書きました。
ECSのコンソール上では、タスクが停止してから1時間以内であれば、以下のように停止理由であるエラー内容を確認できます。
ただし、上記のエラーは、1時間経過すると、以下のように見られなくなります。
過去にさかのぼって確認したい場合、CloudWatch logsなどに保存する必要があります。
今回は、ECSタスクの停止理由をさかのぼって確認するために、タスクが停止になった時のイベント情報をCloudWatch logsに保存する方法について、解説します。
構成図
構成図は、下記です。
汎用性がありますので、CloudFormationで、イベントルールとロググループを作成します。
流れとしては、ECSタスクが停止した時に、EventBridgeがトリガーとなり、ECSタスクの停止ログをCloudWatch Logsに保存します。
また、CloudWatch Logs Insightsでログデータを検索して分析をしてみます。
CloudFormationでスタックを作成
こちらのテンプレート通りに、スタックを作成します。
CloudFormationテンプレート (クリックすると展開します)
AWSTemplateFormatVersion: 2010-09-09 Description: Deploys the resources needed to store events of Amazon ECS stopped tasks in CloudWatch Logs Parameters: CWLogGroupName: Type: String Description: The CloudWatch log group name to store the events Default: /aws/events/ECSStoppedTasksEvent CWLogGroupRetention: Type: Number Description: The number of days to retain the events in the log group AllowedValues: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653] Default: 30 Resources: EventRule: Type: AWS::Events::Rule Properties: Name: ECSStoppedTasksEvent Description: Triggered when an Amazon ECS Task is stopped EventPattern: source: - aws.ecs detail-type: - ECS Task State Change detail: desiredStatus: - STOPPED lastStatus: - STOPPED State: ENABLED Targets: - Arn: !GetAtt LogGroup.Arn Id: ECSStoppedTasks LogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Ref CWLogGroupName RetentionInDays: !Ref CWLogGroupRetention LogEventsPolicy: Type: AWS::Logs::ResourcePolicy Properties: PolicyName: LogEventsPolicy PolicyDocument: !Sub | { "Version": "2012-10-17", "Statement": [ { "Sid": "LogEventsPolicy", "Effect": "Allow", "Principal": { "Service": [ "delivery.logs.amazonaws.com", "events.amazonaws.com" ] }, "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "${LogGroup.Arn}" } ] }
スタックを作成し、CREATE_COMPLETEになったことを確認します。
リソースの確認
スタックの作成により、リソースは2つ作成されますので、確認しておきます。
CloudWatch ロググループ
/aws/events/ECSStoppedTasksEvent
というロググループ名で作成されています。
EventBridgeのルール
ルール名ECSStoppedTasksEvent
で作成されています。
イベントパターンは、ECSがSTOPPED
のステータスであり、ターゲットは、CloudWatch ロググループの/aws/events/ECSStoppedTasksEvent
です。
イベントパターンを特定のクラスターにする
現状のEventBridgeのイベントパターンですと、すべてのクラスターの停止したタスク理由が保存されます。
{ "source": ["aws.ecs"], "detail-type": ["ECS Task State Change"], "detail": { "desiredStatus": ["STOPPED"], "lastStatus": ["STOPPED"] } }
クラスターを以下の通りに指定してあげることで、特定のクラスターのタスクのみをログに残すこともできます。
{ "source": ["aws.ecs"], "detail-type": ["ECS Task State Change"], "detail": { "clusterArn": ["arn:aws:ecs:ap-northeast-1:[アカウントID]:cluster/[クラスター名]"], "desiredStatus": ["STOPPED"], "lastStatus": ["STOPPED"] } }
ちなみに、特定のクラスターの特定のタスク定義に絞ることも可能です。
{ "source": ["aws.ecs"], "detail-type": ["ECS Task State Change"], "detail": { "taskDefinitionArn": ["arn:aws:ecs:ap-northeast-1:[アカウントID]:task-definition/[タスク定義名]:[バージョン数]"], "clusterArn": ["arn:aws:ecs:ap-northeast-1:[アカウントID]:cluster/[クラスター名]"], "desiredStatus": ["STOPPED"], "lastStatus": ["STOPPED"] } }
リソースの絞り方は、後述するCloudWatch Logsに出力される「ログの1例」を参考にしましょう。
ただし、今回は、すべてのクラスターでタスクの停止をログに残すようにします。
異常終了のタスク停止のみをログに出力する
ローリングアップデートによるデプロイのエラーメッセージ「Scaling activity initiated by〜」は検知しないように設定も可能です。
また、ContainersのexitCodeが0以外が異常終了であり、イベントパターンで除外することも可能です。
{ "source": ["aws.ecs"], "detail-type": ["ECS Task State Change"], "detail": { "clusterArn": ["arn:aws:ecs:ap-northeast-1:[アカウントID]:cluster/[クラスター名]"], "desiredStatus": ["STOPPED"], "lastStatus": ["STOPPED"], "containers": { "exitCode": [ { "anything-but": 0 } ] }, "stoppedReason": [ { "anything-but": { "prefix": "Scaling activity initiated by" } } ] } }
タスク停止してみる
ECSのタスクの起動を失敗させるため、プライベートサブネット内でパブリック IPを付与させる設定でタスクを起動し、タスクを停止させました。
/aws/events/ECSStoppedTasksEvent
を見てみると、json形式でログが出力されていました。
~省略~ "stoppedReason": "ResourceInitializationError: unable to pull secrets or registry auth: execution resource retrieval failed: unable to retrieve ecr registry auth: service call has been retried 3 time(s): RequestError: send request failed caused by: Post \"https://api.ecr.ap-northeast-1.amazonaws.com/\": dial tcp 3.112.64.212:443: i/o timeout. Please check your task network configuration." ~省略~
「ログの1例」です。 (クリックすると展開します)
{ "version": "0", "id": "c4b21fc6-c933-7f28-00cd-4f7162c5b304", "detail-type": "ECS Task State Change", "source": "aws.ecs", "account": "[アカウントID]", "time": "2023-04-14T18:07:00Z", "region": "ap-northeast-1", "resources": [ "arn:aws:ecs:ap-northeast-1:[アカウントID]:task/nginx/dcf8140fcd23495bb7c8bb3919fd485a" ], "detail": { "attachments": [ { "id": "67f94f8d-b24a-4066-bcb6-e47df4bf2e93", "type": "elb", "status": "DELETED", "details": [] }, { "id": "e2b1dc3a-ade7-4188-92ff-d36fe3ebf504", "type": "eni", "status": "DELETED", "details": [ { "name": "subnetId", "value": "subnet-0bbe65b1222f92210" }, { "name": "networkInterfaceId", "value": "eni-08caee15559291301" }, { "name": "macAddress", "value": "0a:bc:9f:87:01:5f" }, { "name": "privateDnsName", "value": "ip-10-0-1-72.ap-northeast-1.compute.internal" }, { "name": "privateIPv4Address", "value": "10.0.1.72" } ] } ], "attributes": [ { "name": "ecs.cpu-architecture", "value": "x86_64" } ], "availabilityZone": "ap-northeast-1c", "capacityProviderName": "FARGATE_SPOT", "clusterArn": "arn:aws:ecs:ap-northeast-1:[アカウントID]:cluster/nginx", "connectivity": "CONNECTED", "connectivityAt": "2023-04-14T17:29:26.067Z", "containers": [ { "containerArn": "arn:aws:ecs:ap-northeast-1:[アカウントID]:container/nginx/dcf8140fcd23495bb7c8bb3919fd485a/3ee885e1-3001-4e2f-bb31-d843bdfa2cff", "lastStatus": "STOPPED", "name": "nginx", "image": "[アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/nginx:dev", "imageDigest": "sha256:fd0f22e46bd78637783815f3ea5f98ff3d09de764789eaccf1ca412d97743563", "runtimeId": "dcf8140fcd23495bb7c8bb3919fd485a-2531612879", "taskArn": "arn:aws:ecs:ap-northeast-1:[アカウントID]:task/nginx/dcf8140fcd23495bb7c8bb3919fd485a", "networkInterfaces": [ { "attachmentId": "e2b1dc3a-ade7-4188-92ff-d36fe3ebf504", "privateIpv4Address": "10.0.1.72" } ], "cpu": "0" } ], "cpu": "256", "createdAt": "2023-04-14T17:29:22.086Z", "desiredStatus": "STOPPED", "enableExecuteCommand": false, "ephemeralStorage": { "sizeInGiB": 20 }, "group": "service:nginx", "launchType": "FARGATE", "lastStatus": "STOPPED", "memory": "512", "overrides": { "containerOverrides": [ { "name": "nginx" } ] }, "platformVersion": "1.4.0", "pullStartedAt": "2023-04-14T17:29:53.482Z", "pullStoppedAt": "2023-04-14T17:29:58.077Z", "startedAt": "2023-04-14T17:30:13.12Z", "startedBy": "ecs-svc/4374651048025955904", "stoppingAt": "2023-04-14T18:00:53.753Z", "stoppedAt": "2023-04-14T18:07:00.681Z", "stoppedReason": "ResourceInitializationError: unable to pull secrets or registry auth: execution resource retrieval failed: unable to retrieve ecr registry auth: service call has been retried 3 time(s): RequestError: send request failed caused by: Post \"https://api.ecr.ap-northeast-1.amazonaws.com/\": dial tcp 3.112.64.212:443: i/o timeout. Please check your task network configuration.", "stopCode": "SpotInterruption", "taskArn": "arn:aws:ecs:ap-northeast-1:[アカウントID]:task/nginx/dcf8140fcd23495bb7c8bb3919fd485a", "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:[アカウントID]:task-definition/nginx:1", "updatedAt": "2023-04-14T18:07:00.681Z", "version": 8 } }
これで、いつでもタスクの停止理由を確認することができます。
CloudWatch Logs Insightsを利用し、分析する
CloudWatch Logs Insights を使用すると、ログデータを簡単に検索して分析できます。
今回は、停止したタスクのうち、確認したいタスクの停止理由を検索するクエリをご紹介します。
クエリは、2つ実行します。
- 停止したタスクのタスクIDを検索する
- クエリ:
fields detail.taskArn | filter detail.clusterArn like "クラスター名" and detail.group like "サービス名"
- クエリ:
- 停止したタスクのうち、確認したいタスクのタスクIDから、タスク理由を表示する
- クエリ:
fields detail.stoppedReason, detail.containers.0.name, detail.containers.0.exitCode | filter detail.taskArn like "タスクID"
- クエリ:
詳しいCloudWatch Logs Insights のクエリ構文は、こちらのドキュメントを一読ください
実際の操作画面を表示しながら、クエリを実行します。
私の場合、クラスター名は、nginx
、サービス名は、nginx-service
です。
- [Logs Insightsで表示]をクリックします。
- ロググループと時間を設定後、1つめのクエリを実行しますと、タスクのIDが表示されます。今回、タスクIDの
c92d1197454846d092efca65a4429599
のタスク停止理由を見てみます。 - 2つめのクエリを入力し実行します。
- 停止理由は、
Task stopped by user
、つまりユーザーがタスクを停止したと分かりました exitCode
は、こちらが参考になります。今回は、0
とあるため、ユーザーによって停止と分かります。
- 停止理由は、
他のタスクIDを実行すると、detail.containers.0.exitCode
がないものもありますね。
他の停止理由やエラー内容は、公式ドキュメントで解説されているため、ご参考ください。