-
이코에코(Eco²) Observability #5: 인덱스 전략 및 라이프사이클 관리이코에코(Eco²)/Observability 2025. 12. 19. 03:03

개요
마이크로서비스 환경에서 로그 인덱스를 어떻게 설계할지는 운영 효율성과 비용에 직접적인 영향을 미칩니다.
이 글에서는 빅테크(Uber, Google)와 CNCF 권장사항을 바탕으로 인덱스 분리 전략을 수립하고, ILM을 통한 라이프사이클 관리를 다룹니다.
목표
- 인덱스 분리 전략 선택 (도메인별 vs 앱/인프라 vs 단일)
- Fluent Bit 라우팅 설정
- ILM 정책으로 비용 최적화
- ECS 필드 기반 서비스 구분
현재 클러스터 상태
인덱스 현황
# 2025-12-18 기준 kubectl exec -n logging eco2-logs-es-default-0 -- curl -s "http://localhost:9200/_cat/indices/logs-*?v" index docs.count store.size logs-2025.12.17 1,114,246 421mb logs-2025.12.18 624,036 259mb ─────────────────────────────────────── Total 1,738,282 680mb서비스별 로그 분포

앱 서비스 로그 (비즈니스 로직)
image-api 100 41% auth-api 53 22% chat-api 44 18% scan-api 36 15% location-api 6 2% character-api 3 1% my-api 1 0.4% Total 243 100% 인사이트: 전체 ~1.7M 로그 중 앱 로그는 243개(0.01%)뿐. 나머지는 인프라 로그(istio, argocd, calico 등).
인덱스 분리 전략
아키텍처 결정: 단일 인덱스 선택

왜 단일 인덱스인가?
도메인별 (logs-auth-, logs-scan-) 도메인 격리 샤드 폭발, 크로스 검색 어려움 ❌ 앱/인프라 분리 (logs-app-, logs-infra-) 보존기간 차별화 라우팅 복잡도 증가 △ (계획됨) 단일 인덱스 (logs-YYYY.MM.DD) ✅ 간단, 크로스 검색 용이 보존기간 동일 ✅ (현재) 선택 이유:
- 개발 환경 특성: 단일 ES 노드, 샤드 오버헤드 최소화 필요
- 앱 로그 비율: 전체의 0.01%로 분리 효과 미미
- Cross-service 검색:
trace.id로 auth→scan→character 추적 시 단일 인덱스가 유리 - 운영 단순화: ILM, Index Template, Fluent Bit 설정 최소화
빅테크 사례 분석
회사 전략 특징 교훈 Netflix 단일 + 필드 분리 ELK + Kafka, service_name필터링필드 기반 검색 Uber 도메인별 → ClickHouse 전환 샤드 폭발 경험 인덱스 수 제한 Google SRE 환경별/레벨별 서비스별 ❌ 필드 기반 ✅ Uber의 교훈: "서비스 수가 증가하면서 인덱스 수도 폭발적으로 증가했고, 이로 인해 클러스터 관리가 어려워졌다."
— Uber Engineering Blog
현재 Fluent Bit 설정
OUTPUT 설정 (단일 인덱스)
# workloads/logging/base/fluent-bit.yaml [OUTPUT] Name es Match kube.* Host eco2-logs-es-http.logging.svc.cluster.local Port 9200 HTTP_User ${ES_USER} HTTP_Passwd ${ES_PASSWORD} Logstash_Format On Logstash_Prefix logs # ✅ 단일 prefix Logstash_DateFormat %Y.%m.%d # logs-2025.12.18 Retry_Limit False Replace_Dots Off # ECS dot notation 유지 Suppress_Type_Name On Buffer_Size 5MB Generate_ID On결과 인덱스 패턴:
logs-2025.12.17 logs-2025.12.18 ...Health 로그 필터링 (노이즈 감소)
# 프로브 로그 제외 (일일 ~120,000 로그 감소) [FILTER] Name grep Match kube.* Exclude log /health|ready|healthz|readyz|livez/
Index Template (ECS 호환)
현재 배포된 템플릿
// eco2-logs-ecs (priority: 500) { "index_patterns": ["logs-*"], "template": { "settings": { "number_of_shards": 1, "number_of_replicas": 0 // dev 환경: replica 없음 }, "mappings": { "subobjects": false, // ✅ ECS dot notation 유지 "properties": { "@timestamp": { "type": "date" }, "message": { "type": "text" }, "trace.id": { "type": "keyword" }, "span.id": { "type": "keyword" }, "log.level": { "type": "keyword" }, "service.name": { "type": "keyword" }, "service.version": { "type": "keyword" }, "service.environment": { "type": "keyword" }, "error.type": { "type": "keyword" }, "error.message": { "type": "text" } }, "dynamic_templates": [{ "strings_as_keywords": { "match_mapping_type": "string", "mapping": { "type": "keyword", "ignore_above": 1024 } } }] } } }왜 subobjects: false인가?

