-
이코에코(Eco²) Observability #0: 로깅 파이프라인 아키텍처 설계이코에코(Eco²)/Observability 2025. 12. 18. 12:12
개요
마이크로서비스 아키텍처에서 로깅은 단순한 디버깅 도구를 넘어 시스템 투명성을 확보하는 핵심 인프라입니다.
이 글에서는 15개 노드, 7개 API 서비스로 구성된 Eco² 백엔드의 로깅 파이프라인 아키텍처 설계 과정을 공유합니다.배경
- 도입 전 상태: 각 Pod의 stdout/stderr 로그가 노드에 분산 저장, 구조화된 로깅 정책 부재
- 문제점: 장애 발생 시 여러 노드를 직접 접속해 로그 확인 필요
- 목표: 중앙 집중화된 로그 수집, 저장, 검색, 시각화 환경 구축
기능 요구사항
중앙 집중화 모든 노드의 로그를 한 곳에서 조회 P0 실시간 수집 로그 발생 후 5초 이내 검색 가능 P0 구조화된 로깅 JSON 포맷으로 필드별 검색 P1 트레이스 연동 trace_id로 분산 트레이싱과 연결 P1 대시보드 에러율, 응답시간 등 시각화 P2 비기능 요구사항
가용성 99% 로깅 장애가 서비스에 영향 없어야 함 지연 시간 < 5초 로그 발생 → 검색 가능 저장 용량 50GB (7일) 개발 환경 기준 리소스 격리 전용 노드 API 서비스와 분리
EFK 선택 이유: EDA 전환 효율성
1. Logstash CRD 통합 (ECK Operator)
EDA 도입 시 Kafka를 통한 로그 버퍼링과 복잡한 로그 변환(Saga trace_id 상관관계, CDC 이벤트 파싱)이 필요하다.
ECK Operator를 사용하면:- Logstash CRD 추가 시 ES 연결 자동 설정
- TLS/인증 자동 구성
- 무중단 전환 가능 (Fluent Bit output만 변경)
2. PLG 선택 시 전환 비용
고밀도 로깅 / 분산 환경에서 Logstash가 유리하기에 별도 Consumer 개발 또는 ES로 마이그레이션 필요
3. 로그량 증가 대응
EDA 로드맵에서 분석한 대로, EDA 도입 후 로그량이 폭증함현재: 1 API 요청 → 1~3개 로그 EDA 후: 1 API 요청 → 10~30개 로그/이벤트 - Kafka Producer/Consumer 로그 - Saga 체인 로그 (시작/완료/실패) - CDC 이벤트 로그 (Debezium) - Celery 작업 로그 - 재시도/DLQ 로그Elasticsearch의 전문 검색은 이러한 복잡한 분산 트랜잭션 로그에서 trace_id 기반 검색에 필수적이다.
ELK vs EFK: Logstash 도입 반려
EDA 전환 백서에서 분석한 대로, 향후 EDA 전환 시 로그 파이프라인에 Logstash가 더 유용할 여지가 있다.
그러나 현재 단계에서는 ELK가 아닌 EFK를 선택했다.ELK vs EFK 비교
메모리 1~4GB ~5MB/노드 배포 방식 중앙 집중 DaemonSet (노드별) Pod 친화성 ❌ 낮음 ✅ 높음 로그 변환 ✅ 강력함 (Grok, Ruby) ⚠️ 기본 (Regex, Lua) Kafka 연동 ✅ 네이티브 Input ⚠️ Output만 지원 현재 필요성 낮음 (동기식 로그) 높음 (수집 특화) 🚫 Logstash 반려 이유: 현재 용도에 비해 과도한 리소스
1. 현재 동기식 로그만 발생
현재 Eco² 백엔드는 HTTP 1.1/gRPC 동기식 아키텍처다:현재: Client → API → DB → Response └── 로그: 요청/응답/에러 (단순)이 단계에서 Logstash의 강력한 변환 기능(Grok 파싱, Ruby 필터, 다중 파이프라인)은 용도에 비해 과도
2. 리소스 효율성 계산# ELK (Logstash 포함) Elasticsearch: 5GB + Kibana: 1GB + Logstash: 1GB = 7GB └── 8GB 노드에서 시스템 여유 1GB (위험) # EFK (Fluent Bit) Elasticsearch: 5GB + Kibana: 1GB + Fluent Bit: ~50MB(전체) = 6GB └── 8GB 노드에서 시스템 여유 2GB (안정)3. Logstash 없이 가능한 현재 워크로드
로그 수집 (tail) ❌ ✅ ✅ JSON 파싱 ✅ ✅ ✅ K8s 메타데이터 ⚠️ 플러그인 ✅ 네이티브 ✅ Grok 패턴 파싱 ✅ ⚠️ Regex ❌ (JSON 출력) Kafka Consumer ✅ ❌ ❌ (현재 MQ 없음) 복잡한 라우팅 ✅ ⚠️ 제한적 ❌
로그 수집기: Fluent Bit 선택 근거
Pod 친화적 DaemonSet 아키텍처
ELK: 중앙 집중형 (네트워크 병목)

