ネットワーク障害診断の手法論

このページの目次

診断の目的は、証拠を用いて障害の境界を狭め、修正が元の症状を変化させたかどうかを検証することです。プロトコルの階層構造はチェックマップを提供しますが、毎回イーサネットケーブルからアプリケーションまで全てを調べる必要はなく、ある時点での ping が成功したからといって、ネットワーク層を恒久的に除外することもできません。

「アクセスできない」という状態は、少なくとも以下のように展開する必要があります:どのクライアントが、どのネットワーク名前空間で、どの時間枠で、どのドメインとポートに、どのプロトコルで、どの段階でエラーが発生したのか。また、すべてのリクエストが失敗しているのか、バックエンドの一部のみが失敗しているのか、大きなレスポンスのみが失敗しているのか、初回アクセスのみが遅いのか、長期間アイドル状態の接続で失敗するのかといった条件を区別する必要があります。

診断の閉ループ:1回の対照で説明可能な条件のみを変更する 具体的な症状 誰が、いつ、どこにアクセスし、どのように失敗したか 検証可能な仮説 DNS / パス / ハンドシェイク / アプリケーション段階 最小限の対照 ターゲット、プロトコル、その他の条件を固定する 残された結果 何を支持し、何をまだ除外していないか サービスの復旧と根本原因の証明は異なる結果です。変更のたびに観察可能な指標と終了条件が必要です。

アプリケーションの症状から最も識別力の高い対照を選択する

対照できるだけ一致させるもの区別するのに役立つもの
同一クライアントで固定された1つのターゲットIPURLホスト名、TLSアイデンティティ、パス、リクエストDNS結果/ノード差異とサービス動作
同一サービスでIPv4とIPv6を別々に試すリクエスト、時間帯、クライアントアドレス族、ルーティング、バックエンドの差異
同一パスで小さなレスポンスと大きなレスポンスバックエンドおよびビジネス処理をできるだけ一致させるサイズ、転送時間、バッファリングの問題
同一ノードでのコールド接続とコネクションリユースAPI、負荷、認証接続確立段階とアプリケーション段階
障害ウィンドウの両端での収集5タプル、時間、収集範囲リクエスト/レスポンスのどのセグメントで不可視か

DNS、プロキシ、HTTPバージョン、ターゲットIPを同時に変更し、成功したとしても、どの要因が効いたのかを知ることは困難です。各対照では、何を変更したか、またどの条件が依然として制御不能であるかを記録する必要があります。

DNS:レコードが取得できたからといって、アプリケーションがその回答を使用したわけではない

# 解析器を指定し、ステータスコード、TTL、応答内容を保持する。
dig @192.0.2.53 app.example A
dig @192.0.2.53 app.example AAAA

# dig 自体で反復クエリを実行し、公共委譲の関連動作を確認する。
dig +trace app.example

+short は結果を素早く抽出するのに適していますが、多くの失敗の文脈を隠してしまいます。+trace は企業用再帰サーバー内部の各ステップを表示しない場合があり、またフローティングDNSを迂回する可能性があります。これは現在の環境から直接対応する権威サーバーにアクセスする必要があり、失敗したからといって、アプリケーションが使用している再帰サーバーも失敗したとは限りません。BIND dig

アプリケーションは、システムリゾルバー、hostsファイル、ブラウザのDoH、独自キャッシュ、またはプロキシ側での解析を使用している可能性があります。まず、アプリケーションが実際に接続しているリモートIPを記録し、期待される回答と比較します。NXDOMAIN、SERVFAIL、タイムアウト、または誤ったアドレスの返却など、診断パスは異なり、「DNSの不安定さ」に一律に帰因することはできません。

あるIPがHTTPSドメインのサービスに対してテストする場合、--resolve を使用して、URLホスト名に対応するSNI、証明書検証、HTTPホストアイデンティティを保持します。URLを直接IPに変更してから -k を使用すると、複数の条件が変更されてしまいます。

