プログラマのための IT 教科書

つながらない時に何を見るか

この部の 8 / 12 章 ・ 全体で 29 / 76 章 ・ 読了目安 45 分

この章を読むとできるようになること
  • 下から順に切り分ける手順を持つ
  • refused とタイムアウトから原因を絞れる
  • 切り分けの過程をそのまま報告にできる

「つながりません」

この一言で止まってしまうか、5分で原因を特定できるか。 差は才能ではなく、手順を持っているかどうかです。

デバッグの技術のデバッグと同じで、推測せず、範囲を半分に切っていきます。

切り分けの順番

前章の層の話が、そのまま手順になります。下から上へ確認します。

1. 名前は引けるか            → dig
2. 相手まで届くか            → ping / traceroute
3. そのポートは開いているか   → nc -zv
4. 自分は listen しているか   → ss -ltnp
5. TLS は成立するか          → openssl s_client
6. HTTP のやり取りはどうか    → curl -v
7. 実際に何が流れているか     → tcpdump

上から順に試せば、どこで切れているかが必ず分かります。 そして、どこで切れたかが分かれば、原因の候補は数個に絞られます。

1. 名前は引けるか

dig api.example.com
 
# 要点だけ見る
dig +short api.example.com
 
# 特定の DNS サーバーに聞く(キャッシュを疑う時)
dig @8.8.8.8 api.example.com
 
# 権威サーバーの答えを直接見る
dig +trace api.example.com
;; ANSWER SECTION:
api.example.com.  300  IN  A  203.0.113.10
                  └┬┘            └────┬────┘
                  TTL           これが答え
`dig` は引けるのにアプリだけ解決できない

dig は DNS サーバーに直接聞きますが、 アプリは OS の名前解決の仕組みを通ります。両者は経路が違います。

確認すべき場所は、

cat /etc/hosts          # ここに書いてあると、DNS より優先される
cat /etc/resolv.conf    # 参照する DNS サーバーと、search ドメイン

/etc/hosts に古い行が残っているのは、実際によくあります。 過去に検証用として書いた1行が、半年後に人を数時間悩ませます。

Kubernetes 内なら、search ドメインの設定によって api だけで api.default.svc.cluster.local に解決されることも覚えておいてください。

2. 相手まで届くか

ping 203.0.113.10          # 応答があるか
traceroute 203.0.113.10    # どこまで届いているか
mtr 203.0.113.10           # 継続的に経路と損失率を見る(あれば最も便利)
`ping` が通らない = 到達不能、ではない

多くのサーバーやクラウドは、ICMP(ping)を既定でブロックしています。

ping が通らない  → 到達できないとは限らない
ping が通る      → IP レベルでは届いている(有力な情報)

つまり、通れば情報になるが、通らなくても結論は出せません。 次のポート確認に進んでください。

これを知らずに「ping が通らないからネットワーク障害だ」と報告すると、 切り分けが振り出しに戻ります。

3. ポートは開いているか

ここが最も情報量の多い確認です。

# TCP の 443 に繋がるか(-z は接続だけ、-v で結果を表示)
nc -zv api.example.com 443
 
# タイムアウトを短く
nc -zv -w 3 api.example.com 443

結果の読み方が重要です。

結果意味主な原因
succeeded / open繋がったネットワークは正常。上の層を疑う
Connection refused相手は届いたが、誰も listen していないプロセスが落ちている、ポート違い
タイムアウト(無応答)パケットが捨てられているファイアウォール / セキュリティグループ
refused と timeout の差は決定的
Connection refused  → 相手のホストまでは届いている
                      → アプリが起動していないか、ポートが違う
タイムアウト        → 途中で捨てられている
                      → ファイアウォール、セキュリティグループ、経路

この2つを区別するだけで、調査対象が半分になります。 「繋がりません」と報告する時は、どちらだったかを必ず添えてください。

4. 自分は listen しているか

サーバー側で確認します。

ss -ltnp            # listen 中の TCP ポートとプロセス(Linux)
lsof -i :8080       # そのポートを掴んでいるプロセス(macOS でも使える)
State   Local Address:Port   Process
LISTEN  127.0.0.1:8080       users:(("server",pid=1234))
        └────┬────┘
        ここが 127.0.0.1 だと、外からは繋がらない

0.0.0.0:8080 なら全インターフェースで待ち受け、 127.0.0.1:8080 なら同じマシンからしか繋がりません(Docker を使いこなす)。

「アプリは起動しているのに Connection refused」の原因は、ほぼこれです。

5. TLS は成立するか

# 証明書とハンドシェイクを確認する
openssl s_client -connect api.example.com:443 -servername api.example.com
 
# 有効期限だけ見る
echo | openssl s_client -connect api.example.com:443 2>/dev/null \
  | openssl x509 -noout -dates -subject

証明書まわりのよくある原因です。

