ECS on Fargate 1.4.0で「ResourceInitializationError」を解決する方法

こんにちは。ソーシャル経済メディア「NewsPicks」で検索システムを開発しております崔(ちぇ)です。

弊社の検索システムはAWS EC2(Elastic Compute Cloud、以下、EC2)で動いていました。それを昨年、Amazon ECS(Elastic Container Service、以下、ECS)に移行しました。前回のブログでは、移行のために調べた「アプリケーションをコンテナ化するベストプラクティス」をまとめましたので、ご興味ある方は読んでいただけると嬉しいです。

tech.uzabase.com

今日は、ECS on Fargateのタスク起動に手こずった話をしてみようと思います。タイトル通りFargate 1.4.0 で発生しうる ResourceInitializationError の解決方法について述べるのですが、「まさに今それにハマってた!」という方はぜひ読んでみてください。

検索システムはECS on Fargateを採用していますがNewsPicksではケースによりECS on EC2も採用しております。詳しく比較した記事も公開しておりますので、ご興味ある方はこちらの記事もご確認ください。

tech.uzabase.com

ECS on Fargate でシステムを構築する

AWS Fargate(以下、Fargate)はECSで利用可能なコンテナの実行環境で、プロビジョニングやスケール、OSの管理などといったインスタンスの運用が不要です。

現在Amazon Linux 2とMicrosoft Windows 2019 Server Fullをサポートしています。詳しくは以下のドキュメントをご参照ください。

ResourceInitializationError に遭遇してしまった

ResourceInitializationErrorFargate1.4.0からしか発生しないエラーで、エラー名からわかるように、「タスクを起動するために必要なリソースが用意できないので起動ができないよ!」というものです。その他のエラーに関しては、こちらからご確認できます。

ResourceInitializationError

This error occurs when the container agent for your Fargate task fails to create or bootstrap the resources required to start the container or the task it belongs to.

A common cause for this error is using a VPC that doesn't have DNS resolution enabled.

This error only occurs if you use platform version 1.4.0 or later (Linux) or 1.0.0 or later (Windows).

形式は以下の通りです。

Example: ResourceInitializationError: failed to initialize logging driver: <reason>

このエラーがFargate 1.4.0でしか発生しないのは、1.3.0 ~ 1.4.0のチェンジログを辿ればわかります。AWSのブログAWSのドキュメントによると、いろんな変化がありましたが、その中でも「Task elastic network interface (ENI) now runs additional traffic flows」が影響しています。

FYI

Amazon ENIは、VPC上にある論理ネットワークの一つの要素で、仮想的なNIC(Network Interface Card、あるネットワークに接続するためのカード型の拡張装置)といえます。論理ネットワークというのは、物理的に接続せずIPやMACアドレスなどで通信するものを指します。

軽くまとめると、今までFargate ENIを使ってAWSのVPCエンドポイントにアクセスしていたものを、すべてタスクENIを使うようにしたという話が書かれています。

  • Fargate ENI:AWSが管理するVPCを経由し、AWSの特定のサービスのVPCエンドポイントにアクセスする
  • タスクENI:ユーザが管理するVPCを経由し、AWSの特定のサービスのVPCエンドポイントにアクセスする

理由としては、ユーザが管理するVPCを経由させ、トラフィックの流れをログから監視できるようにしたかった(そうして欲しいとの要望が多かった)らしいです。

上記に添付したAWSのブログに乗っていますが、変更の対象になったサービスと処理は以下です。

  • Amazon ECR(以下、ECR)へのログイン
  • AWS Secrets Manager(以下、Secrets Manager)からシークレットを取得
  • AWS Systems Manager(以下、SSM)からシークレットを取得

ECSのタスクは、内部で動くアプリケーションの通信以外にも、以下のような処理のためにAWSの各サービスと通信する必要があります。

  • Amazon CloudWatch Logs(以下、CloudWatch Logs)にログを出力する
  • ECRからイメージをpullする
  • Secrets Managerからシークレットを取得する
  • SSMからシークレットを取得する

