파드 로그를 한곳에서 보기
수집기와 저장소를 나눠 설치하고, 실제로 겪은 오류 4개를 고친 과정
Fluent Bit과 Loki는 경쟁 제품이 아니라 역할이 다릅니다. Fluent Bit은 노드마다 떠서 컨테이너 로그를 읽고 쿠버네티스 정보를 붙여 보내는 수집기이고, Loki는 그 로그를 받아 저장하고 LogQL로 찾게 해 주는 저장소입니다. kind 클러스터에 Loki를 단일 구성으로, Fluent Bit을 DaemonSet으로 설치해 연결했고, web 파드에 보낸 요청 5개가 Grafana에서 5줄 모두 검색되는 것까지 확인했습니다. 설치 중에는 차트 스키마 오류, 약 11GiB를 요청하는 기본 캐시, inotify 한도 부족 같은 문제를 차례로 만났고 그 해결 방법도 함께 정리했습니다.
1~4편의 kind 클러스터(노드 3개, Gateway API, kube-prometheus-stack의 Grafana)를 이어서 씁니다. Loki 차트 grafana-community/loki 21.0.0(Loki 3.7.9), Fluent Bit 차트 fluent/fluent-bit 0.58.3(Fluent Bit 5.1.3) 기준입니다. Loki 확장 구성, 보관 기간 설정, 알림은 다루지 않고, AWS로 보내는 부분은 구성 예시로만 소개합니다.
Fluent Bit과 Loki는 무엇이 다른가
로그 시스템은 보통 두 부분으로 나뉩니다. 로그를 모아서 보내는 수집기와, 받아서 저장하고 검색하게 해 주는 저장소입니다. Fluent Bit은 앞쪽, Loki는 뒤쪽이라 함께 쓰는 경우가 많습니다.
| 구분 | Fluent Bit | Loki |
|---|---|---|
| 역할 | 로그 수집·가공·전달(에이전트) | 로그 저장·검색(백엔드) |
| 설치 위치 | 노드마다 하나씩(DaemonSet) | 클러스터에 한 곳(필요하면 여러 개로 확장) |
| 주로 하는 일 | 컨테이너 로그 파일을 읽고 파드 정보를 붙여 목적지로 보냄 | 받은 로그를 저장하고 LogQL로 검색 |
| 연결 대상 | Loki, Elasticsearch·OpenSearch, CloudWatch Logs, S3 등 | Fluent Bit, Grafana Alloy 같은 수집기에서 받음 |
| 화면 | 없음 | Grafana에서 조회 |
Loki는 로그 본문 전체가 아니라 namespace, pod 같은 라벨에만 색인을 만듭니다. 그래서 저장 부담이 적고, Prometheus와 같은 라벨 방식이라 Grafana에서 메트릭과 로그를 같은 라벨로 오가기 쉽습니다. 대신 본문 검색은 라벨로 범위를 먼저 좁혀야 빠릅니다. Fluent Bit이 왜 필요한지와 필터 구조는 아래 관련 글에서 더 자세히 다룹니다.
Loki 설치: 차트 위치부터 바뀌었다
Loki 공식 설치 문서는 이제 grafana-community 저장소의 차트를 안내합니다. 실습 시점에 grafana-community/loki는 21.0.0(Loki 3.7.9)이었고, 예전 위치인 grafana/loki는 7.5.0(Loki 3.6.16)에 설명이 Grafana Enterprise Logs용으로 바뀌어 있었습니다. 예전 글을 따라 할 때는 저장소 주소부터 확인합니다. Loki 단일 구성 설치 문서
차트 기본값은 인증이 켜져 있고(auth_enabled: true) 저장소가 S3입니다. 실습용으로는 인증을 끄고 노드 디스크(filesystem)에 저장하도록 바꿨습니다. 다른 배포 모드의 복제 수는 공식 예시처럼 모두 0으로 둡니다(아래는 그중 일부).
loki:
auth_enabled: false
commonConfig:
replication_factor: 1
storage:
type: filesystem
schemaConfig:
configs:
- from: "2024-04-01"
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: loki_index_
period: 24h
singleBinary:
replicas: 1
persistence:
size: 5Gi
read:
replicas: 0
write:
replicas: 0
backend:
replicas: 0
처음에는 공식 문서 예시에 있던 minio: enabled: false도 넣었는데 설치가 바로 거부됐습니다. 차트 21.0.0의 설정 검사(스키마)에 minio 항목이 없기 때문입니다. 문서와 차트 버전이 어긋날 수 있으니, 오류 메시지에 나온 키를 지우면 됩니다.
loki:
- at '': additional properties 'minio' not allowed
helm install loki grafana-community/loki --version 21.0.0 -n loki --create-namespace -f loki-values.yaml --wait
기본 캐시가 메모리 11GiB를 요청한다
설치는 61초 만에 끝났지만 파드 목록을 보면 Loki 본체 외에 게이트웨이, loki-canary(노드마다), 캐시 두 개가 함께 떠 있었습니다. 캐시의 메모리 요청량을 확인하니 로컬 실습에는 너무 컸습니다.
| 파드 | 메모리 요청 | 근거 |
|---|---|---|
| loki-chunks-cache-0 | 9830Mi | chunksCache.allocatedMemory 기본값 8192(MB)의 1.2배 |
| loki-results-cache-0 | 1229Mi | resultsCache.allocatedMemory 기본값 1024(MB)의 1.2배 |
합치면 약 11GiB입니다. 실습 PC는 WSL에 메모리가 23GiB 잡혀 있어서 떴지만, 메모리가 적은 PC라면 이 파드 때문에 다른 파드가 자리를 못 잡을 수 있습니다. 로컬에서는 캐시와 canary를 끕니다.
enabled: false
resultsCache:
enabled: false
lokiCanary:
enabled: false
test:
enabled: false
test.enabled: false도 필요합니다. canary만 끄고 업그레이드하면 Helm test requires the Loki Canary to be enabled라는 오류로 거부되는데, 차트의 helm 테스트가 canary를 쓰기 때문입니다. 네 값을 모두 넣고 helm upgrade하자 파드가 loki-0과 게이트웨이 두 개로 줄었습니다.
Fluent Bit 설치: 출력만 Loki로 바꾸기
Fluent Bit 차트의 기본 설정은 컨테이너 로그를 읽는 tail 입력, kubelet 로그를 읽는 systemd 입력, 쿠버네티스 정보를 붙이는 kubernetes 필터, 그리고 Elasticsearch(elasticsearch-master)로 보내는 출력으로 되어 있습니다. 출력을 Loki로 바꾸고, kind 노드에는 필요 없는 systemd 입력을 뺐습니다. Fluent Bit Loki 출력
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
config:
outputs: |
[OUTPUT]
Name loki
Match kube.*
Host loki.loki.svc.cluster.local
Port 3100
Labels job=fluent-bit, namespace=$kubernetes['namespace_name'], pod=$kubernetes['pod_name'], container=$kubernetes['container_name']
Remove_Keys kubernetes
Line_Format json
Labels의 $kubernetes['…']는 kubernetes 필터가 붙인 값을 꺼내 Loki 라벨로 쓰는 표기입니다. 라벨은 검색 범위를 좁히는 기준이므로 namespace, pod, container처럼 값의 종류가 많지 않은 것만 씁니다. Remove_Keys kubernetes는 라벨로 쓴 정보를 로그 본문에서 빼서 저장 공간을 줄입니다. tolerations가 없으면 Fluent Bit이 제어 플레인 노드에 뜨지 않아, 3편의 Envoy 프록시처럼 그 노드에 있는 파드의 로그를 놓칩니다.
helm install fluent-bit fluent/fluent-bit --version 0.58.3 -n logging --create-namespace -f fluent-bit-values.yaml
CrashLoopBackOff: Too many open files 해결
설치 직후 Fluent Bit 파드 3개가 모두 CrashLoopBackOff가 됐고 재시작이 5번까지 쌓였습니다. 로그에는 tail 입력이 시작하자마자 실패한 기록이 있었습니다.
[error] failed initialize input tail.0
[error] [engine] input initialization failed
파일 개수 문제가 아니라 inotify 한도 문제입니다. Fluent Bit은 로그 파일 변화를 inotify로 감시하는데, kind 노드 3개와 그 안의 파드가 모두 WSL 커널 하나를 같이 써서 fs.inotify.max_user_instances 기본값 128이 모자랐습니다. kind 문서의 알려진 문제에 나오는 권장값으로 WSL에서 올리고, 재부팅 후에도 유지되게 파일로 남겼습니다. kind 알려진 문제
printf 'fs.inotify.max_user_instances = 512\nfs.inotify.max_user_watches = 524288\n' > /etc/sysctl.d/99-kind-inotify.conf
kubectl -n logging rollout restart ds/fluent-bit
kind 노드는 커널을 공유하므로 노드 컨테이너 안에서도 바로 512로 보였고, 다시 시작한 Fluent Bit 3개가 제어 플레인을 포함해 모두 Running이 됐습니다. 로그에는 [output:loki:loki.0] configured, hostname=loki.loki.svc.cluster.local:3100이 찍혔습니다.

