로그의 일생을 따라가며 읽는 노드 배치

홈랩 K3s 클러스터(노드 6대)에서 운영 중인 ELK 스택의 파드 배치를 해부해봅니다. 단순히 “어디에 떠 있나”가 아니라 왜 거기 있어야 하는가 — 배치 하나하나가 어떤 전략이고, 어떤 비용을 지불하고 있는지를 로그의 일생(수집→가공→저장→소비) 순서로 따라갑니다. 모든 수치는 라이브 클러스터에서 실측한 값입니다.

전체 지형 먼저

컴포넌트 파드 노드 메모리 (req/lim)
Collector fluent-bit ×5 5개 노드 상주 (DaemonSet)
가공 logs-ls-0 (Logstash) ilwon 1.5Gi / 2Gi
저장 Hot logs-es-hot-0 ilwon 5Gi / 6Gi
저장 Warm logs-es-warm-0 ilwon 4Gi / 5Gi
저장 Cold logs-es-cold-0 solomon 3Gi / 4Gi
UI logs-kb (Kibana) louise

노드 배경: ilwon은 클러스터에서 RAM이 가장 큰 축(32GB)의 control-plane 노드로, PreferNoSchedule(소프트 테인트)라 “웬만하면 비워두되 필요하면 쓴다”는 정책입니다. solomon은 상대적으로 저사양(15GB)의 control-plane, louise는 일반 워커입니다.

1. 수집 — fluent-bit, 각 노드의 말단 신경

로그의 일생은 각 노드에서 시작합니다. fluent-bit은 DaemonSet으로 노드마다 1개씩 상주하며 컨테이너 로그를 수집해 파이프라인에 흘립니다.

배치 전략은 DaemonSet이라는 형식 자체에 들어 있습니다 — 로그는 발생지에서 수집한다. 중앙에서 긁어오는 pull 방식은 노드 장애 시 그 노드의 마지막 로그(대개 가장 중요한 로그)를 잃습니다. 노드마다 심어둔 에이전트가 로컬에서 읽어 밀어내는 push 방식이라야 “죽기 직전의 유언”까지 건질 가능성이 생깁니다.

가볍다는 것도 전략입니다. Logstash를 노드마다 깔면 노드당 GB 단위를 태우지만, fluent-bit은 수십 MB급입니다. 말단은 가볍게, 무거운 가공은 중앙에서 — 전형적인 collector/aggregator 분리 패턴입니다.

다만 실측에서 사각지대가 하나 드러났습니다. fluent-bit이 5개 노드(ilwon·louise·david·solomon·isagal)에만 있고 lemuel에는 없습니다. lemuel은 control-plane:NoSchedule 하드 테인트 노드인데 DaemonSet에 해당 toleration이 없는 것으로 보입니다. 공교롭게 최근 TTS 워크로드를 lemuel로 옮겼기 때문에, 지금 lemuel에서 도는 파드들의 로그는 ELK에 안 들어옵니다. “전 노드 수집”이라는 설계 의도와 실제 사이의 틈 — 이런 건 배치표를 실측으로 그려봐야만 보입니다.

2. 가공 — Logstash, 파이프라인의 병목이자 심장

수집된 로그는 ilwon의 Logstash(logs-ls-0)로 모입니다. 파싱, 필드 추출, 라우팅 — 파이프라인에서 CPU와 메모리를 가장 예측 불가능하게 쓰는 단계입니다.

ilwon 배치는 데이터 중력(data gravity) 전략입니다. 바로 다음 단계인 ES Hot이 같은 노드에 있어서, 가공을 마친 로그가 네트워크를 건너지 않고 로컬에서 색인됩니다. 홈랩의 1Gbps 내부망에서 로그 트래픽이 노드 간을 오가는 비용을 생각하면 합리적인 응집이죠.

하지만 여기가 현재 가장 아픈 지점이기도 합니다. Logstash 파드는 limit 2Gi 안에 logstash + metricbeat + filebeat 세 컨테이너가 사는데, JVM 기반 Logstash에게 2Gi는 여유 있는 옷이 아닙니다. 실제로 OOM 이슈가 관찰되고 있고요. 더 구조적인 문제는 가장 불안정한 컴포넌트(Logstash)가 가장 중요한 데이터(Hot 인덱스)와 같은 노드에 산다는 것 — 시끄러운 이웃이 옆집에 사는 형국입니다. ilwon의 메모리 사용률이 78%까지 올라와 있는 지금, 이 응집은 성능 최적화인 동시에 리스크 집중이기도 합니다.

3. 저장 — Hot·Warm·Cold, 데이터의 나이에 따른 계급 사회

