【実務・中級編】 パブリックサブネット上のEC2インスタンスへのトラフィック到達性エラー原因 – クラウドインフラと仮想化ネットワーク実践ガイド

はじめに:なぜ「パブリックなはずのEC2」に繋がらないのか?

「おい、新人の〇〇くん、ちょっと画面を見てくれ」
夜間、ピリついた空気が漂うSlackのチャンネルで、私は後輩のエンジニアを呼んだ。

「ステージング環境のWeb APIサーバーをVPCのパブリックサブネットにデプロイして、EIP(Elastic IP)も割り当てたんです。セキュリティグループも 0.0.0.0/0 からの 443 ポートをあけて、IGW(インターネットゲートウェイ)もVPCにアタッチされているはずなのに……ローカルのPCから curl を叩いても、タイムアウトするか接続拒否(Connection Refused)になるんです。どこが間違っているんでしょうか?」

彼のディスプレイには、見慣れたエラーメッセージが無情に光っていた。

$ curl -Iv https://<EC2のEIP>
*   Trying 203.0.113.50:443...
* Connected to 203.0.113.50 (443) port 443 (#0)
* Connection timed out

クラウドインフラの初心者が必ず一度は踏む罠、それが「パブリックサブネットのトラフィック到達性エラー」だ。AWSのコンソール画面上では「パブリック」と銘打たれていても、パケットのルーティングやセキュリティの防壁がほんの1箇所でも噛み合っていなければ、インターネットからの通信は容赦なくAWSの黒い虚空へと消え去る。

今回は、パブリックサブネットにあるEC2インスタンスへのトラフィックがどこでドロップしているのか、パケットの旅路を追いかけながら、現場で使える泥臭いデバッグ手法とともに紐解いていこう。

—

パケットの旅路:インターネットからEC2に到達するまでの5つの関門

パブリックサブネットにあるEC2へ外部からアクセスが届くとき、パケットはAWSの境界ネットワークからOSのカーネルに至るまで、いくつかの厳格な「関門」を通過する。

[クライアント] 
    ↓ (1. インターネット)
[Internet Gateway (IGW)]  <-- 未アタッチなら即死
    ↓ (2. ルートテーブル)
[パブリックサブネット]    <-- 0.0.0.0/0 のルートがないと迷子に
    ↓ (3. Network ACL)      <-- ステートレスなネットワークの関所
    ↓ (4. Security Group)   <-- ステートフルな仮想ファイアウォール
[EC2 インスタンス]
    ↓ (5. OS層 / EIP紐付け) <-- EIP未割当、またはOSのiptables/ufwによるブロック
[アプリケーション]

この5つの関門のどこでパケットが迷子になっているか、あるいは弾き飛ばされているかを見極めるのが、SREの腕の見せ所だ。

—

1. IGW(インターネットゲートウェイ)の不在とアタッチメントエラー

まず大前提として、VPCに「海の玄関口」であるIGWがアタッチされており、かつ稼働状態にある必要がある。

VPCを作っただけでは、その中のリソースは完全に孤立した島だ。IGWはVPC自体に紐付ける(Attach)ことで初めて機能する。もしTerraformやCloudFormationでインフラを自動構築している最中に、IGWの作成とルートの関連付けの順序が狂っていたり、そもそもIGWがアタッチされていなかったりすると、外部からのパケットはVPCの境界で永遠に応答を失う。

AWS CLIを使って、現在のIGWが正しくVPCにアタッチされているか確認してみよう。

# 指定したVPCにIGWがアタッチされているか確認するコマンド
aws ec2 describe-internet-gateways \
    --filters "Name=attachment.vpc-id,Values=vpc-0123456789abcdef0" \
    --query "InternetGateways[*].InternetGatewayId" \
    --output text

もしこのコマンドの出力が空(何も返ってこない)であれば、IGWがVPCにアタッチされていない。以下のコマンドで即座にアタッチを行う必要がある。

# IGWをVPCにアタッチする
aws ec2 attach-internet-gateway \
    --internet-gateway-id igw-0123456789abcdef0 \
    --vpc-id vpc-0123456789abcdef0

—

2. ルートテーブルの欠落:デフォルトルート 0.0.0.0/0 の不在

IGWがアタッチされていても、サブネットの「道案内図」であるルートテーブルがIGWを知らなければ意味がない。これがパブリックサブネットの構成ミスで最も多い原因だ。

サブネットを作成した直後、デフォルトで割り当てられる「メインルートテーブル」には、VPC内部の通信(例: 10.0.0.0/16 -> local)しか定義されていない。インターネットへのすべての通信をさばくには、0.0.0.0/0(すべてのIPv4アドレス)の宛先をIGWに向ける明示的なルートが必要になる。

正しいパブリックルートテーブルの設定例(Terraform)

# パブリックサブネット用のルートテーブルを作成
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  # すべての外部トラフィックをインターネットゲートウェイへルーティング
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet-gateway.main.id
  }

  tags = {
    Name = "prod-public-rt"
  }
}

