Skip to content
Published on

Ingress トラブルシューティング・プレイブック — 502/504/404 から TLS まで

Authors

はじめに

Ingress はクラスタへ入るトラフィックの最初の関門です。そのため何かが壊れると、ユーザーが最初に突き当たるのも Ingress です。502、504、404、TLS 警告 — これらのエラーの根本原因は、コントローラ設定、サービスセレクタ、バックエンドの健全性、証明書、DNS など複数の層に散らばっており、推測でアプローチすると時間を浪費するだけです。

本記事は推測をなくすためのプレイブックです。症状から出発して診断ツリーを下っていけば、いくつかのコマンドだけで原因の層を絞り込めるように構成しています。ingress-nginx を基準実装としますが、診断手順自体は Traefik、HAProxy、Contour など他のコントローラにもそのまま適用できます。

2026年現在、Ingress API は frozen であり Gateway API が後継標準ですが、運用中のクラスタの大半は依然として Ingress の上で動いています。したがってこのプレイブックは当面、現役で役立ちます。最後に、Gateway API で同じ症状をどう診断するかも簡単に触れます。

一般的なデバッグ手順

症状別ツリーに入る前に、ほぼすべての Ingress 問題に共通する点検順序があります。トラフィック経路に沿って、外側から内側へ絞り込むのが要点です。

[クライアント] → [DNS] → [LoadBalancer/ノード] → [Ingress Controller] → [Service] → [Endpoints/Pod]
      1            2              3                      4                 5              6
  1. DNS は正しい LB IP に解決されるか(dig/nslookup)
  2. LB/ノードポートまで TCP 到達できるか(curl -v, telnet)
  3. Ingress リソースがコントローラに認識されているか(kubectl describe ingress)
  4. コントローラが意図したバックエンドへルーティングしているか(コントローラログ)
  5. Service が正しい Pod を選択しているか(kubectl get endpoints)
  6. Pod が実際に応答するか(kubectl exec で直接 curl)

主要コマンド集。

# Ingress の状態とイベント
kubectl describe ingress my-ingress -n my-namespace

# コントローラログのリアルタイム表示
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller -f

# サービスが指すエンドポイント確認(最も重要なコマンドの一つ)
kubectl get endpoints my-service -n my-namespace

# コントローラ Pod 内からバックエンドへ直接リクエスト
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- \
  curl -s -o /dev/null -w "%{http_code}\n" http://my-service.my-namespace.svc:80/

# 外部から Host ヘッダを指定して直接テスト
curl -v -H "Host: app.example.com" http://<LB_IP>/

kubectl get endpoints が空(アドレスなしの表示)なら、ほぼすべての 502/503 の根本原因です。ここから始めると大幅に時間を節約できます。

症状1: 404 Not Found

404 は「コントローラは生きているが、このリクエストをどこへ送るか分からない」というシグナルです。

404 受信
1) ingressClassName が設定/一致しているか?
   ├─ なし/タイポ  → コントローラがこの Ingress を無視。spec.ingressClassName を追加
   └─ 一致         → 次へ
2) host と path がリクエストとマッチするか?
   ├─ Host ヘッダ不一致  → ワイルドカード/正確なホストを確認
   ├─ pathType 問題      → Prefix vs Exact を確認
   └─ マッチ             → 次へ
3) describe ingress の backend が正しいサービス/ポートか?
   ├─ サービス名/ポートのタイポ → 修正
   └─ 正常                       → バックエンドアプリ自体の 404(ルーティングは正常)

確認コマンド。

# この Ingress がどのクラスに属し、ルールがどう見えるか
kubectl get ingress my-ingress -o yaml | grep -A2 ingressClassName
kubectl describe ingress my-ingress

# インストール済み IngressClass の一覧
kubectl get ingressclass

# Host ヘッダを正確に指定してマッチ確認
curl -v -H "Host: app.example.com" http://<LB_IP>/api/users

最も多い原因は ingressClassName の欠落です。コントローラは自分のクラスに該当する Ingress のみを処理するため、クラスがないか別のクラスだと、その Ingress を丸ごと無視してデフォルトの 404 を返します。pathType もよくつまずきます。Prefix はパスセグメント単位の接頭辞マッチ、Exact は完全一致なので、意図と異なるマッチ/ミスが起こります。

症状2: 502 Bad Gateway

502 は「コントローラがバックエンドへ接続を試みたが、正常な応答を受け取れなかった」というシグナルです。

502 受信
1) endpoints は存在するか?
   ├─ 空    → Pod 未準備/セレクタ不一致(実質的に 503 に近い)
   └─ 存在  → 次へ
2) バックエンドのポート/プロトコルは正しいか?
   ├─ targetPort 不一致        → Service.targetPort を点検
   ├─ HTTPS バックエンドに HTTP → backend-protocol アノテーション HTTPS
   └─ 正常                      → 次へ
