AWS NLBの深淵:なぜ「魔法のパケット転送」は低レイテンシーなのか?
SREとして現場に立っていると、ALBとNLBの使い分けについて相談を受けることがよくあります。「とりあえずALBでいいんじゃない?」という声も聞こえてきますが、毎秒数万リクエストを捌くようなシビアな環境や、あるいはTCP/UDPの特殊なプロトコルを扱う際、ALBの「アプリケーション層の解釈」は時にボトルネックとなり、時にオーバーヘッドとなります。
今日は、AWSにおけるインフラの要、Network Load Balancer (NLB)の心臓部にメスを入れてみましょう。教科書的な説明は一旦脇に置き、パケットがどう駆け巡り、なぜNLBが「最強のL4ロードバランサー」足り得るのかを紐解きます。
—
1. NLBのアーキテクチャ:なぜ「高速」なのか?
NLBがALBと決定的に異なるのは、OSI参照モデルのレイヤー4(トランスポート層)で動いているという点です。ALBはHTTP/HTTPSを理解するために一度パケットを終端(Terminate)し、ヘッダーを解析して再構築しますが、NLBは違います。
NLBは、「パケットをいかに速く、ターゲット(EC2やIP)に届けるか」という一点に特化しています。
パケット転送の魔法:フローハッシュ
NLBは、受け取ったパケットの「送信元IP」「送信元ポート」「宛先IP」「宛先ポート」「プロトコル」を基に、独自のハッシュアルゴリズムで転送先を決定します。この処理は極めて軽量で、ハードウェアに近いレベルで実行されます。そのため、数百万リクエスト/秒という過酷な負荷にも耐えうるのです。
—
2. 実践:NLBを通した通信フローを追う
皆さんが開発しているAPIがNLBの背後にある場合、通信は以下のシーケンスを辿ります。
1. Client -> NLB: クライアントがNLBのIP(ENI)に対してTCP SYNを送る。
2. NLB -> Target: NLBはパケットを「書き換え」ずに、そのままターゲットへ転送(透過的転送)。
3. Target -> Client: ターゲットはクライアントのIPを認識し、直接(あるいはNLB経由で戻りパケットを)返送する。
ここで注意すべきは、ターゲット(EC2)側で tcpdump を打った際、送信元IPが「NLBのプライベートIP」ではなく「クライアントのグローバルIP」として見えることです。これが「クライアントIP保持」の正体です。
Pythonによる生存確認(ヘルスチェックの裏側)
NLBは非常に短い間隔でターゲットの死活監視を行っています。以下は、NLBがターゲットに対して投げているであろう「接続確認」の挙動を模したコードです。
import socket
# NLBのターゲットグループに登録されたノードに対して
# 実際にNLBが内部で行っているようなTCP接続テストを再現
def check_health(target_ip, port):
try:
# タイムアウトを短く設定するのがNLB流
with socket.create_connection((target_ip, port), timeout=2) as sock:
print(f"Target {target_ip}:{port} is Healthy")
except Exception as e:
print(f"Target {target_ip}:{port} is Unhealthy: {e}")
# 実行例
check_health("10.0.1.50", 8080)
—
3. 設定の勘所:ここを間違えると障害になる
NLBの実務設定で「ハマりポイント」になりがちなのが、Target Group の設定です。特に Target Type の選択は、将来の拡張性を左右します。
ターゲットタイプ: instance vs ip
instance: EC2のインスタンスIDを指定。NLBが自動的にプライベートIPを解決してくれるため管理が楽。ip: IPアドレスを直接指定。オンプレミスサーバーや、AWS外のリソース、あるいはKubernetesのPodをターゲットにする場合に必須。
Proxy Protocol V2 の活用
もし皆さんのAPIが「クライアントの本来のIP」をログに記録したい場合、かつターゲットがNLBの背後で複雑なNATを通る必要があるなら、Proxy Protocol を有効にしましょう。
# AWS CLIでターゲットグループの属性を変更してProxy Protocolを有効化する例
aws elbv2 modify-target-group-attributes \
--target-group-arn arn:aws:elasticloadbalancing:ap-northeast-1:xxxx:targetgroup/my-tg/xxxx \
--attributes Key=proxy_protocol_v2.enabled,Value=true
これを有効にすると、TCPペイロードの先頭にクライアントIPを含むヘッダーが付与されます。Webサーバー(Nginxなど)側でこれを受け取るための設定も必要になるため、導入時は慎重に。
—
4. SREからのTips:トラブルシューティングの極意
NLBを使っていて「接続が突然切れる」「特定のパケットだけ飛ばない」という事態に遭遇したら、以下の2点を確認してください。
1. Keep-Alive設定の不一致: クライアント側のタイムアウトと、NLBの Idle Timeout(デフォルト350秒)の整合性は取れていますか?NLB側がセッションを先に切断すると、クライアント側に RST パケットが飛び、エラーとなります。
2. セキュリティグループの疎通: ALBと違い、NLBそのものにはセキュリティグループを設定しません(ターゲット側に設定します)。「NLBのIPから通信が来ていること」を忘れて、ターゲットのSGでNLBのIPを許可していないケースが非常に多いです。
最後に
NLBは「黙々とパケットを捌く職人」のような存在です。ALBのような華やかな機能はありませんが、その堅牢性とスループットは、皆さんのサービスの基盤を支える最強の武器になります。
「なぜ今、このパケットがそこに届かないのか?」という問いを常に持ち、パケットの旅路を想像してみてください。そうすれば、インフラの運用はもっと楽しく、そして確実なものになるはずです。
次回は、NLBとKubernetesの LoadBalancer サービスがどう連携するのか、その内部構造を深掘りしたいと思います。それでは、良いインフラライフを!
コメント