Grafana Loki 3.0 완벽 가이드: 로그 수집 시스템 구축 실무 2025

📑 목차


1. 개요

“로그를 저장하는 게 아니라, 로그의 ‘인덱스’만 저장한다.”

Grafana Loki는 Prometheus에서 영감을 받아 만들어진 수평 확장 가능한 로그 집계 시스템입니다. Elasticsearch와 달리 로그 내용을 인덱싱하지 않고 레이블만 인덱싱하여, 비용 효율적이면서도 강력한 로그 검색 기능을 제공하죠.

2024년 출시된 Loki 3.0은 아키텍처와 설정 방식에 큰 변화를 가져왔습니다. 특히 TSDB(Time Series Database) 인덱스가 기본값이 되면서 성능과 안정성이 크게 개선되었어요.

📊 Loki 아키텍처 구조:

┌─────────────┐     ┌──────────┐     ┌──────┐     ┌─────────┐
│ Application │ --> │ Promtail │ --> │ Loki │ --> │ Grafana │
│   (Logs)    │     │ (Agent)  │     │(Store│     │ (Query) │
└─────────────┘     └──────────┘     └──────┘     └─────────┘
                                          │
                                          ↓
                                    ┌──────────┐
                                    │ Object   │
                                    │ Storage  │
                                    │ (S3/GCS) │
                                    └──────────┘

2. 배경 및 필요성

문제 상황

  • 🔴 Elasticsearch의 높은 리소스 요구사항: 로그 전문 인덱싱으로 인한 높은 메모리/CPU 사용량과 스토리지 비용
  • 🔴 복잡한 운영: 클러스터 관리, 샤드 재분배, 인덱스 관리 등 전문적인 운영 지식 필요
  • 🔴 Kubernetes 환경 미적합: 기존 로그 솔루션들은 컨테이너 환경의 동적 특성을 제대로 반영하지 못함

해결 방안

구분Elasticsearch 방식Loki 방식
인덱싱전문 인덱싱 (Full-text)✅ 레이블만 인덱싱
리소스높은 메모리/CPU 사용✅ 낮은 리소스 사용량
운영복잡한 클러스터 관리✅ 단순한 운영
스토리지고가의 SSD 필요✅ S3 등 저렴한 오브젝트 스토리지
비용높음 ($100-500/TB)✅ 낮음 ($23/TB)

3. 아키텍처 이해

핵심 컴포넌트

Loki는 크게 세 가지 주요 컴포넌트로 구성되어 있어요.

컴포넌트역할특징사용 시나리오
Promtail로그 수집 에이전트Kubernetes Pod 로그 자동 수집, 레이블 추가각 노드에 DaemonSet으로 배포
Loki로그 저장/쿼리 서버레이블 인덱싱, 청크 압축 저장중앙 집중식 로그 저장소
Grafana시각화 도구LogQL 쿼리, 대시보드, 알람로그 검색 및 모니터링

데이터 흐름

# 데이터 흐름 예시
1. Application → stdout/stderr
   ↓
2. Promtail → 로그 수집 + 레이블 추가
   ↓
3. Loki → 인덱스(레이블) + 청크(로그 데이터) 저장
   ↓
4. Grafana → LogQL 쿼리로 검색/시각화

💡 핵심 차이점: Elasticsearch는 로그 내용 전체를 인덱싱하지만, Loki는 app=backend, level=error 같은 레이블만 인덱싱해요. 로그 내용은 압축해서 청크로 저장하죠.


4. 왜 Loki는 파일시스템을 권장할까?

데이터베이스 vs 파일시스템

Loki 공식 문서를 보면 인덱스와 청크 저장소로 파일시스템(또는 S3 같은 오브젝트 스토리지)을 권장합니다. 왜 데이터베이스가 아닐까요?

🔴 데이터베이스 방식의 문제점

# Cassandra/DynamoDB를 사용할 때의 문제점
storage_config:
  cassandra:
    addresses: cassandra.svc.cluster.local
    keyspace: loki
    
# 문제 1: 높은 운영 복잡도
- Cassandra 클러스터 별도 관리 필요
- 노드 추가/제거 시 리밸런싱
- Compaction 튜닝 필요
# 문제 2: 비용
- 데이터베이스 인스턴스 비용
- IOPS 비용 (특히 DynamoDB)
- 트래픽 증가 시 급격한 비용 상승
# 문제 3: 확장성 한계
- Write throughput 제한
- Hot partition 문제
- 스키마 변경의 어려움

🟢 파일시스템 방식의 장점

장점설명효과
1️⃣ 단순성별도 DB 클러스터 불필요, 로컬 디스크나 NFS 사용운영 오버헤드 최소화
2️⃣ 비용 효율S3 Standard: $0.023/GB, Glacier: $0.004/GBDB 대비 1/10 수준
3️⃣ 확장성무제한 스토리지, 읽기/쓰기 병렬 처리자동 복제 및 내구성

Loki의 저장 구조

Loki는 데이터를 두 가지로 분리해서 저장해요.

# 1. 인덱스 (Index) - 작고 빠른 검색이 필요
/loki/index/
  ├── compactor/           # TSDB 인덱스 파일들
  │   ├── 19500/          # Tenant ID (기본값: fake)
  │   │   └── 1699000000/ # 타임스탬프 기반 디렉토리
  │   │       ├── index   # TSDB 인덱스
  │   │       └── meta.json
  
# 크기: 전체 로그의 1-5% 수준
# 역할: {app="backend", level="error"} 같은 레이블 검색
# 2. 청크 (Chunks) - 실제 로그 데이터
/loki/chunks/
  ├── fake/               # Tenant ID
  │   └── 6a7b8c9d/      # 청크 ID (해시)
  │       └── 1699000000-1699003600.gz
  
# 크기: 전체 로그의 95-99%
# 역할: 압축된 로그 데이터 (Gzip/Snappy)

💡 왜 이렇게 분리할까?

  • 인덱스: 자주 읽히고 빠른 검색이 필요 → SSD나 로컬 디스크
  • 청크: 크고 순차적으로 읽힘 → S3 같은 저렴한 오브젝트 스토리지

실제 저장 예시

# Loki 2.x 설정 예시
schema_config:
  configs:
    - from: 2024-01-01
      store: boltdb-shipper  # 인덱스 저장소
      object_store: s3       # 청크 저장소
      schema: v11
      index:
        prefix: loki_index_
        period: 24h
storage_config:
  # 인덱스: 로컬 디스크 (BoltDB)
  boltdb_shipper:
    active_index_directory: /loki/index
    cache_location: /loki/boltdb-cache
    
  # 청크: S3 오브젝트 스토리지
  aws:
    s3: s3://us-east-1/my-loki-bucket
    s3forcepathstyle: true

🚀 성능 비교:

항목Cassandra파일시스템 (S3)차이
쓰기 지연시간5-10ms1-3ms (로컬)3-5배 빠름
읽기 지연시간10-50ms20-100ms (S3)비슷하거나 느림
스토리지 비용$100-500/TB$23/TB1/20 수준
운영 복잡도높음 (클러스터 관리)낮음 (매니지드)훨씬 단순

5. Loki 3.0의 주요 변경사항

Loki 3.0은 2024년 4월에 출시되었고, 아키텍처와 설정에 큰 변화가 있었어요. 기존 사용자라면 꼭 알아야 할 내용들입니다.

1️⃣ TSDB 인덱스가 기본값

구분Loki 2.x (구버전)Loki 3.x (신버전)
인덱스 타입BoltDB ShipperTSDB
스키마v11v13
쿼리 성능기준2-5배 향상
인덱스 크기기준30-50% 감소
메모리 사용기준20-30% 감소
# Loki 2.x 설정
schema_config:
  configs:
    - from: 2024-01-01
      store: boltdb-shipper
      object_store: s3
      schema: v11
# Loki 3.x 설정
schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: s3
      schema: v13

📌 TSDB의 장점:

  • 더 빠른 쿼리: Prometheus와 동일한 인덱스 구조로 시계열 검색 최적화
  • 낮은 메모리 사용: 인덱스 크기가 30-50% 감소
  • 더 나은 Compaction: 백그라운드에서 자동으로 인덱스 최적화

2️⃣ 설정 구조 단순화

Loki 3.0에서는 복잡했던 설정이 크게 간소화되었어요.

# ===== Loki 2.x 설정 (복잡함) =====
auth_enabled: false
server:
  http_listen_port: 3100
  grpc_listen_port: 9096
common:
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    instance_addr: 127.0.0.1
    kvstore:
      store: inmemory
schema_config:
  configs:
    - from: 2020-10-24
      store: boltdb-shipper
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 24h
storage_config:
  boltdb_shipper:
    active_index_directory: /loki/boltdb-shipper-active
    cache_location: /loki/boltdb-shipper-cache
    cache_ttl: 24h
    shared_store: filesystem
  filesystem:
    directory: /loki/chunks
compactor:
  working_directory: /loki/boltdb-shipper-compactor
  shared_store: filesystem
limits_config:
  reject_old_samples: true
  reject_old_samples_max_age: 168h
chunk_store_config:
  max_look_back_period: 0s
table_manager:
  retention_deletes_enabled: false
  retention_period: 0s
ruler:
  alertmanager_url: http://localhost:9093
# ===== Loki 3.x 설정 (단순화) =====
auth_enabled: false
server:
  http_listen_port: 3100
  grpc_listen_port: 9096
# 'common' 블록으로 통합
common:
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory
# TSDB로 변경
schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h
# storage_config 대폭 간소화
storage_config:
  tsdb_shipper:
    active_index_directory: /loki/tsdb-index
    cache_location: /loki/tsdb-cache
  filesystem:
    directory: /loki/chunks
# pattern_ingester 추가 (새 기능)
pattern_ingester:
  enabled: true
  
# limits_config 간소화
limits_config:
  retention_period: 744h  # 31일
  
# ruler 설정 간소화
ruler:
  storage:
    type: local
    local:
      directory: /loki/rules

3️⃣ 새로운 기능: Pattern Ingester

Loki 3.0의 가장 큰 신기능은 Pattern Ingester입니다. 로그 패턴을 자동으로 감지하고 분류해줘요.

# Pattern Ingester 활성화
pattern_ingester:
  enabled: true
  
# 예시 로그들:
# 2024-11-02 10:23:45 INFO  User 1234 logged in from 192.168.1.1
# 2024-11-02 10:24:12 INFO  User 5678 logged in from 10.0.0.5
# 2024-11-02 10:25:33 ERROR Failed to connect to database
# Loki가 자동으로 패턴 감지:
# Pattern 1:  INFO  User  logged in from   (2건)
# Pattern 2:  ERROR Failed to connect to database  (1건)

💡 활용 사례:

  • 반복되는 에러 패턴 빠르게 찾기
  • 로그 볼륨 분석 (어떤 패턴이 가장 많이 발생하는지)
  • 이상 탐지 (평소와 다른 패턴 출현 시 알람)

4️⃣ Bloom Filter 인덱스 (실험적)

Loki 3.0에서는 Bloom Filter를 이용한 새로운 인덱싱 방식이 실험적으로 추가되었어요.

# Bloom Filter 활성화 (실험적 기능)
bloom_build:
  enabled: true
  
bloom_gateway:
  enabled: true
  
# 장점:
# - 특정 문자열 포함 여부를 빠르게 확인
# - "error"라는 단어가 있는 청크만 읽기
# - 불필요한 청크 스캔 최소화 (최대 10배 성능 향상)
# 주의:
# - 아직 프로덕션 사용 비권장
# - 추가 스토리지 필요 (인덱스의 5-10%)

5️⃣ 마이그레이션 가이드

Loki 2.x에서 3.x로 업그레이드할 때 주의사항이에요.

# Step 1: 기존 데이터 백업
kubectl exec -n monitoring loki-0 -- tar czf /tmp/loki-backup.tar.gz /loki
# Step 2: 새 스키마 추가 (기존 스키마 유지하면서)
# loki-config.yaml
schema_config:
  configs:
    # 기존 스키마 (계속 읽기 가능)
    - from: 2024-01-01
      store: boltdb-shipper
      object_store: s3
      schema: v11
    
    # 새 스키마 (새 데이터용)
    - from: 2024-11-01  # 업그레이드 날짜
      store: tsdb
      object_store: s3
      schema: v13
# Step 3: Loki 3.x 배포
helm upgrade loki grafana/loki \
  --version 6.0.0 \
  --values loki-config.yaml
# Step 4: 검증
# - 새 로그가 TSDB로 저장되는지 확인
# - 기존 로그 쿼리가 정상 동작하는지 확인
# Step 5: 구 데이터 정리 (선택사항)
# retention_period 설정으로 자동 정리
limits_config:
  retention_period: 720h  # 30일

⚠️ 주의사항:

  • 한 번에 모든 스키마를 바꾸지 마세요 (데이터 손실 위험)
  • 특정 날짜부터 새 스키마 적용 (기존 데이터는 읽기 가능 유지)
  • 최소 1-2주간 모니터링 후 구 스키마 제거

6. 실습 및 구현

Step 1: Kubernetes에 Loki 3.0 설치

# 1. Helm 레포지토리 추가
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
# 2. 네임스페이스 생성
kubectl create namespace monitoring
# 3. Loki 설정 파일 생성
cat > loki-values.yaml <

Step 2: Promtail 설정 (로그 수집)

# promtail-config.yaml
server:
  http_listen_port: 9080
  grpc_listen_port: 0
positions:
  filename: /tmp/positions.yaml
clients:
  - url: http://loki:3100/loki/api/v1/push
scrape_configs:
  # Kubernetes Pod 로그 수집
  - job_name: kubernetes-pods
    kubernetes_sd_configs:
      - role: pod
    
    pipeline_stages:
      # 1. JSON 로그 파싱
      - json:
          expressions:
            level: level
            message: message
            timestamp: timestamp
      
      # 2. 타임스탬프 추출
      - timestamp:
          source: timestamp
          format: RFC3339
      
      # 3. 레이블 추가
      - labels:
          level:
          app:
    
    relabel_configs:
      # Pod 이름을 레이블로 추가
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod
      
      # Namespace를 레이블로 추가
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      
      # 앱 이름을 레이블로 추가
      - source_labels: [__meta_kubernetes_pod_label_app]
        target_label: app
      
      # 컨테이너 이름을 레이블로 추가
      - source_labels: [__meta_kubernetes_pod_container_name]
        target_label: container
      
      # 로그 파일 경로 설정
      - target_label: __path__
        replacement: /var/log/pods/*$1/*.log
        source_labels:
          - __meta_kubernetes_pod_uid
          - __meta_kubernetes_pod_container_name

💡 레이블 설계 원칙:

  • Cardinality 주의: 너무 많은 고유 값을 가진 레이블은 피하세요 (예: request_id, user_id)
  • 필수 레이블: namespace, app, level 정도면 충분
  • 동적 레이블 금지: 타임스탬프나 UUID를 레이블로 사용하지 마세요

Step 3: 애플리케이션 로그 출력 설정

// Node.js 예시 - JSON 형식으로 로그 출력
const winston = require('winston');
const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp({
      format: 'YYYY-MM-DD HH:mm:ss'
    }),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console()
  ]
});
// 사용 예시
logger.info('User logged in', {
  userId: '12345',
  ip: '192.168.1.1',
  action: 'login'
});
// 출력 결과 (JSON):
// {
//   "level": "info",
//   "message": "User logged in",
//   "timestamp": "2024-11-02 10:30:45",
//   "userId": "12345",
//   "ip": "192.168.1.1",
//   "action": "login"
// }
// ⚠️ 주의: 구조화된 로그를 사용하세요!
// ❌ 나쁜 예: logger.info("User 12345 logged in from 192.168.1.1")
// ✅ 좋은 예: logger.info("User logged in", { userId, ip })
# Python 예시 - JSON 형식으로 로그 출력
import logging
import json
from datetime import datetime
class JsonFormatter(logging.Formatter):
    def format(self, record):
        log_data = {
            'timestamp': datetime.utcnow().isoformat(),
            'level': record.levelname,
            'message': record.getMessage(),
            'logger': record.name,
        }
        
        # 추가 필드가 있으면 포함
        if hasattr(record, 'extra_fields'):
            log_data.update(record.extra_fields)
        
        return json.dumps(log_data)
# 로거 설정
logger = logging.getLogger('my_app')
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)
# 사용 예시
logger.info(
    'User logged in',
    extra={
        'extra_fields': {
            'user_id': '12345',
            'ip': '192.168.1.1',
            'action': 'login'
        }
    }
)

Step 4: Grafana에서 로그 조회

# 1. Grafana 접속
kubectl port-forward -n monitoring svc/loki-grafana 3000:80
# 2. 브라우저에서 http://localhost:3000 접속
# 기본 계정: admin / prom-operator
# 3. Explore 메뉴로 이동
# 4. LogQL 쿼리 예시들:
# 기본 쿼리: namespace가 production인 모든 로그
{namespace="production"}
# ERROR 레벨만 필터링
{namespace="production", level="error"}
# 특정 앱의 로그
{namespace="production", app="backend"}
# 로그 내용으로 필터링 (정규표현식)
{namespace="production"} |~ "database.*timeout"
# JSON 필드 추출 및 필터링
{namespace="production"} 
  | json 
  | userId="12345"
# 로그 카운트 (최근 5분간 ERROR 발생 건수)
sum(count_over_time({namespace="production", level="error"}[5m]))
# 로그 속도 (초당 로그 라인 수)
rate({namespace="production"}[1m])
# Pattern 조회 (Loki 3.0 신기능)
{namespace="production"} | pattern
# 특정 패턴만 필터링
{namespace="production"} 
  | pattern 
  | pattern_type="error_connection"

💡 Pro Tip: LogQL은 Prometheus의 PromQL과 매우 유사해요. 메트릭 쿼리 경험이 있다면 금방 익숙해질 거예요.


7. Best Practices

성능 최적화

✅ DO (권장사항) ❌ DON'T (피해야 할 것)
레이블은 최소한으로: 5개 이하 권장 고유값 레이블: user_id, request_id 레이블화
시간 범위 제한: 쿼리는 가능한 짧은 시간 범위로 전체 시간 쿼리: 범위 없는 쿼리 금지
구조화된 로그: JSON 포맷 사용 민감 정보 로깅: 비밀번호, API 키 로그 출력
적절한 retention: 30-90일 정도면 충분 과도한 로그: 초당 수천 줄 이상 출력
청크 압축: Gzip 또는 Snappy 사용 latest 태그: 프로덕션에서 특정 버전 사용

레이블 카디널리티 관리

# ❌ 나쁜 예: 카디널리티가 너무 높음
{
  namespace="production",
  app="backend",
  user_id="12345",        # ← 수백만 개의 고유값
  request_id="abc-123",   # ← 수십억 개의 고유값
  ip="192.168.1.1"        # ← 수만 개의 고유값
}
# 결과: 인덱스 크기 폭발, 쿼리 성능 저하
# ✅ 좋은 예: 카디널리티가 낮음
{
  namespace="production",  # ← 5-10개
  app="backend",          # ← 50-100개
  level="error",          # ← 5-6개
  environment="prod"      # ← 3-4개
}
# 세부 정보는 로그 내용(JSON)에 포함
{
  "level": "error",
  "message": "Database connection failed",
  "user_id": "12345",     # ← 로그 내용으로
  "request_id": "abc-123", # ← 로그 내용으로
  "ip": "192.168.1.1"     # ← 로그 내용으로
}

리소스 할당

환경일일 로그량CPUMemoryStorage
개발/테스트~10GB1 core2GB50GB
스테이징10-100GB2 cores4GB500GB
프로덕션(소규모)100-500GB4 cores8GB2TB
프로덕션(대규모)500GB+8+ cores16GB+5TB+ (S3)
# 프로덕션 환경 리소스 설정 예시
singleBinary:
  replicas: 3  # HA 구성
  resources:
    requests:
      cpu: 4
      memory: 8Gi
    limits:
      cpu: 8
      memory: 16Gi
  
  persistence:
    enabled: true
    size: 500Gi
    storageClass: fast-ssd  # 인덱스는 빠른 스토리지에
# S3 스토리지 사용 (청크용)
storage:
  type: s3
  s3:
    endpoint: s3.amazonaws.com
    bucketnames: my-loki-chunks
    region: us-east-1
    # Lifecycle policy로 30일 후 Glacier로 이동

보안 설정

# 멀티테넌시 활성화
auth_enabled: true
# 테넌트별 리미트 설정
limits_config:
  # 테넌트별 ingestion rate 제한
  ingestion_rate_mb: 10  # 10MB/s
  ingestion_burst_size_mb: 20
  
  # 쿼리 리미트
  max_query_length: 721h  # 30일
  max_query_series: 5000
  max_streams_per_user: 10000
  
  # 레이블 리미트 (카디널리티 제어)
  max_label_names_per_series: 15
  max_label_value_length: 2048
# 민감 정보 필터링 (Promtail에서)
scrape_configs:
  - job_name: kubernetes-pods
    pipeline_stages:
      # 비밀번호 마스킹
      - replace:
          expression: '(password["\s:=]+)(\S+)'
          replace: '${1}***REDACTED***'
      
      # API 키 마스킹
      - replace:
          expression: '(api[_-]?key["\s:=]+)(\S+)'
          replace: '${1}***REDACTED***'
      
      # 신용카드 번호 마스킹
      - replace:
          expression: '\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b'
          replace: '****-****-****-****'

8. 트러블슈팅

🔧 Error: "too many outstanding requests"

증상: Grafana에서 쿼리 시 타임아웃 또는 에러

원인: 동시 쿼리 수가 리미트를 초과

해결:

# loki-config.yaml
limits_config:
  max_query_parallelism: 32  # 기본값: 14
  max_outstanding_per_tenant: 2048  # 기본값: 100
  
querier:
  max_concurrent: 10  # 기본값: 4
🔧 Error: "entry out of order"

증상: Promtail 로그에서 "entry out of order" 에러

원인: 타임스탬프가 순서대로 들어오지 않음

해결:

# Option 1: 순서 보장 비활성화 (권장하지 않음)
limits_config:
  reject_old_samples: false
# Option 2: 타임스탬프를 수신 시간으로 변경 (권장)
scrape_configs:
  - job_name: kubernetes-pods
    pipeline_stages:
      # 타임스탬프를 사용하지 않고 현재 시간 사용
      - timestamp:
          source: timestamp
          format: RFC3339
          action_on_failure: skip  # 파싱 실패 시 현재 시간 사용
🔧 Error: "stream limit exceeded"

증상: "maximum active stream limit exceeded" 에러

원인: 레이블 카디널리티가 너무 높음

해결:

# 1. 리미트 늘리기 (임시 방편)
limits_config:
  max_streams_per_user: 10000  # 기본값: 5000
# 2. 레이블 줄이기 (근본 해결)
# ❌ 나쁜 예
scrape_configs:
  - job_name: kubernetes-pods
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod
      - source_labels: [__meta_kubernetes_pod_ip]  # ← 제거
        target_label: pod_ip
      - source_labels: [__meta_kubernetes_pod_uid]  # ← 제거
        target_label: pod_uid
# ✅ 좋은 예: 필요한 레이블만
scrape_configs:
  - job_name: kubernetes-pods
    relabel_configs:
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      - source_labels: [__meta_kubernetes_pod_label_app]
        target_label: app
🔧 Error: "context deadline exceeded"

증상: 쿼리 시 타임아웃

원인: 쿼리 범위가 너무 넓거나 청크가 많음

해결:

# 1. 쿼리 타임아웃 늘리기
limits_config:
  query_timeout: 5m  # 기본값: 1m
# 2. 쿼리 최적화
# ❌ 나쁜 쿼리
{namespace="production"}  # 전체 namespace (너무 넓음)
# ✅ 좋은 쿼리
{namespace="production", app="backend"}  # 특정 앱
  [5m]  # 시간 범위 제한
# 3. Compaction 활성화 (청크 병합)
compactor:
  working_directory: /loki/compactor
  shared_store: s3
  compaction_interval: 10m
🔧 디스크 용량 부족

증상: Loki Pod가 재시작되거나 로그 수집 중단

원인: 인덱스 또는 청크 디스크 부족

해결:

# 1. 현재 사용량 확인
kubectl exec -n monitoring loki-0 -- df -h /loki
# 2. Retention 설정
limits_config:
  retention_period: 336h  # 14일 (기본값: 0 = 무제한)
# 3. Compactor 로그 확인
kubectl logs -n monitoring loki-0 | grep compactor
# 4. 수동 정리 (비상시)
kubectl exec -n monitoring loki-0 -- \
  rm -rf /loki/chunks/fake/*/  # fake는 기본 tenant ID
# 5. S3로 마이그레이션 (권장)
storage:
  type: s3
  s3:
    bucketnames: my-loki-bucket
    # 오래된 데이터는 S3 Glacier로 자동 이동

9. 정리

핵심 요약:

  • Loki는 레이블만 인덱싱하여 Elasticsearch 대비 1/10 수준의 비용으로 운영 가능
  • 파일시스템(S3) 기반 저장소가 데이터베이스보다 단순하고 저렴하며 확장성이 좋음
  • Loki 3.0의 TSDB 인덱스로 쿼리 성능이 2-5배 향상되고 인덱스 크기 30-50% 감소
  • Pattern Ingester로 로그 패턴 자동 감지 및 분석 가능
  • 레이블 카디널리티 관리가 성능의 핵심 (5개 이하 권장)

Grafana Loki는 Kubernetes 환경에서 로그를 수집하고 관리하는 현대적인 솔루션입니다. Elasticsearch와 비교했을 때 훨씬 단순하고 비용 효율적이며, Prometheus와 통합하면 메트릭과 로그를 함께 모니터링할 수 있어요. Loki 3.0의 TSDB 인덱스와 Pattern Ingester 같은 신기능들은 성능과 사용성을 더욱 향상시켰습니다.

무엇보다 중요한 것은 레이블 설계입니다. 5개 이하의 낮은 카디널리티 레이블을 유지하고, 세부 정보는 JSON 로그 내용에 포함하세요. 그리고 S3 같은 오브젝트 스토리지를 적극 활용하면 스토리지 비용을 크게 절감할 수 있습니다.


📚 참고 자료


답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다