3) バックエンドは正常に応答するか?(コントローラから直接 curl)
   ├─ 接続拒否/リセット  → アプリのクラッシュ/リスニングポート違い
   ├─ タイムアウト       → 504 ツリーへ移動
   └─ 200               → ヘッダ/ボディサイズ、upstream keepalive を疑う

確認コマンド。

# バックエンドを直接呼び出し(ポート/プロトコル検証)
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- \
  curl -sv http://my-service.my-namespace.svc:8080/healthz

# コントローラログから 502 行と upstream 情報
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller | grep " 502 "

典型的な 502 の原因はポート/プロトコルの不一致です。Service の targetPort がコンテナが実際にリスニングするポートと異なるか、バックエンドが HTTPS なのにコントローラが HTTP で接続する場合です。後者は backend-protocol アノテーションで解決します。

metadata:
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"

応答ヘッダが過大(例: 巨大な Set-Cookie)だったり、upstream keepalive 設定が合わなくても 502 が出ます。その場合は proxy-buffer-size の調整が必要になることがあります。

症状3: 503 Service Unavailable (no endpoints)

503 の代表的な原因は「送るべきバックエンドがない」ことです。

503 受信
kubectl get endpoints my-service
   ├─ <none>   → 1) Pod は Ready か? 2) Service selector が Pod label と一致するか?
   │            3) readinessProbe が失敗しトラフィックから外れていないか?
   └─ アドレスあり → コントローラ-サービス間の一時的な同期遅延、または maxUnavailable 中

確認コマンド。

# エンドポイント(最も重要)
kubectl get endpoints my-service -n my-namespace

# Pod の状態と readiness
kubectl get pods -n my-namespace -l app=my-app
kubectl describe pod <pod> -n my-namespace | grep -A5 Conditions

# サービスセレクタと Pod ラベルを比較
kubectl get service my-service -o jsonpath='{.spec.selector}'
kubectl get pods --show-labels -n my-namespace

endpoints が空(アドレスなし)なら、原因は3つのうちの一つです。(1) Pod がまだ Ready でない、(2) Service selector と Pod label の不一致、(3) readinessProbe 失敗でエンドポイントから除外された。デプロイ直後なら一時的ですが、継続する場合はセレクタのタイポや probe 設定を疑います。

症状4: 504 Gateway Timeout

504 は「バックエンドが制限時間内に応答しなかった」というシグナルです。

504 受信
1) バックエンド自体が遅いか?(直接 curl で時間測定)
   ├─ 遅い  → アプリ性能の問題。DB/外部呼び出しを疑う
   └─ 速い  → 次へ
2) タイムアウトのアノテーションが短すぎるか?
   └─ proxy-read-timeout/proxy-send-timeout の引き上げを検討
3) keepalive/コネクションプールの枯渇か?
   └─ upstream-keepalive 設定、同時実行数を点検

確認コマンド。

# バックエンドの応答時間を測定
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- \
  curl -s -o /dev/null -w "time_total=%{time_total}\n" http://my-service.my-namespace.svc:80/slow

# 現在のタイムアウトアノテーションを確認
kubectl get ingress my-ingress -o yaml | grep timeout

タイムアウトをむやみに伸ばすのは症状を覆い隠すだけです。まずバックエンドがなぜ遅いか(DB クエリ、外部 API、GC)を見るべきです。本当に時間のかかる処理(レポート生成など)なら、タイムアウトを明示的に引き上げます。

metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "120"

症状5: TLS エラー(証明書/SNI)

TLS 問題はブラウザ警告、handshake 失敗、誤った証明書の提供などとして現れます。

TLS エラー
1) どの証明書が提供されるか?(openssl s_client)
   ├─ デフォルト/ダミー証明書  → Ingress の tls.secretName または SNI マッチの問題
   ├─ 期限切れ証明書           → cert-manager 更新失敗を確認
   └─ 正しい証明書             → クライアントの信頼チェーンの問題
2) Secret が存在し、形式が正しいか?
   └─ kubernetes.io/tls タイプ、tls.crt/tls.key キーを確認
3) host と証明書の SAN が一致するか?
   └─ SNI/ワイルドカードの範囲を点検

確認コマンド。

# 実際に提供される証明書を SNI 指定で確認
openssl s_client -connect <LB_IP>:443 -servername app.example.com 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

# TLS Secret の存在/形式
kubectl get secret my-tls -n my-namespace -o yaml | grep -E "tls.crt|tls.key|type"

# cert-manager Certificate の状態
kubectl describe certificate my-cert -n my-namespace

最もよくある落とし穴は SNI ベースの証明書選択です。複数のホストが一つのコントローラを共有する場合、クライアントが SNI を送らないか、host にマッチする tls 項目がないと、コントローラはデフォルト(ダミー)証明書を提供します。cert-manager を使うなら、Certificate リソースの Ready 条件と更新イベントをまず見ます。期限切れはほぼ常に更新失敗の結果です。

症状6: リダイレクトループ

ブラウザが「リダイレクトが多すぎます」と表示したら無限ループです。

