【実務・中級編】 プライベートIPアドレス範囲(RFC 1918)のVPC内割当ルール – クラウド&コンテナネットワーク実践ガイド

IP枯渇の悪夢を防げ!AWS/GCPにおけるRFC 1918プライベートIP設計の勘所

こんにちは。クラウドの底なし沼で幾夜ものパケットキャプチャと格闘してきたシニアSREの私です。

本番環境のリリース前夜、夜中の2時に「VPCのIPレンジが足りなくなったので、接続先のオンプレミスとルーティングがバッティングして通信できません」という戦慄のチャットが飛び込んできたことはないでしょうか? 私はあります。冷や汗が一気に噴き出るあの感覚は、何度味わっても慣れるものではありません。

クラウドネイティブなインフラストラクチャやKubernetesクラスタの設計において、VPCのサブネット切り分けとプライベートIPアドレスの割り当ては、すべてのインフラの土台となる極めて重要な工程です。ここで一度設計を誤ると、のちのマルチクラウド接続、KubernetesのPodネットワーク統合、さらには企業買収に伴うVPCピアリングの結合に至るまで、あらゆるフェーズで足かせとなります。

今回は、RFC 1918で定められたプライベートIPv4アドレス空間の正しい理解から、クラウド環境(AWS/GCP)での実践的なアドレッシング設計、そして現場で役立つルーティングの罠まで、泥臭い実務の知見を交えて徹底的に解説します。

—

1. RFC 1918の基本仕様と私たちが背負う制約

インターネットの父たちがグローバルIPアドレスの枯渇を見据え、1996年に発行したのが RFC 1918(Address Allocation for Private Internets) です。これにより、組織内の閉域網で自由に再利用できる3つのプライベートIPアドレスブロックが定義されました。

| ネットワークプレフィックス | ホスト数(理論値) | 一般的な利用シーンと実務での印象 |
| :— | :— | :— |
| 10.0.0.0/8 | 約1,677万 | 大規模企業ネットワーク、広大なAWS/GCPの基幹VPC、K8sクラスタ |
| 172.16.0.0/12 | 約104万 | 中規模システム、Dockerのデフォルトブリッジ(172.17.0.0/16など)の親 |
| 192.168.0.0/16 | 約6.5万 | 家庭用ルーター、小規模な検証環境、社内LAN |

一見すると、「10.0.0.0/8を選んでおけば絶対に枯渇しないだろう」と思いがちですが、ここがクラウド時代の大きな罠です。

クラウド時代における「IPアドレスのインフレ」

オンプレミス全盛期であれば、10.0.0.0/8を一つの企業ネットワークに割り当てるのは贅沢でも何でもありませんでした。しかし、AWSやGCPといったメガクラウド上では、VPCピアリング、Transit Gateway、VPN接続によるオンプレミス網とのルーティングなど、ネットワーク同士を結合する機会が爆発的に増えています。

もし、社内のオンプレミス網で10.0.0.0/16が使われており、AWSのメインVPCにも深く考えず10.0.0.0/16を割り当ててしまったらどうなるでしょう? そう、VPCピアリングを結んだ瞬間にルーティングテーブルが矛盾を起こし、パケットは行き場を失ってブラックホールに消えていきます。

—

2. クラウド(AWS/GCP)におけるVPC設計の現実解

では、私たちはどのようにプライベートIPレンジを切り分けるべきでしょうか。現場で培ったベストプラクティスをいくつか紹介します。

ルール1: 10.0.0.0/8の濫用を避け、/16を基本単位にする

VPCのサイズは、小さすぎても大きすぎても運用コストになります。基本的には10.x.0.0/16(ホスト数65,536)を1つのVPCの標準サイズとして設計することをおすすめします。これであれば、サブネットを綺麗に分割しつつ、将来的な拡張にも耐えられます。

ルール2: Kubernetes(CKS/GKE/EKS)のノード・Pod・Service用レンジを厳格に分離する

Kubernetesを運用する場合、ノード(EC2やGCEインスタンス)に割り当てられるVPCのIPとは別に、PodやServiceのための独立したIPレンジ(Caliper, VPC-native routingなど)が必要になります。

例えば、GCPのGoogle Kubernetes Engine (GKE) でVPCネイティブクラスターを作成する場合の設定イメージを見てみましょう。Terraformでの記述例です。

# GKEクラスタのためのVPCとサブネット設計のTerraformサンプル
resource "google_compute_network" "production_vpc" {
  name                    = "app-production-vpc"
  auto_create_subnetworks = false # 自動サブネット作成はオフ(手動で厳格に管理する)
}

resource "google_compute_subnetwork" "node_subnet" {
  name          = "app-node-subnet"
  ip_cidr_range = "10.100.0.0/20" # ノード(VM)用のプライベートIP (4,096ホスト)
  region        = "asia-northeast1"
  network       = google_compute_network.production_vpc.id

  # KubernetesのPod用セカンダリIPレンジの定義
  secondary_ip_range {
    range_name    = "pod-ip-range"
    ip_cidr_range = "10.104.0.0/14" # Pod用 (約26万IP)
  }

  # KubernetesのService用セカンダリIPレンジの定義
  secondary_ip_range {
    range_name    = "service-ip-range"
    ip_cidr_range = "10.108.0.0/20" # Service用 (4,096IP)
  }
}