Grafana에서 로그 보기: 데이터 소스와 LogQL
4편의 Grafana에 Loki를 연결합니다. kube-prometheus-stack의 Grafana는 grafana_datasource: "1" 라벨이 붙은 ConfigMap을 사이드카가 읽어 데이터 소스로 등록하므로, Grafana를 다시 시작할 필요가 없습니다.
kind: ConfigMap
metadata:
name: loki-datasource
namespace: monitoring
labels:
grafana_datasource: "1"
data:
loki-datasource.yaml: |
apiVersion: 1
datasources:
- name: Loki
type: loki
uid: loki
access: proxy
url: http://loki.loki.svc.cluster.local:3100
적용하고 20초 안에 데이터 소스 목록에 Loki가 생겼고, 상태 점검에서 Data source successfully connected.가 나왔습니다. 다만 그 전에 열어 둔 Grafana 화면의 Explore 데이터 소스 목록에는 Loki가 보이지 않았습니다. 웹 화면이 페이지를 열 때 받아 둔 목록을 쓰기 때문이라, 브라우저를 새로고침하면 나타납니다. 확인용으로 Windows에서 http://localhost:8080/?from=loki-test-1부터 -5까지 요청을 보낸 뒤 Grafana의 Explore에서 Loki를 고르고 검색했습니다.
중괄호 안이 라벨로 범위를 좁히는 부분이고, |=는 그 안에서 본문에 문자열이 들어간 줄을 거릅니다. 요청 5개가 nginx 접근 로그 5줄로 모두 검색됐고, 파드별로는 3줄과 2줄이었습니다. sum by (pod) (count_over_time({…} |= "loki-test" [15m]))처럼 바꾸면 줄 수를 숫자로 세어 메트릭처럼 그래프로 그릴 수도 있습니다.
브라우저로 http://localhost:8080/?from=loki-test-6, -7을 열고 같은 쿼리를 Grafana Explore에서 실행하면 아래처럼 보입니다. 브라우저 요청은 캐시 때문에 상태 코드가 200 대신 304로 찍히기도 합니다. Logs volume 그래프에는 unknown 10건과 error 1건이 나왔는데, error는 브라우저가 자동으로 요청한 favicon.ico를 nginx가 찾지 못해 남긴 오류 로그였습니다. 그 줄의 referrer에 loki-test가 들어 있어 함께 검색된 것이고, nginx 오류 로그는 [error]가 찍혀 stderr로 나오므로 error로 분류됩니다. 접근 로그에는 수준 표시가 없어 unknown으로 나옵니다.

