ABOUT ME

-

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

    개요

    마이크로서비스 환경에서 로그 인덱스를 어떻게 설계할지는 운영 효율성과 비용에 직접적인 영향을 미칩니다.
    이 글에서는 빅테크(Uber, Google)와 CNCF 권장사항을 바탕으로 인덱스 분리 전략을 수립하고, ILM을 통한 라이프사이클 관리를 다룹니다.


    목표

    1. 인덱스 분리 전략 선택 (도메인별 vs 앱/인프라 vs 단일)
    2. Fluent Bit 라우팅 설정
    3. ILM 정책으로 비용 최적화
    4. 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) ✅ 간단, 크로스 검색 용이 보존기간 동일 ✅ (현재)

    선택 이유:

    1. 개발 환경 특성: 단일 ES 노드, 샤드 오버헤드 최소화 필요
    2. 앱 로그 비율: 전체의 0.01%로 분리 효과 미미
    3. Cross-service 검색: trace.id로 auth→scan→character 추적 시 단일 인덱스가 유리
    4. 운영 단순화: 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 유지

    로드맵

    1. Phase 1: logs-app-policy, logs-infra-policy ILM 활성화
    2. Phase 2: Fluent Bit rewrite_tag로 앱/인프라 인덱스 분리 (트래픽 증가 시)

    Reference

    댓글

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