zaki work log

作業ログやら生活ログやらなんやら

[AWS] VPCとS3エンドポイントを使ってインターネット接続が制限されたEC2(サブネット)からECRに接続する [修正済み]

6/23: S3へのエンドポイントも必要なことを追記

概要

インターネットゲートウェイを設定せずにインターネット接続が制限されたサブネットにあるEC2は、ECR(Elastic Container Registry)との疎通もないため、Dockerなどでコンテナイメージをpullしたりすることができない。

ここではVPCエンドポイントを使って、EC2(が接続されているの制限されたサブネット)とECRをプライベート接続して接続できるようにする設定をお試し。

ECRへ接続するための設定は以下の2つのサービスのエンドポイントを作成する。(エンドポイントはそれぞれ1つずつ作成する)
(6/23追記) ECRへ接続するための設定は以下の2つのサービスのVPCエンドポイントとS3ゲートウェイエンドポイントを作成する。(合計3つのエンドポイントを作成)

  • VPCエンドポイント
    • com.amazonaws.<リージョン名>.ecr.api
    • com.amazonaws.<リージョン名>.ecr.dkr
  • S3ゲートウェイエンドポイント
    • com.amazonaws.<リージョン名>.s3

なぜS3(へのアクセス)も必要かというと、ECRはAmazon S3を使用してイメージレイヤーを保存するため、pullの際はS3へのアクセスも発生する。

Amazon ECS タスクで Amazon ECR からプライベートイメージをプルするには、Amazon S3ゲートウェイエンドポイントを作成する必要があります。Amazon ECR は Amazon S3 を使用してイメージレイヤーを保存するため、ゲートウェイエンドポイントが必要です。Amazon ECR からイメージをダウンロードするコンテナは、Amazon ECR にアクセスしてイメージマニフェストを取得してから Amazon S3 にアクセスして実際のイメージレイヤーをダウンロードする必要があります

https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/vpc-endpoints.html#ecr-setting-up-s3-gateway

webポータルから

VPCエンドポイントの作成

VPCmニューの「エンドポイント」から。
「エンドポイントを作成」して以下を入力する。
※ サービスにcom.amazonaws.<リージョン名>.ecr.apiを選択したものとcom.amazonaws.<リージョン名>.ecr.dkrを選択したものを1個ずつ作る

  • エンドポイントの設定
    • 名前タグ: 適当に入力 (apiとdkrがあるので、suffixにつけておくと良さげ)
    • サービスカテゴリ: AWSのサービス
  • サービス
    • サービス: com.amazonaws.<リージョン名>.ecr.apicom.amazonaws.<リージョン名>.ecr.dkr ※1個しか選択できないので2つ作る
  • VPC
    • VPC: 接続対象のVPC
    • 追加設定: デフォルトのまま
      • DNS名を有効
      • DNSレコードのIPタイプ: IPv4
  • サブネット
    • サブネット: 接続対象のAZとサブネットを選択
  • セキュリティグループ
    • 接続(インバウンド443/TCP)に必要なセキュリティグループを選択
  • ポリシー
    • デフォルトのまま (フルアクセス)

入力したら画面下部の「エンドポイントを作成」押下。 しばらく待てばステータスが「使用可能」になり、さらに少し時間を置けば、EC2からECRへ接続できるようになる。

[ec2-user@ip-10-1-2-229 ~]$ curl https://********.dkr.ecr.ap-northeast-1.amazonaws.com/container:tag
Not Authorized

※ 疎通確認用でDocker/Podman入ってないのでとりあえずcurlで疎通のみ確認。(未認証のレスポンスがあるのでHTTPSレベルで疎通できている)

VPCエンドポイントが無い状態だと以下の通り。

[ec2-user@ip-10-1-2-229 ~]$ curl https://********.dkr.ecr.ap-northeast-1.amazonaws.com/container:tag
curl: (7) Failed to connect to ********.dkr.ecr.ap-northeast-1.amazonaws.com port 443: Connection timed out

以下6/23追記

ただしこの時点ではS3への疎通はないため、レジストリへのログイン(podman login)は可能だけど、イメージpullは失敗する。