つまり、Fargate 1.4.0から我々の管理するVPCにENIを作成するので、Fargateがこれらの処理を問題なく完了できるように、AWSサービスごとにVPCエンドポイントを用意する必要があります。

Starting with platform version 1.4.0, all Amazon ECS on Fargate tasks receive a single elastic network interface (referred to as the task ENI) and all network traffic flows through that ENI within your VPC and will be visible to you through your VPC flow logs.

それができていない場合に、以下のような ResourceInitializationError が発生します。

ResourceInitializationError: 
    unable to pull secrets or registry auth: 
    execution resource retrieval failed: 
    unable to retrieve secret from asm: 
    service call has been retried 5 time(s): 
    failed to fetch secret arn:aws:secretsmanager:{region}:{accountId}:secret:{secretName} from secrets manager: 
    RequestCanceled: request context canceled caused by: context deadline exceeded. Please check your task network configuration.

FYI

エラーが発生したらタスクは停止されますが、ECSサービスのDeployments > Eventsからタスクのページを開き、エラー内容を確認することができます。ただ、アクセスできるのは停止されてから最大1時間までなので、注意が必要です。

Fargate 1.4.0 を使う上で満たすべき条件

どういうVPCエンドポイントが必要なのかは、AWSのドキュメントに記載されています。多くの場合は、以下が必要でしょう。

AWSサービス やりたいこと VPCエンドポイントタイプ エンドポイント
ECR イメージをpullする Interface com.amazonaws.region.ecr.dkr
com.amazonaws.region.ecr.api
Gateway com.amazonaws.region.s3
Secrets Manager シークレットを取得する Interface com.amazonaws.region.secretsmanager
SSM シークレットを取得する Interface com.amazonaws.region.ssm
CloudWatch Logs ログを出力する Interface com.amazonaws.region.logs

これらに加えて、全てのセキュリティグループはタスクに紐づいているものにし、各サービスのVPCエンドポイントとタスク間のアクセスを許可する必要があります。

VPCエンドポイントとは別に、データを取得する必要がある各サービスへのアクセス権限をタスク実行ロールにつける必要があります。

例えば以下のようなシステムがあるとしましょう。

  • コンテナイメージをECRで管理する
  • 認証情報をSecretsManagerで管理する
  • タスク起動時にSecretsManagerのシークレットを環境変数としてうまく埋め込んでおく(アプリケーションの内部にSecretsManagerにアクセスするための処理を書きたくない)
  • 全てのログはCloudWatch Logsに出力する

おそらく以下のような権限が必要になります。

アクション リソース
ecr:GetAuthorizationToken *
ecr:BatchCheckLayerAvailability ECRイメージのARN
ecr:GetDownloadUrlForLayer ECRイメージのARN
ecr:BatchGetImage ECRイメージのARN
logs:CreateLogStream ログを出力するロググループのARN
logs:PutLogEvents ログを出力するロググループのARN
secretsmanager:DescribeSecret シークレットのARN
secretsmanager:GetSecretValue シークレットのARN

付録

CDKでインフラを構築する

VPCエンドポイントはCDKで以下のように生成できます。

import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";

interface VpcEndpointTarget {
    readonly service: ec2.InterfaceVpcEndpointAwsService;
    readonly idSuffix: string;
}

