「プルスルーキャッシュルール」を使えば VPC エンドポイント経由でもパブリックイメージから ECS タスクを起動できた話

2023.07.28

AWS事業本部 コンサルティング部の有福です。

Amazon ECSでプライベートサブネットにタスクを起動する場合には、Amazon ECRからコンテナイメージをプルする経路として、NATゲートウェイ経由とVPCエンドポイント経由の2パターンが想定されます。

ただし、ECR VPCエンドポイントはECRパブリックリポジトリをサポートしていないため、VPCエンドポイントを経由したアクセスではECRパブリックのコンテナイメージをプルすることができません。

私はこのことを知らずに若干ハマったのですが、「プルスルーキャッシュルール」を使うことでVPCエンドポイント経由でもパブリックイメージからECSタスクを起動することができたので、そのことを記事にしてみました。

ECRパブリックリポジトリとは

ECRパブリックリポジトリは、Amazon ECRの機能の1つで、このパブリックリポジトリにホストされているコンテナイメージはインターネット上に公開され、Amazon ECR Public Galleryで検索できます。

公開されているコンテナイメージには、Docker Hubで取得可能なDocker公式イメージも含まれています。

ECRパブリックリポジトリについてはこちらの記事でも解説されています。

やろうとしていたこと

構成図のとおり、ECRパブリックリポジトリからVPCエンドポイント経由でNGINXの公式イメージをプルしてきて、プライベートサブネットにECSタスク(Fargate)を起動するという構成で構築を行いました。

パブリックリポジトリがECRの機能の一部なのでVPCエンドポイントを使えばパブリックイメージをプルできるだろうと考えていました。

ちなみに今回使用したVPCエンドポイントは以下の4つです。

  • Amazon S3 ゲートウェイエンドポイント
  • Amazon ECR インターフェースエンドポイント
      - com.amazonaws.region.ecr.dkr
      - com.amazonaws.region.ecr.api
  • CloudWatch Logs インターフェースエンドポイント 〜 ECSタスクのログ出力用

どうなった?

上記の構成をTerraformで適用(terraform apply)して、Terraformの実行は正常に完了しました。

しかし、マネジメントコンソール上でECSタスクを確認してみると…

なにやらタスク起動の失敗を繰り返しているようです。

停止済みのタスクを確認してみると、以下のメッセージが書かれていました。

CannotPullContainerError: pull image manifest has been retried 5 time(s): failed to resolve ref public.ecr.aws/nginx/nginx:latest: failed to do request: Head "https://public.ecr.aws/v2/nginx/nginx/manifests/latest": dial tcp 99.83.145.10:443: i/o timeout

ECRからコンテナイメージがプルできずにタイムアウトになっているようです。

タスク実行ロールにアタッチしているポリシーやVPCエンドポイントのセキュリティグループを確認した限りでは問題なさそうです。

試しにNATゲートウェイを追加してECSタスクからアウトバウンドのパブリックアクセス経路を確保したところ、タスクが起動しました。

ALBにブラウザからアクセスするとNGINXのデフォルトページも表示されました。

NATゲートウェイを利用せずVPCエンドポイントを経由したアクセスの場合にはコンテナイメージがプルできないという状況です。

VPCエンドポイント経由でプルできない原因について

AWSの公式ドキュメントに次のような記載がありました。

現在、VPC エンドポイントは Amazon ECR パブリックリポジトリをサポートしていません。プルスルーキャッシュルールを使用して、VPC エンドポイントと同じリージョンにあるプライベートリポジトリでパブリックイメージをホストすることを検討してください。詳しくは、「プルスルーキャッシュルールの使用」を参照してください。

結論として、Amazon ECR VPCエンドポイントではECRパブリックリポジトリがサポートされておらず、これが原因でした。

ただし、公式ドキュメントでは代替方法として「プルスルーキャッシュルールの使用」について言及しているので、この方法を試してみることにしました。

「プルスルーキャシュルール」について

プルスルーキャッシュルールを作成すると、ECRパブリックリポジトリのコンテナイメージをECRプライベートリポジトリにコピー(キャッシュ)し、そのプライベートリポジトリからイメージをプルできるようになります。

次の記事でもプルスルーキャッシュルールについて解説されています。

プルスルーキャッシュルールを使ってVPCエンドポイント経由でプルしてみた

ということで、代替方法となる「プルスルーキャッシュルール」を使った構成は次のとおりです。

