ネットワークの迷宮を解く:SGとNACLで構築する「二重の鉄壁」の作法
SREとして現場を歩いていると、「なぜか通信が通らない」というSOSが舞い込んでくることがよくあります。その多くは、クラウドのネットワーク設計における「ステートフル」と「ステートレス」という、全く異なる性質を持つ二つの防御層を混同したことに起因します。
今日は、AWSにおける「セキュリティグループ(SG)」と「ネットワークACL(NACL)」という、一見似て非なる二つの門番が、パケットをどのように裁いているのか、現場のリアルな視点から紐解いていきましょう。
—
1. 守護神たちの性格:ステートフル vs ステートレス
まず、この二つの違いを正確に把握しておく必要があります。
- セキュリティグループ (SG): ステートフルな守護神
- 性格: 「一度通した通信は、戻りも許可する」という柔軟性を持っています。
- 場所: インスタンス(NIC)レベル。
- ネットワークACL (NACL): ステートレスな冷徹な門番
- 性格: 「往路」と「復路」を完全に別個の通信として見なします。往路を通したからといって、戻りの通信を自動的に通すことはありません。
- 場所: サブネットレベル。
現場でよくある失敗は、NACLでアウトバウンド(戻り)の通信を考慮せず、インバウンドの許可だけで満足してしまうケースです。
—
2. パケットが辿る「検問所」の序列
パケットがプライベートサブネット内のWebサーバーに届くとき、あるいはサーバーから外へ出るとき、その序列は決まっています。
【インバウンド(外部からサーバーへ)】
1. NACL(サブネットの入口): 最初にパケットをチェック。ここで落とせばインスタンスには届かない。
2. SG(インスタンスの入口): ここで最終判定。
【アウトバウンド(サーバーから外部へ)】
1. SG(インスタンスの出口): 送信元が許可されているか確認。
2. NACL(サブネットの出口): サブネットを出る直前の最終チェック。
この順序を理解していないと、トラブル時に「どこでパケットが消えたのか」の特定に時間がかかります。
—
3. 実践:NACLの「復路」設定を忘れないためのTips
NACLを設定する際、エフェメラルポート(一時ポート)の考慮が不可欠です。Web API通信や、curl による外部API叩きを行う際、戻りの通信は 1024-65535 のポート番号が使われます。
TerraformでのNACL設定例(抜粋)
# Webサーバー用サブネットのNACLルール(アウトバウンド)
resource "aws_network_acl_rule" "outbound_allow" {
network_acl_id = aws_network_acl.main.id
rule_number = 100
egress = true # アウトバウンドルール
protocol = "tcp"
rule_action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 1024 # エフェメラルポートの範囲を指定
to_port = 65535
}
この「1024-65535」という範囲を忘れると、Webサーバーは外からのリクエストを受け取れても、自分のレスポンスがNACLで弾かれ、クライアントには「Connection Timeout」が返るという、非常にタチの悪い現象が発生します。
—
4. トラブルシューティング:現場で使うデバッグの定石
通信が通らないとき、私はまず以下の順で切り分けを行います。
Step 1: curl での疎通確認
まずは接続先が生きているか、curl の詳細フラグを使って確認します。
# -v を付けてハンドシェイクのどこで止まるかを確認
curl -v https://api.external-service.com
Step 2: フローログの解析
クラウドベンダーのフローログ(AWS VPC Flow Logs)を確認しましょう。REJECT されているパケットがあれば、それが「SGによるものか」「NACLによるものか」をログのフィールドから見極めます。
Step 3: Pythonでの簡易チェック
もしアプリケーション層の問題かネットワーク層の問題か迷ったら、Pythonで単純なソケット通信を試すのが一番早いです。
import socket
# 外部APIへの疎通確認
target_host = "api.external-service.com"
target_port = 443
try:
with socket.create_connection((target_host, target_port), timeout=5) as sock:
print(f"成功: {target_host}:{target_port} に接続できました")
except Exception as e:
print(f"失敗: {e}")
—
5. まとめ:SREとしての心構え
セキュリティの鉄則は「多層防御」ですが、二重に防御を敷くということは、それだけ「設定漏れ」の温床が増えるということでもあります。
- SGは「サーバーのアイデンティティ」として管理する: Webサーバーなら
80/443を開けるといった、本来の目的を記述する。 - NACLは「サブネットの交通整理」として扱う: 非常に限定的なアクセス制御や、特定のIPブロック(ブラックリスト)など、広範囲で防ぐために使う。
「なんとなく動くから」と、SGやNACLを 0.0.0.0/0 で全開放してしまうのは、エンジニアとして最も避けたい怠慢です。パケットがどこで通って、どこで弾かれるのか。その地図を頭の中に描けるようになると、ネットワークトラブルは「障害」から「パズル」に変わります。
皆さんのインフラが、今日も安全で軽快に通信を捌けることを願っています。
コメント