⚠️ 문제점
- t3.large 단일 노드 기준 EK 만으로 메모리 70% 점유
- 로그 노드 장애 시 파이프라인 전체 다운 (SOF)
EFK: 분산 수집형 (노드별 처리)

✅ 장점:
- 각 노드에서 로컬 파일 직접 tail (네트워크 비용 최소화)
- 병렬 처리로 네트워크 병목 분산
- 노드 장애 시 해당 노드 로그만 영향 (Blast Radius 최소화)
수집기 비교표
언어 C Ruby JRuby 메모리 ~5MB/노드 ~40MB/노드 ~1-4GB CPU 매우 낮음 낮음 중간 플러그인 기본 제공 풍부함 풍부함 Kubernetes DaemonSet 최적화 가능 비권장 역할 수집/전달 특화 수집/변환 변환/라우팅 특화 선택: Fluent Bit - 경량 수집 에이전트로 리소스 절약, Pod 친화성 확보
향후 확장: EFK → EFKL 전환 계획
현재 EFK를 선택한 것은 Logstash를 영구 배제한 것이 아니다.
EDA 전환 백서에서 예측한 대로, MQ 도입 시 로그가 5~10배 폭증하면 Logstash가 필수가 된다.
Phase 2에서 Logstash 변환이 필요한 사례

1. ECK Operator 기반으로 구축했기 때문에, Logstash CRD 추가만으로 전환 가능.
2. CDC 전환 시 Fluent Bit은 수집기로 유지하고, Logstash는 중앙 파이프라인으로 추가.
3. Fluent Bit의 output만 Kafka로 변경하면 되므로 코드 재사용성 증가.저장소: Elasticsearch vs Loki vs ClickHouse
쿼리 방식 전문 검색 라벨 기반 SQL 인덱싱 전체 텍스트 라벨만 컬럼 메모리 높음 (4GB+) 낮음 중간 에코시스템 Kibana, APM Grafana Grafana 복잡한 쿼리 ✅ 우수 ⚠️ 제한적 ✅ 우수 선택: Elasticsearch - 전문 검색, Kibana 시각화, APM 확장성
배포 방식: Helm vs ECK Operator
관리 복잡도 수동 자동화 TLS/인증 수동 설정 자동 생성 업그레이드 수동 Rolling Update Logstash 통합 별도 설정 CRD로 통합 리소스 오버헤드 없음 ~200MB 선택: ECK Operator - 운영 자동화, 향후 Logstash 확장 용이
아키텍처 설계
Observability 3 Pillars 상세
1. 로깅 (Logs) - 분산 로그 중앙화


