Skip to content
Published on

Ingress オブザーバビリティ — メトリクス、アクセスログ、トレーシング

Authors

はじめに

Ingress コントローラはクラスタのエッジに位置し、すべての外部トラフィックが通過する単一のポイントです。ここで何が起きているか見えないと、ユーザーが体感する遅延や 5xx エラーの原因を、誤ってバックエンドアプリケーションのせいにしてしまいがちです。実際の障害対応の現場で最初に問われるのは、ほぼ必ず「これは Ingress で詰まっているのか、それともバックエンドが遅いのか」です。

この問いに数秒で答えるには、Ingress 層のオブザーバビリティが整っている必要があります。オブザーバビリティとは単に「Prometheus をつなげた」ことではなく、メトリクス・ログ・トレースの三本柱が一貫したラベルで結ばれ、ダッシュボードで異常を見つけたらそのまま該当リクエストのログとトレースに降りていける状態を指します。

本記事では ingress-nginx を基準に、ゴールデンシグナルの定義から、Prometheus メトリクス、Grafana ダッシュボードの主要 PromQL、構造化された JSON アクセスログと Loki への収集、OpenTelemetry 分散トレーシングの連携、アラートルール、per-ingress / per-path 分析、キャパシティプランニング、そして実践的なトラブルシューティングのワークフローまでを段階的に見ていきます。2026年現在、Ingress API 自体は frozen(機能追加なし)であり Gateway API が後継標準となっていますが、オブザーバビリティの原理は両者に等しく当てはまるため、最後に Gateway API での違いにも触れます。

ゴールデンシグナル: 何を測定するか

Google SRE 本が提示する4つのゴールデンシグナルは、Ingress 層にほぼそのままマッピングできます。

ゴールデンシグナルIngress 層での意味代表的な指標
Traffic(トラフィック)秒間の受信 HTTP リクエスト数requests per second (RPS)
Errors(エラー)5xx/4xx の比率5xx ratio, 4xx ratio
Latency(レイテンシ)リクエスト処理時間の分布p50/p90/p99 request duration
Saturation(飽和度)コントローラのリソース/コネクション限界active connections, CPU, reload 頻度

ここに Ingress 特有のシグナルを2つ加えます。1つ目は 帯域(bandwidth) — リクエスト/レスポンスのバイト数で、大容量ダウンロードや異常トラフィックを捉えます。2つ目は upstream(バックエンド)レイテンシと総レイテンシの差 — 総処理時間からバックエンド応答時間を引くと、コントローラ自身が消費した時間が分かり、これが大きくなるとコントローラがボトルネックです。

重要なのは「エラー率が高い」ではなく、「どの ingress の、どの path で、どのバックエンドへ向かうリクエストのエラー率が高いのか」 まで分解できることです。そのため、すべてのメトリクスに ingress/service/path ラベルが必要です。

ingress-nginx の Prometheus メトリクス

ingress-nginx コントローラは、デフォルトで metrics エンドポイントを公開するよう設計されています。Helm values でメトリクスと ServiceMonitor を有効化するのが出発点です。

controller:
  metrics:
    enabled: true
    service:
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "10254"
    serviceMonitor:
      enabled: true
      namespace: monitoring
      additionalLabels:
        release: kube-prometheus-stack
      scrapeInterval: 30s

メトリクスポート(デフォルト 10254)で公開される主要な時系列は次のとおりです。

メトリクス名タイプ意味
nginx_ingress_controller_requestscounter処理したリクエスト数(status, method, host, ingress, service, path ラベル)
nginx_ingress_controller_request_duration_secondshistogram総リクエスト処理時間
nginx_ingress_controller_response_duration_secondshistogramレスポンス時間
nginx_ingress_controller_request_sizehistogramリクエストバイト
nginx_ingress_controller_response_sizehistogramレスポンスバイト
nginx_ingress_controller_nginx_process_connectionsgaugeactive/reading/writing/waiting コネクション
nginx_ingress_controller_config_last_reload_successfulgauge直近の reload 成否
nginx_ingress_controller_config_last_reload_success_timestamp_secondsgauge直近の成功した reload 時刻
nginx_ingress_controller_ssl_expire_time_secondsgauge証明書の有効期限