이 스택에서 가장 교과서적인 부분입니다. Elasticsearch를 단일 노드로 두지 않고 데이터 수명 주기(ILM)에 따라 3계층으로 나눴습니다.

실측한 메모리 배정이 전략을 그대로 보여줍니다:

Hot  (ilwon)    req 5Gi / lim 6Gi   ← 실시간 색인 + 최빈 조회
Warm (ilwon)    req 4Gi / lim 5Gi   ← 색인 종료, 조회는 가끔
Cold (solomon)  req 3Gi / lim 4Gi   ← 보관 위주, 조회는 드물게

데이터의 나이가 들수록 자원 배정이 계단식으로 줄어듭니다. 방금 들어온 로그는 색인 비용과 조회 빈도가 모두 높으니 가장 큰 노드(ilwon)에서 가장 많은 메모리를 받고, 오래된 로그일수록 싼 자리로 밀려납니다. Cold를 저사양 solomon에 보낸 것이 이 철학의 완성입니다 — “일주일 전 로그 검색이 좀 느린 것”은 홈랩에서 지불할 만한 비용이고, 그 대가로 비싼 노드의 자원을 Hot에 몰아줄 수 있습니다.

Hot과 Warm이 같은 노드(ilwon)에 있는 건 양날입니다. Hot→Warm 롤오버 시 셔드 이동이 로컬에서 끝나는 장점이 있지만, ilwon이 죽으면 Hot과 Warm이 동시에 사라집니다. 각 계층이 단일 replica라는 점까지 겹치면, 이 구성은 가용성보다 자원 효율을 선택한 홈랩형 트레이드오프라고 읽는 게 정확합니다. 프로덕션이라면 계층당 replica를 두고 노드를 분산했겠지만, 노드 6대 홈랩에서 그 비용은 과합니다.

4. 소비 — Kibana, 데이터 옆에 있을 필요가 없는 유일한 구성원

Kibana는 워커 노드 louise에 삽니다. 유일하게 “데이터 근처”라는 중력에서 자유로운 배치인데, 이유는 간단합니다 — Kibana는 상태가 없습니다. 조회 요청을 ES에 위임하는 UI일 뿐이라, 쿼리 결과가 오가는 네트워크 홉 하나는 사람의 체감에 영향을 주지 않습니다.

오히려 데이터 노드와 떨어뜨리는 게 맞습니다. 무거운 대시보드 렌더링이 ES 힙과 자원을 다투지 않도록, UI는 UI대로 워커에 격리 — 저장과 소비의 관심사 분리입니다.

종합: 이 배치가 말하는 세 가지 원칙

배치표를 다 읽고 나면 일관된 설계 언어가 보입니다.

  1. 비용은 데이터의 나이를 따른다 — Hot/Warm/Cold의 계단식 자원 배정. 모든 로그를 평등하게 대접하지 않는 것이 한정된 자원에서의 정의다.
  2. 무거운 것끼리는 모으고(중력), 가벼운 것은 흩어놓는다(신경) — 가공·저장의 응집(ilwon) vs 수집의 분산(DaemonSet), 그리고 상태 없는 UI의 자유 배치(louise).
  3. 모든 최적화는 리스크의 재배치다 — ilwon 응집은 네트워크 비용을 아끼는 대신 단일 노드 리스크를 산다. 홈랩은 그 거래를 의식적으로 선택할 수 있는 환경이고, 그래서 배치표가 곧 우선순위 선언문이 된다.

남은 숙제 (실측이 알려준 것)

  • Logstash 다이어트 또는 이사 — 2Gi limit의 OOM 압박. limit 상향이 1차 처방이지만, 근본적으론 Hot ES와의 동거를 재검토할 시점. ilwon 78%는 경고등이다.
  • lemuel 수집 사각지대 — fluent-bit DaemonSet에 control-plane toleration을 추가해 6/6 노드 수집을 완성할 것. 워크로드가 이미 lemuel에서 돌기 시작한 지금은 더 이상 이론적 문제가 아니다.
  • Hot의 백업 전략 — replica 없는 Hot 계층은 스냅샷 주기가 곧 데이터 손실 허용선이다. ILM과 스냅샷 정책을 함께 봐야 완성이다.

로그 파이프라인은 “잘 돌아갈 때는 보이지 않는” 인프라입니다. 하지만 배치표를 실측으로 그려보면 — 설계의 의도, 지불 중인 비용, 그리고 설계와 현실 사이의 틈(사각지대)까지, 시스템이 스스로에 대해 꽤 많은 것을 고백합니다.