AWSのネットワーク設計において、VPCピアリング(VPC Peering)は最も基本的でありながら、実は多くのインフラエンジニアが「おや?」と躓くポイントを秘めた機能です。
「別のVPCにあるWeb APIサーバーへ、パブリックIPを介さずにプライベートIPで直接アクセスしたい」
そう思ってVPCピアリングをサクッと設定したものの、なぜか通信できない。ルートテーブルを見直しても問題なさそうなのに、パケットがブラックホールに吸い込まれていくように消えていく……。夜な夜なログを漁り、セキュリティグループのインバウンド規則を睨みつける。そんな泥臭いデバッグ作業を経験した読者の方も少なくないでしょう。
今回は、AWSのVPCピアリングにおける「プライベートIPルーティングの仕組み」、そして避けて通れない「CIDRブロックの重複制約」と「非推移的ルーティング(Non-transitive routing)」という2つの大きな壁について、パケットの挙動をイメージしながら実務目線で徹底解説します。
—
1. VPCピアリングの基本思想と「パケットの旅」
VPCピアリングは、2つのVPC間をAWSのバックボーンネットワーク上で直接接続し、両VPCのプライベートIPアドレス同士でトラフィックをやり取りできるようにする機能です。インターネットを介さないため、データ転送のレイテンシが低く、セキュリティ面でも安全です。
しかし、ここで一つ重要な事実を認識しておく必要があります。
「VPCピアリングは、ルーター同士を直結するものではなく、2つのVPCのルーティングテーブルに経路を流し込むマジックである」ということです。
通信フローの裏側(シーケンス)
例えば、VPC A(10.1.0.0/16)にあるEC2インスタンスから、VPC B(10.2.0.0/16)にあるプライベートAPIサーバー(10.2.10.50)へ、curlでリクエストを投げるシーンを考えてみましょう。
1. ルーティング判定(VPC A側):
VPC Aのインスタンスが宛先 10.2.10.50 へのパケットを生成します。インスタンスのOSが所属するサブネットのルートテーブルを参照し、「10.2.0.0/16 宛てのパケットは、VPCピアリング接続(pcx-xxxxxxxxxxxxxxxxx)へ投げろ」というエントリにヒットします。
2. AWSバックボーンへの送出:
パケットはAWSの仮想ルーターに捕捉され、VPCピアリングのインターフェースを経由してVPC Bの空間へルーティングされます。
3. セキュリティグループとNACLのチェック:
VPC Bに入る際、宛先リソースのセキュリティグループおよびネットワークACL(NACL)が評価されます。ここでVPC Aからのトラフィックが許可されていなければ、情け容赦なくパケットはドロップされます。
4. 宛先到達と返送:
パケットが無事にVPC BのAPIサーバーに届き、処理結果(レスポンス)が返されます。この時、VPC B側のルートテーブルにも「10.1.0.0/16 宛てはVPCピアリングへ」という戻り経路の設定が必ず必要になります。片方向だけ設定しても通信は成立しません。
—
2. 現場を悩ませる2大制約:重複CIDRと非推移的ルーティング
AWSの設計ドキュメントを読めばサラッと書いてありますが、実務の現場で牙をむくのが次の2点です。
① CIDRブロックの重複禁止ルール(Overlapping CIDRs)
VPCピアリングを締結する2つのVPCの間で、CIDRブロックが完全に重複している場合、ピアリング接続の作成自体が拒否されます。また、部分的に重複(CIDR Overlap)している場合も、ルーティングが一意に決まらないためピアリングは設定できません。
- NGの例: VPC A(
10.0.0.0/16)と VPC B(10.0.0.0/24) - NGの例: VPC A(
10.0.0.0/16)と VPC B(10.1.0.0/16)←これは重複ではありませんが、後述のサブネット単位での競合には注意が必要です。
もし全社的なIPアドレス管理(IPAM)が不徹底な組織で、買収した別会社のAWSアカウントとVPCを接続しようとした際、両者とも 10.0.0.0/16 を使っていた……というのは、クラウドインフラあるあるの悪夢です。この場合、どちらかのVPCを作り直すか、Transit Gatewayを使った複雑なNAT構成を組むなど、余分なコストと手間が発生します。VPCの設計初期段階からCIDR設計は慎重に行うことが、シニアSREからの最大の教訓です。
② 非推移的ルーティング(Non-transitive Routing)の罠
「AとBをピアリングし、BとCをピアリングした。だから、AからCへも通信できるはずだ」
――残念ながら、これはできません。
AWSのVPCピアリングは非推移的(Non-transitive)です。
- VPC A ⇔ VPC B (ピアリング接続あり)
- VPC B ⇔ VPC C (ピアリング接続あり)
- VPC A ⇔ VPC C (直接のピアリングがないため、Bを中継して通信することは不可能)
もしAからCへパケットを飛ばそうとしても、VPC Bの仮想ルーターは「お前宛てのパケットじゃないよ」とばかりにパケットを破棄します。もしハブ&スポーク型のネットワークトポロジーを構築したい場合は、VPCピアリングではなく AWS Transit Gateway を採用するのが現代の正しいアーキテクチャ選択となります。
—
3. 実践:VPCピアリング設定とアプリケーションからの疎通確認
それでは、実際にAWS CLIを使ったVPCピアリングの作成手順と、アプリケーションコードからプライベートAPIを叩く実装例を見ていきましょう。
3.1 AWS CLIによるピアリング接続の構築
ここでは、リrequester(VPC A)からaccepter(VPC B)へピアリングを申請し、それぞれのルートテーブルを更新する一連の流れを記述します。
# 1. VPCピアリング接続の作成をリクエスト
# リクエスタ側VPCから、アクセプター側VPC(別アカウントまたは同アカウント)へ申請
aws ec2 create-vpc-peering-connection \
--vpc-id vpc-0123456789abcdef0 \
--peer-vpc-id vpc-abcdef01234567890 \
--peer-region ap-northeast-1 \
--tag-specifications 'ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=api-to-db-peer}]'
# 2. アクセプター側でリクエストを承認
# (別アカウントの場合は、アクセプター側の権限で実行する必要があります)
aws ec2 accept-vpc-peering-connection \
--vpc-peering-connection-id pcx-0123456789abcdef0
# 3. リクエスタ側のルートテーブルに宛先ルートを追加
# VPC BのCIDR (10.2.0.0/16) へのトラフィックをピアリングへ流す
aws ec2 create-route \
--route-table-id rtb-requester-subnets \
--destination-cidr-block 10.2.0.0/16 \
--vpc-peering-connection-id pcx-0123456789abcdef0
# 4. アクセプター側のルートテーブルに戻り経路(Return Route)を追加
# VPC AのCIDR (10.1.0.0/16) へのトラフィックをピアリングへ流す
aws ec2 create-route \
--route-table-id rtb-accepter-subnets \
--destination-cidr-block 10.1.0.0/16 \
--vpc-peering-connection-id pcx-0123456789abcdef0
—
3.2 アプリケーションコード(Python)からのプライベートAPI呼び出し
VPCピアリングが正しく機能していれば、アプリケーション側は「パブリックIPではなく、プライベートIP(またはプライベートDNS名)」を指定してHTTPリクエストを送るだけです。特別なネットワークライブラリは不要で、通常のHTTPクライアントがそのまま使えます。
以下は、Pythonの requests ライブラリを用いて、VPC B側にあるプライベートAPIサーバー(例: http://10.2.10.50:8080/api/v1/data)にアクセスするサンプルコードです。
import logging
import requests
from requests.exceptions import RequestException
# ログの設定
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# VPCピアリングを経由するプライベートIPアドレス
# ※DNS(Route 53プライベートホストゾーン)を使っている場合は、内部ドメイン名を指定してもOKです
PRIVATE_API_URL = "http://10.2.10.50:8080/api/v1/data"
def fetch_data_from_private_api():
headers = {
"Content-Type": "application/json",
"X-Client-Service": "vpc-a-frontend"
}
payload = {
"action": "get_metrics",
"target": "all"
}
try:
logger.info(f"Connecting to private API at {PRIVATE_API_URL} via VPC Peering...")
# タイムアウトを必ず設定し、ネットワーク切断時のハングを防ぐ
response = requests.post(
PRIVATE_API_URL,
json=payload,
headers=headers,
timeout=5.0
)
# HTTPステータスコードが 4xx, 5xx の場合に例外を発生させる
response.raise_for_status()
logger.info("Successfully fetched data from private API.")
return response.json()
except RequestException as e:
logger.error(f"Failed to communicate with private API: {e}")
# 実務ではここでリトライ処理やサーキットブレーカーを呼び出す設計にします
raise
if __name__ == "__main__":
try:
data = fetch_data_from_private_api()
print("API Response:", data)
except Exception as err:
print("Execution failed:", err)
—
4. トラブルシューティングの現場知見:パケットが届かないときのチェックリスト
もし上記のコードを実行してタイムアウト(requests.exceptions.Timeout)や接続拒否(ConnectionRefusedError)が発生した場合、シニアSREが現場で必ず確認する「絶望のチェックリスト」を以下に共有します。
1. ルートテーブルの片方向落ち:
リクエスタ側だけでなく、アクセプター側のルートテーブルにも「相手のCIDR」が登録されているか?(戻りパケットの経路忘れは全体の8割を占める原因です)
2. セキュリティグループの自己参照 / 相互許可:
VPC B側のAPIサーバーのセキュリティグループにおいて、インバウンドルールで「VPC AのCIDR(例: 10.1.0.0/16)」からのポート(例: 8080)が明示的に許可されているか?
3. ネットワークACL(NACL)の暗黙の拒否:
サブネットレベルのNACLで、一時ポート(エフェメラルポート: 1024-65535)のインバウンド・アウトバウンド通信が塞がれていないか?
4. DNS解決の罠:
もしIPアドレスではなく api.internal.local のようなプライベートドメインで通信している場合、リクエスタ側のVPCで DNSホスト名(EnableDnsHostnames) と DNSサポート(EnableDnsSupport) が有効になっており、かつプライベートホストゾーンが両方のVPCにアソシエーションされているか?
—
まとめ
VPCピアリングは、AWSネットワークの基本でありながら、IPルーティングの基本原則(CIDRの一意性、双方向のルート設定、非推移性)を正確に理解していないと、細かな設定ミスの特定に何時間も費やしてしまう機能です。
「なぜこのパケットはここで捨てられるのか?」
頭の中でパケットのルーティングテーブル参照からセキュリティグループ評価までの旅をトレースできるようになれば、あなたも立派なクラウドネットワークのスペシャリストです。本記事が、皆さんの日々のインフラ運用やアーキテクチャ設計の一助となれば幸いです。
コメント