Skip to content

Split View: Istio 관찰성 내부 구현: 메트릭, 트레이싱, 로깅

|

Istio 관찰성 내부 구현: 메트릭, 트레이싱, 로깅

들어가며

관찰성(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 기반):
AppEnvoyMixerPrometheus/Zipkin
              (별도 서비스, 높은 레이턴시)

Istio 1.12+ (Telemetry API v2):
AppEnvoy (내장 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 (트레이스와 연결)

스팬 생성 상세

FrontendReviewsRatings 호출 시:

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 APIVirtualService, 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 프록시의 풍부한 텔레메트리 기능 위에 구축됩니다. 핵심 포인트를 정리하면:

  1. 메트릭: Envoy의 Stats 필터가 istio_requests_total 등 표준 메트릭을 생성하고, Prometheus가 수집
  2. 트레이싱: Envoy가 자동으로 스팬을 생성하지만, 애플리케이션이 트레이스 헤더를 전파해야 end-to-end 트레이싱이 가능
  3. 로깅: Envoy 액세스 로그가 모든 요청/응답을 기록하며, Telemetry API로 조건부 로깅 가능
  4. 시각화: Kiali가 Prometheus 메트릭을 기반으로 서비스 그래프를 생성

Istio Internals 시리즈를 통해 컨트롤 플레인, 트래픽 관리, 보안, Ambient Mesh, 관찰성의 내부 동작을 살펴보았습니다. 이러한 내부 이해가 실무에서 서비스 메시를 효과적으로 운영하는 데 도움이 되길 바랍니다.

Istio Observability Internals: Metrics, Tracing, Logging

Introduction

Observability is one of the core values of a service mesh. Istio automatically generates metrics, distributed tracing, and access logging without any application code changes.

This post analyzes how Istio generates telemetry data, what processing occurs in the Envoy filter chain, and how it integrates with external systems.

Envoy Statistics System

Statistics Types

Envoy generates three types of statistics:

TypeDescriptionExample
CounterMonotonically increasing valueTotal requests, total errors
GaugeValue that can increase/decreaseActive connections, pending requests
HistogramDistribution of valuesRequest latency, response size

Statistics Generation in the Filter Chain

Request flow and statistics generation points:

Listener (connection stats)
HTTP Connection Manager (request stats)
    ├── JWT Authn Filter → auth success/failure counters
    ├── RBAC Filter → authz allow/deny counters
    ├── Fault Filter → injected delay/abort counters
    ├── Stats Filter (istio.stats)Istio standard metrics generation
    └── Router Filter → upstream request statistics
Cluster (upstream stats)
Endpoint (connection/request stats)

Istio Standard Metrics

Core HTTP Metrics

istio_requests_total (Counter)

Core metric tracking request counts:

istio_requests_total{
  reporter="source",                    # or "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)

Request processing time distribution:

istio_request_duration_milliseconds_bucket{
  ...,  # same labels as above
  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)

Tracks request/response size distribution.

TCP Metrics

istio_tcp_sent_bytes_total         # Total bytes sent
istio_tcp_received_bytes_total     # Total bytes received
istio_tcp_connections_opened_total # Connections opened
istio_tcp_connections_closed_total # Connections closed

Metric Generation Location: Source vs Destination

Frontend Pod                    Reviews Pod
[App][Envoy]  ──────→  [Envoy][App]
         │                    │
    reporter="source"    reporter="destination"
    (outbound side)      (inbound side)

Both Envoys generate metrics, distinguished by the reporter label. Generally, using "source" reporter gives client-side perspective, while "destination" gives server-side perspective.

Telemetry API v2

Architecture Evolution

Istio 1.x (Mixer-based):
AppEnvoyMixerPrometheus/Zipkin
              (separate service, high latency)

Istio 1.12+ (Telemetry API v2):
AppEnvoy (built-in Stats/Trace filters)Prometheus/Zipkin
      (in-proxy processing, low latency)

After Mixer removal, metric generation is performed directly inside the Envoy proxy.

Telemetry Resource

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: mesh-default
  namespace: istio-system # mesh-wide application
spec:
  # Metrics configuration
  metrics:
    - providers:
        - name: prometheus
      overrides:
        - match:
            metric: REQUEST_COUNT
            mode: CLIENT_AND_SERVER
          tagOverrides:
            request_host:
              operation: UPSERT
              value: 'request.host'

  # Tracing configuration
  tracing:
    - providers:
        - name: zipkin
      randomSamplingPercentage: 1.0
      customTags:
        environment:
          literal:
            value: 'production'

  # Access logging configuration
  accessLogging:
    - providers:
        - name: envoy
      filter:
        expression: 'response.code >= 400'

Metric Customization

# Disable specific metric
spec:
  metrics:
  - providers:
    - name: prometheus
    overrides:
    - match:
        metric: REQUEST_BYTES
      disabled: true

# Add custom tags
    overrides:
    - match:
        metric: REQUEST_COUNT
      tagOverrides:
        custom_tag:
          operation: UPSERT
          value: "request.headers['x-custom-tag']"

Distributed Tracing

Trace Propagation Mechanism

What Envoy does automatically:
├── Extract trace headers from inbound requests
├── Create spans and record timing
├── Send spans to trace collector
└── Add trace headers to outbound requests

What the application must do:
└── Copy trace headers from inbound to outbound requests
    (without this, traces are broken)

Supported Trace Headers

B3 Headers (Zipkin):

x-b3-traceid:      128-bit trace ID
x-b3-spanid:       64-bit span ID
x-b3-parentspanid: 64-bit parent span ID
x-b3-sampled:      sampling decision (0 or 1)
x-b3-flags:        debug flag

W3C TraceContext:

traceparent: 00-TRACE_ID-SPAN_ID-FLAGS
tracestate:  vendor-specific key=value pairs

Envoy Internal Header:

x-request-id: UUID generated by Envoy (linked to trace)

Span Generation Detail

When FrontendReviewsRatings:

Frontend Envoy (outbound):
  Span: "reviews.prod.svc.cluster.local:9080/*"
  ├── Start: request send start
  ├── End: response receive complete
  ├── Tags: upstream_cluster, http.method, http.status_code
  └── Parent: inbound span

Reviews Envoy (inbound):
  Span: "reviews.prod.svc.cluster.local:9080/*"
  ├── Start: request received
  ├── End: response sent
  └── Tags: downstream_cluster, peer.address

Reviews Envoy (outbound):
  Span: "ratings.prod.svc.cluster.local:9080/*"
  ├── Start: request send start
  ├── End: response receive complete
  └── Parent: inbound span

Trace Sampling

# Set in MeshConfig
meshConfig:
  defaultConfig:
    tracing:
      sampling: 1.0 # 1% (default)
      # sampling: 100.0  # 100% (for debugging)

# Or set via Telemetry API
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: tracing
spec:
  tracing:
    - randomSamplingPercentage: 1.0

The sampling decision is made at the first Envoy and propagated via the x-b3-sampled header. Subsequent Envoys follow this decision.

Trace Collector Integration

# Zipkin integration
meshConfig:
  defaultConfig:
    tracing:
      zipkin:
        address: zipkin.istio-system:9411

# Jaeger integration (using Zipkin-compatible endpoint)
meshConfig:
  defaultConfig:
    tracing:
      zipkin:
        address: jaeger-collector.observability:9411

# OpenTelemetry Collector integration
meshConfig:
  extensionProviders:
  - name: otel
    opentelemetry:
      service: otel-collector.observability.svc.cluster.local
      port: 4317

Access Logging

Envoy Access Log Format

Default log format:

[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

Response Flags

FlagMeaning
-Normal response
UHUpstream unhealthy (all ejected)
UFUpstream connection failure
UOUpstream overflow (circuit breaker)
NRNo route
URXRetry limit exceeded
DCDownstream connection termination
LHLocal health check failure
UTUpstream timeout
RLRate limited
UAEXExternal authorization denied
RLSERate limit service error

Conditional Logging

Conditional access logging using the 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) expressions allow fine-grained control over logging conditions.

Kiali: Service Mesh Visualization

Kiali Architecture

Kiali data sources:
├── PrometheusMetric-based service graph
├── Kubernetes APIWorkload, service information
├── Istio Config APIVirtualService, DestinationRule, etc.
└── Jaeger/TempoDistributed traces (optional)

Information Provided by Kiali

1. Service Graph (Topology)
   ├── Traffic flow between services
   ├── Request success/failure rates
   ├── Requests per second
   └── Response times

2. Workload Health
   ├── Health score based on error rate
   ├── Inbound/outbound metrics
   └── Pod status

3. Istio Configuration Validation
   ├── VirtualService validity
   ├── DestinationRule conflict detection
   ├── Reference integrity (non-existent hosts, etc.)
   └── Best practice violations

4. Traffic Analysis
   ├── Traffic trends over time
   ├── Error pattern identification
   └── Latency distribution

Prometheus Integration

Metric Collection Configuration

Istio leverages Prometheus service discovery:

# Prometheus scrape configuration
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'

Each Envoy proxy exposes Prometheus metrics on port 15090.

Useful PromQL Queries

# Per-service request success rate (last 5 minutes)
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 latency
histogram_quantile(0.99,
  sum(rate(istio_request_duration_milliseconds_bucket{
    reporter="source"
  }[5m])) by (le, destination_service_name)
)

# Requests per second by service
sum(rate(istio_requests_total{
  reporter="destination"
}[5m])) by (destination_service_name)

Grafana Dashboards

Istio Standard Dashboards

Istio provides the following Grafana dashboards:

1. Mesh Dashboard
   └── Mesh-wide summary (service count, error rate, traffic)

2. Service Dashboard
   └── Per-service detail (inbound/outbound, error rate, latency)

3. Workload Dashboard
   └── Per-workload detail (pod-level metrics)

4. Control Plane Dashboard
   └── istiod performance (xDS pushes, response time, errors)

5. Performance Dashboard
   └── Envoy resource usage (memory, CPU, connections)

Jaeger/Zipkin/Tempo Integration

Distributed Tracing Backends

Supported tracing backends:

Zipkin
├── Lightweight, simple installation
├── In-memory or Cassandra/Elasticsearch storage
└── Default Istio support

Jaeger
├── Zipkin-compatible API
├── Various storage backend support
├── Spark-based analytics
└── Recommended for production

Tempo (Grafana)
├── Object storage based (S3, GCS)
├── High scalability
├── Native Grafana integration
└── Cost-effective

Trace Data Flow

[1] Request enters the mesh
[2] First Envoy generates trace ID
    (if no existing headers)
[3] Each Envoy creates spans and sends to collector
    ├── Zipkin: HTTP POST /api/v2/spans
    ├── Jaeger: UDP/gRPC
    └── OTLP: gRPC (OpenTelemetry)
[4] Collector assembles spans into traces
[5] Query traces in UI

Debugging Tips

Checking Metrics

# Check Envoy metrics directly for a specific pod
kubectl exec PODNAME -c istio-proxy -- \
  curl -s localhost:15090/stats/prometheus | grep istio_requests

# Check statistics via Envoy admin API
kubectl exec PODNAME -c istio-proxy -- \
  curl -s localhost:15000/stats | grep -E "^cluster\."

# Envoy server info
kubectl exec PODNAME -c istio-proxy -- \
  curl -s localhost:15000/server_info

Checking Tracing

# Verify trace header propagation
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))
"

Checking Access Logs

# Real-time access log viewing
kubectl logs PODNAME -c istio-proxy -f | grep -v healthz

# Filter error responses only
kubectl logs PODNAME -c istio-proxy | grep -E '"[45][0-9]{2}"'

Conclusion

Istio observability is built on Envoy proxy's rich telemetry capabilities. Key takeaways:

  1. Metrics: Envoy Stats filter generates standard metrics like istio_requests_total, collected by Prometheus
  2. Tracing: Envoy automatically creates spans, but applications must propagate trace headers for end-to-end tracing
  3. Logging: Envoy access logs record all requests/responses, with conditional logging via Telemetry API
  4. Visualization: Kiali generates service graphs based on Prometheus metrics

Through the Istio Internals series, we have explored the internals of control plane, traffic management, security, Ambient Mesh, and observability. We hope this understanding helps you effectively operate service meshes in practice.