Fluentd OOM — 메모리 버퍼 vs 파일 버퍼
각 노드에서 Fluent Bit 이 로그를 걷어 Kafka 로 보내고, Fluentd 가 그걸 받아 Elasticsearch 에 넣는 파이프라인을 쓰고 있었습니다. 중간에 Kafka 를 둔 건 Elasticsearch 가 잠깐 느려져도 로그를 잃지 않으려는 것이었는데, 정작 그 뒤에 선 Fluentd 가 며칠에 한 번씩 죽었습니다.
메모리 버퍼 무제한 증가
설정을 열어보니 네 가지가 겹쳐 있었습니다.
@type memory buffer가 메모리에만 있음
total_limit_size 미설정 → 무제한 증가
resources: {} 메모리 제한 없음 → OOM 대상
volumeClaimTemplates 없음 → 재시작하면 버퍼 유실
Elasticsearch 로 나가는 쪽이 잠깐 느려지면 버퍼가 쌓입니다. 상한이 없으니 메모리를 계속 먹고, 결국 OOM 으로 죽습니다. 죽으면 메모리에 있던 버퍼가 통째로 사라집니다. 로그 유실과 파드 재시작이 같이 옵니다.
파일 버퍼 + PVC + 상한
버퍼를 디스크로 내리고 상한을 걸었습니다.
<buffer tag, app_name>
@type file
path /mnt/buffer/es/stdout
flush_mode interval
flush_interval 10s
flush_at_shutdown true
flush_thread_count 4
chunk_limit_size 8m
total_limit_size 5GB
overflow_action drop_oldest_chunk
retry_type exponential_backoff
retry_wait 5s
retry_max_interval 60s
retry_timeout 24h
</buffer>
total_limit_size 와 overflow_action 이 짝입니다. 상한만 걸고 넘쳤을 때
동작을 안 정하면 기본값으로 예외가 나면서 파이프라인이 멈춥니다.
drop_oldest_chunk 는 오래된 것부터 버립니다. 로그가 유실되지만 파이프라인은
살아 있습니다.
flush_at_shutdown: true 는 재시작할 때 버퍼를 비우고 나가게 합니다.
버퍼가 디스크에 있어야 하므로 StatefulSet 에 PVC 를 붙였습니다.
volumeClaimTemplates:
- metadata:
name: buffer-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 30Gi
storageClassName: gp2
메모리 상한이 없으면 파일 버퍼로 바꿔도 다른 경로로 다시 커질 수 있어서, requests·limits 도 같이 박았습니다.
resources:
requests:
memory: "512Mi"
cpu: "100m"
limits:
memory: "1Gi"
cpu: "1000m"
배포하면서 막힌 다섯 곳
StorageClass 이름이 안 맞았습니다. 클러스터에 어떤 게 있는지부터 확인해야 합니다.
kubectl get sc
# gp2 (default), ebs-sc, local-storage
Elasticsearch 플러그인이 이미지에 없었습니다. 기본 Fluentd 이미지에는
fluent-plugin-elasticsearch 가 안 들어 있습니다. 컨테이너가 뜰 때
fluent-gem 으로 설치하고 원래 엔트리포인트를 이어받게 했습니다.
command:
- /bin/sh
- -c
- |
fluent-gem install fluent-plugin-s3
fluent-gem install fluent-plugin-rewrite-tag-filter
fluent-gem install fluent-plugin-elasticsearch
exec /fluentd/entrypoint.sh
이미지를 새로 빌드하는 게 정석이지만, 시작 때마다 gem 을 받는 방식으로 먼저 넘어갔습니다.
CPU 가 부족해 스케줄이 안 됐습니다. requests 를 넣으면 노드에 그만큼 여유가 있어야 합니다. 넣기 전에는 뜨던 파드가 넣고 나서 Pending 이 됩니다.
PVC 가 Pending 에서 안 넘어갔습니다. StatefulSet 의
volumeClaimTemplates 는 immutable 이라 kubectl apply 로 안 바뀝니다.
StatefulSet 을 지우고 다시 만들어야 합니다.
Kustomize patch 가 적용되지 않았습니다. patch 대상의 이름이나 종류가 한 글자라도 다르면 조용히 무시됩니다. 에러가 안 나므로 적용 결과를 직접 확인해야 합니다.
남은 것
drop_oldest_chunk 는 버퍼가 5GB 를 넘으면 로그를 버립니다. 지금은 버린
양을 세지 않습니다. Fluentd 가 노출하는 버퍼 지표를 Prometheus 로 가져오면
상한에 얼마나 근접했는지, 실제로 얼마나 버려졌는지가 보입니다.
gem 을 시작할 때마다 설치하는 것도 임시입니다. 네트워크가 안 되면 파드가 못 뜨고, 시작 시간도 길어집니다. 플러그인이 포함된 이미지를 만들어 두는 게 맞습니다.
댓글 0