パケットはどこへ行く?VPCカスタムルートと宛先ベースのトラフィック制御の深層
こんにちは。クラウドの底なし沼と日々格闘しているシニアSREの私です。
Webアプリケーションのパフォーマンスチューニングやマイクロサービス間通信の設計に熱中するあまり、インフラの足元である「VPC(Virtual Private Cloud)のルーティング」を見落としていませんか? 「とりあえずインターネットに出るなら 0.0.0.0/0 をインターネットゲートウェイ(IGW)に向けておけば動くっしょ」なんて軽く考えていると、ある日突然、セキュアであるべき社内APIへの通信がパブリックインターネットにダダ漏れになっていたり、マルチAZ間通信で思わぬデータ転送量(クロスAZ料金)の請求書を見て冷や汗をかくことになります。
クラウドネイティブな世界において、ルーティングは単なる「荷物の配送係」ではありません。セキュリティ境界線を描き、コストを最適化し、システムの耐障害性を担保するための最強のトラフィック制御装置です。
今回は、AWSのAmazon VPCやGCPのVPCを念頭に置きながら、カスタムルートと宛先ベースのトラフィック制御の勘所を、実務の現場で使える知見を交えて徹底的に解説します。
—
1. クラウドVPCのルーティングにおける「基本のキ」と落とし穴
プライベートクラウド時代のネットワーク機器(CiscoやYAMAHAなど)を触ってきたベテランほど、クラウドVPCのルーティングテーブルの挙動に最初は戸惑います。
クラウドのルートテーブルは、従来のL3スイッチのようなダイナミックルーティング(BGPやOSPF)が基本ではなく、「最長一致(Longest Prefix Match)」の原則に基づく静的ルーティングの組み合わせで成り立っています。
クラウドネットワークの基本原則:最長一致とローカルルート
VPCを作成すると、必ずデフォルトで作成される「ローカルルート」が存在します。例えば、VPCのCIDRブロックに 10.0.0.0/16 を割り当てた場合、ルートテーブルには以下のようなエントリが自動生成されます。
- 宛先 (Destination):
10.0.0.0/16 - ターゲット (Target):
local
この local ルートは削除できません。このエントリが存在するため、同じVPC内のサブネット間やインスタンス間(例: 10.0.0.5 から 10.0.1.20 への通信)のパケットは、VPC内の仮想ルーターによって直接ルーティングされます。
ここで多くのエンジニアがハマる落とし穴が、「VPCピアリング」や「トランジットゲートウェイ(TGW)」を導入した際のルーティングの競合です。
例えば、自VPCが 10.0.0.0/16 で、接続先のパートナーVPCが 10.1.0.0/16 だとします。この場合、ルートテーブルに明示的に 10.1.0.0/16 をVPCピアリング接続へ向けるカスタムルートを追加しなければ、パケットは local ルートに吸い寄せられるか、存在しない宛先として捨てられてしまいます。
—
2. 実務で頻出する4つのカスタムルートパターンと通信フロー
実際のシステムアーキテクチャでは、サブネットの性格(パブリック、プライベート、アイソレート)に応じて、カスタムルートを巧みに使い分ける必要があります。
ここでは、代表的な4つのトラフィック制御パターンを見ていきましょう。
パターンA: 外部API通信をセキュアに隠蔽する「NATゲートウェイ行き」
パブリックなWeb API(StripeやSendGridなど)を叩くバックエンドワーカー(プライベートサブネットに配置)からの通信を制御します。
- 宛先:
0.0.0.0/0 - ターゲット:
nat-xxxxxxxxxxxxxxxxx(NATゲートウェイ) - 意図: プライベートサブネットからインターネットへの「外向き」の通信だけを許可し、インターネットからの「内向き」の不正アクセスを完全に遮断します。
パターンB: 機密データを守る「VPCエンドポイント(Gateway型)行き」
S3やDynamoDBへのトラフィックを、インターネットやNATゲートウェイを経由させず、AWSのバックボーンネットワーク内に閉じ込めます。
- 宛先:
pl-xxxxxxxx(S3のプレフィックスリスト) - ターゲット:
vpce-xxxxxxxxxxxxxxxxx(ゲートウェイVPCエンドポイント) - 意図: 通信のレイテンシー低下だけでなく、NATゲートウェイのデータ処理課金(GB単価)を劇的に削減する、SREマストのコスト最適化テクニックです。
パターンC: オンプレミスとのハイブリッド接続「VPN/Direct Connect行き」
社内システムやデータセンターとAWSを接続する場合のルーティングです。
- 宛先:
192.168.100.0/24(社内ネットワークのCIDR) - ターゲット:
vgw-xxxxxxxx(仮想プライベートゲートウェイ) またはtgw-xxxxxxxxx - 意図: 特定の社内セグメント宛てのトラフィックだけを、暗号化されたIPsecトンネルや専用線に確実に流し込みます。
パターンD: セキュリティアプライアンス(次世代FWなど)を挟む「ENI/インスタンス転送」
IDS/IPSやWAF、あるいはカスタムNATインスタンスを経由してパケットを検査・フィルタリングしたい場合に用います。
- 宛先:
0.0.0.0/0 - ターゲット:
eni-xxxxxxxxxxxxxxxxx(セキュリティアプライアンスのネットワークインターフェース) - 意図: クラウドネイティブなルーターの挙動をバイパスし、特定の仮想アプライアンスに一度パケットを強制送還(Src/Dstチェックの無効化が必須)します。
—
3. 実践!AWS CLIとTerraformによるカスタムルート設定
口で言うのは簡単ですが、インフラはコード(IaC)で美しく管理してこそプロです。ここでは、Terraformを使った実用的なルートテーブルの構築コードを見てみましょう。
以下の設定例では、「パブリックサブネット(IGW直結)」と「プライベートサブネット(NAT/エンドポイント経由)」のルートを明確に分離しています。
# --------------------------------------------------
# 1. プライベートサブネット用ルートテーブルの定義
# --------------------------------------------------
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
# デフォルトルート(0.0.0.0/0)をNATゲートウェイに向ける
# これにより、プライベートインスタンスからの外部API呼び出しが可能になる
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
# Amazon S3へのトラフィックをNAT経由ではなく、Gatewayエンドポイントへ直接流す(コスト削減とセキュリティ向上)
route {
destination_prefix_list_id = data.aws_prefix_list.s3.id
gateway_id = aws_vpc_endpoint.s3.id
}
tags = {
Name = "prod-private-rt"
Environment = "production"
}
}
# --------------------------------------------------
# 2. サブネットとルートテーブルの関連付け (Association)
# --------------------------------------------------
resource "aws_route_table_association "private_1a" {
subnet_id = aws_subnet.private_1a.id
route_table_id = aws_route_table.private.id
}
—
4. アプリケーション層からの検証とトラブルシューティング
カスタムルートを設定したはいいものの、「本当に想定通りのパスを通っているか?」を確認できなければSRE失格です。
ここでは、アプリケーションコード(Python)やデバッグツール(curl, traceroute)を使った実地での検証手法を解説します。
パターン1: curlを用いたルーティングおよびエンドポイントの疎通確認
例えば、S3エンドポイントやNATゲートウェイ経由の外部API通信が正しく機能しているかを、HTTPヘッダーやレスポンスタイムも含めて検証します。
# プライベートインスタンス内から外部API(例: 疎通確認用パブリックIP)へリクエストを飛ばし、
# 送信元IP(グローバルIP)がNATゲートウェイのものに変換されているかを確認する
curl -Iv https://api.ipify.org?format=json
# 期待される出力(NATのIPから出ていればOK)
# * Connected to api.ipify.org (192.0.2.1) port 443 (#0)
# < HTTP/2 200
# {"ip":"203.0.113.50"} <-- ここがNAT GatewayのElastic IPになっているべき
パターン2: Python (Requests) を用いたAPIクライアントのタイムアウト・ルーティング監視
Web APIを叩くマイクロサービス側で、ネットワークのルーティングミスやブラックホール化(パケットが捨てられる現象)を検知するための堅牢なリクエスト処理の実装例です。
import logging
import requests
from requests.exceptions import Timeout, RequestException
# ロガーの設定
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def call_external_api_safely(api_url: str, payload: dict) -> dict:
"""
カスタムルートの誤設定やネットワークのブラックホール化(タイムアウト)を
適切にハンドリングするAPIクライアントのサンプル
"""
headers = {
"Content-Type": "application/json",
"X-Custom-Source": "SRE-Verification-Script"
}
try:
# 接続タイムアウト(3秒)、読み込みタイムアウト(5秒)を厳格に設定
# ルート設定ミスによるパケットの迷子(ブラックホール)を早期に検知する
response = requests.post(
api_url,
json=payload,
headers=headers,
timeout=(3.0, 5.0)
)
# HTTPステータスコードが400以上の場合に例外を発生させる
response.raise_for_status()
logger.info(f"API呼び出し成功: Status {response.status_code}")
return response.json()
except Timeout as e:
logger.error(f"【ネットワーク異常の可能性】APIリクエストがタイムアウトしました。ルートテーブルの宛先設定やセキュリティグループを確認してください: {e}")
raise
except RequestException as e:
logger.error(f"【通信エラー】API呼び出しに失敗しました: {e}")
raise
# 実行例
if __name__ == "__main__":
# 閉じたVPC環境からNAT/エンドポイント経由で叩く想定のURL
target_api = "https://httpbin.org/post"
test_payload = {"service": "order-api", "action": "checkout"}
# call_external_api_safely(target_api, test_payload)
デバッグの鉄則:パケットが迷子になったときのチェックリスト
もし通信がうまくいかない場合、以下の順序でレイヤーを上から下へ剥がすようにデバッグを行ってください。
1. ルートテーブルの最長一致確認: 宛先IPが、より具体的なプレフィックス(例: /32 や /24)に横取りされていないか?
2. セキュリティグループ(SG)とネットワークACL(NACL): ルートがあっても、ファイアウォール側でインバウンド/アウトバウンドが塞がれていないか?(特にNACLのステートレスな仕様による戻りパケットのブロックに注意)
3. VPCフローログの解析: REJECT されたパケットの dstaddr と srcaddr、および pkt-src-addr をCloudWatch LogsやAthenaでクエリし、パケットがどのルーターインターフェースで捨てられたのかを特定する。
—
5. まとめ
VPCのカスタムルートと宛先ベースのトラフィック制御は、クラウドインフラストラクチャの「交通整理」そのものです。
なんとなく動くからとデフォルトルートに頼り切るのではなく、
- どのトラフィックをどこに通し、どこに通すべきではないのか
- コストとセキュリティのバランス(NAT vs Gatewayエンドポイント)は最適か
これらをコードと論理的思考でコントロールできるようになれば、あなたの設計するシステムは、トラフィックの急増や複雑なマルチVPC環境の拡大に対しても、びくともしない堅牢性を手に入れることができます。
今日のデプロイが、素晴らしいネットワークのつながりと共にあることを祈っています。それではまた次の現場でお会いしましょう!
コメント