이코에코(Eco²) Knowledge Base/Troubleshooting
분산 트레이싱 트러블슈팅: OpenTelemetry 커버리지 확장
mango_fr
2025. 12. 19. 03:00

시스템 컴포넌트 OTEL 적용
개요
시스템 컴포넌트(Istio, ArgoCD)에도 분산 추적을 적용하여 trace.id를 로그에 포함.
적용 대상
| 시스템 | OTEL 지원 | 적용 방법 | 결과 |
|---|---|---|---|
| Istio (Envoy) | ✅ | EnvoyFilter | Access log에 trace.id 포함 |
| ArgoCD | ✅ | ConfigMap | Jaeger에 트레이스 전송 |
| Calico | ❌ | 미지원 | - |
| Kubernetes | ⚠️ | 제한적 | - |
1. Istio Access Log with Trace ID
문제: ext-authz 거부/404 요청에 trace.id 없음
초기 설정에서 %REQ(X-B3-TRACEID)%를 사용했으나, 클라이언트가 헤더를 보내지 않으면 빈 값:
# 문제 상황
/api/v1/auth/register → 401 (ext-authz 거부) → trace.id: 없음
/api/v1/nonexistent → 404 (라우팅 실패) → trace.id: 없음
/api/v1/auth/refresh → 401 (앱 도달) → trace.id: 있음
해결: 사용
Envoy 내부 변수 %TRACE_ID%를 사용하면 모든 요청에 trace가 자동 생성됨.
| 변수 | 설명 | 값 보장 |
|---|---|---|
%REQ(X-B3-TRACEID)% |
클라이언트가 보낸 헤더 | ❌ 없으면 빈 값 |
%TRACE_ID% |
Envoy 내부 trace ID | ✅ 항상 자동 생성 |
EnvoyFilter 설정 (workloads/istio/base/envoy-filter-access-log.yaml):
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: enable-access-log
namespace: istio-system
spec:
configPatches:
- applyTo: NETWORK_FILTER
match:
context: ANY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: MERGE
value:
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog"
path: "/dev/stdout"
log_format:
json_format:
# ECS 표준 필드명 (dot notation)
trace.id: "%TRACE_ID%" # ✅ Envoy 자동 생성
span.id: "%REQ(X-B3-SPANID)%"
http.request.method: "%REQ(:METHOD)%"
url.path: "%REQ(:PATH)%"
http.response.status_code: "%RESPONSE_CODE%"
http.response.body.bytes: "%BYTES_SENT%"
start_time: "%START_TIME%"
duration_ms: "%DURATION%"
upstream_host: "%UPSTREAM_HOST%"
source.address: "%REQ(X-FORWARDED-FOR)%"
request_id: "%REQ(X-REQUEST-ID)%"
검증 결과
# %TRACE_ID% 적용 후
/api/v1/auth/register → 401 (ext-authz 거부) → trace.id: e8f47ed65ec9ece3... ✅
/api/v1/test404 → 404 (라우팅 실패) → trace.id: e05895c1b6571707... ✅
/api/v1/auth/refresh → 401 (앱 도달) → trace.id: 4698731e87d0b18b... ✅
모든 요청에 trace.id가 포함되어 에러 추적 가능
출력 예시
{
"trace.id": "e8f47ed65ec9ece3d4c629cf2374f680",
"http.request.method": "POST",
"url.path": "/api/v1/auth/register",
"http.response.status_code": 401
}
2. ArgoCD OTEL 트레이싱
ConfigMap 설정 (workloads/argocd/base/otel-config.yaml):
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cmd-params-cm
namespace: argocd
data:
otlp.address: jaeger-collector-clusterip.istio-system.svc.cluster.local:4317
ArgoCD 작업(sync, refresh 등)이 Jaeger에 트레이스로 표시됨
적용 명령
# Istio 설정 적용
kubectl apply -f workloads/istio/base/
# ArgoCD 설정 적용 후 재시작
kubectl apply -f workloads/argocd/base/
kubectl rollout restart deployment argocd-server -n argocd
kubectl rollout restart deployment argocd-repo-server -n argocd
kubectl rollout restart statefulset argocd-application-controller -n argocd
Kibana 검색
# istio-proxy 로그에서 특정 trace 검색
trace_id:15434b0153e43190afcbfb316469ccfe AND k8s_container_name:istio-proxy
# 앱 로그 + istio-proxy 같이 검색
trace_id:* AND (service.name:auth-api OR k8s_container_name:istio-proxy)
커밋
feat(istio): add EnvoyFilter for JSON access log with trace.id
- Enable structured JSON access logging via EnvoyFilter
- Include trace.id, span.id, request_id in access logs
- Add Telemetry API configuration for mesh-wide access logging
- Use ECS standard field names (dot notation)
fix(istio): use %TRACE_ID% for all requests including ext-authz denials
- Change from %REQ(X-B3-TRACEID)% to %TRACE_ID% in EnvoyFilter
- %TRACE_ID% is auto-generated by Envoy for all requests
- Enables trace correlation for 401 (ext-authz denied) and 404 errors
- Before: ext-authz denied requests had no trace.id
- After: all requests have trace.id for full error tracking
feat(argocd): enable OTEL tracing to Jaeger
- Configure otlp.address in argocd-cmd-params-cm
- ArgoCD operations now visible in Jaeger
OpenTelemetry 커버리지 분석
OTEL이 커버하는 범위
| 컴포넌트 | 방식 | trace.id 지원 | 비고 |
|---|---|---|---|
| Python API (auth, chat 등) | OTEL SDK 자동 계측 | ✅ | opentelemetry-instrument |
| Istio Sidecar | Envoy 내장 tracing | ✅ | %TRACE_ID% 변수 |
| Istio Ingress Gateway | Envoy 내장 tracing | ✅ | Trace 생성 원점 |
| ArgoCD | 내장 OTLP 지원 | ✅ | otlp.address 설정 |
OTEL이 커버하지 않는 범위
| ext-authz (Go gRPC) | OTEL SDK 미적용 | gRPC 메타데이터에서 B3 헤더 추출 |
| Calico | 네트워크 레이어 | N/A (trace 불필요) |
| Kubernetes 컴포넌트 | 제한적 지원 | N/A |
gRPC 서비스 Trace 추적 (ext-authz)
문제 상황
ext-authz는 Go로 작성된 gRPC 서비스로, Python API처럼 OTEL 자동 계측이 불가능.
초기 상태:
- ext-authz 로그에 trace.id 없음
- istio-proxy 로그에만 trace.id 존재
- 인증 실패 원인 추적 시 trace 연결 불가
해결: gRPC 메타데이터에서 Trace Context 추출
Istio sidecar가 ext-authz로 gRPC 요청 시 메타데이터에 B3 헤더를 주입한다.
1. 상수 정의
const (
// B3 Trace Context headers (Istio/Envoy)
HeaderB3TraceID = "x-b3-traceid"
HeaderB3SpanID = "x-b3-spanid"
)
2. gRPC 메타데이터 추출
import "google.golang.org/grpc/metadata"
// extractTraceInfo extracts B3 trace context from gRPC metadata
func extractTraceInfo(ctx context.Context, req *authv3.CheckRequest) logging.TraceInfo {
trace := logging.TraceInfo{}
// 1. gRPC metadata (Istio sidecar가 주입)
if md, ok := metadata.FromIncomingContext(ctx); ok {
if vals := md.Get("x-b3-traceid"); len(vals) > 0 {
trace.TraceID = vals[0]
}
if vals := md.Get("x-b3-spanid"); len(vals) > 0 {
trace.SpanID = vals[0]
}
}
// 2. Fallback: HTTP 헤더 (클라이언트가 직접 전송한 경우)
if trace.TraceID == "" && req.Attributes != nil {
headers := req.Attributes.Request.Http.Headers
trace.TraceID = headers["x-b3-traceid"]
}
return trace
}
3. 로그에 trace.id 포함
func (l *Logger) WithTrace(traceID, spanID string) *Logger {
if traceID == "" {
return l
}
return &Logger{
Logger: l.With(
slog.String("trace.id", traceID),
slog.String("span.id", spanID),
),
}
}
결과
{
"@timestamp": "2025-12-18T12:02:06.845Z",
"service.name": "ext-authz",
"trace.id": "a593d6809fe6f036728dc73cfd170b0e",
"span.id": "3e491beac3443f3c",
"msg": "Authorization denied",
"event.outcome": "failure",
"event.reason": "missing_auth_header"
}
전체 요청 흐름 추적 (동일 trace.id)
trace.id:a593d6809fe6f036728dc73cfd170b0e
| 12:02:06.845 | ext-authz | Authorization denied |
| 12:02:06.846 | istio-proxy | gRPC /Authorization/Check → 200 |
| 12:02:07.742 | istio-proxy | HTTP /api/v1/auth/register → 401 |
Trace 전파 경로 요약
┌─────────────────────────────────────────────────────────────────────────┐
│ Trace ID Propagation │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [Client Request] │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Istio Ingress │ ◀── trace.id 생성 (%TRACE_ID%) │
│ │ Gateway │ │
│ └────────┬─────────┘ │
│ │ gRPC + B3 메타데이터 │
│ ▼ │
│ ┌──────────────────┐ │
│ │ ext-authz │ ◀── gRPC metadata에서 trace.id 추출 │
│ │ (Go gRPC) │ → 로그에 포함 │
│ └────────┬─────────┘ │
│ │ 인증 결과 │
│ ▼ │
│ ┌──────────────────┐ │
│ │ App Sidecar │ ◀── X-B3-TraceId 헤더 전파 │
│ │ (istio-proxy) │ │
│ └────────┬─────────┘ │
│ │ HTTP + B3 헤더 │
│ ▼ │
│ ┌──────────────────┐ │
│ │ App (Python) │ ◀── OTEL SDK가 B3 헤더 읽음 │
│ │ + OTEL SDK │ → 동일 trace.id로 span 생성 │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
커밋
feat(ext-authz): add B3 trace context to authorization logs
- Extract x-b3-traceid from gRPC metadata (Istio sidecar injects here)
- Fallback to HTTP headers if client sent them
- Add trace.id and span.id to all authorization log entries
- Enables end-to-end trace correlation in Kibana