Loki
Grafana의 로그 집계 시스템
Grafana의 로그 집계 시스템
Loki는 Grafana Labs가 개발한 수평 확장 가능한 로그 집계 시스템입니다. Prometheus에서 영감을 받아 설계되었으며, 로그 본문을 인덱싱하지 않고 레이블만 인덱싱하는 방식으로 운영 비용과 복잡성을 크게 낮췄습니다. "Prometheus, but for logs"라는 슬로건이 특징을 잘 설명합니다.
전통적인 로그 시스템(Elasticsearch 등)은 로그의 모든 텍스트를 인덱싱해서 강력한 전문 검색을 제공하지만, 인덱싱에 많은 리소스가 필요합니다. Loki는 레이블(예: app=frontend, env=prod)만 인덱싱하고 로그 본문은 압축 저장합니다. 쿼리 시점에 필요한 로그 청크만 읽어서 검색하는 방식입니다.
LogQL은 Loki의 쿼리 언어로, PromQL과 유사한 문법을 사용합니다. 레이블 셀렉터로 로그 스트림을 선택하고, 파이프라인으로 필터링/파싱/집계합니다. 예를 들어 `{app="api"} |= "error" | json | rate(5m)`은 api 앱의 error가 포함된 로그를 JSON 파싱 후 5분 단위 발생률을 계산합니다.
Loki 스택은 Promtail(로그 수집 에이전트), Loki(로그 저장/쿼리), Grafana(시각화)로 구성됩니다. Kubernetes 환경에서 Promtail이 각 노드에서 Pod 로그를 수집하고, Loki에 푸시하며, Grafana에서 대시보드와 알림을 구성합니다. Fluentd/Fluent Bit이나 Vector로 Promtail을 대체할 수도 있습니다.
# LogQL 쿼리 예제
# 기본: 레이블로 로그 스트림 선택
{app="order-service", env="production"}
# 로그 본문 필터링 (포함)
{app="order-service"} |= "error"
{app="order-service"} |= "payment" |= "failed"
# 정규식 필터링
{app="api"} |~ "user_id=(\\d+)"
# 제외 필터
{app="api"} != "healthcheck" !~ "GET /metrics"
# JSON 로그 파싱
{app="api"} | json | level="error"
{app="api"} | json | response_time > 1000
# 라인 포맷 (출력 형식 변경)
{app="api"} | json | line_format "{{.timestamp}} [{{.level}}] {{.message}}"
# 메트릭 쿼리 - 분당 에러 로그 수
sum(rate({app="api"} |= "error" [5m])) by (instance)
# 로그 볼륨 계산 (bytes/sec)
sum(bytes_rate({app="api"}[5m])) by (app)
# 지연시간 히스토그램 (파싱된 값 사용)
{app="api"} | json | unwrap response_time_ms | histogram_quantile(0.95)
# promtail-config.yaml - Kubernetes Pod 로그 수집
server:
http_listen_port: 9080
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
relabel_configs:
# Pod 네임스페이스를 레이블로
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
# Pod 이름
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
# 컨테이너 이름
- source_labels: [__meta_kubernetes_pod_container_name]
target_label: container
# app 레이블
- source_labels: [__meta_kubernetes_pod_label_app]
target_label: app
pipeline_stages:
# JSON 로그 파싱
- json:
expressions:
level: level
message: msg
# 레벨별 레이블 추가
- labels:
level:
SRE: "ES 클러스터 운영 비용이 너무 높아요. Loki로 전환하면 어떨까요? 우리 쿼리 패턴이 대부분 레이블 필터 + grep 수준이라..."
개발자: "Kibana에서 자유로운 전문 검색이 안 된다는 게 단점 아닌가요?"
시니어: "맞아요, 하지만 우리는 90%가 app, trace_id, level로 필터링해요. Grafana Explore에서 LogQL로 충분히 커버됩니다. 비용은 1/5로 줄 거예요."
면접관: "로그 시스템 구축 경험이 있으신가요?"
지원자: "Loki 기반 로그 스택을 구축했습니다. Promtail로 Pod 로그를 수집하고, 레이블에 app, namespace, level을 추가했어요. Grafana에서 Loki와 Prometheus 데이터소스를 연결해서 메트릭과 로그를 같은 대시보드에서 보도록 했습니다. 알림은 LogQL 쿼리로 분당 에러 수가 임계치 넘으면 발생하게 설정했어요."
리뷰어: "Promtail 설정에 레이블 cardinality 주의하세요. user_id 같은 high cardinality 값을 레이블로 넣으면 Loki 성능이 급격히 떨어져요."
작성자: "아, user_id는 레이블 대신 로그 본문에 남기고 LogQL로 파싱해서 필터하도록 수정하겠습니다."
원인: user_id, request_id를 레이블로 추가, 수백만 개의 고유 레이블 조합 생성
영향: Loki 인덱스 크기 폭발, 쿼리 타임아웃, Ingester OOM 발생
해결: high cardinality 값을 레이블에서 제거, 로그 본문에만 포함하고 LogQL로 파싱
교훈: 레이블은 app, env, level 같은 low cardinality 값만, 고유 식별자는 로그 본문에
원인: Grafana 대시보드에서 시간 필터 없이 `{app="api"}` 쿼리 실행
영향: 90일 분량 로그 전체 스캔, Querier CPU 100%, 다른 쿼리 전부 타임아웃
해결: 쿼리 강제 중단, max_query_length 설정으로 시간 범위 제한
교훈: query_range 제한 설정, 대시보드에 기본 시간 범위 강제 적용
Q1. Loki가 Elasticsearch 대비 저렴한 이유는?
Q2. Loki에서 레이블로 사용하면 안 되는 값은?
Q3. LogQL에서 `{app="api"} |= "error" | json | rate(5m)`의 의미는?
이 페이지에 오류가 있거나 추가하고 싶은 내용이 있다면 알려주세요!