# 直接接続を明確にテスト;ターゲットアドレス、名前、パスは実際の環境に合わせて置換する。
curl --noproxy '*' --resolve app.example:443:192.0.2.10 \
  --connect-timeout 5 --max-time 15 -sS -o /dev/null \
  -w 'ip=%{remote_ip} code=%{http_code} total=%{time_total}\n' \
  https://app.example/health

ここで --noproxy '*' は、プロキシ変数の影響を明確に除外しています。もし元の問題がプロキシパスにあった場合、プロキシ設定を別途保持して対照する必要があります。固定IPは異なるCDNノードを選択してしまう可能性があるため、「固定IPが正常である」ことは、解析とノード差異の継続的なチェックを支持するだけであり、DNSプロトコルの障害を即座に証明するものではありません。curl --resolve

接続:SYNの送信先と応答の帰路を分離する

# ルーティングクエリは現在の名前空間内で行われ、リモートプローブは行わない。
ip route get 192.0.2.10
ip -6 route get 2001:db8::10

# リスニング状態と接続状態を表示し、実際のポートでフィルタリングする。
ss -ltnp 'sport = :443'
ss -tinp 'dport = :443'

ルーティング結果は、ソースアドレス、mark、ポリシールール、VRF、および名前空間の影響を受けます。現在のシェルでのクエリは、コンテナ内や特殊なマーク付きのビジネスフローを再現しない場合があります。サービスが 127.0.0.1、特定のアドレス、IPv6アドレス、または別の名前空間でリスニングしている場合、外部からの到達可能性も変化します。ip route

双方向で観測された事実優先的に絞り込むべき箇所依然として除外できないもの
クライアント側でSYNをキャッチしたが、サービス側ではキャッチしていない送信パスまたは観測点の間フィルターが狭すぎる、収集の欠如、実際には別のノードを通過している
サービス側でSYNをキャッチしたが、SYN-ACKが見えないサービス側の処理とポリシーパケットキャプチャポイントの前後、ファイアウォール、リソースとリスニング状態
サービス側でSYN-ACKを送信したが、クライアント側でキャッチしていない帰路またはクライアント前方非対称ルーティングと収集の欠如
RSTを受信した両端のいずれか、または中間デバイスが拒否/終了したソースIPのみでは、アプリケーション由来であることを証明できない
3ウェイハンドシェイクが完了したその時点ではその接続で小さなハンドシェイクメッセージの交換が可能後のMTU、輻輳、TLS、およびアプリケーションの問題

pingはICMPを使用しますが、ビジネスではTCP、UDP、異なるアドレス族、さらには異なるパスを使用する可能性があります。pingが成功したとしても、そのICMPプローブに応答が返ってきたことを証明するに過ぎません。pingが禁止されているからといって、ビジネスが利用できないわけではありません。パケットキャプチャの詳細についてはtcpdump と Wireshark を参照してください。

TLS:トランスポートハンドシェイクと認証を正しく区別する

openssl s_client -connect 192.0.2.10:443 -servername app.example \
  -verify_hostname app.example -verify_return_error -brief </dev/null

-servername はSNIを提供し、-verify_hostname は期待されるホスト名をチェックしますが、これらは互いに代替できません。s_client のデフォルトの診断動作では、証明書検証エラーが発生した後に処理が続く場合があります。-verify_return_error を使用して初めて、検証失敗を明確に失敗条件として扱います。プライベートCAの場合、実際の信頼できるCAファイルを設定する必要があります。OpenSSL s_client

TLSの確立に成功しても、HTTPルーティング、ユーザー認証、またはバックエンド依存関係が検証されたわけではありません。アプリケーションのみが失敗する場合、そのSNI、ALPN、クライアント証明書、CAコレクション、および実際の接続ターゲットを比較する必要があります。「コマンドラインではハンドシェイクできる」という理由だけで、すべてのTLS設定の差異を除外しないでください。

1回の低速リクエストを説明可能な時間に変換する

curl --noproxy '*' --connect-timeout 5 --max-time 15 -sS -o /dev/null \
  -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} first=%{time_starttransfer} total=%{time_total}\n' \
  https://app.example/health

