이 글에서 다루는 것
Pod 재시작 시 로그가 유실되지 않도록 NFS 기반 PV/PVC를 구성하고, Loguru로 구조화된 파일 로깅을 적용하여 FastAPI 서빙 로그를 영속화하는 과정을 다룹니다.
선수지식
- MLOps 운영 고도화 3단계: 모델 롤백 자동화 — 스냅샷 기반 자동 롤백
이 단계에서 해결하려는 문제
FastAPI Pod가 재시작되거나 스케일 다운되면 컨테이너 내부에 쌓인 로그가 모두 사라진다. 핫스왑 이력, 예측 요청 기록, 에러 트레이스가 유실되면 장애 원인을 추적할 수 없다. Pod 라이프사이클과 독립적으로 로그를 보존하는 구조가 필요하다.
🎯 핵심 요약
- NFS PV/PVC로 FastAPI 로그 디렉토리(
/app/logs)를 Pod 외부에 영속화 - Loguru로 파일 로테이션(일별 10MB) + retention(7일) + 구조화 포맷 적용
- LOG_TO_STDOUT 환경변수로 콘솔 이중 출력 제어 (dev: true, prod: false)
- dev/prod PVC 분리:
fastapi-logs-pvc-dev/fastapi-logs-pvc-prod
1️⃣ 전체 구조
NFS Server (/mnt/nfs_share/mlops/fastapi-logs/)
├─ dev/ ← fastapi-logs-pvc-dev
└─ prod/ ← fastapi-logs-pvc-prod
FastAPI Pod
└─ /app/logs/ (PVC 마운트)
├─ fastapi.log (현재 로그)
├─ fastapi.log.2025-08-07 (전일 로그)
└─ ...
2️⃣ PV / PVC 구성
PersistentVolume (NFS)
# envs/dev/pv/fastapi-logs-pv-dev.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: fastapi-logs-pv-dev
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.18.141
path: /mnt/nfs_share/mlops/fastapi-logs/dev
PersistentVolumeClaim
# envs/dev/pvc/fastapi-logs-pvc-dev.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fastapi-logs-pvc-dev
namespace: fastapi-dev
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
volumeName: fastapi-logs-pv-dev
persistentVolumeReclaimPolicy: Retain— ArgoCD prune 시에도 로그 아티팩트 보호
3️⃣ Helm Values — 볼륨 마운트
# charts/fastapi/values/dev.yaml (발췌)
volumes:
- name: logs
persistentVolumeClaim:
claimName: fastapi-logs-pvc-dev
volumeMounts:
- name: logs
mountPath: /app/logs
securityContext:
fsGroup: 1000
runAsUser: 1000
fsGroup/runAsUser설정으로 NFS 디렉토리 쓰기 권한을 보장한다.
4️⃣ Loguru 설정
# charts/fastapi/app/utils/logger.py
import os
from loguru import logger
import sys
LOG_DIR = "/app/logs"
LOG_FILE = os.path.join(LOG_DIR, "fastapi.log")
# 파일 로깅: 일별 로테이션, 10MB 제한, 7일 보관
logger.add(
LOG_FILE,
rotation="10 MB",
retention="7 days",
compression="gz",
format="{time:YYYY-MM-DD HH:mm:ss} | {level:<8} | {name}:{function}:{line} | {message}",
level="INFO",
enqueue=True, # 비동기 쓰기 (NFS I/O 지연 완화)
)
# 환경변수로 콘솔 이중 출력 제어
if os.environ.get("LOG_TO_STDOUT", "false").lower() == "true":
logger.add(sys.stdout, level="DEBUG",
format="{time:HH:mm:ss} | {level:<8} | {message}")
주요 설정 포인트
| 설정 | 값 | 이유 |
|---|---|---|
enqueue=True | 비동기 쓰기 | NFS I/O 지연이 요청 처리를 차단하지 않도록 |
rotation="10 MB" | 파일 크기 제한 | 단일 파일이 과도하게 커지는 것을 방지 |
retention="7 days" | 보관 기간 | 디스크 고갈 방지 + 최근 장애 추적에 충분 |
compression="gz" | 로테이션된 파일 압축 | NFS 스토리지 절약 |
5️⃣ FastAPI 코드에서 사용
# routes/predict.py, routes/reload.py 등
from utils.logger import logger
# 예측 로그
logger.info(f"[Predict] alias={alias}, mode={mode}, client_id={client_id}")
# 핫스왑 로그
logger.info(f"[Reload] alias={alias} 모델 교체 완료: v{version}")
# 에러 로그
logger.exception("예측 실패")
6️⃣ 검증
# PVC 상태 확인
kubectl -n fastapi-dev get pvc fastapi-logs-pvc-dev
# Pod 내 로그 파일 확인
kubectl -n fastapi-dev exec $(kubectl -n fastapi-dev get po -l app=fastapi-dev -o name | head -1) \
-- ls -la /app/logs/
# NFS 서버에서 직접 확인
ls -la /mnt/nfs_share/mlops/fastapi-logs/dev/
# 로그 내용 확인
kubectl -n fastapi-dev exec $(kubectl -n fastapi-dev get po -l app=fastapi-dev -o name | head -1) \
-- tail -20 /app/logs/fastapi.log
7️⃣ 체크리스트
- PV/PVC 생성 및
Bound상태 확인 (dev/prod 각각) - FastAPI Deployment에
volumeMounts설정 반영 -
fsGroup/runAsUser설정으로 NFS 쓰기 권한 확보 -
LOG_TO_STDOUT환경변수 dev/prod 차별 설정 - Pod 재시작 후에도 이전 로그 파일이 유지되는지 확인
🧩 팁
- NFS 마운트 실패 시 Pod가
ContainerCreating에서 멈춘다 — NFS 서버 접근 가능 여부를 먼저 확인 - **Loguru
enqueue=True**는 비동기 버퍼이므로, Pod가 갑자기 종료되면 마지막 몇 줄이 유실될 수 있다 — 크리티컬 이벤트는 Slack 알림으로 이중 보장 - prod 환경에서는
LOG_TO_STDOUT=false로 설정하여 stdout 노이즈를 줄이되, Loki/Promtail 연동 시에는 stdout도 활성화 고려
설계 판단 (Why This Way?)
NFS PVC를 선택하여 Pod 라이프사이클과 독립적인 로그 보존과 ReadWriteMany 동시 쓰기를 확보했고, Loguru의 enqueue=True 비동기 쓰기로 NFS I/O 지연이 FastAPI 요청 처리를 차단하지 않도록 했습니다.
다음에 읽을 글
→ MLOps 운영 고도화 5단계: Airflow 안정화 & FastAPI HTTPS — TLS 보안과 스케줄러 안정화