ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 이코에코(Eco²) Observability #6: Log-Trace 연동 및 Kibana 검색 구조
    이코에코(Eco²)/Observability 2025. 12. 19. 03:10

    개요

    분산 시스템에서 로그와 트레이스를 연결하는 것은 디버깅의 핵심입니다.

    이 문서에서는 Kibana에서 trace.id로 로그를 검색할 수 있도록 구성한 과정과 현재 구현 상태를 다룹니다.


    현재 클러스터 상태

    Trace 커버리지 통계

    전체 로그 1,750,699
    trace.id 있는 로그 125,398
    커버리지 7.16%

    서비스별 trace.id 분포

    istio-proxy 125,179 99.8%
    chat-api 40 0.03%
    scan-api 34 0.03%
    ext-authz 11 0.01%
    auth-api 10 0.01%
    image-api 4 -
    location-api 2 -
    my-api 1 -

     

    istio-proxy (EnvoyFilter)가 대부분의 trace를 생성. 앱 로그는 요청 처리 시에만 trace.id 포함.


    현재 구현 구조

    Trace 생성 흐름

    컴포넌트별 trace.id 지원

    컴포넌트 trace.id 소스 구현 방식 상태
    Istio Gateway %TRACE_ID% EnvoyFilter
    istio-proxy (Sidecar) %TRACE_ID% EnvoyFilter
    ext-authz (Go gRPC) gRPC metadata 수동 추출
    Python APIs OTEL SDK 자동 계측
    시스템 로그 N/A 미지원

    실제 로그 예시

    1. istio-proxy 로그 (EnvoyFilter)

    {
      "@timestamp": "2025-12-18T18:05:28.383Z",
      "service.name": "istio-proxy",
      "trace.id": "c8fd3e757ca339685a7309846a5821b6",
      "http.request.method": "GET",
      "url.path": "/healthz/ready",
      "http.response.status_code": 200
    }

    2. ext-authz 로그 (gRPC metadata 추출)

    {
      "@timestamp": "2025-12-18T12:02:06.845Z",
      "service.name": "ext-authz",
      "trace.id": "a593d6809fe6f036728dc73cfd170b0e",
      "span.id": "3e491beac3443f3c",
      "msg": "Authorization denied",
      "event.outcome": "failure"
    }

    3. Python App 로그 (OTEL SDK)

    {
      "@timestamp": "2025-12-18T09:31:11.811+00:00",
      "service.name": "auth-api",
      "trace.id": "5fdc8e113b2618f6006a00c89347d78a",
      "span.id": "44ea44a93a45564f",
      "log.level": "warning",
      "message": "HTTP 401 UNAUTHORIZED: Missing refresh token"
    }

    핵심 구현 상세

    1. EnvoyFilter (Istio Access Log)

    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:
                      # ✅ %TRACE_ID% - 헤더 없어도 Envoy가 자동 생성
                      trace.id: '%TRACE_ID%'
                      span.id: '%REQ(X-B3-SPANID)%'
                      http.request.method: '%REQ(:METHOD)%'
                      url.path: '%REQ(:PATH)%'
                      http.response.status_code: '%RESPONSE_CODE%'

    %TRACE_ID% vs %REQ(X-B3-TRACEID)% 비교

    변수 값 보장 설명
    %REQ(X-B3-TRACEID)% 클라이언트가 보낸 헤더만
    %TRACE_ID% Envoy가 항상 자동 생성

    2. Index Template (ECS 호환)

    subobjects: false로 dot notation 필드명 유지:

    {
      "index_patterns": ["logs-*"],
      "template": {
        "mappings": {
          "subobjects": false,
          "properties": {
            "trace.id": { "type": "keyword" },
            "span.id": { "type": "keyword" },
            "log.level": { "type": "keyword" },
            "service.name": { "type": "keyword" }
          }
        }
      }
    }

    3. Fluent Bit 설정

    [OUTPUT]
        Name            es
        Match           kube.*
        Replace_Dots    Off    # ✅ ECS dot notation 유지
        ...

    Kibana 검색 가이드

    검색 쿼리 예시

    # 특정 trace의 모든 로그 (cross-service)
    trace.id:5fdc8e113b2618f6006a00c89347d78a
    
    # 특정 서비스의 에러 로그
    service.name:auth-api AND log.level:error
    
    # istio-proxy 401 에러
    service.name:istio-proxy AND http.response.status_code:401
    
    # 앱 로그만 (시스템 제외)
    trace.id:* AND service.name:(auth-api OR scan-api OR chat-api)

    Jaeger ↔ Kibana 연동 워크플로우

    실제 사용

    1. Jaeger: https://jaeger.dev.growbin.app/trace/{trace_id}
    2. Kibana: https://kibana.dev.growbin.app/app/discovertrace.id:{trace_id}

    ECS 필드 매핑 현황

    앱 로그 필드 (Python OTEL)

    필드 타입 소스 예시
    trace.id keyword OTEL SDK 5fdc8e113b2618f6...
    span.id keyword OTEL SDK 44ea44a93a45564f
    service.name keyword App 코드 auth-api
    service.version keyword App 코드 1.0.7
    log.level keyword App 코드 info, warning, error
    message text App 코드 로그 메시지

    Istio 로그 필드 (EnvoyFilter)

    필드 타입 소스 예시
    trace.id keyword %TRACE_ID% c8fd3e757ca33968...
    span.id keyword %REQ(X-B3-SPANID)% B3 헤더 또는 빈값
    http.request.method keyword %REQ(:METHOD)% GET, POST
    url.path keyword %REQ(:PATH)% /api/v1/auth/refresh
    http.response.status_code integer %RESPONSE_CODE% 200, 401, 500

    시스템 로그 필드 (Lua 자동 생성)

    필드 타입 소스 예시
    service.name keyword K8s 라벨 calico-node, argocd-server
    service.environment keyword namespace kube-system, argocd
    kubernetes.namespace keyword K8s 메타 kube-system
    kubernetes.pod.name keyword K8s 메타 calico-node-xv9c8

    Trace Propagation 아키텍처

    ┌─────────────────────────────────────────────────────────────────────────┐
    │                       Trace ID Propagation                              │
    ├─────────────────────────────────────────────────────────────────────────┤
    │                                                                         │
    │  [Client Request]                                                       │
    │       │                                                                 │
    │       ▼                                                                 │
    │  ┌──────────────────┐                                                  │
    │  │ Istio Ingress    │ ◀── trace.id 생성 (%TRACE_ID%)                   │
    │  │ Gateway          │     → ES에 access log 전송                       │
    │  └────────┬─────────┘                                                  │
    │           │ gRPC + B3 메타데이터                                        │
    │           ▼                                                             │
    │  ┌──────────────────┐                                                  │
    │  │ ext-authz        │ ◀── gRPC metadata에서 trace.id 추출              │
    │  │ (Go gRPC)        │     → ES에 auth log 전송                         │
    │  └────────┬─────────┘                                                  │
    │           │ 인증 결과                                                   │
    │           ▼                                                             │
    │  ┌──────────────────┐                                                  │
    │  │ App Sidecar      │ ◀── B3 헤더 전파                                 │
    │  │ (istio-proxy)    │     → ES에 access log 전송                       │
    │  └────────┬─────────┘                                                  │
    │           │ HTTP + B3 헤더                                              │
    │           ▼                                                             │
    │  ┌──────────────────┐                                                  │
    │  │ App (Python)     │ ◀── OTEL SDK가 B3 헤더 읽음                      │
    │  │ + OTEL SDK       │     → ES에 app log 전송 (동일 trace.id)          │
    │  └──────────────────┘                                                  │
    │                                                                         │
    └─────────────────────────────────────────────────────────────────────────┘

    트러블슈팅

    Issue 1: trace.id로 검색 안됨

    증상:

    Kibana: trace.id:xxx → No results

    원인: Fluent Bit Merge_Log_Key 설정으로 필드가 중첩됨

    해결: Nest lift 필터 추가

    [FILTER]
        Name          nest
        Match         kube.*
        Operation     lift
        Nested_under  log_processed

    Issue 2: ECS dot notation이 object로 변환됨

    증상:

    // 의도: "trace.id": "abc"
    // 실제: "trace": { "id": "abc" }

    해결:

    1. Index Template: subobjects: false
    2. Fluent Bit: Replace_Dots Off

    Issue 3: ext-authz 거부 시 trace.id 없음

    증상: 401 에러에 trace.id가 없어서 추적 불가
    해결: gRPC metadata에서 B3 헤더 추출

    if md, ok := metadata.FromIncomingContext(ctx); ok {
        if vals := md.Get("x-b3-traceid"); len(vals) > 0 {
            trace.TraceID = vals[0]
        }
    }

    OpenTelemetry 커버리지 요약

    OTEL이 커버하는 범위

    Python APIs OTEL SDK 자동 계측
    Istio Sidecar Envoy 내장 tracing
    Istio Gateway Envoy 내장 tracing

    OTEL이 커버하지 않는 범위

    ext-authz (Go gRPC) SDK 미적용 gRPC metadata 수동 추출
    시스템 로그 (calico 등) 지원 안함 N/A (trace 불필요)

    Reference


    결론

    trace.id 커버리지 7.16% (주로 istio-proxy)
    ECS 필드명 ✅ dot notation 유지
    Cross-service 검색 ✅ 단일 인덱스로 가능
    ext-authz trace ✅ gRPC metadata 추출
    Jaeger ↔ Kibana 연동 ✅ trace.id로 검색

    Service

    댓글

ABOUT ME

🎓 부산대학교 정보컴퓨터공학과 학사: 2017.03 - 2023.08
☁️ Rakuten Symphony Jr. Cloud Engineer: 2024.12.09 - 2025.08.31
🏆 2025 AI 새싹톤 우수상 수상: 2025.10.30 - 2025.12.02
🌏 이코에코(Eco²) 백엔드/인프라 고도화 중: 2025.12 - Present

Designed by Mango