# パブリックサブネットとルートテーブルを関連付け
resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

「サブネットに名前がついているからパブリックだ」と思い込みがちだが、AWSの判定基準は「そのサブネットが関連付けられたルートテーブルに 0.0.0.0/0 -> igw-xxxx が存在するかどうか」だけである。ここを間違えると、パケットは出口を見つけられずに破棄される。

—

3. EIP(Elastic IP)の未割当とENIのプライベートIP問題

「ルートもIGWもある。よし、アクセスできるはずだ」――そう思ってパブリックIPなしでEC2を立ち上げていないだろうか?

AWSのEC2インスタンスをパブリックサブネットに起動した場合、インスタンスにパブリックIPv4アドレス(またはEIP)が割り当てられていなければ、外部のインターネットから直接アクセスすることはできない。
(※NATゲートウェイを経由したアウトバウンド通信や、ALBを通じたインバウンド通信であれば話は別だが、今回はEC2単体に直接トラフィックを流すケースを想定している)

AWSでは、EC2のネットワークインターフェース(ENI)にパブリックIPが紐づくことで、AWS側のSDN(Software Defined Networking)層でSNAT/DNAT(アドレス変換)が行われ、インターネット上のグローバルIPとEC2のプライベートIPがマッピングされる。

障害切り分け:ENIのIP構成を確認する

以下のAWS CLIコマンドで、対象のEC2インスタンス(例: i-0123456789abcdef0)にパブリックIPが正しく紐づいているか確認しよう。

aws ec2 describe-instances \
    --instance-ids i-0123456789abcdef0 \
    --query "Reservations[*].Instances[*].{InstanceId:InstanceId, PublicIp:PublicIpAddress, PrivateIp:PrivateIpAddress}" \
    --output table

もし PublicIp が空欄(None)になっている場合は、EIPを新しく取得して対象のインスタンスのENIにアタッチする必要がある。

# 1. EIPの新規割当
aws ec2 allocate-address --domain vpc --query "AllocationId" --output text

# 2. 取得したAllocationIdとインスタンスのENIを紐付け
aws ec2 associate-address \
    --instance-id i-0123456789abcdef0 \
    --allocation-id eipalloc-0123456789abcdef0

—

4. セキュリティグループ(SG)とNACLのブロック

さて、パケットがIGWを通り、ルートテーブルに導かれ、正しいENIのIPに到達した。しかし、ここからがセキュリティの真価が問われる関門だ。多くのエンジニアが「セキュリティグループは開けたのに繋がらない!」と頭を抱えるが、その原因の多くはネットワークACL(NACL)のステートレスな仕様の誤解にある。

セキュリティグループ(SG)の確認

SGは「ステートフル」である。つまり、インバウンド(受信)で 443 ポートを許可していれば、アウトバウンド(送信)のルールを明示的に書かなくても、レスポンスパケットは自動的に通る。しかし、そもそもインバウンドのルールが間違っていれば一巻の終わりだ。

ネットワークACL(NACL)の罠

NACLはサブネット単位で適用される「ステートレス」なファイヤーウォールだ。ここがSGと大きく異なる点である。
NACLでは、インバウンドの許可だけでなく、アウトバウンド(戻り通信)の許可も明示的にルールとして書かなければならない。特に、エフェメラルポート(一時ポート: 1024-65535)に対するアウトバウンドの許可ルールをNACLからごっそり削ってしまい、見事に通信が塞き止められている現場を何度も目撃してきた。

現場のトラブルシューティングでは、まず以下のようにPythonスクリプトやローカルのテストツールを使い、どのレイヤーでパケットが止められているかを推測する。

import socket
import sys

