Fluent Bit vs Vector, 로그 파이프라인 뭘 쓸까
로그 수집기를 바꿔야 할 이유는 여러가지다. 우리 팀은 작년까지 Fluentd를 쓰고 있었는데, 노드 밀도가 올라가면서 에이전트 하나가 잡아먹는 메모리가 계속 눈에 걸렸다. 결국 후보로 좁혀진 게 Fluent Bit과 Vector였다. 이 글은 두 도구를 실제로 나란히 돌려보면서 정리한 비교 노트다. 완벽한 벤치마크는 아니고 "우리 워크로드에선 이랬다" 정도로 봐주면 좋겠다.
두 도구의 성격이 다르다
먼저 결론부터 말하면, Fluent Bit과 Vector는 겉보기엔 비슷하지만 만들어진 목적이 살짝 다르다. Fluent Bit은 C로 쓰였고 CNCF 프로젝트이며 태생이 "edge collector" 다. 노드마다 뿌려서 로그를 긁어 파이프라인 뒷단으로 넘기는 걸 잘한다. 메모리 풋프린트가 노드당 1~5MB 수준이고, 128MB RAM에서도 무리없이 돈다.
Vector는 Datadog이 만들고 Rust로 쓴 프로젝트로, edge agent와 aggregator 두 역할을 다 노린다. Guaranteed delivery(on-disk buffer + acknowledgement) 같은 신뢰성 기능이 처음부터 내장돼 있고, VRL(Vector Remap Language)이라는 자체 변환 언어가 강력하다. 대신 리소스는 더 먹는다. Rust 바이너리라 20MB 대의 RSS는 기본이다.
CPU vs 처리량, 뭘 우선할까
우리 팀 벤치마크 결과를 요약하면 이렇다. 노드당 초당 3만 로그 라인이 들어오는 워크로드에서:
| 항목 | Fluent Bit 3.2 | Vector 0.42 |
|---|---|---|
| CPU (avg) | 0.08 core | 0.12 core |
| RSS | 32MB | 78MB |
| 처리량 한계 | 노드당 8만 LPS | 노드당 15만 LPS |
| 백프레셔 처리 | filesystem 버퍼 | disk buffer + ack |
CPU만 놓고 보면 Fluent Bit이 확실히 가볍다. 노드 수백 대짜리 클러스터에서 이 차이는 무시 못한다. 반대로 처리량 상한이나 신뢰성이 중요하면 Vector 쪽이 낫다.
변환 언어 - Lua/스트림 처리 vs VRL
로그 파이프라인의 핵심은 사실 수집이 아니라 변환이다. 필드 파싱, PII 마스킹, 라우팅. 여기서 두 도구의 개발 경험이 크게 갈린다.
Fluent Bit은 built-in filter를 조합하고, 복잡한 로직은 Lua 스크립트로 짠다. 이게 단순한 케이스에선 편한데, 조건 분기가 많아지면 금방 지저분해진다. 스트림 프로세서(SQL-like)도 있는데 문법이 살짝 어색하다.
-- Fluent Bit Lua filter 예시
function mask_email(tag, ts, record)
if record["email"] then
record["email"] = string.gsub(record["email"], "([^@]+)@", "***@")
end
return 1, ts, record
end
Vector의 VRL은 처음부터 로그 변환을 위해 만들어진 DSL이다. 타입 시스템이 있고, 컴파일 타임에 문법 오류를 잡아준다.
# Vector VRL 예시
.email = redact(.email, filters: [r'[^@]+@'])
.severity = downcase(.severity)
if .status_code >= 500 {
.alert = true
}
VRL이 문법 자체는 배우기 쉬운 편이고, vector vrl REPL로 인터랙티브하게 테스트할 수 있어서 실무에서 훨씬 편하다. 우리 팀은 이 부분이 Vector로 기운 결정적 이유였다.
신뢰성 - 여기서 Vector가 확실히 앞선다
우리가 Fluentd에서 겪었던 문제 중 하나가 downstream(그때는 Elasticsearch)이 잠깐 흔들리면 메모리 버퍼가 터지면서 로그 유실이 나는 거였다. Fluent Bit도 filesystem storage backend를 켜면 어느 정도 완화되지만, ack 기반 재시도가 없어서 duplicate/loss 둘 다 가능하다.
Vector는 sink 별로 disk buffer + ack semantics를 잡는다. 예를 들어 Kafka sink는 producer ack를 기다렸다가 buffer에서 지운다. downstream이 30분 죽어있어도 disk에 쌓아뒀다가 복구되면 보내는데, 이게 그냥 설정 한 줄로 켜진다.
[sinks.kafka_out]
type = "kafka"
inputs = ["parsed"]
bootstrap_servers = "kafka:9092"
topic = "app-logs"
buffer.type = "disk"
buffer.max_size = 268435488 # 256MB
buffer.when_full = "block"
물론 disk buffer를 켜면 IOPS를 먹으니 트레이드오프는 있다. 우리는 gp3 볼륨에 IOPS 3000으로 올려서 대응했다.
Kubernetes 통합
두 도구 다 K8s 메타데이터를 붙이는 filter/transform이 있다. Fluent Bit의 kubernetes filter는 오래돼서 안정적이고, Merge_Log, Keep_Log, Annotations 같은 세밀한 옵션이 다 있다. 대신 API server 호출이 많아서 대형 클러스터에선 캐시 튜닝을 신경써야 한다.
Vector는 kubernetes_logs source가 파일 tail과 메타데이터 붙이기를 한번에 처리한다. 설정이 훨씬 짧다.
[sources.k8s]
type = "kubernetes_logs"
이 한 줄이면 끝난다. 다만 kubelet API 사용 방식이 Fluent Bit보다 공격적이라 API server 부하가 좀 더 걸린다. 노드 5백 대 넘어가면 rate limiting 옵션을 챙겨야 한다.
우리 팀은 어디에 뭘 썼나
결국 우리는 edge에는 Fluent Bit, aggregator에는 Vector 조합으로 갔다. 이유는 단순하다.
- 노드 수가 많고(600+) 노드마다 리소스가 빡빡하다. edge에선 CPU와 RSS를 최대한 아끼고 싶었다.
- 하지만 파이프라인 뒷단에서는 라우팅, 마스킹, 재시도가 복잡해서 VRL의 표현력이 필요했다.
- 뒷단이 죽었을 때 disk buffer로 버텨주는 게 감사하다.
Fluent Bit → Kafka → Vector aggregator → S3/Loki 이런 구조다. 완벽한 정답은 아니고, 팀에 따라 아예 Vector로 통일하거나, 반대로 Fluent Bit만 쓰는 것도 충분히 합리적이다.
그래서 뭘 쓸까
한 문장으로 답을 강요하기 어려운 질문이다. 굳이 정리하면:
- 노드 수가 많고 리소스가 빡빡하며, 로그 변환이 단순하다 → Fluent Bit
- 처리량이 크고, 변환/라우팅이 복잡하고, 유실을 정말 싫어한다 → Vector
- 둘 다 필요하다 → 우리처럼 조합
혹시 aggregator 없이 edge에서 Vector 하나로만 쓰시는 분 있으면 어떻게 튜닝했는지 궁금하다. 특히 대형 클러스터에서 API server 부하 잡는 노하우가 있다면.
태그: Fluent Bit, Vector, 로그수집, observability, 모니터링, K8s