こんにちは、シニアSREの私だ。深夜の障害対応で冷や汗をかいた経験がある人間なら誰しも、AWSのVPC内部からS3へのデータ転送で「なぜかタイムアウトする」「NATゲートウェイの通信費が爆発した」という悪夢のような現象に直面したことがあるだろう。
今日は、そんなインフラエンジニアの財布とメンタルを救う救世主、「Gateway型VPCエンドポイント」の核心に迫ろうと思う。特に、エンドポイントを作成した瞬間にルートテーブルへ魔術のように吸い込まれていく「プレフィックスリスト(pl-xxxxxx)」の自動インジェクションの仕組みについて、パケットの挙動を交えながら泥臭く、かつ深く解説していく。
教科書通りの「ボタンをポチポチ押せば繋がります」という説明は一切しない。現場で明日から使える実務の知見を叩き込むので、心してついてきてほしい。
—
1. なぜGateway型VPCエンドポイントが必要なのか?
まず前提を共有しておこう。Amazon S3やDynamoDBは、AWSのマネージドサービスでありながら、その実体はVPCの外側(AWSのグローバルネットワーク空間)に存在するマルチテナントなエンドポイントだ。
プライベートサブネット(NATゲートウェイなし、またはインターネットへのルートを持たない環境)にあるEC2インスタンスからS3のバケットにアクセスしようとしたとき、標準のルーティングではパケットはどこへ向かうべきか迷子になる。インターネットゲートウェイ(IGW)へのデフォルトルート(0.0.0.0/0)がない限り、パケットはブラックホール行きだ。
ここでNATゲートウェイを置くという手もあるが、テラバイト級のログをS3に吐き出すようなワークロードでは、データ処理課金(GBあたり数セント)の請求書を見て経営陣がひっくり返ることになる。
そこで登場するのが、VPCとS3/DynamoDBをAWSのバックボーンネットワーク上で直接結びつけるGateway型VPCエンドポイントだ。インターネットを経由せず、AWSの内部ルーター網にダイレクトにパケットをねじ込むことができる。しかも、料金は無料である。使わない理由が見つからない。
—
2. ルートテーブル自動インジェクションの正体
AWSマネジメントコンソールやTerraformでGateway型VPCエンドポイントを作るとき、必ず「どのルートテーブルに関連付けるか(アタッチするか)」を選択するはずだ。
あの瞬間、裏側で何が起きているか知っているだろうか?
エンドポイントを作成すると、AWSは指定されたルートテーブルに対して、以下のようなプレフィックスリストをターゲットとするルートを自動的に追加(インジェクション)する。
宛先: pl-xxxxxxxx(S3のプレフィックスリストID)
ターゲット: vpce-xxxxxxxx(作成したVPCエンドポイントID)
プレフィックスリスト(Prefix List)とは何か?
S3やDynamoDBが裏側で抱えているIPアドレスの範囲(CIDRブロック)は、AWSの巨大なネットワーク拡張に伴い、刻一刻と変化している。もし人間が手動で数千個もあるS3のIPレンジをルートテーブルに書き込もうとしたら、ルートテーブルのエントリ数上限(標準では50個、上限緩和でも100個程度)に即座に抵触して爆発するだろう。
それを解決するのがプレフィックスリストだ。pl- で始まるこのオブジェクトは、AWSが管理する「変動し続ける数十〜数百のIP CIDRの集合体」を丸ごとカプセル化した仮想的な宛先である。
ルートテーブルには、このプレフィックスリストIDをポツリと1行置くだけで済む。AWSのコントロールプレーンが、S3のIPアドレス変更に合わせて、このプレフィックスリストの中身を裏側でよしなに最新化してくれる。これが「自動インジェクション」の本質だ。
—
3. 通信のシーケンス:パケットはどこを走るのか?
では、プライベートサブネット上のアプリケーションから実際にS3へリクエストを飛ばしたとき、パケットのルーティングとカプセル化はどのように行われているのか。頭の中でパケットの旅をトレースしてみよう。
[プライベートEC2]
│ (1. HTTPリクエスト送信: 宛先IP = S3のパブリックIP)
▼
[VPCルーター (仮想ルーター)]
│ (2. ルートテーブル参照: pl-xxxxxx にヒット!)
▼
[Gateway型VPCエンドポイント (vpce-xxxxxx)]
│ (3. AWS内部バックボーンへカプセル化してダイレクトルーティング)
▼
[Amazon S3 バックエンド]
1. アプリケーションからの発信:
Pythonの boto3 や curl が https://my-bucket.s3.ap-northeast-1.amazonaws.com/ に対してリクエストを投げる。名前解決(DNS)により返されるのは、S3のパブリックIPアドレスだ。
2. VPCルーターでのルーティング判定:
EC2のOSからパケットがVPCの仮想ルーターに到達する。VPCルーターはルートテーブルを上から順に評価する。ここで、通常の 0.0.0.0/0 よりも、プレフィックスリストのルートのほうが最長一致(Longest Prefix Match)の原則により優先的にマッチする。
3. エンドポイントへの転送:
パケットの宛先IPはS3のパブリックIPのままだが、ターゲットがインターネットゲートウェイ(IGW)ではなく、VPCエンドポイント(vpce-xxxxxx)に強制ルーティングされる。
4. AWS内部バックボーンへの突入:
パケットはインターネットに出ることなく、AWSの誇る超高速なリージョン内バックボーンネットワークへとダイレクトに流し込まれる。
—
4. 実務で直面する「あるある」トラブルとデバッグTips
現場のSREとして、この自動インジェクション機能にまつわるトラブルシューティングを数え切れないほど経験してきた。よくある罠をいくつか共有しておこう。
トラブル1: エンドポイントを作ったのに繋がらない(ルートの競合)
「エンドポイントを作ったのに、なぜかパケットがNATゲートウェイ経由で外に出ていってしまう、あるいはタイムアウトする」という現象だ。
原因:
既存のルートテーブルに、より特異な(あるいは競合する)ルートが存在しているケースや、プレフィックスリストのルートが正しくインジェクションされていないケースがある。特に、カスタムルートテーブルを複雑に組み上げている環境では、ルートの評価順序でハマることが多い。
デバッグコマンド(AWS CLI):
現在のルートテーブルに本当にプレフィックスリストが正しくインジェクションされているか、以下のコマンドで泥臭く確認しよう。
# ルートテーブルの詳細をJSONで取得してルーティングエントリを確認する
aws ec2 describe-route-tables \
--route-table-ids rtb-0123456789abcdef0 \
--query "RouteTables[0].Routes" \
--output json
出力結果の中に、DestinationPrefixListId が含まれており、かつ State が active になっていることを自分の目で確認するんだ。
トラブル2: セキュリティグループやVPCエンドポイントポリシーの落とし穴
Gateway型VPCエンドポイント自体には、ENI(Elastic Network Interface)が存在しないため、セキュリティグループをアタッチすることができない(Interface型エンドポイントとはここが大きく異なる)。
その代わり、エンドポイントには「VPCエンドポイントポリシー(JSON)」を設定する。ここをデフォルトの Allow All にしたままだと、セキュリティガバナンスの観点で監査に引っかかる。
以下は、特定のS3バケットへのアクセスのみを許可する堅牢なエンドポイントポリシーのサンプルだ。実務では必ずこのように最小権限の原則を適用してほしい。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSpecificBucketAccessOnly",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::my-production-data-bucket",
"arn:aws:s3:::my-production-data-bucket/*"
]
}
]
}
—
5. コードからの挙動確認(Python / boto3)
最後に、プライベートサブネット上のインスタンスから、Gateway型VPCエンドポイント経由でS3に正しくアクセスできているかをプログラムから検証する方法を示そう。
特筆すべきは、エンドポイントを作ったからといって、アプリケーション側のコード(Pythonの boto3 など)を変更する必要は一切ないという点だ。DNS名前解決は通常のパブリックエンドポイントを指すが、パケットは自動的にバックボーンへ迂回する。
import boto3
from botocore.exceptions import ClientError
def test_s3_gateway_endpoint():
# 通常通りのクライアント初期化
# 内部的には public な s3.ap-northeast-1.amazonaws.com を叩きに行くが、
# Gateway型エンドポイントによりVPC内から外に出ない。
s3_client = boto3.client('s3', region_name='ap-northeast-1')
bucket_name = 'my-production-data-bucket'
object_key = 'healthcheck.txt'
try:
# S3バケットへの簡単な疎通確認(オブジェクトのメタデータ取得)
response = s3_client.head_object(Bucket=bucket_name, Key=object_key)
print(f"[SUCCESS] Gateway型VPCエンドポイント経由での通信に成功しました。HTTP Status: {response['ResponseMetadata']['HTTPStatusCode']}")
except ClientError as e:
error_code = e.response['Error']['Code']
print(f"[ERROR] S3へのアクセスに失敗しました。エラーコード: {error_code}")
print("ヒント: ルートテーブルのプレフィックスリスト(pl-xxxx)やVPCエンドポイントポリシーを確認してください。")
if __name__ == "__main__":
test_s3_gateway_endpoint()
—
まとめ
Gateway型VPCエンドポイントのルートテーブル自動インジェクションは、一見すると「ただルートが自動で追加されるだけの便利な機能」に見える。しかしその裏では、刻々と変化するAWSの巨大なIPアドレス群をプレフィックスリスト(pl-xxxxxx)という抽象概念で包み込み、VPCルーターの最長一致ルーティングによってシームレスにバックボーンへ導くという、非常に洗練されたネットワークアーキテクチャが動いている。
この仕組みをしっかりと理解しておけば、万が一の通信断やセキュリティ監査の際にも、迷うことなくルートテーブルとエンドポイントポリシーを突き合わせて原因を特定できるようになるはずだ。
現場のインフラをよりセキュアに、そしてコスト効率良く保つために、今日の知識をぜひ役立ててほしい。それでは、また次の現場でお会いしよう。
コメント