-
분산 트레이싱 트러블슈팅: Log-Trace 연동 및 Kibana 검색 구조이코에코(Eco²)/Troubleshooting 2025. 12. 19. 02:47

개요
분산 시스템에서 로그와 트레이스를 연결하는 것은 디버깅의 핵심입니다.
이 문서에서는 Kibana에서trace_id로 로그를 검색할 수 없었던 문제를 분석하고 해결한 과정을 다룹니다.
문제 상황
증상
Jaeger에서 확인한
trace_id로 Kibana에서 로그 검색 시 결과 없음.# Kibana 검색 (실패) trace.id:1598486b3c023e05db07590939154b51 → No results match your search criteria기대 동작
애플리케이션 로그에
trace.id가 포함되어 있으므로, 해당 값으로 검색 가능해야 함.// 애플리케이션 로그 출력 (kubectl logs) { "@timestamp": "2025-12-18T07:04:51.886+00:00", "message": "Scan pipeline finished", "trace.id": "ef7445d2d5c540c585bcef3896fd960b", "span.id": "35ecc51ed1958ea4", "service.name": "scan-api" }
로그 파이프라인 분석
로그 흐름
App (JSON) → containerd (CRI) → Fluent Bit → Elasticsearch → Kibana각 단계별 로그 구조 변화
1. 애플리케이션 출력 (ECS JSON)
{ "@timestamp": "2025-12-18T07:04:51.886+00:00", "message": "Scan pipeline finished", "log.level": "info", "trace.id": "ef7445d2d5c540c585bcef3896fd960b", "span.id": "35ecc51ed1958ea4", "service.name": "scan-api" }2. containerd CRI 래핑
containerd가 로그를 CRI 포맷으로 래핑:
2025-12-18T16:04:51.886+09:00 stdout F {"@timestamp": "2025-12-18T07:04:51.886+00:00", "message": "Scan pipeline finished", ...}필드 값 설명 time2025-12-18T16:04:51.886+09:00containerd 타임스탬프 streamstdout출력 스트림 logtagFFull (완전한 라인) log{...JSON...}원본 JSON이 문자열로 저장 3. Fluent Bit 처리 후 (Elasticsearch 저장)
{ "@timestamp": "2025-12-18T07:04:51.886Z", "time": "2025-12-18T16:04:51.886+09:00", "stream": "stdout", "logtag": "F", "log": "{\"@timestamp\": \"2025-12-18T07:04:51.886+00:00\", ...}", "log_processed": { "@timestamp": "2025-12-18T07:04:51.886+00:00", "message": "Scan pipeline finished", "log_level": "info", "trace_id": "ef7445d2d5c540c585bcef3896fd960b", "span_id": "35ecc51ed1958ea4", "service_name": "scan-api" }, "cluster": "eco2-dev", "environment": "dev", "k8s_namespace_name": "scan", "k8s_pod_name": "scan-api-59d5788d7-q7qcc", "k8s_container_name": "scan-api", "k8s_labels": { "app": "scan-api", "domain": "scan" } }
원인 분석
Fluent Bit 설정 검토
[FILTER] Name kubernetes Match kube.* Merge_Log On Merge_Log_Key log_processed # ← 여기가 문제 ... [OUTPUT] Name es Replace_Dots On # ← trace.id → trace_id ...문제점
설정 효과 결과 Merge_Log OnJSON 로그를 파싱 ✅ 정상 Merge_Log_Key log_processed파싱 결과를 중첩 키에 저장 ⚠️ 필드 접근 복잡 Replace_Dots On.을_로 변환trace.id→trace_id실제 검색 경로
# 원하는 검색 trace.id:xxx # 실제 필요한 검색 (문제 원인) log_processed.trace_id:xxx
해결: Fluent Bit Nest Lift Filter 추가
수정 내용
workloads/logging/base/fluent-bit.yaml:[FILTER] Name nest Match kube.* Operation lift Nested_under kubernetes Add_prefix k8s_ # [신규 추가] log_processed 필드를 최상위로 올림 [FILTER] Name nest Match kube.* Operation lift Nested_under log_processedNest Lift Filter 동작
# Before (중첩) { "log_processed": { "trace_id": "abc123", "message": "hello" } } # After (평탄화) { "trace_id": "abc123", "message": "hello" }적용
# ConfigMap 업데이트 kubectl apply -f workloads/logging/base/fluent-bit.yaml # DaemonSet 재시작 kubectl rollout restart daemonset fluent-bit -n logging
Kibana 검색 가이드
검색 필드 경로
시기 검색 쿼리 비고 수정 전 log_processed.trace_id:xxx중첩 경로 필요 수정 후 trace_id:xxx최상위 필드로 직접 검색 유용한 검색 쿼리
# 특정 trace의 모든 로그 trace_id:ef7445d2d5c540c585bcef3896fd960b # 특정 서비스의 에러 로그 service_name:scan-api AND log_level:error # 특정 시간대 + trace 조합 trace_id:xxx AND @timestamp >= "2025-12-18T07:00:00"
Elasticsearch 필드 매핑 총정리
ECS 표준 필드 (현재 사용)
Replace_Dots Off+subobjects: false설정으로 dot notation 유지.서비스 관련 필드
필드 타입 앱 로그 시스템 로그 설명 service.namekeyword ✅ 앱에서 출력 ✅ Lua 자동 생성 서비스 식별자 service.environmentkeyword ✅ 앱에서 출력 ✅ Lua 자동 생성 환경 (dev/prod) service.versionkeyword ✅ 앱에서 출력 ⚠️ 라벨 있을 때만 버전 정보 트레이싱 필드
필드 타입 앱 로그 시스템 로그 설명 trace.idkeyword ✅ OTEL 자동 주입 ❌ 없음 분산 추적 ID span.idkeyword ✅ OTEL 자동 주입 ❌ 없음 Span ID 로깅 메타데이터
필드 타입 앱 로그 시스템 로그 설명 log.levelkeyword ✅ 앱에서 출력 ⚠️ 일부만 로그 레벨 (info, error) log.loggerkeyword ✅ 앱에서 출력 ❌ 없음 로거 이름 ecs.versionkeyword ✅ 앱에서 출력 ❌ 없음 ECS 버전 (8.11.0) messagetext ✅ 앱에서 출력 ⚠️ log 필드 사용 로그 메시지 Kubernetes 메타데이터 (Lua 필터 생성)
필드 타입 앱 로그 시스템 로그 설명 kubernetes.namespacekeyword ✅ ✅ 네임스페이스 kubernetes.pod.namekeyword ✅ ✅ Pod 이름 kubernetes.container.namekeyword ✅ ✅ 컨테이너 이름 kubernetes.labelsobject ✅ ✅ 주요 라벨 객체 에러 관련 필드
필드 타입 앱 로그 시스템 로그 설명 error.typekeyword ✅ 에러 시 ❌ 없음 예외 타입 error.messagetext ✅ 에러 시 ❌ 없음 에러 메시지 error.stack_tracetext ✅ 에러 시 ❌ 없음 스택 트레이스
Fluent Bit 생성 필드
필드 타입 설명 @timestampdate 로그 타임스탬프 timekeyword containerd CRI 타임스탬프 streamkeyword stdout / stderr logtagkeyword F (Full) / P (Partial) logtext 원본 로그 (JSON 문자열) clusterkeyword 클러스터 이름 (eco2-dev) environmentkeyword 환경 (dev) K8s 메타데이터 (k8s_ prefix)
필드 설명 k8s_namespace_name네임스페이스 k8s_pod_namePod 이름 k8s_pod_idPod UID k8s_container_name컨테이너 이름 k8s_container_image컨테이너 이미지 k8s_host노드 이름 k8s_labels라벨 객체
앱 로그 vs 시스템 로그 비교
앱 로그 (chat-api 예시)
{ "@timestamp": "2025-12-18T09:50:26.958+00:00", "message": "Chat message received", "log.level": "info", "log.logger": "domains.chat.services.chat", "ecs.version": "8.11.0", "service.name": "chat-api", "service.version": "1.0.7", "service.environment": "dev", "trace.id": "632602a1d3946d5aba7ea9592034f576", "span.id": "4b04fd1e7c05437f", "kubernetes.namespace": "chat", "kubernetes.pod.name": "chat-api-74456ccd68-7lgml", "kubernetes.container.name": "chat-api", "kubernetes.labels": { "app": "chat-api", "domain": "chat", "version": "v1", "tier": "business-logic" }, "cluster": "eco2-dev", "k8s_namespace_name": "chat", "k8s_pod_name": "chat-api-74456ccd68-7lgml" }시스템 로그 (calico-node 예시)
{ "@timestamp": "2025-12-18T10:38:54.614Z", "log": "2025-12-18 10:38:54.614 [INFO][55] felix/int_dataplane.go...", "service.name": "calico-node", "service.environment": "kube-system", "kubernetes.namespace": "kube-system", "kubernetes.pod.name": "calico-node-xv9c8", "kubernetes.container.name": "calico-node", "kubernetes.labels": { "k8s-app": "calico-node" }, "cluster": "eco2-dev", "k8s_namespace_name": "kube-system", "k8s_pod_name": "calico-node-xv9c8" }시스템 로그 (ArgoCD 예시)
{ "@timestamp": "2025-12-18T10:38:57.428Z", "msg": "Alloc=220295 TotalAlloc=9444918855...", "level": "info", "service.name": "argocd-application-controller", "service.environment": "argocd", "kubernetes.namespace": "argocd", "kubernetes.pod.name": "argocd-application-controller-0", "kubernetes.container.name": "argocd-application-controller", "kubernetes.labels": { "app.kubernetes.io/name": "argocd-application-controller" }, "cluster": "eco2-dev" }
필드 소스 요약
필드 그룹 앱 로그 소스 시스템 로그 소스 service.*앱 코드 (ECSJsonFormatter) Lua 필터 (K8s 라벨) trace.*,span.*OpenTelemetry SDK ❌ 없음 log.*앱 코드 (ECSJsonFormatter) ⚠️ 일부만 (level) kubernetes.*Lua 필터 Lua 필터 k8s_*Fluent Bit K8s 필터 Fluent Bit K8s 필터 cluster,environmentFluent Bit Modify 필터 Fluent Bit Modify 필터
Kibana 검색 쿼리 예시
# 특정 서비스 로그 service.name:auth-api # 트레이스 추적 (앱 로그만) trace.id:632602a1d3946d5aba7ea9592034f576 # 에러 로그 log.level:error OR log.level:ERROR # 시스템 로그 제외 service.name:* AND NOT kubernetes.namespace:(kube-system OR argocd OR logging) # 특정 Pod 로그 kubernetes.pod.name:auth-api-* # 라벨 기반 필터 kubernetes.labels.tier:business-logic
Jaeger ↔ Kibana 연동 워크플로우
Trace ID로 로그 찾기