このうち requests カウンタと request_duration ヒストグラムが、ゴールデンシグナルの大半をカバーします。ヒストグラムはバケット境界が重要です。デフォルトのバケットがアプリケーションのレイテンシ分布と合わないと p99 が不正確になるため、レイテンシの短い API ではより細かいバケットが必要になることがあります。

Grafana ダッシュボードと主要 PromQL

ダッシュボードの最初の画面は、常に4つのゴールデンシグナルであるべきです。以下はそのままパネルに入れられる PromQL です。

リクエストレート(RPS)を ingress 単位に分解。

sum(rate(nginx_ingress_controller_requests[5m])) by (ingress)

5xx エラー率(全体に対する比率)。分母が0のときに備えるパターン。

sum(rate(nginx_ingress_controller_requests{status=~"5.."}[5m])) by (ingress)
/
sum(rate(nginx_ingress_controller_requests[5m])) by (ingress)

p99 レイテンシ(ヒストグラム分位数)。分位数計算のため le ラベルを保持する必要があります。

histogram_quantile(
  0.99,
  sum(rate(nginx_ingress_controller_request_duration_seconds_bucket[5m])) by (le, ingress)
)

p50/p90/p99 を1つのパネルに重ねて描くと、テールレイテンシの増加が一目で分かります。p50 は安定しているのに p99 だけが跳ね上がる場合は、一部の遅いバックエンドや GC、コネクションプールの枯渇を疑います。

upstream レイテンシに対するコントローラのオーバーヘッド。

histogram_quantile(0.99, sum(rate(nginx_ingress_controller_request_duration_seconds_bucket[5m])) by (le))
-
histogram_quantile(0.99, sum(rate(nginx_ingress_controller_response_duration_seconds_bucket[5m])) by (le))

アクティブコネクション(飽和度)。

sum(nginx_ingress_controller_nginx_process_connections) by (state)

reload 頻度 — 頻繁な reload はコネクション切断とレイテンシスパイクの原因です。

changes(nginx_ingress_controller_config_last_reload_success_timestamp_seconds[15m])

帯域(秒間レスポンスバイト)。

sum(rate(nginx_ingress_controller_response_size_sum[5m])) by (ingress)

ダッシュボード構成のコツ: 上段にクラスタ全体のゴールデンシグナル、中段に ingress 別テーブル(RPS・エラー率・p99 を1行に)、下段に reload/コネクション/証明書期限といったコントローラの健全性指標を配置します。ingress をテンプレート変数にしてドリルダウンすれば、per-ingress 分析が自然に行えます。

構造化アクセスログと Loki への収集

メトリクスは「何が問題か」を教えますが、「どのリクエストが問題か」はログが答えます。デフォルトの nginx ログフォーマットは空白区切りのテキストでパースが面倒です。JSON 構造化ログに変えると、Loki / Elasticsearch でフィールドベースのクエリが可能になります。

ingress-nginx のログフォーマットは ConfigMap で変更します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  log-format-escape-json: "true"
  log-format-upstream: >-
    {"time": "$time_iso8601",
     "remote_addr": "$remote_addr",
     "x_forwarded_for": "$proxy_add_x_forwarded_for",
     "request_method": "$request_method",
     "host": "$host",
     "uri": "$uri",
     "status": $status,
     "request_time": $request_time,
     "upstream_addr": "$upstream_addr",
     "upstream_response_time": "$upstream_response_time",
     "upstream_status": "$upstream_status",
     "request_length": $request_length,
     "bytes_sent": $bytes_sent,
     "namespace": "$namespace",
     "ingress_name": "$ingress_name",
     "service_name": "$service_name",
     "trace_id": "$opentelemetry_trace_id"}

ここでの要点は、request_time(総時間)と upstream_response_time(バックエンド時間)の両方を記録することです。2つの差がそのままコントローラのオーバーヘッドになります。また trace_id をログに埋め込んでおくと、ログからトレースへジャンプ(log-to-trace)できます。

Promtail / Grafana Alloy で Loki に送る際は、JSON をパースし、status・namespace・ingress_name をラベルに昇格させます。ただしラベルのカーディナリティ爆発を避けるため、trace_id や remote_addr のような高カーディナリティのフィールドは、ラベルではなくログ行の中にのみ置きます。