□ 期限切れ                    → notAfter を確認
□ 名前が一致しない            → SAN に対象のホスト名が含まれているか
□ 中間証明書が足りない        → ブラウザでは通るのに curl や Go で失敗する
□ 時刻がずれている            → クライアント側の時計が狂うと検証に失敗する
「ブラウザでは見られるのにアプリからは失敗する」

中間証明書の設定漏れが典型的な原因です。

ブラウザは足りない中間証明書を補完してくれることがありますが、 curl や各言語の HTTP クライアントは補完しません。

# 証明書チェーンが正しく送られているか確認する
openssl s_client -connect api.example.com:443 -showcerts

6. HTTP のやり取りを見る

curl -v https://api.example.com/health
 
# ヘッダだけ
curl -I https://api.example.com/health
 
# 時間の内訳を見る(どこで時間を使っているか)
curl -w "dns:%{time_namelookup} connect:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n" \
     -o /dev/null -s https://api.example.com/health

最後のコマンドは非常に便利です。 「遅い」の原因が、 DNS なのか、接続なのか、TLS なのか、サーバーの処理なのかが一撃で分かります。

dns:0.004 connect:0.021 tls:0.089 ttfb:1.204 total:1.210
                                   └── ここが大きい = サーバー側の処理が遅い

その他、実務でよく使うオプションです。

curl --resolve api.example.com:443:203.0.113.10 https://api.example.com/   # DNS を迂回して特定の IP へ
curl -x http://proxy:8080 https://api.example.com/                          # プロキシ経由
curl -k https://...                                                          # 証明書検証を無効化(切り分け専用)
`-k` は切り分けにだけ使う

-k(証明書検証を無効化)で通るなら、証明書の問題だと特定できます。

しかし、これをアプリのコードに持ち込んではいけません。 「とりあえず検証を無効にして動かす」は、 中間者攻撃に対して完全に無防備になります(セキュリティ)。

切り分けの道具であって、解決策ではありません。

7. 実際に何が流れているかを見る

ここまでで分からなければ、パケットを直接見ます。

sudo tcpdump -i any -n port 443 and host 203.0.113.10
 
# ファイルに保存して、後で Wireshark で開く
sudo tcpdump -i any -n -w capture.pcap port 443

「送っているつもりのリクエストが、実際には出ていない」 といったことが、ここで初めて分かります。

日常的に使うものではありませんが、 「使える」と知っていることが重要です。

原因の分類表

どこで切れたかが分かったら、この表で当たりを付けます。

症状疑うもの
名前が引けない/etc/hosts、DNS 設定、TTL とキャッシュ、レコードの設定ミス
名前は引けるが古い IPDNS キャッシュ(TTL の分だけ待つ)
タイムアウトファイアウォール / セキュリティグループ、経路、NAT
Connection refusedプロセスが落ちている、ポート違い、listen が 127.0.0.1
TLS エラー期限切れ、SAN 不一致、中間証明書、時刻ずれ
4xx / 5xx が返るここから先はアプリの問題(Web と HTTP・監視とオンコール)
小さいリクエストは通るMTU(ネットワークの基礎)
しばらく動くと繋がらなくなるエフェメラルポート枯渇 / ファイルディスクリプタ枯渇(プロセスとメモリ)
遅いcurl -w で内訳を見る。DNS / 接続 / TLS / サーバー処理のどれか

経路上にいるものを把握する

現代の通信は、あなたのアプリと相手の間に何段も装置があります。

[ブラウザ] → [CDN] → [WAF] → [ロードバランサ] → [リバースプロキシ] → [アプリ]
                                                            → [サービスメッシュ] → [別サービス]

どこで切れているかを特定するには、経路上に何がいるかを知る必要があります。

装置役割切り分けでの注意
CDN静的配信・キャッシュ古い内容が返る。パージが必要
WAF攻撃の遮断正常なリクエストが誤検知で弾かれることがある
L4 ロードバランサIP:ポートで振り分け中身を見ない。TLS はそのまま通す
L7 ロードバランサHTTP を理解して振り分けヘッダを書き換える。TLS を終端することが多い
リバースプロキシ前段でヘッダ付与・圧縮タイムアウト値が別に設定されている
どこで切れたかを特定する道具
X-Forwarded-For     元のクライアント IP(プロキシが付ける)
X-Request-Id        リクエストの識別子。全段のログを串刺しにできる
traceparent         分散トレースの識別子(監視とオンコール)

ログにリクエスト ID を出していれば、 「LB までは来ているがアプリに届いていない」が即座に分かります。

出していなければ、各段のログを時刻で突き合わせることになり、何倍も時間がかかります。 これは監視とオンコールの可観測性が、そのまま効いてくる場面です。

タイムアウトが多段になっている
ブラウザ 30秒 / CDN 60秒 / LB 30秒 / アプリ 10秒 / DB 5秒

手前のタイムアウトが短いと、奥が正常に処理を終えても切られます。

