Amazon ECSのrun-taskでタスク実行が失敗する

Go言語で書いたプッシュ通知システムをAmazon ECS上で構築・運用し始めてから安定稼働していました。

ある日、朝会でチームメンバーとシステムのメトリクスを見ていたら、SQSのメッセージが前日から滞留している不穏なメトリクスを確認しました。

その障害内容と調査内容、原因、対応内容について備忘録として書いていきます。

プッシュ通知システムの前提知識・その他

  • Amazon ECS on Fargate上で構築・運用(Fargate Platform Versionは1.4.0)
  • Go言語で実装
  • プッシュ通知システムはいくつかの種類のECSタスク(以下、タスクと呼ぶ)で構成されている
    • その中でいくつかのタスクは他のタスクから Amazon ECSの run-task APIをコールして実行されている
  • 障害が発生した当日に、別チームがVPCのデフォルトセキュリティグループを削除する対応を行っていた

障害内容

Amazon ECSの run-task APIをコールして実行されるタスクが起動に失敗しており、ステータスが PENDINGSTOPPED となっており、メッセージの処理ができず、プッシュ通知を送信できていませんでした。

調査内容

エラー内容を確認する

タスクのステータスが PENDINGSTOPPED の状態ではアプリケーションはログを出力しませんが、AWSの管理コンソールからタスクの停止理由を確認することができます。

確認すると以下のように表示されていました。

ResourceInitializationError: unable to pull secrets or registry auth: execution resource retrieval failed: unable to retrieve secrets from ssm: service call has been retried 5 time(s): RequestCanceled: request context canceled caused by: context deadli...

ドキュメントでエラーコードの内容を確認すると、以下のように記載がありました。

docs.aws.amazon.com

このエラーは、コンテナの起動に必要なリソースやタスクの所属先のリソースを作成またはブートストラップすることに Fargate エージェントが失敗した場合に発生します。 このエラーは、プラットフォームバージョン 1.4 以降を使用している場合にのみ発生します。

エラー内容やドキュメントを見て、イメージの取得やネットワーク周りが原因だろうと考えました。

アプリケーションコードを確認する

run-task を実行する場合に以下のようなインプットを渡して実行しています。

func (c ECSClient) NewRunTaskInput(clusterName string, arn string, subnets []string, securityGroupId *string, count int64) ecs.RunTaskInput {
    input := ecs.RunTaskInput{
        Cluster:        aws.String(clusterName),
        PlatformVersion: aws.String("1.4.0"),
        Count:          aws.Int64(count),
        TaskDefinition: aws.String(arn),
        NetworkConfiguration: &ecs.NetworkConfiguration{
            AwsvpcConfiguration: &ecs.AwsVpcConfiguration{
                Subnets: aws.StringSlice(subnets),
            },
        },
        LaunchType: aws.String("FARGATE"),
    }
    if securityGroupId != nil {
        input.NetworkConfiguration.AwsvpcConfiguration.SecurityGroups = []*string{securityGroupId}
    }
    return input
}

今回障害が発生してしまったタスクに関しては、セキュリティグループのIDを引数として渡していないのを確認しました。(それでも今までは動作していた)

おそらくこのSecurityGroupを指定していないことが原因だろうと思い、アウトバウンドを許可したセキュリティグループのIDを渡すように書き換えたところ、検証環境で正常に動作することが確認できました。

原因

いくつかの要因が重なり、障害が発生しました。

  • 障害発生当日に別チームがセキュリティリスクを減らすため、VPCのデフォルトのセキュリティグループを削除する対応を行っていた
  • 障害が発生したプッシュ通知システムのタスクでは特定のセキュリティグループを指定せずに実行していた

特定のセキュリティグループIDを指定せずに run-task を実行すると、どうなるのか?

aws-sdk-goのコメントに記載がありました。

aws-sdk-go > service > ecs > api.go

// The security groups associated with the task or service. If you do not specify
// a security group, the default security group for the VPC is used. There is
// a limit of 5 security groups that can be specified per AwsVpcConfiguration.
//
// All specified security groups must be from the same VPC.
SecurityGroups []*string `locationName:"securityGroups" type:"list"`

特定のセキュリティグループIDがある場合はそれを使用し、指定がない場合はVPCのデフォルトのセキュリティグループを利用して実行されると明記されています。

つまり、今までVPCのデフォルトセキュリティグループで実行されていたタスクは、デフォルトのセキュリティグループが削除されたことでアタッチできず、ECRと通信ができなくなりイメージを取得できなくなった。

その結果、タスクの実行に失敗していました。

対応内容

run-task 実行時のインプットにセキュリティグループIDを指定するよう修正しました。

まとめ

  • ちゃんとライブラリのコードを読もう
  • run-task 実行時のインプットには必ず適切なセキュリティグループIDを指定しよう
    • VPCのデフォルトのセキュリティグループを使っちゃダメ