scrape_configs:
  - job_name: ingress-nginx
    static_configs:
      - targets: [localhost]
        labels:
          job: ingress-nginx
    pipeline_stages:
      - json:
          expressions:
            status: status
            namespace: namespace
            ingress_name: ingress_name
            request_time: request_time
      - labels:
          status:
          namespace:
          ingress_name:

LogQL で 5xx のみを抽出し、最も遅いリクエストを探すクエリは次のとおりです。

{job="ingress-nginx"} | json | status >= 500
  | request_time > 1.0
  | line_format "{{.host}}{{.uri}} {{.status}} {{.request_time}}s"

OpenTelemetry 分散トレーシングの連携

メトリクスとログで「遅いリクエスト」まで絞り込めたら、トレーシングは「そのリクエストがどのサービスのどの区間で時間を使ったか」を示します。ingress-nginx は OpenTelemetry モジュールを組み込みでサポートし、コントローラをトレースの最初のスパン(root span または edge span)にできます。

ConfigMap で OpenTelemetry を有効化します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  enable-opentelemetry: "true"
  opentelemetry-trace-sampler-ratio: "0.1"
  otlp-collector-host: "otel-collector.observability.svc"
  otlp-collector-port: "4317"
  otel-service-name: "ingress-nginx"

サンプリング比率(sampler-ratio)はコストに直結します。全リクエストのトレーシングは負荷が大きいため、通常は1〜10パーセントから始め、エラーや遅いリクエストは tail-based sampling で collector 側に100パーセント保持する戦略が一般的です。

核心は コンテキスト伝播(context propagation) です。コントローラが W3C traceparent ヘッダをバックエンドへ転送して初めて、バックエンドのスパンが同じトレースに紐づきます。ingress-nginx は OpenTelemetry が有効になると traceparent を自動で注入・伝播するため、バックエンドアプリケーションが同じ標準を使えば end-to-end トレースが完成します。

トレース・メトリクス・ログをつなぐラベル規約を統一することが、オブザーバビリティの最後のピースです。ログの trace_id、トレースの service.name、メトリクスの ingress ラベルが Grafana で相互にリンクするようデータソース間の correlation を設定すれば、ダッシュボードのエラー率スパイク → その時間帯のログ → 問題リクエストのトレース、へ3クリック以内で移動できます。

アラートルールの例

ダッシュボードは人が見る必要がありますが、アラートは人が見ていないときに起こしてくれます。Prometheus alerting rule の例です。

groups:
  - name: ingress-nginx.rules
    rules:
      - alert: IngressHigh5xxRate
        expr: |
          sum(rate(nginx_ingress_controller_requests{status=~"5.."}[5m])) by (ingress)
          /
          sum(rate(nginx_ingress_controller_requests[5m])) by (ingress)
          > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Ingress の 5xx 比率が5パーセントを超過"
          description: "ingress 単位の 5xx 比率が5分以上にわたり5パーセントを超えました。"

      - alert: IngressHighLatencyP99
        expr: |
          histogram_quantile(0.99,
            sum(rate(nginx_ingress_controller_request_duration_seconds_bucket[5m])) by (le, ingress)
          ) > 1
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Ingress の p99 レイテンシが1秒を超過"

      - alert: IngressConfigReloadFailed
        expr: nginx_ingress_controller_config_last_reload_successful == 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Ingress 設定の reload に失敗"

      - alert: IngressCertExpiringSoon
        expr: |
          (nginx_ingress_controller_ssl_expire_time_seconds - time()) / 86400 < 14
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "TLS 証明書が14日以内に期限切れ"

アラート設計の原則は、症状ベース(symptom-based)で発報することです。「CPU が高い」よりも「ユーザーが 5xx を受け取っている」のほうがページングに値するシグナルです。reload 失敗と証明書期限は、ユーザーがまだ影響を受けていなくても、まもなく受ける兆候なので別途捉えます。

per-ingress / per-path 分析

運用規模が大きくなると、「全体のエラー率」は意味が薄れます。1つのコントローラが数十の ingress を提供する場合、特定の ingress や path だけが壊れても全体平均に埋もれてしまうからです。

ingress-nginx は metrics-per-host と path ラベルをサポートします。path ラベルはカーディナリティが高くなりうるため、静的なパス中心にのみ有効化し、動的な ID を含むパスは正規化するのが安全です。

特定の path のエラーを分解する PromQL。

