AWS NLBの「セキュリティグループ連携」という深淵:実務でハマるパケットの挙動を解き明かす
こんにちは。SREの現場に身を置いていると、ネットワーク周りのトラブルシューティングほど、胃が痛くなるものはありません。特にAWSのElastic Load Balancing(ELB)の中でも、ネットワーク層の王者「NLB(Network Load Balancer)」は、その圧倒的なスループットと引き換えに、L7のALBとは一味違う「冷徹な挙動」を見せることがあります。
今回は、エンジニアたちが意外と見落としがちな「NLBのセキュリティグループ(SG)連携」という、地味ながらも極めて重要なテーマに焦点を当てます。
—
1. なぜ「NLBのSG」が混乱を招くのか
かつてのNLBは、SGを直接適用できず、クライアントのIPをそのままターゲット(EC2やIPターゲット)に到達させるための「IP透過性」に依存していました。しかし、アップデートによりNLB自体にSGが適用可能になり、利便性が大幅に向上しました。
ここで重要なのは、「NLBのSGは、あくまでNLBのフロントエンド(クライアントからの入口)を保護するものであり、バックエンドへのパケット制御は依然としてターゲット側のSGが主導権を握る」という二重構造です。
パケットの旅路:シーケンスの理解
1. Client -> NLB: クライアントがNLBのフロントエンドIPに接続。ここでNLBのSGが適用される。
2. NLB -> Target: NLBがターゲットへ通信を転送。ここでターゲットのSGが適用される。
もしNLBのSGで 80/443 を開けても、ターゲット側のSGでそのIPが拒否されていれば、パケットはそこで遮断されます。この「二段階の門」を意識していないと、トラフィックがどこで消えたのかを特定するのに数時間をドブに捨てることになります。
—
2. セキュリティグループ連携の実務的な設定
NLBにSGを適用する際、気をつけるべきは「ステートフルな制御」です。AWSのSGはステートフルですから、インバウンドを許可すれば、戻りのアウトバウンドは自動的に許可されます。しかし、NLB特有の「クライアントIP保持」を有効にしている場合、ターゲット側でのSG設定に工夫が必要です。
ターゲット側SGのベストプラクティス
ターゲット側のSGでは、NLBのIPではなく、クライアントのIPを直接許可するように設計します(NLB経由の通信であっても、IPはクライアントのものが届くため)。
# Terraformでの定義例:ターゲットグループのセキュリティグループ
resource "aws_security_group" "target_sg" {
name = "web-server-sg"
description = "Web server security group"
# クライアントからのリクエストを許可(NLBを通してもIPは維持される)
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # 本来は特定の許可IPレンジを推奨
}
# ヘルスチェック用:NLBのサブネットからの通信を許可
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["10.0.0.0/24"] # NLBが配置されたサブネット範囲
}
}
—
3. 実践:デバッグとトラブルシューティング
「なぜかNLB経由だと繋がらない」。そんな時、私はまず curl で特定のヘッダーやレスポンスを確認しつつ、パケットがどこでドロップされているかを見極めます。
疎通確認のためのコマンド例
NLBのDNS名に対して、curl で詳細情報を出力してみましょう。
# -v でヘッダーを確認し、レスポンスがない場合は接続が拒否されている(SGの問題)
# --connect-timeout でタイムアウト時間を指定し、無応答時間を測定
curl -Iv http://my-nlb-123456789.elb.ap-northeast-1.amazonaws.com/ --connect-timeout 5
もし Connection refused になるなら、ターゲットのOSレベル(iptablesやnftables)の可能性もあります。一方で Connection timed out であれば、十中八九SGのルール設定ミスか、ルートテーブルの不備です。
Pythonによる簡易チェック(接続テスト)
自動化ツールの一部として、以下のようなスクリプトを仕込んでおくと便利です。
import socket
def check_nlb_port(host, port):
"""
NLBの疎通を確認する単純なソケット接続スクリプト
"""
try:
with socket.create_connection((host, port), timeout=3) as sock:
print(f"Success: {host}:{port} is reachable.")
except socket.timeout:
print(f"Error: {host}:{port} timed out. Check SG rules.")
except Exception as e:
print(f"Error: {e}")
# 実行例
check_nlb_port("my-nlb-123456789.elb.ap-northeast-1.amazonaws.com", 80)
—
4. シニアSREからのアドバイス:運用時の注意点
最後に、現場でよく見る「ハマりポイント」を2つ共有します。
1. NLBのIPは変動する:
Elastic IPを使用しない限り、NLBのIPは伸縮します。SGのインバウンドルールに「NLBのプライベートIP」をハードコードしてはいけません。必ず「NLBのSG ID」自体をターゲットのSGで参照(セルフリファレンスではなくSG間参照)するようにしましょう。
2. ヘルスチェックの罠:
NLBのヘルスチェックは、NLBの各ノードがターゲットに対して行います。ターゲット側のSGには、NLBが配置されているすべてのサブネットからの通信を許可しておく必要があります。「自分のパケットは通るのに、NLB経由だとヘルスチェックが落ちる」という場合、大抵はNLBの別AZノードからの通信がブロックされています。
NLBは高速で強力ですが、その分、ネットワーク層のルールに忠実です。パケットの通り道を頭の中で可視化し、どこで許可し、どこで遮断すべきか。この地図さえ描ければ、どんな複雑な構成も怖くありません。
次回の運用で、「あれ?繋がらないぞ?」と焦ったときは、まずこの「二段階の門」を思い出してください。現場からは以上です。
コメント