妥協なきクラウドネットワーキング:NATゲートウェイ vs 自作NATインスタンス。パケットの旅路とLinuxカーネルから解き明かすコストと性能の真実
クラウドの黎明期からインフラを支え続ける永遠のテーマがある。プライベートサブネットからインターネットへアウトバウンド通信を行う際、私たちは「マネージドな贅沢」をとるべきか、それとも「自作の泥臭さ」をとるべきか——。
AWSの NAT Gateway と、自前でEC2インスタンスを立てて iptables や nftables でルーティングするいわゆる NATインスタンス。この2つの選択は、単なる「マネージドか否か」「コストが高いか安いか」という表層的な議論にとどまらない。
パケットがLinuxカーネルのネットワークスタックをどう通過し、コネクションプールがどう管理され、ハードウェアアクセラレーションやスケール制限がどう牙をむくのか。ネットワークプロトコルとLinuxカーネルの深淵を愛するSREの視点から、この永遠の問いに決着をつけよう。
—
1. パケットレベルの裏側:マネージドとカーネル空間の非対称性
まず、プライベートサブネットから送出されたパケットが、インターネット上の宛先へ到達し、戻ってくるまでのレイヤーでの挙動を解剖する。
フルマネージドNATゲートウェイのブラックボックス
AWSの NAT Gateway は、裏側で高度に分散化されたSoftware-Defined Networking(SDN)基盤上で稼働している。ユーザーからは単なる単一のIPアドレスとルートテーブルの宛先に見えるが、内部ではトラフィックの負荷分散と冗長化が自動で行われている。
パケットはエンクロージャーを抜け、AWSの巨大なエッジルーター群を通過する際にSNAT(Source Network Address Translation)処理を受ける。このプロセスは完全にハードウェアオフロードされており、数Tbps規模の帯域であっても、遅延(Jitter)は極めて小さく抑えられる。
自作NATインスタンスのLinuxカーネル制約
一方、自作の NATインスタンス(例:Amazon Linux 2023 をベースにしたEC2)では、パケットの運命は完全にLinuxカーネルの肩にかかっている。
プライベートIPを持つインスタンスから送出されたパケットがNATインスタンスのENI(Elastic Network Interface)に届くと、カーネルは以下のステップを踏む。
1. Netfilter (conntrack) テーブルのルックアップ: パケットが既存のコネクションに属しているか確認し、ステートフルに追跡する。
2. SNAT / Masquerading 処理: 送信元IPアドレスをNATインスタンスのパブリックIPに書き換える。
3. ルーティング決定と送信: eth0 または eth1 を介してインターネット側へ送出する。
ここでボトルネックになるのが、Linuxカーネルの netfilter におけるコネクション追跡モジュール(nf_conntrack)だ。高負荷環境では、このテーブルが溢れたり、CPUのソフトIRQ(SoftIRQ)が特定のvCPUに偏ることで、パケットロスやレイヤー4での遅延悪化を引き起こす。
—
2. スループットとスケーリングの限界
アーキテクトが最も頭を悩ませるのが、スループットの天井とスケールアップ・アウトの挙動だ。
NATゲートウェイ:バーストと「見えない壁」
マネージドな NAT Gateway は、初期状態ではシームレスにスケーリングし、最大45 Gbpsまでのバーストトラフィックを許容する。しかし、急激なトラフィック増(例えば、数万のクライアントが一斉に大容量ファイルをダウンロードするようなシナリオ)に直面した際、AWS側の内部プールが追いつくまでにごく一時的なスループットの頭打ちやパケットドロップが発生することがある。これらはユーザー側でメトリクス(PacketsDropCount など)を監視する以外に直接介入する手段がない。
NATインスタンス:インスタンスタイプがすべての運命を握る
自作NATインスタンスの場合、性能は選定したEC2のインスタンスタイプ(特にネットワーク帯域とEBS最適化、ENIの数)に完全に縛られる。
例えば、c6i.2xlarge のようなコンピュート最適化インスタンスを選べば、AWSの Enhanced Networking(ENA)の恩恵を受け、高いパケット処理性能(PPS: Packets Per Second)を確保できる。
しかし、単一インスタンスである以上、ハードウェア障害時のダウンタイムリスクは免れない。Auto Scaling Group(ASG)とライフサイクルフック、そしてElastic IPの動的アタッチを組み合わせた高可用性(HA)構成を自前で実装・維持運用するコストは、決して小さくない。
—
3. 運用・セキュリティ上のトレードオフ
ポート枯渇(Port Exhaustion)問題の深層
TCP/UDP通信において、NATデバイスは限られたポート番号(最大65,535からシステム予約ポートを除いた数)を、背後にいる膨大なプライベートIPのクライアント間で共有しなければならない。
- NATゲートウェイ: 1つのNATゲートウェイIPあたり、宛先IP/ポートの組み合わせに対して最大55,000の同時コネクションを確立できる。これが枯渇した場合、複数のNATゲートウェイIPをアタッチすることでスケールアウト可能である。
- NATインスタンス: カーネルパラメータのチューニングを行わないデフォルト状態では、すぐに
connection table fullやポート枯渇エラーに直面する。
後述するLinuxカーネルチューニングを怠ると、プロダクション環境で突然 Cannot assign requested address エラーが頻発する悪夢を見る事になる。
—
4. 実践:極限までチューニングされたNATインスタンスの設定
「それでもコストやセキュリティ上の理由から自作NATインスタンスを採用せざるを得ない」というテックリードのために、パケットロスを極限まで減らし、高スループットを叩き出すためのLinuxカーネルパラメータ設定(/etc/sysctl.conf)と、必須の nftables 設定の決定版を共有する。
カーネルパラメータのチューニング(/etc/sysctl.conf)
# パケットの転送(ルーティング)を有効化
net.ipv4.ip_forward = 1
# conntrack(コネクション追跡)の最大数とハッシュサイズの拡大
# ※メモリ搭載量に合わせて調整すること(1エントリーあたり約300バイト消費)
net.netfilter.nf_conntrack_max = 2097152
net.nf_conntrack_max = 2097152
net.ipv4.netfilter.ip_conntrack_max = 2097152
# コネクションが閉じられた後のタイムアウト時間を短縮し、ポートの早期再利用を促進する
net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait = 30
net.ipv4.netfilter.ip_conntrack_tcp_timeout_fin_wait = 30
# TCPポートの動的割り当て範囲を拡大(利用可能なエフェメラルポートを増やす)
net.ipv4.ip_local_port_range = 1024 65535
# TIME_WAIT状態のソケットを迅速に再利用(TCPパフォーマンステストや高負荷Webスクレイピング等で有効)
net.ipv4.tcp_tw_reuse = 1
# SYNパケットに対するキューの長さを拡大し、SYNフラッドやバースト接続に対応
net.ipv4.tcp_max_syn_backlog = 65535
net.core.somaxconn = 65535
# 受信・送信キューのバッファサイズを拡大(高スループット・高レイテンシ回線対策)
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
設定を反映するには、以下のコマンドを実行する。
# 設定を即時適用する
sudo sysctl -p
nftables(またはiptables)によるマスカレード設定
現代のモダンなLinuxディストリビューションでは iptables の代わりに nftables が標準となっている。以下の設定を /etc/nftables.conf に記述し、パケットのフォワードとマスカレードを行う。
# /etc/nftables.conf の設定例
flush ruleset
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
# 内部プライベートネットワークからのトラフィックを、外向きインターフェース(例: eth0)のIPにマスカレード
oifname "eth0" masquerade
}
}
table ip filter {
chain forward {
type filter hook forward priority filter; policy drop;
# 確立済みのコネクションおよび関連するパケットの通過を許可(ステートフルインスペクション)
ct state established,related accept
# 内部サブネット(例: 10.0.0.0/16)からの新しいアウトバウンド通信を許可
ip saddr 10.0.0.0/16 ct state new accept
# その他すべてのフォワードパケットを破棄
drop
}
}
—
5. 結論:どちらを選ぶべきか?
アーキテクトとしての冷徹な判断を下そう。
1. マネージドNATゲートウェイを選ぶべきケース:
- 人的リソースが限られており、OSパッチ適用や可用性担保(マルチAZ化)の運用負荷をゼロにしたい場合。
- トラフィックの変動が激しく、予測不可能なバーストに自動で追従してほしい場合。
- 費用対効果において、エンジニアの人件費や障害対応コストがマネージドサービスの利用料金を上回る場合。
2. 自作NATインスタンスを選ぶべきケース:
- 膨大なクロスAZデータ転送が発生し、マネージドNATゲートウェイの「データ処理料金(GBあたりの課金)」が予算を圧倒的に圧迫する場合(大規模なデータレイクや動画配信プラットフォームなど)。
- パケットインスペクション、カスタムルーティング、特定のセキュリティ監査要件のために、カーネルレベルでのパケット操作やログ取得が必要な場合。
「コストの安さ」という甘い言葉の裏には、深夜のカーネルパニックやポート枯渇のトラブルシューティングという「技術的負債の金利」が潜んでいることを忘れてはならない。インフラの文脈において、何を捨てて何を買うか。その選択こそが、SREの技量が試される最大の舞台なのだ。
コメント