로그를 모으는 네임스페이스는 default, envoy-gateway-system, kube-system, logging, loki, monitoring 여섯 곳이었습니다. Loki는 Fluent Bit이 붙인 라벨 외에 service_name과 stream 라벨을 스스로 추가합니다.
쿼리를 직접 쓰지 않아도 됩니다. Explore의 Builder 모드에서 Label filters에 namespace = monitoring을 고르면 아래에 {namespace="monitoring"} 식이 만들어지고, 실습 화면에서는 Grafana 자신의 로그가 214줄 나왔습니다. 위쪽 Logs volume 그래프는 시간대별 로그 양을 error·info·warning으로 나눠 보여 줬는데(이때 error 9, info 138, warning 30), 이 수준은 Fluent Bit 설정에 넣은 값이 아니라 로그 본문의 level=info 같은 표시를 Grafana와 Loki가 읽어 detected_level 필드로 나눈 것입니다. 에러가 몰린 시간대를 그래프에서 먼저 찾고 그 구간의 로그만 펼쳐 보는 식으로 쓰면 편합니다.

Fluent Bit의 확장성: AWS로 함께 보내는 구성 예시
Fluent Bit은 출력을 여러 개 둘 수 있어서, 같은 로그를 Loki와 다른 저장소로 동시에 보낼 수 있습니다. 예를 들어 운영 환경에서는 실시간 조회는 Loki, 장애 대응용 보관은 CloudWatch Logs, 장기 보관은 S3로 나누는 구성을 생각할 수 있습니다. 아래는 이 실습 클러스터에 적용하지 않은 구성 예시로, 설정 이름은 Fluent Bit 공식 문서 기준이고 로그 그룹·버킷 이름은 가상의 값입니다. Fluent Bit CloudWatch 출력
Name cloudwatch_logs
Match kube.*
Region ap-northeast-2
Log_Group_Name /lab/kubernetes/containers
Log_Stream_Prefix from-fluent-bit-
Auto_Create_Group On
Log_Retention_Days 7
[OUTPUT]
Name s3
Match kube.*
Region ap-northeast-2
Bucket example-lab-log-archive
Total_File_Size 50M
Upload_Timeout 10m
Compression gzip
S3_Key_Format /kubernetes/%Y/%m/%d/%H/$UUID.gz
같은 Match kube.*를 가진 출력이 여러 개면 Fluent Bit은 같은 로그를 각각에 보냅니다. CloudWatch Logs 출력은 Auto_Create_Group On이면 로그 그룹이 없을 때 만들고, S3 출력은 Total_File_Size나 Upload_Timeout 중 먼저 닿는 조건에서 파일을 올립니다. 공식 문서에 적힌 필요 권한은 아래와 같습니다.
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:PutRetentionPolicy"],
"Resource": "LOG_GROUP_ARN_PATTERN"
},
{
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": "LOG_BUCKET_OBJECTS_ARN"
}
]
}
Fluent Bit 파드가 AWS에 접근하려면 이 권한을 가진 자격 증명이 필요합니다. 로컬 kind에서는 자격 증명을 따로 넣어야 하고, 키를 매니페스트에 직접 쓰지 않도록 주의합니다. LOG_GROUP_ARN_PATTERN은 리전과 계정 ID, /lab/kubernetes/로 시작하는 로그 그룹 이름을 담은 CloudWatch Logs 로그 그룹 ARN으로, LOG_BUCKET_OBJECTS_ARN은 버킷 example-lab-log-archive 안의 모든 객체를 가리키는 S3 ARN(버킷 ARN 뒤에 /*)으로 바꿉니다. 공식 문서의 예시 정책은 리소스를 *로 두지만, 이렇게 로그 그룹과 버킷 이름으로 좁히는 편이 안전합니다.
자주 묻는 질문
Promtail이나 Grafana Alloy 대신 Fluent Bit을 써도 되나요?
됩니다. Loki는 수집기를 가리지 않고 푸시 API로 로그를 받습니다. Grafana 문서와 그대로 맞추고 싶다면 Grafana Alloy가 편하고, 같은 로그를 Loki 말고 CloudWatch Logs·S3·Elasticsearch로도 보내야 한다면 Fluent Bit이 맞습니다.
Loki 게이트웨이는 꼭 필요한가요?
이 실습에서는 Fluent Bit과 Grafana 모두 Loki 서비스(3100)에 직접 연결했기 때문에 게이트웨이를 거치지 않습니다. 게이트웨이는 여러 구성 요소로 나눈 배포에서 요청을 나눠 주거나 인증을 앞에서 받을 때 쓰입니다.
작성 방식과 다음 편
이 글의 명령, 오류 메시지, 결과는 1편의 실습 PC에서 직접 실행한 것이며, 설치와 설정은 Loki·Fluent Bit·kind 공식 문서를 기준으로 했습니다. AWS 출력 부분만 실행하지 않은 구성 예시입니다. 실행 기록 정리와 글 작성에 AI 도구를 함께 사용했습니다. 다음 편에서는 Keycloak을 설치하고 Grafana 로그인을 SSO로 바꿉니다.
'Kubernetes Guides' 카테고리의 다른 글
| kube-prometheus-stack 설치와 Grafana 접속: kind 클러스터 모니터링과 target down 해결 (0) | 2026.11.08 |
|---|---|
| NGINX Ingress 대신 Gateway API: kind 클러스터 앱을 Windows localhost로 접속하기 (0) | 2026.11.08 |
| Headlamp 설치와 사용법: 로컬 쿠버네티스를 화면으로 보고 파드 배포하기 (0) | 2026.11.08 |
| Windows에서 무료 쿠버네티스 실습 환경 만들기: WSL2 + Docker Engine + kind (0) | 2026.11.08 |
| 쿠버네티스 PVC 용량 확장이 반영되지 않을 때: 요청 용량·CSI·파일시스템 점검하기 (0) | 2026.09.09 |