동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.
전 시스템, 서비스에 걸쳐 실시간으로 로그를 수집하며 색인 및 시각화를 수행한다.
로깅 정책 및 ES JSON 표준화를 통해 trace_id, http_method/status, service_name 등 풍부한 Data View를 제공하도록 구성했다.
마이크로서비스 환경에선 로그가 각 노드에 흩어져 있어서, 장애 발생하면 여러 파드를 돌아다니며 로그 찾아야 한다.
EFK 파이프라인으로 Fluent Bit가 각 노드의 로그를 수집, Elasticsearch에 보관 및 색인, Kibana로 시각화했다.Fluent Bit 로그 수집 에이전트 각 노드에 DaemonSet으로 배포, ~5MB 초경량 Elasticsearch 로그 저장/인덱싱 전문 검색, ECS 스키마 기반 Kibana 시각화/대시보드 KQL로 로그 검색, Lens 시각화 Fluent Bit이 각 노드의
/var/log/containers/*.log파일을 tail해서 ES로 전송.
앱은 stdout으로 구조화된 JSON만 하면 자동 수집.
🔍 2. 트레이싱 (Traces) - 분산 트레이스 중앙화


동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.
Envoy Sidecar 트레이스 자동 수집 Istio가 모든 Pod에 주입, Zipkin 프로토콜 OTEL SDK 앱 내부 트레이스 FastAPI 자동 계측, B3 propagator Jaeger 트레이스 저장/시각화 Service Map, Dependencies 그래프
현재 trace.id 커버리지 100%, trace span은 모든 HTTP 1.1 프로토콜, 일부 gRPC를 전방위로 추적한다.
하나의 API 요청이 여러 서비스를 거칠 때, 어디서 병목이 생기는지 추적하려면 분산 트레이싱의 중요도가 급부상한다.
Istio Service Mesh의 Envoy Sidecar가 자동으로 트레이스 정보를 수집한다.
현 이코에코 클러스터는 Istio Envoy 중심으로 트레이싱이 이뤄진다.(Pod 커버리지 100%)
Istio를 SSOT로 선정한 후 trace.id를 발급, 해당 trace.id를 각 Observability 툴에 전파해 트레이스 현황을 자동 추적 및 시각화한다.OpenTelegraphy와 호환되도록 B3 프로토콜 및 헤더(
x-b3-traceid)로 공유하도록해 E2E 추적 커버리지를 높였으며 분산 시스템 전반의 E2E 추적이 가능해졌다.
트레이스 연결 구조istio-ingressgateway (Span 1) └── auth-api.auth (Sidecar Span 2) └── auth-api (App OTEL Span 3) └── asyncpg: SELECT... (DB Span 4)
3. 메트릭 (Metrics) - Prometheus Pull 모델


동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.
메트릭은 Prometheus가 각 서비스의
/metrics엔드포인트를 Pull 방식으로 수집한다,
HTTP 1.1 기반이라 Service Mesh와 호환성이 높다.컴포넌트 역할 특징 Prometheus Exporter 메트릭 노출 /metrics엔드포인트, HTTP 1.1Prometheus 메트릭 수집/저장 Pull 모델, PromQL, TSDB Grafana 시각화/알림 대시보드, 알림 룰 ServiceMonitor로 자동 발견:
apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: api-services spec: selector: matchLabels: tier: business-logic endpoints: - port: http path: /metrics
3 Pillars 통합: trace_id로 연결
로그, 트레이스, 메트릭은 trace_id를 공유해서 세 지표를 연결할 수 있다.

연결 방법 용도 로그 → 트레이스 Kibana에서 trace.id클릭 → Jaeger 이동에러 로그의 전체 호출 흐름 확인 트레이스 → 로그 Jaeger span에서 로그 링크 특정 span의 상세 로그 확인 메트릭 → 트레이스 Grafana Exemplar 느린 요청의 샘플 트레이스 확인
배경
- 현재 상태: 각 Pod의 stdout/stderr 로그가 노드에 분산 저장
- 문제점: 장애 발생 시 여러 노드를 직접 접속해 로그 확인 필요
- 목표: 중앙 집중화된 로그 수집, 저장, 검색, 시각화 환경 구축
로그 데이터 파이프라인

단계 컴포넌트 역할 1️⃣ FastAPI App ECS JSON 로그 출력 ( stdout)2️⃣ containerd CRI 포맷으로 래핑 ( timestamp stream flag log)3️⃣ Fluent Bit CRI 파싱 → K8s 메타 추가 → 노이즈 필터 4️⃣ Elasticsearch logs-YYYY.MM.DD인덱스 저장5️⃣ Kibana DataView logs-*로 검색/시각화Fluent Bit 필터 체인

파싱 단계별 데이터 변환:
단계 입력 출력 Raw 2025-12-18T02:42:48.982Z stdout F {"@timestamp":...}- CRI Parser - time,stream,logtag,log분리K8s Filter log(JSON)log_processed.*필드로 병합ES 문서 - + k8s_namespace_name,k8s_pod_name,cluster클러스터 노드 토폴로지

노드 격리 전략
로깅 시스템은 메모리/디스크 I/O 집약적이므로 API 서비스와 물리적으로 격리한다.
# 로깅 노드 구성 Node: k8s-logging ├── Label: workload=logging ├── Taint: domain=observability:NoSchedule ├── Instance: t3.large (2 vCPU, 8GB RAM) └── Storage: 100GB gp3리소스 배분 상세

Elasticsearch 5GB (heap) 1 core 로그 저장/검색 Kibana 1GB 0.5 core 시각화/대시보드 ECK Operator 200MB 0.1 core ES/Kibana 관리 System 1.8GB 0.4 core OS/kubelet Fluent Bit 리소스 (노드당)

항목 값 비고 기본 메모리 ~5MB C 언어 경량 구현 버퍼 50MB Mem_Buf_Limit설정총계 ~55MB 15개 노드 = 825MB, 경량 에이전트
🔄 점진적 확장 계획
Phase 1 vs Phase 2 아키텍처 비교


Phase 1 (현재): EFK 직접 연결
현재 구성의 장점:
- ✅ 단순한 구조 (컴포넌트 3개)
- ✅ 빠른 구축 (1일)
- ✅ 리소스 효율적 (Fluent Bit ~5MB/노드)
- ✅ 개발 환경 적합
Phase 2 (EDA 도입 시): EFKL + Kafka 버퍼

Phase 2에서 Logstash가 필요한 이유:
변환 작업 설명 Fluent Bit Logstash Saga 체인 파싱 saga_id추출 및 상관관계❌ ✅ Grok CDC 이벤트 보강 Debezium 메타데이터 추가 ⚠️ 제한적 ✅ Ruby 조건부 라우팅 에러→별도 인덱스 ⚠️ 기본 ✅ 강력함 DLQ 라벨링 재시도 횟수 태깅 ❌ ✅ Phase 1 → Phase 2 전환 작업
Kafka 배포 (별도 노드) 30분 없음 - Logstash CRD 추가 15분 없음 ✅ ES 연결 자동 ES 메모리 조정 (5GB → 3GB) 15분 Rolling ✅ Fluent Bit output 변경 15분 없음 - 총계 ~1.5시간 ~5분 로그량 예측

현재 (동기) 10,000 70,000 - ~5 EDA 도입 후 10,000 350,000 5-10x ~50
디렉토리 구조
backend/ ├── workloads/ │ └── logging/ │ ├── base/ │ │ ├── kustomization.yaml │ │ ├── elasticsearch.yaml # ECK ES CR │ │ ├── kibana.yaml # ECK Kibana CR │ │ ├── fluent-bit.yaml # DaemonSet │ └── dev/ │ └── kustomization.yaml ├── clusters/ │ └── dev/ │ └── apps/ │ ├── 62-eck-operator.yaml # ECK Operator │ └── 63-efk-stack.yaml # EFK 스택 └── docs/ └── decisions/ └── ADR-001-logging-architecture.md
핵심 설계 결정 (ADR)
ADR-001: ECK Operator 사용
결정: StatefulSet 직접 관리 대신 ECK Operator 사용
이유:- Elasticsearch ↔ Kibana ↔ Logstash 간 인증/TLS 자동 구성
- Rolling Upgrade 자동화
- Phase 2에서 Logstash CRD 추가만으로 확장 가능
트레이드오프:
- Operator Pod 추가 리소스 (~200MB)
- ECK 버전 종속성
Reference
- 이코에코(Eco²) EDA 전환 로드맵 - 본 로깅 시스템 구축의 배경
- Elastic Cloud on Kubernetes (ECK)
- Fluent Bit Documentation
- Kubernetes Logging Architecture
- Grafana Loki vs Elasticsearch
Service
Elastic
Please upgrade your browser This Elastic installation has strict security requirements enabled that your current browser does not meet.
kibana.dev.growbin.app
Grafana
If you're seeing this Grafana has failed to load its application files 1. This could be caused by your reverse proxy settings. 2. If you host grafana under subpath make sure your grafana.ini root_url setting includes subpath. If not using a reverse proxy m
snapshots.raintank.io
Jaeger UI
jaeger.dev.growbin.app
'이코에코(Eco²) > Observability' 카테고리의 다른 글
이코에코(Eco²) Observability #5: 인덱스 전략 및 라이프사이클 관리 (0) 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