def check_tcp_connection(host, port, timeout=3):
    """
    指定されたホストとポートに対してTCP接続テストを行う。
    Connection timed out -> ルートテーブル、IGW、またはSG/NACLのブロックの可能性大
    Connection refused   -> パケットは届いているが、OS上のプロセスが待ち受けていない
    """
    try:
        print(f"Connecting to {host}:{port}...")
        with socket.create_connection((host, port), timeout=timeout):
            print(f"[SUCCESS] Successfully connected to {host}:{port}")
    except socket.timeout:
        print(f"[ERROR] Connection timed out. Check IGW, Route Table, or SG/NACL inbound rules.")
    except ConnectionRefusedError:
        print(f"[ERROR] Connection refused. Packet reached EC2, but no service is listening on port {port}.")
    except Exception as e:
        print(f"[ERROR] Unexpected error: {e}")

if __name__ == "__main__":
    # 実際のテスト対象のEIPとポートを指定
    target_ip = "203.0.113.50"
    target_port = 443
    check_tcp_connection(target_ip, target_port)

このスクリプトの出力が Connection refused であれば、パケット自体は確実にEC2のOS層まで到達している。逆に Connection timed out であれば、AWSのネットワーク網(IGW・ルートテーブル・SG/NACL)のどこかでパケットがドロップされている確証が得られる。

—

5. OS層のブロック:iptables、UFW、およびアプリのバインド設定

見落としがちな最後の関門が、EC2インスタンスの内部(ゲストOS)だ。
「AWS側の設定はパーフェクトなはずなのに…」という時に限って、OSのローカルファイアウォールやアプリケーションサーバーの設定が原因であるケースが多い。

OS内部のチェックリスト

1. リバースプロキシやAPIサーバーのバインドIP

  • アプリケーションが 127.0.0.1 (localhost) だけで待ち受けていないか? 外部からのパケットを受け取るためには、0.0.0.0 (すべてのインターフェース) にバインドされている必要がある。

2. OS標準ファイヤーウォール(UFW / firewalld / iptables)

  • UbuntuなどのUFWが有効になっており、ポート 443 や 80 がブロックされていないか。

EC2にSSHやSession Managerでログインし、以下のコマンドでリスニング状態を確認してみよう。

# どのIPとポートでプロセスが待ち受けているか確認
sudo ss -tulpn | grep LISTEN

もし出力結果のIPアドレス部分が 127.0.0.1:443 や [::1]:443 になっていた場合、それはローカルループバックからの通信しか受け付けない状態だ。これを 0.0.0.0:443(IPv4全開放)あるいは [::]:443 に変更し、アプリケーションを再起動する必要がある。

また、Ubuntu環境でUFWが原因で弾かれている場合は、以下のコマンドで許可を与える。

# UFWでHTTPSトラフィックを許可
sudo ufw allow 443/tcp
sudo ufw reload

—

まとめ:障害発生時のスマートな切り分けフロー

パブリックサブネット上のEC2への到達性エラーに直面したとき、パニックになってあちこちの設定をいじるのは悪手だ。シニアエンジニアとして、以下のステップで冷静にデバッグを進めてほしい。

1. 手元から nc や telnet、あるいは前述のPythonスクリプトで疎通を試みる。

  • Connection timed out ならネットワーク境界(AWS側)の不備。
  • Connection refused ならOS・アプリ層の不備。

2. AWSネットワークの確認(タイムアウトの場合)

  • VPCにIGWはアタッチされているか?
  • パブリックサブネットのルートテーブルに 0.0.0.0/0 -> igw-xxx はあるか?
  • ENIに正しいEIP/パブリックIPがアタッチされているか?
  • セキュリティグループのインバウンドルール、およびNACLのインバウンド/アウトバウンドルールに漏れはないか?

3. OS・アプリケーション層の確認(拒否の場合)

  • アプリケーションが 0.0.0.0 でリッスンしているか?
  • iptables や ufw などのローカルファイアウォールが邪魔をしていないか?

クラウドのネットワークは、パケットの正確な挙動さえ頭に描けていれば、決してブラックボックスではない。一つひとつの関門を論理的にクリアしていくことで、どんな複雑なインフラトラブルも必ず解決の糸口が見つかるはずだ。さあ、明日からの現場でのトラブルシューティングに役立ててほしい。

コメント

タイトルとURLをコピーしました