クラウドの請求書に潜む「見えない刺客」を倒せ:クロスAZ通信コストを最適化するNATゲートウェイ戦略
AWSやGCPでのインフラ構築において、マルチAZ構成は「高可用性のための聖域」とされています。しかし、その聖域に足を踏み入れた途端、我々SREを待ち受けているのが「AZ間データ転送コスト」という名の、見えない請求書です。
特に、プライベートサブネットからインターネットへ向かうトラフィックを処理する「NATゲートウェイ」の配置をミスると、アプリケーションのトラフィックがAZの壁を越えるたびに課金が積み上がり、月末に驚愕する羽目になります。今日は、現場の泥臭い経験から導き出した「AZローカルルーティング」の最適解を伝授します。
—
なぜ「NATゲートウェイへの道」は高くつくのか?
まず、クラウドのネットワーク課金の鉄則を復習しましょう。多くのパブリッククラウドにおいて、同一AZ内での通信は無料ですが、AZを跨いだ通信にはデータ転送量に応じた課金が発生します。
典型的なアンチパターンはこれです。
- AZ-AのプライベートサブネットにあるEC2が、AZ-BにあるNATゲートウェイを経由してインターネットへ通信する。
この通信フローが発生すると、EC2からNATゲートウェイまでの「AZ間の移動」に対してデータ転送コストが発生します。NATゲートウェイは決して安くないリソースです。これに加えてAZ間転送コストが乗算されると、トラフィック量が多いWeb APIなどでは、コストが雪だるま式に膨れ上がります。
—
最適解:AZローカルルーティングの設計
この問題を解決する唯一の手段は、「各AZにNATゲートウェイを配置し、ルーティングテーブルで『地産地消』させる」という構成です。
1. 構成の基本原則
- 各AZにNATゲートウェイを1つずつ配置する。
- 各サブネット専用のルートテーブルを作成し、デフォルトルート(
0.0.0.0/0)の宛先を「そのAZにあるNATゲートウェイ」に固定する。
こうすることで、パケットはAZの境界を越えることなく、最短距離でインターネットへ駆け抜けていきます。
2. AWS CDK (TypeScript) による実装例
現場では手動設定は禁物です。IaC(Infrastructure as Code)で確実に制御しましょう。以下は、AZごとにNATゲートウェイを独立させるための設定例です。
// 各AZに対してループ処理でNATゲートウェイを割り当てる設計
for (const az of availabilityZones) {
// 特定AZのプライベートサブネットを作成
const privateSubnet = new ec2.PrivateSubnet(this, `PrivateSubnet-${az}`, {
vpcId: vpc.vpcId,
availabilityZone: az,
cidrBlock: '...',
});
// そのAZ専用のNATゲートウェイを配置
const natGateway = new ec2.CfnNatGateway(this, `NAT-${az}`, {
subnetId: publicSubnetInAz.subnetId, // そのAZのパブリックサブネットを指定
allocationId: eip.attrAllocationId,
});
// ルートテーブルを作成し、デフォルトルートをこのNATへ向ける
const routeTable = new ec2.CfnRouteTable(this, `RT-${az}`, { vpcId: vpc.vpcId });
new ec2.CfnRoute(this, `Route-${az}`, {
routeTableId: routeTable.attrRouteTableId,
destinationCidrBlock: '0.0.0.0/0',
natGatewayId: natGateway.ref, // 他AZに行かせない強力なフック
});
}
—
トラブルシューティング:本当にローカルで通信しているか?
「設定はしたけれど、本当に意図した通りに動いているのか?」と疑うのがSREの性(さが)です。実務では traceroute やパケットキャプチャを活用して、経路を確認します。
Pythonによる疎通確認とヘッダー監視
もしWeb APIのレスポンスが遅い、あるいはコストが落ちない場合は、アプリケーションがどのIPを経由しているかを追跡します。
import requests
# 外部のグローバルIP確認用APIを叩く
# 期待値:各AZのNATゲートウェイに割り当てたEIPが返ってくること
def check_egress_ip():
try:
response = requests.get('https://ifconfig.me', timeout=5)
print(f"現在の出口IP: {response.text}")
except Exception as e:
print(f"通信失敗: {e}")
# これを全AZのEC2で実行し、IPがAZごとに切り替わっているか確認する
デバッグの現場Tips
1. VPCフローログの活用: dstaddr がNATゲートウェイのプライベートIPであることを確認してください。もし他のAZのサブネット範囲が含まれていれば、それは「ルーティングの漏れ」です。
2. コスト配分タグ: NATゲートウェイにタグを付け、AWS Cost Explorerで「タグ別」にコストを可視化してください。「どのAZのNATにお金がかかっているか」が一目瞭然になります。
—
最後に:エンジニアが守るべき「規律」
NATゲートウェイの配置戦略は、一度構築してしまえば終わりではありません。新しいAZが追加されたり、VPCの構成を変更したりするたびに、この「ローカルルーティング」の整合性をチェックする必要があります。
「とりあえず動く」だけのインフラは、数ヶ月後に必ずコストという形で牙を剥きます。「パケットがどこを通り、どこでコストが発生しているか」を常に意識し、無駄なAZ越えをさせない。その細部へのこだわりこそが、ビジネスを支える強靭なSREの証です。
今日の設計が、皆さんの明日のインフラコストを最適化する一助となれば幸いです。それでは、良いデプロイを!
コメント