このように、ノード、Pod、Serviceで明確にプレフィックスを分けることで、トラブルシューティング時に「あ、このトラフィックはPod間通信だな」「これは外部からのAPIリクエストだな」とパケットの出所を即座に特定できるようになります。

—

3. 通信フロー:プライベートIP間のルーティングとNATの挙動

ここで、VPC内のプライベートIPを持つWebアプリケーションコンテナから、外部のWeb APIへリクエストを飛ばす際のパケットの旅路を追ってみましょう。

[App Pod (10.104.0.15)] 
       │
       ▼ (VPC内ルーティング)
[Node (10.100.0.5)] 
       │
       ▼ (NAT Gateway / SNAT)
[インターネット上のWeb API (例: 93.184.216.34)]

1. アプリケーションからの発信: Pod(10.104.0.15)から宛先IP(例: 外部API)に向けてHTTPリクエストが送信されます。
2. ノードでのパケット処理: パケットはホストノードのルーティングテーブルを通過し、VPCの仮想ルーターに送られます。
3. NAT GatewayによるソースNAT(SNAT): プライベートサブネットからインターネットへ向かう通信は、AWSのNAT GatewayやGCPのCloud NATを通過します。この瞬間、パケットの送信元IPアドレス(Source IP)が、プライベートIP(10.104.0.15)からNAT Gatewayに割り当てられたElastic IP(パブリックIP)に書き換えられます。

実務でのデバッグ:PythonスクリプトによるIP到達性テスト

現場で「本当に正しくルーティングされているか」「NAT経由で外に出ていけているか」を確認するため、私はよく次のようなシンプルなPythonスクリプトをデバッグ用Pod上で実行します。

import urllib.request
import json
import socket

def check_network_path():
    target_url = "https://httpbin.org/ip"
    
    # 現在のホスト名とIPアドレスの確認
    hostname = socket.gethostname()
    local_ip = socket.gethostbyname(hostname)
    print(f"[*] 実行中ホスト: {hostname} (ローカルIP: {local_ip})")
    
    try:
        # 外部APIへリクエストを送信し、外部からどう見えているか(グローバルIP)を確認
        req = urllib.request.Request(target_url, headers={'User-Agent': 'SRE-Network-Checker/1.0'})
        with urllib.request.urlopen(req, timeout=5) as response:
            data = json.loads(response.read().decode('utf-8'))
            print(f"[+] 外部から観測されたグローバルIP (NAT成功): {data.get('origin')}")
    except Exception as e:
        print(f"[-] エラー発生: パケットが外部に出ていません。ルートテーブルやNAT設定を確認してください。 -> {e}")

if __name__ == "__main__":
    check_network_path()

もしここでタイムアウトが発生する場合、サブネットのルートテーブル(Route Table)に 0.0.0.0/0 の宛先としてNAT Gatewayへのルートが正しくアタッチされているか、セキュリティグループやファイアウォールルールが外向きの通信(Egress)をブロックしていないかを疑います。

—

4. 現場のシニアが教える、アドレッシング設計の3大アンチパターン

最後に、私がこれまで数々の障害現場で目撃してきた「やっちまった」設計のアンチパターンを共有します。反面教師として参考にしてください。

1. 社内ネットワークとクラウドのIPレンジ被り

  • 症状: AWSと社内LANをAWS Direct ConnectやVPNで接続した途端、一部のオンプレサーバーにアクセスできなくなった。
  • 対策: クラウドを導入する前に、全社規模のIPアドレス管理台帳(IPAM)を作成し、10.0.0.0/8等の巨大なブロックを部門別・環境別に厳格に割り当てましょう。

2. 「とりあえず一番大きい/16を切っとけ」の悲劇

  • 症状: 検証環境用のVPCに10.0.0.0/16を切ったところ、のちに本番環境や別プロジェクトのVPCとピアリングする際、CIDRが完全に重複して接続不可能に。
  • 対策: 検証環境やステージング環境であれば、10.200.0.0/24や172.30.0.0/20など、必要最小限のプレフィックスから始めましょう。

3. 可用性を無視した単一サブネット構成

  • 症状: 1つのアベイラビリティゾーン(AZ)だけにすべてのプライベートサブネットを集中させ、障害時にシステム全体が沈没した。
  • 対策: クラウドの強みはマルチAZです。最低でも2つ、できれば3つのAZにまたがってサブネットを等しく切り、トラフィックを分散させましょう。

—

まとめ

プライベートIPアドレスの設計は、地味で、派手な機能改善の陰に隠れがちな作業です。しかし、一度稼働し始めたシステムにおいて、後からVPCのCIDRブロックやサブネットを根本から変更することは、心臓移植に等しい大工事を伴います。

「たかがIP、されどIP」。
これから新規にVPCを設計する、あるいはKubernetesクラスタを構築する際は、将来の拡張性や他システムとの接続性を頭の片隅に置きながら、美しくスケーラブルなアドレッシングを描き切ってください。あなたのインフラ人生における夜間呼出しが少しでも減ることを、心から願っています。

コメント

タイトルとURLをコピーしました