export class AppDeployment extends Construct {
    constructor(scope: Construct, id: string, props: {}) {
        super(scope, id);

        const vpc = ec2.Vpc.fromLookup(this, "AppDeploymentVpc", { vpcName: "{vpcName}" });
        const subnet = ec2.Subnet.fromSubnetAttributes(this, "AppDeploymentSubnet", {
            subnetId: "{subnetId}",
            availabilityZone: "{availabilityZoneId}",
            routeTableId: "{routeTableId}",
        });
        const securityGroup = ec2.SecurityGroup.fromSecurityGroupId(
            this,
            "AppDeploymentSecurityGroup",
            "{securityGroupId}",
        );

        const necessaryVpcEndpointTargets: VpcEndpointTarget[] = [
            { service: ec2.InterfaceVpcEndpointAwsService.ECR, idSuffix: "EcrApi" },
            { service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER, idSuffix: "EcrDkr" },
            { service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS, idSuffix: "CloudWatchLogs" },
            { service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER, idSuffix: "SecretsManager" },
        ];
        necessaryVpcEndpointTargets.forEach((target) => {
            new ec2.InterfaceVpcEndpoint(this, `VpcEncpoint${target.idSuffix}`, {
                service: target.service as ec2.InterfaceVpcEndpointAwsService,
                vpc,
                subnets: { subnets: [subnet] },
                securityGroups: [securityGroup],
                privateDnsEnabled: false, 
            });
        });
        new ec2.GatewayVpcEndpoint(this, "VpcEncpointS3", {
            service: ec2.GatewayVpcEndpointAwsService.S3,
            vpc: props.loadedEcsNetwork.iVpc,
            subnets: [{ subnets: [subnet] }],
        });

FYI

同じCDK Stack内でVPCを作るのではなく、既存のものをロードする場合は、fromAttributes ではなく fromLookup を使った方がいいです。インターフェース型VPCエンドポイントを作る際に発生する Cannot perform this operation: 'vpcCidrBlock' was not supplied when creating this VPC エラーが回避できます。

VPCエンドポイントの作成時に注意すべきこと

インターフェース型VPCエンドポイントは固定料金がかかります

インターフェース型VPCエンドポイントは固定料金がかかるので、最低限の数だけ生成できるように、同じVPC内に複数のサブネットでシェアできる設計を考えた方がいいです。

最初作ったVPCエンドポイントにサブネットを追加すればいいのでは?と思うかもしれませんが、現時点ではCDK L1とL2ともにVPCエンドポイントの新規作成時にしかサブネットが設定できないため、後から追加することが難しいです。

一方で、ゲートウェイ型VPCエンドポイントは無料ですが、VPCあたり一つしか作れないので、それも要注意です。ちなみに、今のところゲートウェイ型VPCエンドポイントが必要なのは、Amazon S3とAmazon DynamoDBだけです。

インターフェース型VPCエンドポイントは privateDnsEnabled の設定に気をつけましょう

すでに同じサービスのVPCエンドポイントが作られている場合は、privateDnsEnabledfalse にする必要があります。同じ名前のプライベートホストゾーンが作れないからです。詳しくはAWS Knowledge Centerの記事をご参照ください。

If you turn on PrivateDNS when creating interface endpoints, then a private hosted zone is automatically created and associated with your VPC. AWS services and AWS Marketplace partner services have PrivateDNS turned on by default. So, creating a second interface VPC endpoint for the same service with PrivateDNS turned on causes the conflicting DNS domain error. To fix this, turn off the PrivateDNS option when creating the interface endpoint. Use endpoint-specific DNS hostnames for the second VPC interface endpoint for that service.

終わりに

ECS化のプロジェクトは時間がかかった分、はまりポイントも多く、学びも多かったのでまたこうやってブログを書かせていただきました。

「アプリケーションさえ正常に起動できれば、タスクなんて当たり前に起動するっしょ!」と安易に思っていたので、真っ直ぐ ResourceInitializationError にハマってしまいました。

最初はECSのタスクの停止理由をどこから確認すればいいのかも知らない状態で、色々ググったりAWSのドキュメントを読んだりしました。そうしていたら、「Fargateのデフォルトバージョンって1.4.0なんだ」から始まり「各サービスのVPCエンドポイントをこちらで用意する必要がある」までがやっとわかりました。きっと私と同じくハマっている方がいると思うので、その際にこの記事がお役に立てればと思います。

また、新たに学んだりハマったりチャレンジした記事を書きますので、ご期待ください!

Page top