로그 수집 을 여러 각도 에서 — *어디서·어떻게·무엇을·얼마에* 모으나
“로그 를 수집 한다” 는 말 은 쌓는다 처럼 들리지만, 실제 로 는 여러 결정 의 집합 이다 — 어디서 뽑고, 어떻게 흘리고, 무슨 형태 로 담고, 얼마 를 태울지. 각 축 에서 잘못 고르면, 정작 장애 났을 때 검색 이 안 되거나 · 로그 가 없거나 · 청구서 가 폭발 한다.
이 글 은 로그 수집 을 컴포넌트(ELK 각 부품 은 따로 다뤘다) 가 아니라 결정 의 축 으로 훑는다. 내 홈랩 의 실제 스택(6-node K3s, fluent-bit DaemonSet 5개, Elasticsearch hot/warm/cold 3-tier) 을 근거 로.
1. 어디서 뽑나 — 수집 지점 의 네 갈래
로그 를 어느 지점 에서 잡느냐 가 첫 결정 이다:
| 방식 | 어떻게 | 장 | 단 |
|---|---|---|---|
| stdout + 노드 에이전트 | 앱 은 stdout 에만 찍고, 노드 의 DaemonSet(fluent-bit)이 컨테이너 로그 파일 을 tail | 앱 이 로깅 인프라 를 모름, k8s 표준 | 노드 파일 경로·로테이션 에 의존 |
| 사이드카 | 파드 마다 로그 수집 컨테이너 를 붙임 | 앱 별 커스텀 파이프라인 | 파드 마다 리소스 ×2, 복잡 |
| 앱 직접 push | 앱 이 로그 를 직접 ES/Loki 로 전송 | 중간 단계 없음 | 앱 이 수집처 장애 에 물림(결합) |
| 시스템/커널 | journald·syslog·감사 로그 | 인프라 레벨 가시성 | 앱 로그 와 포맷 이 다름 |
k8s 의 정답 은 대개 첫 번째 — 앱 은 stdout 에만 찍고(12-factor), 노드 DaemonSet 이 걷는다. 내 클러스터 도 fluent-bit 를 노드 마다(5개) 띄워 모든 컨테이너 stdout 을 수집 한다. 앱 은 로그 를 어디로 보내는지 조차 몰라도 된다 — 이 무지 가 곧 느슨한 결합 이다.
2. 어떻게 흘리나 — 에이전트 → 수집기 → 저장 → 조회
수집 은 파이프라인 이다: 경량 에이전트(fluent-bit) → 수집기·가공(Logstash) → 저장·색인(Elasticsearch) → 조회(Kibana).
여기서 중요한 건 유실 지점 이다. 각 단계 의 경계 마다 로그 가 샐 수 있다:
- 에이전트 가 tail 하다 파일 이 로테이션 되며 놓침
- 버퍼 가 가득 차 드롭
- 수집기 가 다운 된 사이 의 로그
그래서 좋은 수집 은 at-least-once + 백프레셔 를 설계 한다 — 에이전트 에 디스크 버퍼 를 두고, 수집처 가 죽으면 로컬 에 쌓았다가 재전송. “로그 는 유실 돼도 되는 데이터” 라는 통념 은 디버깅 하는 새벽 3시 에 배신 한다.
3. 무엇 을 담나 — 구조화 로그 가 이긴다
같은 정보 라도 형태 가 검색가능성 을 가른다:
# 비구조화 — grep 은 되지만 집계·필터 가 지옥
2026-07-11 03:00:01 ERROR payment failed for user 42 amount 32000
# 구조화(JSON) — 필드로 검색·집계·알림
{"ts":"...","level":"ERROR","event":"payment_failed","user_id":42,"amount":32000,"trace_id":"abc"}
구조화 로그 의 값어치 는 셋 이다 — 필드 검색(level:ERROR AND user_id:42), 집계(에러율·금액 합), 상관관계(trace_id 로 요청 전체 추적). 특히 trace_id/request_id 를 모든 로그 에 심으면, 흩어진 로그 가 하나의 요청 스토리 로 꿰어진다.
로그 는 사람 이 읽는 문장 이 아니라 기계 가 질의 하는 데이터 다. 이 관점 의 전환 이 구조화 로깅 의 핵심.
4. 수집 의 함정 — 버퍼·유실·PII·볼륨
실무 에서 실제 로 데는 지점 들:
- 버퍼링 / flush. 프로세스 가 stdout 을 버퍼 링 하면, 크래시 직전 로그 가 안 찍힌 채 사라진다. 정작 원인 인 마지막 로그 를. 내 데이터 파이프라인 에서도 이걸 겪어 — unbuffered 출력 + 라인 마다 flush 로 강제 해 해결했다. 크래시 로그 는 버퍼 에 있으면 없는 것 이다.
- 순서 와 유실. 분산 수집 에서 로그 순서 는 보장 되지 않는다. 그래서 타임스탬프 를 앱 이 찍어야지, 수집 시각 에 의존 하면 안 된다.
- PII. 로그 에 비밀번호·토큰·주민번호 가 찍히면, 한 번 색인 된 순간 회수 불가. 수집 단계 에서 마스킹 필터 를 건다.
- 볼륨 폭발. DEBUG 를 프로덕션 에 켜두면 로그 가 앱 보다 무겁다. 레벨·샘플링 이 곧 비용 통제.
5. 얼마에 모으나 — 로그 는 공짜 가 아니다
가장 자주 잊는 축. 로그 는 전송 + 색인 + 저장 + 조회 마다 돈·CPU 를 먹는다. 그래서 수집 설계 는 비용 설계 이기도 하다:
- 레벨·샘플링 — 성공 요청 은 1% 만, 에러 는 100%. 정보량 대비 비용 을 맞춘다.
- 저장 티어링(hot/warm/cold) — 최근 로그 는 빠른 SSD(hot), 오래된 건 느린 디스크(cold). 내 ES 는 hot/warm/cold 3-tier + ILM(수명 정책) 으로 자동 강등·삭제. 최근 며칠 만 빨라야 하고, 90일 전 로그 는 싸게 보관 하면 된다.
- self-host vs managed — ELK 를 직접 굴리면 인프라·운영 비용, Datadog 같은 SaaS 는 GB당 요금. 이건 기술 도입비용 의 저울 그대로 — 트래픽·팀 규모 로 결정 한다. (Datadog vs 무료 관측성 에서 깊게)
무제한 으로 다 모으는 건 설계 가 아니라 방치 다. 무엇 을 안 모을지 를 정하는 게 로그 수집 설계 의 절반.
6. 3기둥 에서 로그 의 자리 — 혼자서는 부족하다
로그 는 관측성 의 한 기둥 일 뿐 이다:
- Logs — 무슨 일 이 있었나 (이벤트·에러의 상세). 카디널리티 높음, 비쌈.
- Metrics — 얼마나 (요청수·지연·에러율). 싸고 집계 친화적, 상세 없음.
- Traces — 어디서 (요청이 서비스들 을 관통한 경로). 병목·분산 디버깅.
셋 은 대체 가 아니라 보완 이다. 흔한 실수 는 메트릭 으로 충분한 걸 로그 로 세는 것 — “요청 수” 를 로그 count 로 뽑으면 비싸고 느리다. 그건 메트릭 의 일. 로그 는 메트릭 이 이상 을 알리면 그 원인 을 파는 자리 다.
7. 수집 그다음 — 파싱·enrich·알림
수집 은 끝 이 아니라 시작 이다. 걷은 로그 에 가치 를 더하는 단계:
- 파싱 — 비구조 로그 를 필드 로 쪼갬(grok/정규식)
- enrich — k8s 메타(namespace·pod·node), 지오IP, 서비스 태그 를 붙임
- 라우팅 — 감사 로그 는 오래, 디버그 는 짧게 — 인덱스 를 분리
- 알림 — 로그 에서 이상 을 능동 감지. 내 클러스터 는 5분 마다 ES 를 쿼리 해
level:ERROR/FATAL을 찾으면 텔레그램 으로 쏘는 크론(log-error-alerter) 이 돈다. 대시보드 는 볼 때만 보이지만, 이건 터지면 온다.
마무리 — 수집 은 시작 일 뿐
정리 하면, 로그 수집 은 여섯 축 의 결정 이다:
어디서(지점) · 어떻게(파이프라인·유실) · 무엇을(구조화) · 함정(버퍼·PII·볼륨) · 얼마에(비용·티어) · 그다음(파싱·알림).
내 홈랩 은 이걸 fluent-bit(수집) → Logstash(가공) → ES hot/warm/cold(저장·티어) → Kibana(조회) + log-error-alerter(알림) 으로 엮었다. 하지만 도구 는 부차적 이다 — 핵심 은 “장애 났을 때 3분 안 에 원인 로그 를 검색 할 수 있나” 와 “그걸 감당 가능한 비용 으로 하나” 다.
로그 를 많이 모으는 건 쉽다. 필요한 로그 를, 검색 가능 하게, 감당 가능한 비용 으로 모으는 게 엔지니어링 이다. 수집 은 시작 일 뿐, 진짜 게임 은 검색가능성 과 비용 에서 난다.