これらの時間は通常、操作開始から各段階までの累積時間であり、直接加算できる5つのセグメントの所要時間ではありません。以下では、新規の直接HTTPS接続、リダイレクトなし、コネクションリユースなし、かつリクエストボディの送信影響が最初のバイト待ちに含まれていると仮定します。

同一リクエストの累積時間。段階的な所要時間を得るには減算が必要 20 ms DNS 40 ms TCP 80 ms TLS 200 ms リクエストから最初のバイトまで 60 ms レスポンス受信 0 60 140 340 400 ms 直接HTTPS接続、リダイレクトなし、コネクションリユースなしと仮定;最初のバイトまでの待機時間をすべてサーバーCPUのせいにはできない。
from decimal import Decimal as D
cumulative = list(map(D, ['0', '0.020', '0.060', '0.140', '0.340', '0.400']))
stages = [b - a for a, b in zip(cumulative, cumulative[1:])]
assert stages == list(map(D, ['0.020', '0.040', '0.080', '0.200', '0.060']))
assert sum(stages) == D('0.400')
print('DNS 20、TCP 40、TLS 80、最初のバイト前 200、受信 60 ms')

time_starttransfer - time_appconnect には、リクエスト送信、ネットワーク往復、サーバーキュー待ち、および処理などが含まれており、すべてサーバー側の計算時間として記録することはできません。プロキシ、リダイレクト、コネクションリユース、マルチプレクシング、および失敗したリクエストは、フィールドの解釈を変更します。欠落しているかゼロである段階を無理やり正常な測定値として計算しないでください。curl write-out

TLSが速く、最初のバイトが遅い場合は、リクエストID、プロキシアップストリーム時間、サーバーキュー、および依存呼び出しを関連付けて調査を続けます。最初のバイトが速いがダウンロードが遅い場合は、レスポンスサイズ、アプリケーション出力リズム、帯域幅、再送、および受信バックプレッシャーを比較します。HTTP 503は、HTTPレスポンスが既に存在することを示しており、レスポンス送信者とアップストリームの状態を優先して確認する必要があり、依然としてpingを繰り返すだけでは不十分です。

5つの計測値をなぜ直接加算できないのか?

TLSの時間のみを増加させると、appconnect、starttransfer、totalの3つの累積値が同時に増加します。これらは3つの独立した新しい所要時間ではありません。時間バーは隣接する差に展開されて初めて加算でき、真の総時間が得られます。

図を準備しています
5つの計測値をなぜ直接加算できないのか?

累積値には過去の待機時間が含まれる。段階的な所要時間は隣接する累積値の差から得られ、最初のバイトまでの待機時間はサーバーのCPU時間とは等しくない。

MTUブラックホール:互いに支持し合う証拠のセットで確認する

典型的な仮説は、パスのどこかで大きなパケットを処理できず、パケットがドロップされ、送信側がより小さいPMTUを正しく取得または適応していないというものです。ハンドシェイクと小さなリクエストは成功するが、大きなデータは継続的に停止するというのは、手がかりではあるが排他的な症状ではありません。アプリケーションのバッファリング、コンテンツベースのポリシーフィルタリング、および受信ウィンドウの停止も同様に見える可能性があります。

# IPv4、IPオプションなしの場合:1472 Bペイロード + 8 B ICMP + 20 B IP = 1500 B。
ping -4 -c 3 -M do -s 1472 192.0.2.10
ping -4 -c 3 -M do -s 1200 192.0.2.10

大きなpingの失敗は、ローカルPMTUチェック、相手側ICMPポリシー、パス、または帰路に起因する可能性があります。1回でブラックホールを確認することはできません。まず、ローカルの message too long、ICMPエラーの受信、および完全なタイムアウトを区別し、実際のビジネスの大きなパケット、再送、およびICMP Packet Too Big/Fragmentation Neededが関連しているかどうかを観察します。ping 手册

