curl -wで遅い区間を名指しする
一言でいうと
curl -wのタイミングフィールドは、1つのリクエストを名前解決 / 接続 / TLS / 最初のバイト / 全体の5つの区間に分解してくれます。「遅い」という報告を区間の名前に置き換えることが、診断の出発点です。
なぜ必要なのか
「APIが遅い」は、情報がほとんどない文です。名前解決が遅いのと、TLSハンドシェイクが遅いのと、バックエンドのクエリが遅いのは、まったく別の問題で、担当チームも違います。ところが、この3つはユーザーの目には、同じように「遅い」と見えます。
どう動くのか
1行で終わります。
curl -o /dev/null -s -w \
'dns=%{time_namelookup} conn=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' \
https://api.example.com/health
各フィールドは、リクエスト開始からの累積時間です。そのため、区間ごとの所要時間は引き算で求めます。
| 区間 | 計算 | 長いときに疑う場所 |
|---|---|---|
| 名前解決 | time_namelookup |
リゾルバー、searchドメイン、ndots |
| TCP接続 | time_connect - time_namelookup |
ファイアウォール、距離、SYNの再送 |
| TLS | time_appconnect - time_connect |
証明書チェーン、OCSP照会、プロトコルのネゴシエーション |
| サーバー処理 | time_starttransfer - time_appconnect |
アプリケーション、DB |
| 本文の転送 | time_total - time_starttransfer |
帯域幅、応答サイズ |
読み方のコツは次のとおりです。connectがタイムアウトまで伸びるならファイアウォール、appconnectだけが長ければTLS、appconnectは速いのにttfbだけが長ければアプリケーションかDBです。
診断にぜひ必要な、残りのオプションです。
curl -v https://api.example.com/health # 요청/응답 헤더 전체
curl --resolve api.example.com:443:10.0.1.50 https://api.example.com/health
curl -H 'Host: api.example.com' http://10.0.1.10:8080/health
curl -sD - -o /dev/null https://api.example.com/ # 헤더만
curl -L --max-redirs 10 -v https://example.com 2>&1 | grep '< [Ll]ocation'
--resolveは、DNSに触れずに、特定の名前を特定のIPに向かわせます。デプロイ前に新しいサーバーを実際の名前で試すときに必須です。-H 'Host:'は、IPで直接つなぎつつ、仮想ホストのルーティングは元のとおりに通す方法です。LBの裏のバックエンドを直接叩くときにHostを合わせないと、比較そのものが無意味になります。
-IはHEADリクエストを送ります。サーバーがHEADを別に処理していたり、まったく塞いでいたりする場合は、GETと結果が異なるため、疑わしいときは-sD - -o /dev/nullでGETしながらヘッダーだけを見るほうが安全です。
よくある誤診
502を「アップストリームが死んでいる」と断定する誤診です。502は、プロキシがアップストリームから有効でない応答を受け取ったという意味です。応答ヘッダーがプロキシのバッファより大きいと、アップストリームが問題なくても502が出ます。そして、502に対してタイムアウトを延ばしても無効です。それは504の処方です。
有効期限だけを見て証明書を問題なしとする誤診です。ブラウザーは中間証明書をキャッシュするため、開発者のPCでは正常に見えます。サーバー対サーバーの呼び出しはキャッシュがないため失敗します。チェーンの数を数えて確認します。
echo | openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts 2>/dev/null | grep -c 'BEGIN CERTIFICATE'
リダイレクトループの原因をアプリケーションに求める誤診です。たいていは、TLSの終端点とアプリケーションのHTTPS強制が、互いを知らない状況です。X-Forwarded-Protoを渡し、境界で上書きする必要があります。
リダイレクトのコードも、正確に知っておきましょう。302はPOSTをGETに変えてもよく、303はGETを強制し、307/308はメソッドを保持します。プロトコルの強制には308、フォーム送信後の画面遷移には303が適切です。
現場での姿
「curlでは動くのに、ブラウザーだけ失敗します」。 この場合は、サーバーではなくブラウザー固有のルールを見る必要があります。CORSプリフライト、SameSiteクッキー、混在コンテンツのブロック、HSTS、HTTP/2のコネクション統合(coalescing、421)が候補です。curlはこれらのルールをまったく適用しません。
次のラボですること
診断用のHTTPサーバーを起動し、ステータスコード・ヘッダー・リダイレクト・仮想ホスト・タイミングを順に取り出します。--resolveでDNSなしの名前接続を作ってみるステップが、見どころです。