こんにちは!クラウドの裏側でうねるパケットの流れを想像するのが大好物な、現場のSREエンジニアです。
皆さんはAWSなどのクラウド環境でシステムを構築する際、インターネットからの玄関口として「ロードバランサー(LB)」を当たり前のように使っていることと思います。中でも、秒間数百万というすさまじいリクエストを、まるで息をするかのようにさばき続けるのがNLB(Network Load Balancer)です。
このNLB、実はレイヤー4(トランスポート層)で動作するため、HTTPの細かい中身を見ずに、TCPやUDPのパケットをガツンと高速にバックエンドへ転送してくれます。しかし、この「超高速・高スループット」という強力なメリットの裏側で、インフラエンジニアを長年悩ませてきた「ある厄介な仕様」が潜んでいます。それが、「クロスゾーン負荷分散」と「クライアントIPの保持」です。
「あれ、別のAZ(アベイラビリティゾーン)にあるサーバーへ勝手にパケットが飛んでいって通信費(クロスゾーンデータ転送量)がかさんでいる……」
「バックエンドのログを見たら、アクセス元が全部ロードバランサーのプライベートIPになっていて、誰が来たのか分からない!」
こんな悲鳴を現場で上げたことはありませんか?
今回は、初めてネットワークやロードバランサーに触れる方でもスッと腑に落ちるように、身近な例えを交えながら、このNLBの挙動の秘密を優しく紐解いていきたいと思います。一歩ずつ、丁寧に理解していきましょう!
—
1. そもそもNLBとAZの関係って?(郵便配達に例えてみよう)
まずは、AWSの「AZ(アベイラビリティゾーン)」と、そこに配置されるNLBの関係をイメージしてみましょう。
例えば、あなたが「東京」という大都市に住んでいて、街が大きく3つのエリア(AZ-A、AZ-B、AZ-C)に分かれているとします。
NLBを有効にすると、AWSはそれぞれのエリアの玄関口に「郵便受け(NLBのノード)」をポコン、ポコン、ポコンと設置します。
ここで、インターネットの向こう側からユーザー(クライアント)が手紙(リクエスト)を送ってきたとしましょう。ユーザーに一番近い、あるいはインターネットの経路的に一番スムーズなエリアの郵便受けに、その手紙が届きます。これが「NLBがリクエストを受け取る瞬間」です。
さて、ここからが本題です。
手紙を受け取ったそのエリアの郵便受けは、中身を読んで「よし、うちのエリアにあるアパート(EC2などのバックエンドサーバー)に配りに行こう!」と考えるでしょうか? それとも、「いや、隣のエリアのアパートの方が空室があるから、そっちへ持って行こうかな?」と考えるでしょうか?
この「他のエリアへ手紙を回すかどうか」を決めるスイッチこそが、今回主役となる「クロスゾーン負荷分散」なのです。
—
2. クロスゾーン負荷分散の「有効」と「無効」の正体
それでは、クロスゾーン負荷分散のスイッチが「オフ(無効)」のときと「オン(有効)」のときで、世界がどう変わるのかを見ていきましょう。
パターンA:クロスゾーン負荷分散が「無効」(デフォルトの挙動)
クロスゾーン負荷分散を無効にしている場合、NLBの各エリアの郵便受けは、「原則として、自分がいる同じエリア(同一AZ)の中にあるサーバーにしか手紙を渡さない」という頑固なルールを持っています。
- メリット: エリアをまたぐ通信(クロスゾーンデータ転送)が発生しないため、余計なデータ転送費(AWSの通信料)がかからないというお財布に優しいメリットがあります。また、パケットが余計な旅をしないため、わずかですがレイテンシー(遅延)も抑えられます。
- 落とし穴: ここに大きな罠があります。もし「AZ-A」に届いた手紙の量がすごく多くて、AZ-Aにあるサーバーたちがパンクしかけているとします。一方で、隣の「AZ-B」にあるサーバーたちはヒマを持て余しています。それなのに、クロスゾーンが無効だと、AZ-Bへの応援派遣ができません。「うちのエリアの仕事はうちのエリアで完結させるんだ!」という縦割り社会になってしまうため、サーバーの台数バランスが偏っていると、片方だけが悲鳴を上げる事態になるのです。
パターンB:クロスゾーン負荷分散が「有効」
では、このスイッチを「オン」にしてみましょう。
すると、NLBの郵便受けたちは手を取り合い、チームワークを発揮し始めます。
- 挙動: 「AZ-A」に大量の手紙が届いたとき、AZ-Aの郵便受けは「おい、こっちの手紙が多すぎるから、すまないがAZ-Bのヒマなサーバーにもいくつか配ってくれよ!」と、エリアの垣根を超えて仕事を分散させます。
- メリット: バックエンドのサーバー群全体で、綺麗に均等に負荷を分担できます。「特定のAZだけ過負荷でダウンした」というインシデントを防ぎ、可用性がぐっと高まります。
- 注意点: エリアをまたいでパケットが行き交うため、AWSのクロスゾーンデータ転送費用が発生します。システムの大規模化に伴い、コストの明細を見て「おや?」と驚かないように頭の片隅に入れておきましょう。
—
3. もう一つの大問題:クライアントIPが消える!?
NLBを語る上で避けて通れないのが、「クライアントの本当のIPアドレスがバックエンドサーバーに届くのか問題」です。
レイヤー7で動くALB(Application Load Balancer)であれば、HTTPヘッダーに X-Forwarded-For: 192.0.2.1 という親切な付箋を貼ってくれるので、バックエンドのWebサーバーは「おっ、アクセスしてきたのはあの人だな」と簡単に分かります。
しかし、NLBはレイヤー4(TCP/UDP)のルーターのようなものです。中身のHTTPなんて覗き見しません(そもそも暗号化されているHTTPSならなおさら見えません)。
そのため、デフォルトではNLBがパケットの送信元IPアドレスを「自分自身のIPアドレス(NLBのプライベートIP)」に書き換えて(NAT処理して)バックエンドサーバーに送り届けてしまいます。
これの何が困るかというと、バックエンドのWebサーバーやセキュリティログを見たときに、アクセス元がすべて「NLBのIPアドレス」になってしまい、「誰がどこからアクセスしてきたのか全く分からない(アクセス解析やIP制限ができない)」という絶望的な状況に陥るのです。
この問題を華麗に解決するのが、「クライアントIP保存(Client IP Preservation)」と、それに伴う「Proxy Protocol v2」という仕組みです。
—
4. クライアントIPを守り抜く仕組み(Proxy Protocol v2)
クライアントIP保存を有効にすると、NLBはバックエンドサーバーへパケットを送る際、通信のパケットの中にコッソリと「本当のクライアントのIPアドレスとポート番号」を記した小さな封筒(ヘッダー情報)をそっと忍ばせます。これがProxy Protocol v2です。
ただし、ここで一つ大きなネットワーク上の「矛盾」が生じます。
「クライアントIP保存」を有効にするということは、バックエンドサーバーから見ると「通信の相手(送信元IP)」が、NLBではなく「インターネットの向こうにいる本当のクライアントのIPアドレス」になって返信しようとします。
ここで、ネットワークのルーティングの基本を思い出してください。
サーバーが「知らないIPアドレス」からリクエストを受け取ったとき、その返信はどこに飛ぶでしょうか? そう、デフォルトゲートウェイ(ネットワークの出口)を通って、インターネットの向こうのクライアントへ直接直帰しようとします。
しかし、往路はNLBを通ってきたのに、復路(返信)がNLBを通らずに直接クライアントに返ってしまったらどうなるでしょう?
ルーターやファイアウォールは、「えっ?行きと帰りの通信のつじつまが合わないぞ!これはいけない、不正な通信だ!」と判断し、パケットを問答無用でドロップ(破棄)してしまいます。通信が成立しなくなるわけですね。
この問題を回避するため、クライアントIP保存を有効にした環境のバックエンドサーバーでは、「自分宛てではない、あるいは特殊なルートを通ってきた返信パケットを、ちゃんとNLBや適切なゲートウェイに戻してあげる設定(非対称ルーティングやルーティングテーブルの調整)」や、ロードバランサーのターゲットグループ側での適切なターゲットタイプの選択が必要になります。
—
5. 実務で役立つ!設定のベストプラクティスとTerraformコード例
理屈が分かったところで、実際のインフラ構築現場でどのように設定すべきかを見てみましょう。
ここでは、現代のインフラ構築のデファクトスタンダードであるTerraformを例に、NLBとターゲットグループの設定スニペットをご紹介します。
# AWSプロバイダーの設定などは省略しています
# 1. ネットワークロードバランサー(NLB)の作成
resource "aws_lb" "main_nlb" {
name = "app-production-nlb"
internal = false # 外部公開用(インターネット向け)
load_balancer_type = "network"
subnets = [aws_subnet.public_a.id, aws_subnet.public_b.id]
# 【重要】クロスゾーン負荷分散を有効にする設定
# 可用性と負荷の均等化を優先するため、trueに設定するのが近年のベストプラクティスです
enable_cross_zone_load_balancing = true
tags = {
Environment = "production"
Service = "web"
}
}
# 2. ターゲットグループの作成(バックエンドへの接続定義)
resource "aws_lb_target_group" "app_tg" {
name = "app-backend-tg"
port = 443
protocol = "TCP" # レイヤー4なのでTCPを指定
vpc_id = aws_vpc.main.id
target_type = "instance" # または "ip"(ECS on Fargateなどの場合)
# 【重要】クライアントIP保存の有効化
# これにより、バックエンドのインスタンスにクライアントのIPアドレスが引き継がれます
preserve_client_ip = true
health_check {
enabled = true
protocol = "TCP"
port = "443"
interval = 30
healthy_threshold = 3
unhealthy_threshold = 3
}
}
# 3. プロキシプロトコルの有効化(必要な場合)
# ターゲットグループの属性としてProxy Protocol v2を有効化します
resource "aws_lb_target_group_attachment" "target" {
target_group_arn = aws_lb_target_group.app_tg.arn
target_id = aws_instance.backend_server.id
port = 443
}
💡 実務でのワンポイントアドバイス
- ターゲットタイプが
ipの場合(ECS on Fargateなど):
Fargateタスクなどに直接トラフィックを流す場合、preserve_client_ip を有効にすると、タスク側のタスクネットワーキング(awsvpcモード)でルーティングのループや非対称ルーティング問題が発生しにくいため、非常にスムーズにクライアントIPを維持できます。
- NginxやApacheなどのバックエンド側の設定:
もしバックエンドで受け取るサーバーソフト(Nginx等)がProxy Protocol v2を受け取る場合、設定ファイル(nginx.conf など)側でも proxy_protocol の有効化(例:listen 443 ssl proxy_protocol;)を忘れないようにしましょう。これを忘れると、サーバーがパケットの最初の数バイトをヘッダーと気付かずに解釈しようとして、接続エラー(502 Bad Gatewayなど)を引き起こす原因になります。
—
6. まとめ
今回は、NLBの「クロスゾーン負荷分散」と「クライアントIP保存」の挙動について、郵便配達の例えを交えながら深く掘り下げて解説しました。
- クロスゾーン負荷分散は、AZの壁を越えて負荷を美しく均等に散らしてくれる頼もしい機能(有効化推奨!ただしデータ転送量コストには注意)。
- クライアントIP保存は、アクセス元の足跡をバックエンドまでしっかり届けるための仕組みだが、ネットワークの「行きと帰りのルール(ルーティング)」に少し気配りが必要。
クラウドのネットワークは、一見すると黒魔術のように複雑怪奇に見えますが、一つひとつのパケットが「どこから来て、どこへ行こうとしているのか」という地図を頭の中に思い描きながら紐解いていくと、非常に論理的で美しい仕組みに満ちています。
皆さんの日々のインフラ設計や、トラブルシューティングの際の「お守り」として、この記事の知識が少しでもお役に立てれば幸いです。
それでは、快適なクラウドライフを!
コメント