プルスルーキャッシュルールを使用するにあたり、Terraformコードに次の変更を行いました。

  1. プルスルーキャッシュルール(aws_ecr_pull_through_cache_rule)を作成
    resource "aws_ecr_pull_through_cache_rule" "ecr_public" {
      ecr_repository_prefix = "ecr-public"
      upstream_registry_url = "public.ecr.aws"
    }
  2. ECSのタスク定義のコンテナ定義でコンテナイメージのパスを変更
    # before (ECRパブリックリポジトリのパス)
    image = "public.ecr.aws/nginx/nginx:stable"
    ↓
    # after (プルスルーキャッシュイメージのパス)
    image = "(アカウントID).dkr.ecr.(リージョン).amazonaws.com/ecr-public/nginx/nginx:stable"

    上記のとおり、ECRパブリックリポジトリのパスのpublic.ecr.aws/の部分を(アカウントID).dkr.ecr.(リージョン).amazonaws.com/ecr-public/で置き換えたものがプルスルーキャッシュルールを使用した場合のイメージのパスになります。

  3. ECSのタスク実行ロールにプルスルーキャッシュルールの使用に必要なポリシーを追加

    resource "aws_iam_policy" "pull_through_cache" {
      name = "PullThroughCachePermission"
      policy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "ecr:BatchImportUpstreamImage",
              "ecr:CreateRepository",
            ]
            Resource = "*"
          }
        ]
      })
    }
    
    resource "aws_iam_role_policy_attachment" "pull_through_cache" {
      role = aws_iam_role.ecs_task_execution.name
      policy_arn = aws_iam_policy.pull_through_cache.arn
    }

ちなみに、上記のIAMポリシーを追加しないとECSタスクの起動に失敗してしまいますが、起動に失敗して停止済みになったタスクを確認すると指定パスからコンテナイメージが見つからない旨のエラーメッセージ (failed to resolove ref (中略) not found)が表示されます。

ECSタスクがうまく起動せずに上記エラーが表示される場合、純粋にコンテナイメージのパスが誤っているという原因のほかに、タスク実行ロールに必要な許可ポリシーがアタッチされていない可能性も考えられるという点に注意です。(私にとっては、ここもハマりポイントでした)

なお、AWS re:Postに「CannotPullContainerError」の解決方法に関するAWS公式からの回答が掲載されていました。

こちらの内容でもエラー原因の可能性の1つとしてIAMロールの権限不足が挙げられていました。

CannotPullContainerError は、次のいずれかの問題が原因で発生します。

  • ネットワークが正しく設定されていないため、Amazon Elastic Compute Cloud (Amazon EC2) 起動タイプのタスクがイメージをプルできない。

  • AWS Identity and Access Management (IAM) ロールに、イメージをプルするための適切なアクセス許可がない。

  • DockerHub にダウンロード率の制限がある。

  • イメージの名前またはタグが存在しない。


上記の変更を行ったので、Terraformで適用したうえでマネジメントコンソールで確認を行いました。

  • ECRのプルスルーキャッシュルールが作成されています。

  • プルスルーキャッシュルールを作成したことで、ECRのプライベートリポジトリにコンテナイメージが保存(キャッシュ)されています。

  • このプライベートリポジトリにキャッシュされたパブリックイメージをプルできるようになったので、ECSタスクが起動するようになりました。

  • ブラウザでALBのURIにアクセスしみるとNGINXのデフォルトページも確認できました。

ということで、プルスルーキャッシュルールを使うことでVPCエンドポイント経由でもパブリックイメージをプルしてECSタスクを起動することに成功しました。

プルスルーキャッシュルール使用時の考慮事項に関して

プルスルーキャッシュルールの使用に関してAWS公式ドキュメントを確認すると、「プルスルーキャッシュルールを使用するための考慮事項」として12項目の記述があります。

今回、VPCエンドポイント経由でECRにアクセスしているということで、次の項目が気にかかりました。

  • 初めてプルスルーキャッシュルールを使用してイメージをプルするとき、AWS PrivateLink を使って、インターフェイス VPC エンドポイントを使用するように Amazon ECR を設定した場合、NAT ゲートウェイを使用して、同じ VPC 内にパブリックサブネットを作成し、プルが機能するように、プライベートサブネットから NAT ゲートウェイへのすべてのアウトバウンドトラフィックをインターネットにルーティングする必要があります。その後のイメージプルでは、これは必要ありません。詳細については、Amazon Virtual Private Cloud ユーザーガイドの「シナリオ: プライベートサブネットからインターネットにアクセスする」を参照してください。

この記述どおりであれば、プルスルーキャッシュルールを作成後、初めてイメージをプルする時にはNATゲートウェイを使用してECS側からのアウトバウンド通信をインターネットにルーティングしておく必要があるということになります。

ただ、私が検証した限りでは、NATゲートウェイを使用しなくともVPCエンドポイント経由で初回のイメージプルが可能でした。

プル対象のコンテナイメージの種類を代えて全リソースを構築し直したり、新規作成したAWSアカウントでリソースを新規構築したりといったことを試しましたが、いずれもNATゲートウェイなしのVPCエンドポイント経由でECSタスクを起動することができました。

そもそも公式ドキュメントの解釈が違っているのか、ECSからECRにアクセスする際の仕様なのか、結論には至っていないのでこの点については引き続き調査したいと思っています。

最後に

今回は、VPCエンドポイントを経由してAmazon ECRからパブリックイメージをプルしてECSタスクを起動する方法として「プルスルーキャッシュルール」を使ったという内容でした。

VPCエンドポイントを経由したアクセスでもプルスルーキャッシュルールを使えばパブリックイメージを間接的にプルできるようになります。

ユースケースとしてはパブリックイメージからECSタスクを起動したいけれど、ECSタスクに外部通信は許可したくないというようなシチューエーションに限定されるかもしれませんが、何かの参考にしていただけると幸いです。