- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- ゴールデンシグナル: 何を測定するか
- ingress-nginx の Prometheus メトリクス
- Grafana ダッシュボードと主要 PromQL
- 構造化アクセスログと Loki への収集
- OpenTelemetry 分散トレーシングの連携
- アラートルールの例
- per-ingress / per-path 分析
- キャパシティプランニング
- トラブルシューティングのワークフロー
- Gateway API 時代のオブザーバビリティ
- おわりに
- 参考資料
はじめに
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_requests | counter | 処理したリクエスト数(status, method, host, ingress, service, path ラベル) |
| nginx_ingress_controller_request_duration_seconds | histogram | 総リクエスト処理時間 |
| nginx_ingress_controller_response_duration_seconds | histogram | レスポンス時間 |
| nginx_ingress_controller_request_size | histogram | リクエストバイト |
| nginx_ingress_controller_response_size | histogram | レスポンスバイト |
| nginx_ingress_controller_nginx_process_connections | gauge | active/reading/writing/waiting コネクション |
| nginx_ingress_controller_config_last_reload_successful | gauge | 直近の reload 成否 |
| nginx_ingress_controller_config_last_reload_success_timestamp_seconds | gauge | 直近の成功した reload 時刻 |
| nginx_ingress_controller_ssl_expire_time_seconds | gauge | 証明書の有効期限 |
このうち 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 はもはやブラックボックスではなく、最も信頼できる最初の診断ポイントになります。
参考資料
- Kubernetes Ingress コンセプト: https://kubernetes.io/docs/concepts/services-networking/ingress/
- ingress-nginx メトリクス/モニタリング: https://kubernetes.github.io/ingress-nginx/user-guide/monitoring/
- ingress-nginx ログフォーマット設定: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/log-format/
- ingress-nginx ConfigMap オプション: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/
- Prometheus クエリ(PromQL): https://prometheus.io/docs/prometheus/latest/querying/basics/
- Grafana Loki LogQL: https://grafana.com/docs/loki/latest/query/
- OpenTelemetry ドキュメント: https://opentelemetry.io/docs/
- Gateway API: https://gateway-api.sigs.k8s.io/
- Contour(Envoy ベースの Ingress/Gateway): https://projectcontour.io/docs/
- cert-manager(TLS 証明書の自動化): https://cert-manager.io/docs/