- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며
- Envoy 통계 시스템
- Istio 표준 메트릭
- Telemetry API v2
- 분산 트레이싱
- 액세스 로깅
- Kiali: 서비스 메시 시각화
- Prometheus 통합
- Grafana 대시보드
- Jaeger/Zipkin/Tempo 통합
- 디버깅 팁
- 마무리
들어가며
관찰성(Observability)은 서비스 메시의 핵심 가치 중 하나입니다. Istio는 애플리케이션 코드 수정 없이 메트릭, 분산 트레이싱, 액세스 로깅을 자동으로 생성합니다.
이 글에서는 Istio가 어떻게 이러한 텔레메트리 데이터를 생성하고, Envoy 필터 체인에서 어떤 처리가 이루어지며, 외부 시스템과 어떻게 통합되는지 내부 구현을 분석합니다.
Envoy 통계 시스템
통계 타입
Envoy는 세 가지 타입의 통계를 생성합니다:
| 타입 | 설명 | 예시 |
|---|---|---|
| Counter | 단조 증가하는 값 | 총 요청 수, 총 에러 수 |
| Gauge | 증가/감소 가능한 현재 값 | 활성 연결 수, 대기 중 요청 수 |
| Histogram | 값의 분포 | 요청 지연 시간, 응답 크기 |
필터 체인에서의 통계 생성
요청 흐름과 통계 생성 위치:
Listener (connection stats)
│
▼
HTTP Connection Manager (request stats)
│
├── JWT Authn Filter → 인증 성공/실패 카운터
├── RBAC Filter → 인가 허용/거부 카운터
├── Fault Filter → 주입된 지연/중단 카운터
├── Stats Filter (istio.stats) → Istio 표준 메트릭 생성
└── Router Filter → 업스트림 요청 통계
│
▼
Cluster (upstream stats)
│
▼
Endpoint (connection/request stats)
Istio 표준 메트릭
핵심 HTTP 메트릭
istio_requests_total (Counter)
요청 수를 추적하는 핵심 메트릭:
istio_requests_total{
reporter="source", # 또는 "destination"
source_workload="frontend",
source_workload_namespace="prod",
source_principal="spiffe://cluster.local/ns/prod/sa/frontend",
destination_workload="reviews",
destination_workload_namespace="prod",
destination_principal="spiffe://cluster.local/ns/prod/sa/reviews",
destination_service="reviews.prod.svc.cluster.local",
destination_service_name="reviews",
destination_service_namespace="prod",
request_protocol="http",
response_code="200",
response_flags="-",
connection_security_policy="mutual_tls"
}
istio_request_duration_milliseconds (Histogram)
요청 처리 시간 분포:
istio_request_duration_milliseconds_bucket{
..., # 위와 동일한 라벨
le="1"
} 100
istio_request_duration_milliseconds_bucket{le="5"} 250
istio_request_duration_milliseconds_bucket{le="10"} 380
istio_request_duration_milliseconds_bucket{le="25"} 450
istio_request_duration_milliseconds_bucket{le="50"} 490
istio_request_duration_milliseconds_bucket{le="100"} 498
istio_request_duration_milliseconds_bucket{le="+Inf"} 500
istio_request_bytes / istio_response_bytes (Histogram)
요청/응답 크기 분포를 추적합니다.
TCP 메트릭
istio_tcp_sent_bytes_total # 전송된 바이트 총량
istio_tcp_received_bytes_total # 수신된 바이트 총량
istio_tcp_connections_opened_total # 열린 연결 수
istio_tcp_connections_closed_total # 닫힌 연결 수
메트릭 생성 위치: Source vs Destination
Frontend Pod Reviews Pod
[App] → [Envoy] ──────→ [Envoy] → [App]
│ │
reporter="source" reporter="destination"
(아웃바운드 측 기록) (인바운드 측 기록)
양쪽 Envoy 모두 메트릭을 생성하지만, reporter 라벨로 구분됩니다. 일반적으로 "source" 리포터를 사용하면 클라이언트 관점, "destination"을 사용하면 서버 관점의 메트릭을 얻습니다.
Telemetry API v2
아키텍처 진화
Istio 1.x (Mixer 기반):
App → Envoy → Mixer → Prometheus/Zipkin
(별도 서비스, 높은 레이턴시)
Istio 1.12+ (Telemetry API v2):
App → Envoy (내장 Stats/Trace 필터) → Prometheus/Zipkin
(프록시 내부 처리, 낮은 레이턴시)
Mixer가 제거된 후, 메트릭 생성은 Envoy 프록시 내부에서 직접 수행됩니다.
Telemetry 리소스
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: mesh-default
namespace: istio-system # 메시 전체 적용
spec:
# 메트릭 설정
metrics:
- providers:
- name: prometheus
overrides:
- match:
metric: REQUEST_COUNT
mode: CLIENT_AND_SERVER
tagOverrides:
request_host:
operation: UPSERT
value: 'request.host'
# 트레이싱 설정
tracing:
- providers:
- name: zipkin
randomSamplingPercentage: 1.0
customTags:
environment:
literal:
value: 'production'
# 액세스 로깅 설정
accessLogging:
- providers:
- name: envoy
filter:
expression: 'response.code >= 400'
메트릭 커스터마이징
# 특정 메트릭 비활성화
spec:
metrics:
- providers:
- name: prometheus
overrides:
- match:
metric: REQUEST_BYTES
disabled: true
# 커스텀 태그 추가
overrides:
- match:
metric: REQUEST_COUNT
tagOverrides:
custom_tag:
operation: UPSERT
value: "request.headers['x-custom-tag']"
분산 트레이싱
트레이스 전파 메커니즘
Envoy가 자동으로 수행하는 것:
├── 인바운드 요청에서 트레이스 헤더 추출
├── 스팬(span) 생성 및 타이밍 기록
├── 스팬을 트레이스 수집기에 전송
└── 아웃바운드 요청에 트레이스 헤더 추가
애플리케이션이 해야 하는 것:
└── 인바운드 요청의 트레이스 헤더를 아웃바운드 요청에 복사
(이것을 하지 않으면 트레이스가 끊어짐)
지원하는 트레이스 헤더
B3 헤더 (Zipkin):
x-b3-traceid: 128비트 트레이스 ID
x-b3-spanid: 64비트 스팬 ID
x-b3-parentspanid: 64비트 부모 스팬 ID
x-b3-sampled: 샘플링 여부 (0 또는 1)
x-b3-flags: 디버그 플래그
W3C TraceContext:
traceparent: 00-TRACE_ID-SPAN_ID-FLAGS
tracestate: vendor-specific key=value pairs
Envoy 내부 헤더:
x-request-id: Envoy가 생성하는 UUID (트레이스와 연결)
스팬 생성 상세
Frontend → Reviews → Ratings 호출 시:
Frontend Envoy (아웃바운드):
Span: "reviews.prod.svc.cluster.local:9080/*"
├── Start: 요청 전송 시작
├── End: 응답 수신 완료
├── Tags: upstream_cluster, http.method, http.status_code
└── Parent: 인바운드 스팬
Reviews Envoy (인바운드):
Span: "reviews.prod.svc.cluster.local:9080/*"
├── Start: 요청 수신
├── End: 응답 전송
└── Tags: downstream_cluster, peer.address
Reviews Envoy (아웃바운드):
Span: "ratings.prod.svc.cluster.local:9080/*"
├── Start: 요청 전송 시작
├── End: 응답 수신 완료
└── Parent: 인바운드 스팬
트레이스 샘플링
# MeshConfig에서 설정
meshConfig:
defaultConfig:
tracing:
sampling: 1.0 # 1% (기본값)
# sampling: 100.0 # 100% (디버깅용)
# 또는 Telemetry API로 설정
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: tracing
spec:
tracing:
- randomSamplingPercentage: 1.0
샘플링 결정은 첫 번째 Envoy에서 이루어지고, x-b3-sampled 헤더를 통해 전파됩니다. 이후 Envoy들은 이 결정을 따릅니다.
트레이스 수집기 통합
# Zipkin 통합
meshConfig:
defaultConfig:
tracing:
zipkin:
address: zipkin.istio-system:9411
# Jaeger 통합 (Zipkin 호환 엔드포인트 사용)
meshConfig:
defaultConfig:
tracing:
zipkin:
address: jaeger-collector.observability:9411
# OpenTelemetry Collector 통합
meshConfig:
extensionProviders:
- name: otel
opentelemetry:
service: otel-collector.observability.svc.cluster.local
port: 4317
액세스 로깅
Envoy 액세스 로그 형식
기본 로그 형식:
[2026-03-20T10:30:00.000Z] "GET /api/reviews HTTP/1.1" 200 - via_upstream
- "-" 0 1234 5 3
"-" "curl/7.68.0" "abc-123-def"
"reviews.prod.svc.cluster.local:9080"
inbound|9080||reviews.prod.svc.cluster.local
10.244.1.5:9080 10.244.0.3:48292
outbound_.9080_.v1_.reviews.prod.svc.cluster.local default
로그 필드 설명
[타임스탬프] "메서드 경로 프로토콜" 상태코드 응답플래그
- "-" 요청바이트 응답바이트 처리시간(ms) 업스트림시간(ms)
"-" "User-Agent" "Request-ID"
"업스트림 호스트"
라우트 이름
다운스트림 주소 업스트림 주소
클러스터 이름 네임스페이스
응답 플래그 (Response Flags)
| 플래그 | 의미 |
|---|---|
| - | 정상 응답 |
| UH | 업스트림 건강하지 않음 (모두 ejected) |
| UF | 업스트림 연결 실패 |
| UO | 업스트림 오버플로우 (서킷 브레이커) |
| NR | 라우트 없음 |
| URX | 리트라이 한도 초과 |
| DC | 다운스트림 연결 종료 |
| LH | 로컬 헬스 체크 실패 |
| UT | 업스트림 타임아웃 |
| RL | 레이트 리밋 |
| UAEX | 외부 인가 거부 |
| RLSE | 레이트 리밋 서비스 에러 |
조건부 로깅
Telemetry API를 사용한 조건부 액세스 로깅:
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: access-log-errors
namespace: production
spec:
accessLogging:
- providers:
- name: envoy
filter:
expression: 'response.code >= 400 || connection.mtls == false'
CEL(Common Expression Language) 표현식을 사용하여 로깅 조건을 세밀하게 제어할 수 있습니다.
Kiali: 서비스 메시 시각화
Kiali 아키텍처
Kiali 데이터 소스:
├── Prometheus → 메트릭 기반 서비스 그래프
├── Kubernetes API → 워크로드, 서비스 정보
├── Istio Config API → VirtualService, DestinationRule 등
└── Jaeger/Tempo → 분산 트레이스 (선택사항)
Kiali가 제공하는 정보
1. 서비스 그래프 (Topology)
├── 서비스 간 트래픽 흐름
├── 요청 성공/실패 비율
├── 초당 요청 수
└── 응답 시간
2. 워크로드 건강 상태
├── 에러율 기반 건강 점수
├── 인바운드/아웃바운드 메트릭
└── Pod 상태
3. Istio 구성 검증
├── VirtualService 유효성
├── DestinationRule 충돌 감지
├── 참조 무결성 (존재하지 않는 host 등)
└── 베스트 프랙티스 위반
4. 트래픽 분석
├── 시간별 트래픽 추이
├── 에러 패턴 식별
└── 레이턴시 분포
Prometheus 통합
메트릭 수집 구성
Istio는 Prometheus의 서비스 디스커버리를 활용합니다:
# Prometheus scrape 구성
scrape_configs:
- job_name: 'envoy-stats'
metrics_path: /stats/prometheus
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_container_name]
action: keep
regex: istio-proxy
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: (.+)
replacement: 'target:15090'
각 Envoy 프록시는 포트 15090에서 Prometheus 메트릭을 노출합니다.
유용한 PromQL 쿼리
# 서비스별 요청 성공률 (최근 5분)
sum(rate(istio_requests_total{
response_code!~"5.*",
reporter="destination"
}[5m])) by (destination_service_name)
/
sum(rate(istio_requests_total{
reporter="destination"
}[5m])) by (destination_service_name)
# P99 레이턴시
histogram_quantile(0.99,
sum(rate(istio_request_duration_milliseconds_bucket{
reporter="source"
}[5m])) by (le, destination_service_name)
)
# 서비스별 초당 요청 수
sum(rate(istio_requests_total{
reporter="destination"
}[5m])) by (destination_service_name)
Grafana 대시보드
Istio 표준 대시보드
Istio는 다음 Grafana 대시보드를 제공합니다:
1. Mesh Dashboard
└── 메시 전체 요약 (서비스 수, 에러율, 트래픽)
2. Service Dashboard
└── 서비스별 상세 (인바운드/아웃바운드, 에러율, 레이턴시)
3. Workload Dashboard
└── 워크로드별 상세 (파드 단위 메트릭)
4. Control Plane Dashboard
└── istiod 성능 (xDS 푸시 수, 응답 시간, 에러)
5. Performance Dashboard
└── Envoy 리소스 사용량 (메모리, CPU, 연결 수)
Jaeger/Zipkin/Tempo 통합
분산 트레이싱 백엔드
지원하는 트레이싱 백엔드:
Zipkin
├── 경량, 간단한 설치
├── 인메모리 또는 Cassandra/Elasticsearch 저장
└── Istio 기본 지원
Jaeger
├── Zipkin 호환 API
├── 다양한 스토리지 백엔드 지원
├── Spark 기반 분석
└── 프로덕션 권장
Tempo (Grafana)
├── 오브젝트 스토리지 기반 (S3, GCS)
├── 높은 확장성
├── Grafana와 네이티브 통합
└── 비용 효율적
트레이스 데이터 흐름
[1] 요청이 메시 진입
│
[2] 첫 번째 Envoy가 트레이스 ID 생성
(기존 헤더가 없는 경우)
│
[3] 각 Envoy가 스팬 생성 및 수집기에 전송
├── Zipkin: HTTP POST /api/v2/spans
├── Jaeger: UDP/gRPC
└── OTLP: gRPC (OpenTelemetry)
│
[4] 수집기가 스팬을 트레이스로 조합
│
[5] UI에서 트레이스 조회
디버깅 팁
메트릭 확인
# 특정 파드의 Envoy 메트릭 직접 확인
kubectl exec PODNAME -c istio-proxy -- \
curl -s localhost:15090/stats/prometheus | grep istio_requests
# Envoy 관리 API로 통계 확인
kubectl exec PODNAME -c istio-proxy -- \
curl -s localhost:15000/stats | grep -E "^cluster\."
# Envoy 서버 정보
kubectl exec PODNAME -c istio-proxy -- \
curl -s localhost:15000/server_info
트레이싱 확인
# 트레이스 헤더 전파 확인
kubectl exec PODNAME -c istio-proxy -- \
curl -s localhost:15000/config_dump | python3 -c "
import json, sys
config = json.load(sys.stdin)
for c in config.get('configs', []):
if 'tracing' in str(c):
print(json.dumps(c, indent=2))
"
액세스 로그 확인
# 실시간 액세스 로그 확인
kubectl logs PODNAME -c istio-proxy -f | grep -v healthz
# 에러 응답만 필터링
kubectl logs PODNAME -c istio-proxy | grep -E '"[45][0-9]{2}"'
마무리
Istio의 관찰성은 Envoy 프록시의 풍부한 텔레메트리 기능 위에 구축됩니다. 핵심 포인트를 정리하면:
- 메트릭: Envoy의 Stats 필터가 istio_requests_total 등 표준 메트릭을 생성하고, Prometheus가 수집
- 트레이싱: Envoy가 자동으로 스팬을 생성하지만, 애플리케이션이 트레이스 헤더를 전파해야 end-to-end 트레이싱이 가능
- 로깅: Envoy 액세스 로그가 모든 요청/응답을 기록하며, Telemetry API로 조건부 로깅 가능
- 시각화: Kiali가 Prometheus 메트릭을 기반으로 서비스 그래프를 생성
Istio Internals 시리즈를 통해 컨트롤 플레인, 트래픽 관리, 보안, Ambient Mesh, 관찰성의 내부 동작을 살펴보았습니다. 이러한 내부 이해가 실무에서 서비스 메시를 효과적으로 운영하는 데 도움이 되길 바랍니다.