실제 사용 예시
- Jaeger에서 문제 trace 식별
- URL:
https://jaeger.dev.growbin.app/trace/ef7445d2d5c540c585bcef3896fd960b - 느린 span 또는 에러 발견
- URL:
- Kibana에서 상세 로그 확인
- URL:
https://kibana.dev.growbin.app/app/discover - 쿼리:
trace_id:ef7445d2d5c540c585bcef3896fd960b - 해당 요청의 모든 로그 확인
- URL:
참고: Replace_Dots 설정 이유
문제: Elasticsearch 필드명 제약
Elasticsearch는 기본적으로 필드명에
.이 포함되면 object hierarchy로 해석한다.// 의도: 단일 필드 { "trace.id": "abc" } // ES 해석: 중첩 객체 { "trace": { "id": "abc" } }ES 8.x 이후: subobjects 옵션
Elasticsearch 8.3+에서
subobjects: false매핑 옵션이 추가되었다.PUT logs-template { "mappings": { "subobjects": false, "properties": { "trace.id": { "type": "keyword" }, "span.id": { "type": "keyword" }, "log.level": { "type": "keyword" } } } }이 설정으로 dot 필드명을 평탄하게 유지할 수 있다. 현재 클러스터 버전 8.11.0에서 사용 가능.
참고: Elasticsearch subobjects 공식 문서
현재 선택: Replace_Dots On
옵션 장점 단점 Replace_Dots On설정 간단, 호환성 보장 ECS 필드명과 불일치 ( trace_id)subobjects: falseECS 표준 유지 ( trace.id)Index Template 설정 필요 현재 선택: Fluent Bit 파싱 +
Replace_Dots Off+subobjects: falseIndex Template핵심: Fluent Bit에서 JSON 파싱 (부하분산) + ES에서 dot 필드명 유지
- Fluent Bit:
Merge_Log On- JSON 파싱하여 필드 추출 (각 노드에서 분산 처리) - Fluent Bit OUTPUT:
Replace_Dots Off- dot notation 그대로 ES 전송 - Index Template:
subobjects: false- ES가 dot을 nested로 해석하지 않음
App (JSON log) → Fluent Bit (parse + distributed) → ES (subobjects:false) → Index ↓ ↓ 각 노드에서 분산 파싱 trace.id, log.level 등 ECS 필드명 유지구현 파일
파일 설정 workloads/logging/base/fluent-bit.yamlMerge_Log On,Replace_Dots Offworkloads/logging/base/elasticsearch-index-template.yamlsubobjects: falseIndex Template장점
- 부하분산: 각 노드의 DaemonSet에서 분산 파싱 (ES 부하 감소)
- ECS 표준: dot notation 필드명 유지 (
trace.id,log.level) - 단순성: ES Ingest Pipeline 불필요
Kibana에서 service.name 표시 확인
문제
Kibana에서
service.name컬럼이-로 표시됨.원인
- Data View 필드 미갱신: 새 필드가 아직 인식되지 않음
- 일부 로그만 해당 필드 보유: 시스템 로그(argocd 등)에는
service.name이 없음
확인
ES에서 직접 검색하면 정상:
# service.name으로 검색 curl "ES/_search" -d '{"query":{"term":{"service.name":"auth-api"}}}' # 결과 { "message": "HTTP 401 UNAUTHORIZED: Missing refresh token", "service.name": "auth-api" # ✅ 정상 저장됨 }해결 방법
- Kibana Data View 새로고침:
- Stack Management → Data Views → logs-* → Refresh field list
- 검색 필터로 애플리케이션 로그만 표시:
service.name:* AND NOT k8s_namespace_name:(kube-system OR argocd OR logging)
커밋
feat(logging): lift log_processed fields to top level for trace correlation - Add nest lift filter to promote log_processed fields to root level - Enables direct trace_id/span_id search in Kibana without nested path - Before: log_processed.trace_id:xxx - After: trace_id:xxxSHA:
39b662a7
feat(auth): add error logging with trace context - Log HTTP errors (401, 403, etc.) with trace.id for correlation - Log validation errors with field information - Log unexpected exceptions with full traceback - Enables trace.id search in Kibana for error debuggingSHA:
eecc958b
시스템 로그 ECS 표준화
문제 상황
Kibana Discover에서
service.name필드가 Available fields에 표시되지 않음.통계
- service.name 있는 로그: 57건 (0.3%) - 전체 로그: 19,015건 (100%) → 99.7%가 시스템 로그 (calico, argocd 등)로 service.name 없음Kibana Discover 동작 원리
영역 표시 필드 Available fields 현재 검색 결과에 값이 있는 필드만 표시 Empty fields 현재 검색 결과에 값이 없는 필드 Data View Management 전체 매핑된 필드 표시 (521개) service.name이 0.3%에만 있으니 기본 검색에서 Empty fields로 분류됨.해결: 시스템 로그에 ECS 필드 자동 매핑
K8s 메타데이터를 활용하여 모든 로그에
service.name자동 추가.라벨 분석
# 앱 로그 (우리 서비스) app=auth-api, domain=auth, environment=dev, version=v1 # 시스템 로그 (ArgoCD, Istio) app.kubernetes.io/name=argocd-server # 시스템 로그 (Calico) k8s-app=calico-nodeECS 매핑 전략
ECS 필드 소스 (우선순위) service.nameapp>app.kubernetes.io/name>k8s-app>container_nameservice.environmentenvironment라벨 >namespaceservice.versionversion>app.kubernetes.io/versionkubernetes.namespacenamespace 정보 kubernetes.pod.namePod 이름 kubernetes.labels.*모든 라벨 보존 구현: Fluent Bit Lua 필터
workloads/logging/base/fluent-bit.yaml:# ECS 필드 자동 매핑 - K8s 메타데이터에서 ECS 표준 필드 생성 [FILTER] Name lua Match kube.* script /fluent-bit/etc/ecs-enrichment.lua call enrich_with_ecs_fieldsLua 스크립트 (ecs-enrichment.lua)
function enrich_with_ecs_fields(tag, timestamp, record) local modified = false -- 1. service.name 매핑 (앱 로그에서 이미 있으면 유지) if not record["service.name"] then local service_name = record["k8s_labels_app"] or record["k8s_labels_app.kubernetes.io/name"] or record["k8s_labels_k8s-app"] or record["k8s_container_name"] if service_name then record["service.name"] = service_name modified = true end end -- 2. service.environment 매핑 if not record["service.environment"] then local env = record["k8s_labels_environment"] or record["k8s_namespace_name"] if env then record["service.environment"] = env modified = true end end -- 3. service.version 매핑 if not record["service.version"] then local version = record["k8s_labels_version"] or record["k8s_labels_app.kubernetes.io/version"] if version then record["service.version"] = version modified = true end end -- 4. kubernetes.* ECS 필드 매핑 if record["k8s_namespace_name"] then record["kubernetes.namespace"] = record["k8s_namespace_name"] modified = true end if record["k8s_pod_name"] then record["kubernetes.pod.name"] = record["k8s_pod_name"] modified = true end -- 5. kubernetes.labels 객체로 라벨 보존 local labels = {} local label_keys = {"app", "domain", "environment", "version", "tier", ...} for _, key in ipairs(label_keys) do local label_field = "k8s_labels_" .. key if record[label_field] then labels[key] = record[label_field] end end if next(labels) ~= nil then record["kubernetes.labels"] = labels modified = true end if modified then return 1, timestamp, record else return 0, timestamp, record end end적용 후 결과
시스템 로그 (Calico)
{ "service.name": "calico-node", "service.environment": "kube-system", "kubernetes.namespace": "kube-system", "kubernetes.pod.name": "calico-node-4t5k9", "kubernetes.labels": { "k8s-app": "calico-node" } }시스템 로그 (ArgoCD)
{ "service.name": "argocd-server", "service.environment": "argocd", "kubernetes.namespace": "argocd", "kubernetes.labels": { "app.kubernetes.io/name": "argocd-server" } }앱 로그 (auth-api) - 기존 유지
{ "service.name": "auth-api", "service.environment": "dev", "service.version": "1.0.0", "trace.id": "abc123...", "kubernetes.labels": { "app": "auth-api", "domain": "auth", "tier": "business-logic" } }장점
항목 효과 검색 일관성 모든 로그에 service.name보유 → Kibana 필터 항상 사용 가능기존 로그 호환 앱 로그의 ECS 필드 유지 (Lua에서 조건부 처리) 라벨 보존 kubernetes.labels객체로 원본 라벨 보존ECS 표준 준수 kubernetes.*필드셋은 ECS 공식 스펙적용 방법
# 수동 # ConfigMap 업데이트 kubectl apply -f workloads/logging/base/fluent-bit.yaml # DaemonSet 재시작 kubectl rollout restart daemonset fluent-bit -n logging # 확인 kubectl get pods -n logging -w # 자동 (GitOps) # workloads/logging/base/fluent-bit.yaml Config Map 수정 # commit -> push -> PR(develop) -> MERGE(develop) # ArgoCD App of apps, sync-wave로 클러스터 컴포넌트 선언적으로 관리커밋
feat(logging): add ECS enrichment for system logs via Lua filter - Add Lua filter to map K8s labels to ECS fields (service.name, etc.) - Priority: app > app.kubernetes.io/name > k8s-app > container_name - Preserve app logs' existing ECS fields (conditional mapping) - Add kubernetes.labels object for label preservation - All logs now have service.name for consistent Kibana filtering'이코에코(Eco²) > Troubleshooting' 카테고리의 다른 글
Message Queue 트러블슈팅: RabbitMQ 구축 (0) 2025.12.22 분산 트레이싱 트러블슈팅: OpenTelemetry 커버리지 확장 (0) 2025.12.19 선언적 배포 트러블슈팅: eck-custom-resources operator 도입 실패 포스트모템 (0) 2025.12.18 분산 트레이싱 트러블슈팅: NetworkPolicy, Zipkin, OpenTelemetry (0) 2025.12.18 EFK 트러블슈팅: Fluent Bit CRI Parser 오류 (0) 2025.12.18 - Jaeger에서 문제 trace 식별