ES 8.x subobjects: false 기능:
trace.id,span.id등 ECS 표준 필드명을 그대로 유지- Fluent Bit
Replace_Dots: Off와 함께 사용 - Kibana에서
trace.id: "abc123*"검색 가능
필드 기반 서비스 구분
도메인별 인덱스 대신 ECS 필드로 서비스를 구분합니다:
{ "@timestamp": "2025-12-18T12:00:00.000Z", "service.name": "auth-api", "service.version": "1.0.7", "service.environment": "dev", "kubernetes.namespace": "auth", "kubernetes.pod.name": "auth-api-xxx", "log.level": "INFO", "message": "User login successful", "trace.id": "49069056832712b6d1a76403290e3520" }Kibana 쿼리 예시
# 특정 서비스 에러 로그 service.name: "auth-api" AND log.level: "ERROR" # Cross-service 트랜잭션 추적 (단일 인덱스이므로 간단!) trace.id: "49069056832712b6d1a76403290e3520" # 특정 네임스페이스 전체 로그 kubernetes.namespace: "auth" # 인프라 vs 앱 로그 구분 service.name: ("auth-api" OR "scan-api" OR "chat-api") service.name: ("istio-proxy" OR "argocd-*" OR "calico-*")
ILM (Index Lifecycle Management)
현재 상태
정책 상태 설명 logs(기본)✅ 사용 중 Hot phase만, 30일 rollover logs-app-policy⏳ 정의됨 Hot→Warm→Delete (14일) logs-infra-policy⏳ 정의됨 Hot→Warm→Delete (7일) ILM 라이프사이클 단계
┌─────────┐ 3일 ┌─────────┐ 14일 ┌─────────┐ │ Hot │ ────────► │ Warm │ ───────► │ Delete │ │ (쓰기) │ │(읽기전용)│ │ │ └─────────┘ └─────────┘ └─────────┘ rollover shrink delete set_priority forcemerge계획된 ILM 정책 (StackConfigPolicy)
# workloads/logging/base/stack-config-policy.yaml spec: elasticsearch: indexLifecyclePolicies: # App 로그: 14일 보존 logs-app-policy: phases: hot: actions: rollover: max_primary_shard_size: 30gb max_age: 1d set_priority: { priority: 100 } warm: min_age: 3d actions: shrink: { number_of_shards: 1 } forcemerge: { max_num_segments: 1 } delete: min_age: 14d actions: { delete: {} } # Infra 로그: 7일 보존 logs-infra-policy: phases: hot: actions: rollover: max_primary_shard_size: 30gb max_age: 1d delete: min_age: 7d actions: { delete: {} }
샤드 최적화
현재 샤드 상태
kubectl exec -n logging eco2-logs-es-default-0 -- curl -s "http://localhost:9200/_cat/shards/logs-*?v" index shard prirep state docs store logs-2025.12.17 0 p STARTED 1114246 421mb logs-2025.12.17 0 r UNASSIGNED # replica 없음 (단일 노드) logs-2025.12.18 0 p STARTED 623687 259mb왜 replica 0인가?
dev 0 단일 노드, replica 할당 불가 prod 1+ 고가용성 필요 # Index Template 설정 "settings": { "number_of_shards": 1, "number_of_replicas": 0 # dev 환경 }
스토리지 비용 최적화
일일 로그량
2025-12-17 1,114,246 421MB ~13/s 2025-12-18 624,036 259MB ~7/s 비용 절감 전략 (적용됨)
Health 로그 제외 ~120,000/일 감소 Fluent Bit grep 필터 replica 0 스토리지 50% 절감 Index Template strings_as_keywords 인덱싱 효율 dynamic_template 추가 최적화 (계획)
Infra 7일 보존 디스크 ~70% 절감 ⏳ 계획됨 Warm forcemerge 추가 50% 압축 ⏳ 계획됨
결론
결정 선택 이유 인덱스 분리 단일 (logs-YYYY.MM.DD) 개발 환경, 앱 로그 0.01%, 크로스 검색 서비스 구분 ECS 필드 service.name,trace.id샤드 1 primary, 0 replica 단일 노드 환경 ILM Hot only (현재) 단순화, 추후 확장 ECS 호환 subobjects: false dot notation 유지 로드맵

- Phase 1:
logs-app-policy,logs-infra-policyILM 활성화 - Phase 2: Fluent Bit rewrite_tag로 앱/인프라 인덱스 분리 (트래픽 증가 시)
Reference
'이코에코(Eco²) > Observability' 카테고리의 다른 글
이코에코(Eco²) Observability #6: Log-Trace 연동 및 Kibana 검색 구조 (1) 2025.12.19 이코에코(Eco²) Observability #4: 분산 트레이싱 통합 (0) 2025.12.19 이코에코(Eco²) Observability #3: 도메인별 ECS 구조화 로깅 (0) 2025.12.19 이코에코(Eco²) Observability #2: 로깅 정책 수립 (0) 2025.12.19 이코에코(Eco²) Observability #1: EFK 파이프라인 구축 (0) 2025.12.19