이 글에서 다루는 것
dev/prod 완전 분리된 Loki + Promtail 로그 파이프라인을 구축하고, LogQL 기반으로 FastAPI/Airflow 로그를 중앙에서 조회할 수 있는 구조를 만드는 과정을 다룹니다.
선수지식
이 단계에서 해결하려는 문제
운영형 MLOps에서 가장 위험한 장애는 모델이 아닌 로그가 안 보이는 순간이다. FastAPI, MLflow, Airflow가 모두 돌아가도, 로그가 한 번 섞이거나 누락되면 장애 원인을 찾지 못해 운영 자체가 멈춰버린다. dev/prod를 완전히 분리한 Loki + Promtail 로그 파이프라인을 구축해, 모델 핫스왑, 에러 패턴, API 이상 징후까지 한눈에 보이도록 만든다.
🎯 핵심 요약
- 로그 파이프라인 전체 구성: Promtail -> Loki -> Grafana(LogQL), dev/prod 모두 완전 분리
- Ingress + TLS + GitOps 자동 적용
- fastapi-dev / fastapi-prod / airflow-dev / airflow-prod 로그 전부 정상 수집 확인
- Query-Range 기반 로그 조회 (Instant Query 불가 원리 포함)
- LogQL 핵심 패턴 포함
1️⃣ 전체 구조 개요
🧩 전체 파이프라인
[Pod Log] → Promtail → Loki Distributor → Ingester → TSDB → Querier → Grafana
🌱 환경별 완전 분리
loki-dev/loki-prodpromtail-dev/promtail-prodgrafana-dev.local/grafana-prod.local
2️⃣ Loki Helm 구성 (single-binary + NFS PVC)
2-1. 왜 single-binary?
dev/prod 각각 1개의 Loki 인스턴스이므로 single-binary 모드가 최적이다. 필요 시 distributed 모드로 확장 가능하다(distributor, ingester, querier, compactor 분리).
2-2. 도입한 주요 옵션
loki:
commonConfig:
replication_factor: 1
storage:
type: filesystem
filesystem:
chunks_directory: /var/loki/chunks
rules_directory: /var/loki/rules
persistence:
enabled: true
storageClassName: nfs-observability
size: 20Gi
TSDB는 쓰기 빈도가 Prometheus보다 낮아 NFS로도 충분하다. Prometheus와는 반대로, Loki는 NFS 기반으로 오래 보관하는 구조가 안정적이다.
3️⃣ Promtail Helm 구성 (네임스페이스별 + 라벨 기반)
3-1. 핵심: dev/prod 라벨 구분
config:
clients:
- url: http://loki-dev:3100/loki/api/v1/push
snippets:
pipelineStages:
- docker: {}
- labeldrop:
- filename
- match:
selector: '{namespace="fastapi-dev"}'
stages:
- regex:
expression: '.*'
clients.url로 환경별 Loki 엔드포인트 분리match.selector로 네임스페이스 기준 필터링
3-2. worker2 종료 지연 문제 해결
원인: Promtail DaemonSet + NFS Unmount 지연으로 종료 대기 시간 증가
terminationGracePeriodSeconds: 10
종료 대기 시간을 짧게 잡아, 노드 종료 지연을 완화한다.
4️⃣ Grafana / Loki API 쿼리 (LogQL + Instant Query 이슈)
4-1. Instant Query 오류
증상: log queries are not supported as an instant query type
원인: /loki/api/v1/query는 특정 시점의 메트릭 조회용 엔드포인트이다. 로그는 시간 범위를 지정하는 스트림 데이터이므로 Instant Query 개념 자체와 충돌한다.
4-2. Query Range 사용
curl -G http://loki-dev:3100/loki/api/v1/query_range \
--data-urlencode 'query={namespace="fastapi-dev"}' \
--data-urlencode 'start=1732440000000000000' \
--data-urlencode 'end=1732440600000000000'
start, end는 나노초 단위 Unix 타임스탬프이다. 범위를 지정해야만 로그 스트림이 올바르게 조회된다.
5️⃣ Grafana Log Dashboard 구성
5-1. FastAPI 전용 관측판
패널 구성 예시: 최근 1시간 FastAPI 로그 스트림, level="ERROR" 필터 패널, /variant/{alias} API 로그 상태 패널, 모델 reload 성공/실패 로그 패널.
5-2. 대표 LogQL 예시
fastapi-dev 전체 로그:
{namespace="fastapi-dev", container="fastapi"}
ERROR만:
{namespace="fastapi-dev"} |= "ERROR"
모델 Reload 성공/실패 필터:
{namespace="fastapi-dev"} |= "Reload"
이상 요청 탐지 (5xx 응답):
{namespace="fastapi-prod"} |= "predict" |~ "5\d{2}"
6️⃣ GitOps 기반 전체 구조
6-1. ArgoCD Application (loki-dev 예시)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: loki-dev
spec:
project: dev
source:
repoURL: https://grafana.github.io/helm-charts
chart: loki
targetRevision: 5.41.3
helm:
values: |
loki:
commonConfig:
replication_factor: 1
persistence:
enabled: true
storageClassName: nfs-observability
destination:
namespace: loki-dev
server: https://kubernetes.default.svc
prod는 project: prod, namespace: loki-prod로 동일 구조.
6-2. Promtail Application (values 예시)
promtail:
config:
clients:
- url: http://loki-dev:3100/loki/api/v1/push
positions:
filename: /var/log/positions.yaml
persistence:
enabled: true
storageClassName: nfs-observability
dev/prod 각각 별도 namespace + 별도 PVC로 구성한다. positions.yaml이 환경 간 섞이지 않도록 분리해야 한다.
7️⃣ Ingress + TLS + 인증
Grafana/Alertmanager와 동일한 패턴을 사용한다.
ingress:
enabled: true
className: nginx
hosts:
- loki-dev.local
tls:
- secretName: loki-tls
hosts:
- loki-dev.local
prod: loki-prod.local, loki-prod-tls 등으로 분리. TLS Secret은 SealedSecret 기반으로 Git에 저장 가능하다.
8️⃣ 대표 검증 명령
# fastapi-dev 로그 확인 (Loki API)
curl -G http://loki-dev:3100/loki/api/v1/query_range \
--data-urlencode 'query={namespace="fastapi-dev"}' \
--data-urlencode 'limit=50'
# Promtail -> Loki Path 확인
kubectl -n promtail-dev logs daemonset/promtail | grep "loki-dev"
# Loki 스토리지 사용량 확인
kubectl -n loki-dev exec -it deploy/loki-dev -- df -h | grep loki
🧩 팁
- 로그 필터링에서 가장 중요한 축은 namespace + container
- FastAPI 로그 포맷이 일관되면, Promtail에서 전용 파서(logfmt/json/datetime regex)를 붙여 메트릭/라벨로 뽑을 수 있음
- Promtail
positions.yaml이 NFS 전역에서 공유되면 오염 가능성이 있으므로, dev/prod 각각 PVC를 분리하는 것이 안전 - LogQL은
|=(substring),|~(regex),!~(negative regex) 이 3개만으로도 실제 운영에서 80~90%는 해결 가능
설계 판단 (Why This Way?)
Loki는 single-binary로 시작하여 운영 복잡도를 최소화하고, append-only 쓰기 패턴에 적합한 NFS를 스토리지로 선택했습니다. Promtail의 positions.yaml을 dev/prod로 분리하여 로그 읽기 오프셋 공유로 인한 누락·중복 수집을 방지합니다.
다음에 읽을 글
→ Observability 5단계: 운영 중 실제 이슈 & 해결 과정 — 실전 트러블슈팅 사례