リダイレクトループ
1) Ingress が HTTPS リダイレクトをかけるのに、バックエンドも HTTP へ再リダイレクト?
   └─ 二重リダイレクト。一箇所のみで処理
2) TLS 終端が LB で行われるのに、バックエンドが X-Forwarded-Proto を無視?
   └─ use-forwarded-headers を有効化し、アプリがヘッダを信頼するように
3) force-ssl-redirect とアプリ自身のリダイレクトが衝突?
   └─ 片方を無効化

典型的なシナリオは、TLS が外部 LB で終端され、コントローラ/バックエンドは平文 HTTP を受け取るのに、アプリが「HTTP だから HTTPS へリダイレクト」を繰り返すことです。解決は X-Forwarded-Proto ヘッダを信頼するよう設定することです。

# ConfigMap(コントローラ全体)
data:
  use-forwarded-headers: "true"
  compute-full-forwarded-for: "true"

症状7: 413 Request Entity Too Large(大容量アップロード)

ファイルアップロードが一定サイズで失敗するなら 413 です。コントローラのボディサイズ制限が原因です。

確認と解決。

# コントローラログから 413 / "client intended to send too large body"
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller | grep "too large"

該当 Ingress にアノテーションで上限を引き上げます。

metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"

全体で変えるには ConfigMap の proxy-body-size を調整します。ただし無制限(0)はメモリ/ディスクバッファリングの面で危険なので、実際のアップロード上限に合わせた合理的な値を推奨します。また大容量アップロードは proxy-request-buffering 設定と併せて検討すると、メモリの急増を防げます。

よくある設定ミスのカタログ

診断ツリーとは別に、繰り返し人を苦しめる設定ミスを集めました。

ミス症状解決
ingressClassName 欠落404、コントローラが無視spec.ingressClassName を明示
Service targetPort のタイポ502コンテナのリスニングポートと一致
selector と Pod label の不一致503 no endpointsselector を修正
backend-protocol 未設定(HTTPS バックエンド)502backend-protocol HTTPS アノテーション
pathType の混同(Prefix vs Exact)404 または過剰マッチ意図に合わせて指定
rewrite-target と path キャプチャグループの不一致誤ったパスが転送される正規表現キャプチャと rewrite を整合
use-forwarded-headers 未設定リダイレクトループ、誤ったクライアント IPConfigMap で有効化
proxy-body-size デフォルト値413 アップロード失敗アノテーションで引き上げ
TLS Secret の誤ったタイプ証明書が適用されないkubernetes.io/tls タイプを確認

特に rewrite-target は、キャプチャグループ番号がずれると静かに誤ったパスをバックエンドへ送ります。この種は 404 や 500 ではなく「違うページが表示される」形で現れるため、発見が遅れがちです。

事前点検チェックリスト

デプロイ前、あるいはトラブルシューティングの締めくくりとして見るチェックリストです。

  • spec.ingressClassName が明示され、インストール済みクラスと一致するか
  • host と pathType が意図と合うか
  • Service の targetPort がコンテナポートと一致するか
  • kubectl get endpoints にアドレスが埋まっているか
  • TLS Secret が kubernetes.io/tls タイプで、host と SAN が一致するか
  • cert-manager の Certificate が Ready か
  • proxy-body-size が実際のアップロード上限をカバーするか
  • タイムアウトアノテーションがバックエンドの最悪応答時間をカバーするか
  • use-forwarded-headers が LB トポロジに合わせて設定されているか
  • コントローラログに reload 失敗や config 警告がないか

Gateway API での同じ症状

2026年現在、Gateway API が後継標準です。症状は似ていますが、診断対象が変わります。404 は HTTPRoute のマッチ規則と Gateway の listener/hostname、そして ReferenceGrant(ネームスペース間参照の許可)を見ます。503 は backendRef が指すサービスのエンドポイントを、TLS は Gateway listener の certificateRefs を点検します。

良い点は、Gateway API が status 条件をはるかに豊富に公開することです。HTTPRoute と Gateway の両方が kubectl describe で Accepted/Programmed/ResolvedRefs といった条件とその理由(reason)を示すため、「なぜルーティングできないのか」がアノテーションの推測ではなく明示的な status として現れます。本プレイブックの「症状→層の絞り込み」という思考はそのまま持ち越し、確認対象だけを Ingress リソースから Gateway/HTTPRoute の status 条件へ移せばよいのです。

おわりに

Ingress トラブルシューティングの核心は推測を断つことです。症状から出発し、トラフィック経路を外側から内側へ絞り、各層で1〜2個のコマンドで事実を確認すれば、大半の問題は数分で原因の層が明らかになります。特に kubectl get endpoints とコントローラからの直接 curl の2つを習慣化するだけで、502/503 の半分以上が即座に解決します。

このプレイブックをチームの wiki やランブックに貼り、新しい症状に出会うたびにツリーとカタログを更新してください。時間が経つにつれ、この文書はチームの集合的記憶となり、同じ落とし穴に二度はまらないようにしてくれます。

参考資料