ipv4_probe = 1472 + 8 + 20
ipv6_probe_without_extensions = 1452 + 8 + 40
assert ipv4_probe == ipv6_probe_without_extensions == 1500
path_mtu = 1400
assert path_mtu - 20 - 20 == 1360  # IPv4 TCP 基本ヘッダー下のデータ上限。
assert path_mtu - 40 - 20 == 1340  # IPv6 拡張ヘッダーなし。
print('プローブペイロード、IPパケット長、TCPデータ予算は別々に計算する')

これらの予算には、TCPオプションや追加のカプセル化は含まれていません。修正は、実際のパスMTU、必要なICMPフィードバック、または適用可能なPLPMTUDメカニズムを対象とする必要があります。MSS clampingは、TCPネゴシエーションの関連方向にのみ影響し、任意のUDP/QUICを修復するものではなく、すでに確立された接続にも自動的に適用されません。したがって、1つのグローバルファイアウォールの変更を診断ステップとして扱うべきではありません。DPLPMTUD

断続的な問題:累積値の大小ではなく、時間の相関性を見る

mtrは、繰り返しプローブして応答を要約することで、継続的または断続的なパターンを発見するのに役立ちますが、中間ルーターがプローブ応答を制限することは、ビジネスパケットの転送もドロップされることを意味しません。第4ホップで60%の応答なし、第5ホップとターゲットが正常である場合、通常、「第4ホップでビジネストラフィックの60%がドロップされた」という結論を支持するものではありません。

# 適用可能な場合は、ビジネスに近いTCPターゲットポートでプローブを使用するが、依然として実際のビジネスリクエストではない。
mtr --report --report-cycles 100 --tcp --port 443 app.example

各ホップの応答には独自の帰路があります。ECMP、プローブフィールド、およびプロトコルの差異も観察に影響を与えます。後のホップとターゲットで同様の劣化が継続的に発生した場合、エンドツーエンドのビジネス証拠と併せてそのセグメントをチェックする価値がありますが、帰路にも注意を払う必要があります。mtr 手册

データ使用方法一般的な誤解
NIC error/drop障害ウィンドウの前後で差を取り、ドライバフィールドのセマンティクスを確認するすべてのdropを同じ種類の回線損傷と見なす
qdisc backlog/drop負荷、レイテンシ、および特定のキューポリシーと関連付けるdropが見えるとbufferbloatを断言する
conntrack count/max割り当て失敗、ドロップカウント、およびログと組み合わせて確認するcountが高いだけでテーブル満杯を確認する
TCP 再送/RTT同一方向、同一時間枠、および接続範囲で比較する累積再送総量を直接パケット損失率と同等と見なす
CPU/softirq/アプリケーションキュー帯域幅またはテールレイテンシと同期して変化しているかどうかを比較する全体平均CPUが非常に低いことのみを見る

Linux TCPの ss キューフィールドは状態を区別する必要があります:LISTENのRecQ/SendQは、完了してacceptを待っているキューとその上限を表します。確立された接続の受信キューは、アプリケーションによってまだ読み取られていないデータを表し、送信キューは書き込まれたがまだ確認されていないシーケンス空間を含み、未送信のデータも含みます。一律に「オンラインで未ACKデータ」と呼ぶことはできません。これらの口径はLinux tcp_diag_get_info() で照合できます。ss 手册

結論を再検証可能な因果連鎖として記述する

有用な記録は、「10:02–10:08、ノードAのIPv6新規接続がTLS前にタイムアウト、IPv4対照は正常;サーバー側では対応するIPv6 SYNが見えない、クライアントインターフェースで再送が見える;現在の証拠は境界を2つの収集ポイントの間に狭めたが、中間ミラーの収集漏れをまだ除外していない」といったものかもしれません。これは「ネットワークの揺らぎ、再起動済み」という記録よりも、次のステップの協業に役立ちます。

修正後、元の失敗条件を再実行し、エラー率、時間分布、およびリソースの副作用を比較します。1回のリクエストが成功したとしても、それはその時点での成功を証明するに過ぎません。断続的な障害には、元の障害条件を十分にカバーする観察ウィンドウが必要です。一時的な対策でサービスが回復したが根本原因が特定されていない場合、回復事実と根本原因の仮説は別々に記録する必要があります。