[ec2-user@ip-10-1-2-72 ~]$ podman login -u AWS ********.dkr.ecr.ap-northeast-1.amazonaws.com
Password: 
Login Succeeded!

[ec2-user@ip-10-1-2-72 ~]$ podman image pull ********.dkr.ecr.ap-northeast-1.amazonaws.com/redis:7
Trying to pull ********.dkr.ecr.ap-northeast-1.amazonaws.com/redis:7...
WARN[0030] Failed, retrying in 1s ... (1/3). Error: parsing image configuration: Get "https://prod-ap-northeast-1-starport-layer-bucket.s3.ap-northeast-1.amazonaws.com/ ... : dial tcp *.*.*.*:443: i/o timeout 

S3へのアクセスエラーはpublic ipが表示されている。

S3用ゲートウェイエンドポイントの作成

VPCエンドポイントの作成と同じ要領で、

  • サービスカテゴリ: AWSのサービス
  • サービス: gatewayでフィルターして、タイプが「Gateway」になっているcom.amazonaws.<リージョン名>.s3を選択
  • VPC: 接続対象のVPC
  • ルートテーブル: オフラインのサブネットが使うルートテーブルを選択

以上でエンドポイントを作成する。

これでECRに正しくアクセスできるようになる。

[ec2-user@ip-10-1-2-72 ~]$ podman image pull ********.dkr.ecr.ap-northeast-1.amazonaws.com/redis:7
Trying to pull ********.dkr.ecr.ap-northeast-1.amazonaws.com/redis:7...
Getting image source signatures
Copying blob 1adf6e0f68a8 done   | 
Copying blob 1adf6e0f68a8 done   | 
Copying blob e50738daf1e5 done   | 
:
:

Terraformで構築

上で操作した内容をそのままコードに落とし込めばOK
使うリソースはaws_vpc_endpoint

registry.terraform.io

VPCaws_vpc.example_dev_vpcで、サブネットはaws_subnet.example_dev_disconnected_subnetで作成済みで、リージョン名は変数でvar.region_nameにセットされてる構成。
また、ルートテーブルはVPCのデフォルトのものを使用。

resource "aws_vpc_endpoint" "example_dev_endpoint_to_ecr_api" {
  vpc_id = aws_vpc.example_dev_vpc.id
  service_name = "com.amazonaws.${var.region_name}.ecr.api"
  vpc_endpoint_type = "Interface"
  private_dns_enabled = true

  subnet_ids = [
    aws_subnet.example_dev_disconnected_subnet.id
  ]
  security_group_ids = [
    aws_security_group.example_dev_sg.id
  ]
  tags = {
    Name = "example-endpoint-ecr-api"
  }
}

resource "aws_vpc_endpoint" "example_dev_endpoint_to_ecr_dkr" {
  vpc_id = aws_vpc.example_dev_vpc.id
  service_name = "com.amazonaws.${var.region_name}.ecr.dkr"
  vpc_endpoint_type = "Interface"
  private_dns_enabled = true

  subnet_ids = [
    aws_subnet.example_dev_disconnected_subnet.id
  ]
  security_group_ids = [
    aws_security_group.example_dev_sg.id
  ]
  tags = {
    Name = "example-endpoint-ecr-dkr"
  }
}

resource "aws_vpc_endpoint" "example_dev_endpoint_to_s3" {
  vpc_id = aws_vpc.example_dev_vpc.id
  service_name = "com.amazonaws.${var.region_name}.s3"
  vpc_endpoint_type = "Gateway"

  # これはオフラインのサブネットはデフォルトのルートテーブルを使用してる場合
  route_table_ids = [aws_vpc.example_dev_vpc.default_route_table_id]

  tags = {
    Name = "example-endpoint-s3"
  }
}

参考

6/23追記

ECR用エンドポイントだけ作ってcurlHTTPS的にリポジトリと疎通確認してOKだと思い込んでたけど、実際にpodman image pullるとうまくいかず、S3のエンドポイント設定が必要なことに後から気づいた。
ドキュメントはちゃんと最後までよんで、最後までやりたい操作をきちんと実行して確認しましょう。