Skip to content

필사 모드: Ingress 관측성 — 메트릭, 액세스 로그, 트레이싱

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

클러스터의 입구(edge)에 해당하는 Ingress 컨트롤러는 모든 외부 트래픽이 통과하는 단일 지점입니다. 이곳에서 무슨 일이 벌어지는지 보이지 않으면, 사용자가 겪는 지연이나 5xx 에러의 원인을 백엔드 애플리케이션 탓으로 잘못 돌리기 쉽습니다. 실제 장애 대응 현장에서 가장 먼저 던지는 질문은 거의 항상 "이게 Ingress에서 막힌 건가, 백엔드에서 느린 건가?"입니다.

이 질문에 수 초 안에 답하려면 Ingress 계층의 관측성(observability)이 갖춰져 있어야 합니다. 관측성은 단순히 "Prometheus를 붙였다"가 아니라, 메트릭·로그·트레이스 세 축(three pillars)이 일관된 라벨로 연결되어 있어, 대시보드에서 이상 징후를 발견하고 곧바로 해당 요청의 로그와 트레이스로 내려갈 수 있는 상태를 말합니다.

이 글에서는 ingress-nginx를 기준으로 골든 시그널 정의부터 Prometheus 메트릭, Grafana 대시보드의 핵심 PromQL, 구조화된 JSON 액세스 로그와 Loki 수집, OpenTelemetry 분산 트레이싱 연동, 알림 룰, per-ingress/per-path 분석, 용량 계획, 그리고 실전 트러블슈팅 워크플로까지 단계적으로 살펴봅니다. 2026년 현재 Ingress API 자체는 frozen 상태(더 이상 기능 추가가 없음)이고 Gateway API가 후계 표준으로 자리 잡았지만, 관측성의 원리는 두 세계 모두에 동일하게 적용되므로 마지막에 Gateway API에서의 차이도 짚습니다.

골든 시그널: 무엇을 측정할 것인가

Google SRE 책이 제시한 네 가지 골든 시그널은 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 특유의 신호 두 가지를 더합니다. 첫째는 **대역폭(bandwidth)** — 요청/응답 바이트 수로, 대용량 다운로드나 비정상 트래픽을 잡아냅니다. 둘째는 **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

대시보드의 첫 화면은 항상 골든 시그널 네 개여야 합니다. 아래는 곧바로 패널에 넣을 수 있는 PromQL입니다.

요청률(RPS), ingress별로 분해.

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

5xx 에러율(전체 대비 비율). 분모가 0일 때를 대비해 clamp를 쓰는 패턴.

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를 한 패널에 겹쳐 그리면 꼬리 지연(tail latency)의 증가를 한눈에 봅니다. p50은 안정적인데 p99만 치솟는다면, 일부 느린 백엔드나 GC, 커넥션 풀 고갈을 의심합니다.

업스트림 지연 대비 컨트롤러 오버헤드.

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를 한 줄로), 하단에 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(백엔드 시간)을 둘 다 기록하는 것입니다. 두 값의 차이가 곧 컨트롤러 오버헤드입니다. 또한 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 분석

운영 규모가 커지면 "전체 에러율"은 의미가 옅어집니다. 하나의 컨트롤러가 수십 개 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 비용이 일정하다면, 두 분기 후 필요한 replica 수를 선형 외삽으로 추정할 수 있습니다. 다만 reload 부하와 TLS 비용은 비선형이므로, 부하 테스트(예: k6, vegeta)로 실제 한계를 주기적으로 확인해야 합니다. saturation 메트릭(active connections, CPU)이 70퍼센트를 넘기기 시작하면 스케일아웃 트리거로 삼는 것이 보수적인 기준입니다.

트러블슈팅 워크플로

관측성이 갖춰지면 장애 대응은 다음과 같은 일관된 흐름이 됩니다.

1. 알림 수신 (예: IngressHigh5xxRate, ingress=payment)

2. 대시보드 확인 — 골든 시그널 중 무엇이 깨졌나?

├─ 에러율만 ↑, 지연 정상 → 백엔드 5xx 의심

├─ 지연 ↑, 에러율 정상 → 백엔드 느림 또는 컨트롤러 포화

└─ reload 횟수 급증 → 잦은 배포/설정 변경 의심

3. per-ingress/path 분해 — 어느 path가 원인인가?

4. LogQL로 해당 path의 5xx 로그 추출 — upstream_status, request_time 확인

├─ upstream_status 5xx → 백엔드 애플리케이션 문제

├─ upstream_addr 비어 있음 → 엔드포인트 없음(503), 서비스/셀렉터 점검

└─ 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/

현재 단락 (1/222)

클러스터의 입구(edge)에 해당하는 Ingress 컨트롤러는 모든 외부 트래픽이 통과하는 단일 지점입니다. 이곳에서 무슨 일이 벌어지는지 보이지 않으면, 사용자가 겪는 지연이나 ...

작성 글자: 0원문 글자: 10,038작성 단락: 0/222