□ 504 Gateway Timeout が出るのに、アプリのログでは正常終了している
   → 途中のどれかが先に諦めている

タイムアウトは外側ほど長く、内側ほど短くなるように設計します(性能と負荷対策)。

クラウドと Kubernetes で詰まる場所

□ セキュリティグループ / ファイアウォールルール(インバウンドとアウトバウンドの両方)
□ サブネットが「パブリック」か「プライベート」か(外に出る経路があるか)
□ NAT ゲートウェイの有無(プライベートサブネットから外部 API を呼べない原因)
□ ロードバランサのヘルスチェックが失敗している(コンテナと Kubernetesの Probe)
□ Service のセレクタが Pod のラベルと一致していない(Endpoints が空)
□ NetworkPolicy で通信が遮断されている
# k8s: Service に Pod が紐づいているか(空なら、セレクタかラベルの問題)
kubectl get endpoints my-service
 
# Pod の中から直接叩いてみる
kubectl exec -it my-pod -- curl -v http://my-service:8080/health
 
# 一時的なデバッグ用 Pod を立てる
kubectl run tmp --rm -it --image=nicolaka/netshoot -- bash
切り分けは「中から」やる

外から繋がらない時、中からは繋がるのかを必ず確認してください。

Pod の中 → Service   繋がる → 問題は Ingress / LB / DNS / FW にある
Pod の中 → Service   繋がらない → 問題は Service / Pod / アプリにある

1回の確認で調査範囲が半分になります。

報告の仕方

切り分けた結果は、そのまま報告に使えます(ドキュメントを書く)。

【症状】api.example.com への接続が、本番の Pod からのみ失敗する
【切り分け】
  - dig: 正常に解決(203.0.113.10)
  - nc -zv 443: タイムアウト(refused ではない)
  - 同じ Pod から別ホストの443: 成功
  - 手元の PC から同じ宛先: 成功
【仮説】本番サブネットからのアウトバウンドが、
        セキュリティグループで許可されていない
【確認したいこと】このサブネットのアウトバウンド規則を見せてもらえますか

「つながりません」との差は圧倒的です。 そして、ここまで書けていると、たいてい書いている途中で自分で解決します。

実務の落とし穴まとめ

  1. ping が通らない = 障害と結論づける — ICMP はよくブロックされる
  2. refused とタイムアウトを区別しない — 調査範囲が倍になる
  3. /etc/hosts を見ない — 古い1行が数時間を奪う
  4. listen アドレスを確認しない — 127.0.0.1 は外から繋がらない
  5. -k で通ったのでコードにも入れる — 検証を捨ててはいけない
  6. 中間証明書を確認しない — ブラウザでは通るのにアプリで失敗
  7. 経路上の装置を把握していない — どこで切れたか分からない
  8. リクエスト ID を出していない — 段ごとのログを突き合わせられない
  9. タイムアウトが外側より内側で長い — 正常終了しているのに 504
  10. 中からの疎通を試さない — 範囲を半分にできる確認を飛ばしている

まとめ

  • 切り分けは下から上へ。dig → ping → nc → ss → openssl → curl → tcpdump
  • ping が通らなくても結論は出せない。ICMP は塞がれていることが多い
  • Connection refused は「届いているが誰も居ない」、 タイムアウトは「途中で捨てられている」。この区別が最も効く
  • サーバー側は ss -ltnp で listen アドレスを確認する
  • curl -w で時間の内訳を見れば、遅さの原因が一撃で分かる
  • -k は切り分け専用。コードに持ち込まない
  • 経路上の CDN / WAF / LB / プロキシを把握し、 リクエスト ID で串刺しにする
  • タイムアウトは外側ほど長く
  • k8s では kubectl get endpoints と中からの疎通確認が最短
  • 切り分けの過程は、そのまま報告になる

さらに学ぶには

資料特徴
Cloudflare Learning CenterDNS・TLS・CDN・DDoS の仕組みが図で分かる
MDN: HTTP日本語。ヘッダとステータスの正確な仕様を引く用
High Performance Browser Networking全文無料。TCP/TLS/HTTP の性能特性を深く
man tcpdump / Wireshark の公式ドキュメントパケットを本気で読む段階になったら

公式ドキュメント

対象リンク
Wireshark ドキュメントhttps://www.wireshark.org/docs/
RFC Editor(プロトコルの仕様)https://www.rfc-editor.org/
man dig / man tcpdump手元で man <コマンド名>。オプションの意味は必ずここで確認する

章末問題

本番の Pod から外部 API を呼ぶと必ずタイムアウトします。手元の PC からは成功します。次に確認すべきことは?

`nc -zv db.internal 5432` が `Connection refused` を返しました。何が分かりますか。

次の章では、HTTP 以外も含めた通信の形式の地図を見ます。 SOAP・MQTT・SFTP——実務では HTTP 以外にも出会います。

読み終わったら記録しておくと、目次で進み具合が分かります。