topk(10,
  sum(rate(nginx_ingress_controller_requests{status=~"5.."}[5m])) by (ingress, path)
)

これにより「決済 ingress の /checkout path だけ 5xx が跳ねている」といった診断が即座にできます。さらに LogQL で同じ path をフィルタして実際のエラーメッセージを見れば、メトリクスの「何が」とログの「なぜ」がつながります。

キャパシティプランニング

観測データは事後対応だけでなく、事前計画にも使えます。コントローラのキャパシティプランニングの入力値は次のとおりです。

  • ピーク RPS とその増加トレンド(週/月単位の回帰)
  • リクエストあたりの CPU コスト — コントローラの CPU 使用率を RPS で割った値
  • 同時アクティブコネクション数と worker connection の限界
  • TLS handshake コスト(特に keep-alive が短いとき)
  • reload 頻度と reload あたりの瞬間負荷

例えばピーク RPS が四半期ごとに30パーセントずつ増えており、現在のリクエストあたり CPU コストが一定なら、2四半期後に必要な replica 数を線形外挿で推定できます。ただし reload 負荷と TLS コストは非線形なので、負荷テスト(例: k6, vegeta)で実際の限界を定期的に確認すべきです。飽和度メトリクス(active connections, CPU)が70パーセントを超え始めたら、スケールアウトのトリガーとするのが保守的な基準です。

トラブルシューティングのワークフロー

オブザーバビリティが整うと、障害対応は次のような一貫した流れになります。

1. アラート受信 (例: IngressHigh5xxRate, ingress=payment)
2. ダッシュボード確認 — ゴールデンシグナルのどれが壊れたか?
   ├─ エラー率のみ ↑、レイテンシ正常  → バックエンド 5xx を疑う
   ├─ レイテンシ ↑、エラー率正常       → バックエンド遅延 or コントローラ飽和
   └─ reload 回数が急増               → 頻繁なデプロイ/設定変更を疑う
3. per-ingress/path 分解 — どの path が原因か?
4. LogQL で該当 path の 5xx ログを抽出 — upstream_status, request_time を確認
   ├─ upstream_status 5xx     → バックエンドアプリの問題
   ├─ upstream_addr が空       → エンドポイントなし(503)、service/selector を点検
   └─ request_time が大きい     → トレースへ移動
5. trace_id で分散トレースを照会 — どの区間で時間を消費?
6. 根本原因を確定 → 修正 → ダッシュボードで回復を確認

このワークフローの価値は、推測をデータに置き換えることにあります。「バックエンドのせいだろう」ではなく、upstream_response_time が request_time の95パーセントを占めるという事実でバックエンドを指し示すのです。

Gateway API 時代のオブザーバビリティ

2026年現在、Ingress API は frozen であり Gateway API が後継標準です。オブザーバビリティの観点での朗報は、原理がそのまま引き継がれることです。Gateway 実装(Envoy ベースの Contour、Istio、Cilium、NGINX Gateway Fabric など)も同じゴールデンシグナルを公開し、多くは Envoy の豊富な統計とネイティブの OpenTelemetry サポートを活用します。

違いはラベルの次元がより豊かになる点です。Gateway API は GatewayClass → Gateway → HTTPRoute の3層なので、メトリクスに gateway、route、backend といったラベルが自然に付き、per-route 分析が ingress-nginx より精緻になります。また Envoy ベースのデータプレーンは、サーキットブレーキングやアウトライア検出といった追加シグナルを提供します。いま ingress-nginx でゴールデンシグナル・構造化ログ・トレーシングをきちんと整えておけば、Gateway API へ移行する際にダッシュボードとアラートルールの概念をほぼそのまま再利用できます。

おわりに

Ingress オブザーバビリティの核心はツールではなくつながりです。メトリクスで異常を検知し、per-ingress/path で範囲を絞り、構造化ログで原因を見て、トレースで区間を特定する流れが途切れなくつながって初めて、「Ingress かバックエンドか」という問いに数秒で答えられます。

始めは小さくて構いません。まずメトリクスとゴールデンシグナルのダッシュボードを立て、次に JSON アクセスログを Loki へ送り、最後に trace_id をログに埋め込んでトレーシングをつなぎます。三本柱が同じラベルで結ばれた瞬間、Ingress はもはやブラックボックスではなく、最も信頼できる最初の診断ポイントになります。

参考資料