Kubernetes 환경에서 애플리케이션이 하나뿐일 때는 kubectl logs만으로도 충분할 수 있다.
하지만 실제 운영 환경에서는 상황이 달라진다.
FastAPI Pod가 여러 개 실행되고 있고, Nginx Ingress가 요청을 처리하며, PostgreSQL과 Redis 같은 인프라도 함께 운영된다면 장애가 발생했을 때 여러 Pod의 로그를 각각 확인해야 한다.
예를 들어 FastAPI Pod가 다음과 같이 3개 실행되고 있다고 가정해보자.
backend-7d8f6c9f5-abcde
backend-7d8f6c9f5-fghij
backend-7d8f6c9f5-klmno
사용자 요청이 어느 Pod로 전달되었는지 모르는 상황에서 특정 요청의 오류를 찾으려면 각각의 로그를 확인해야 한다.
kubectl logs backend-7d8f6c9f5-abcde
kubectl logs backend-7d8f6c9f5-fghij
kubectl logs backend-7d8f6c9f5-klmno
Pod가 수십 개, 수백 개로 증가하면 이런 방식은 사실상 불가능하다.
이 문제를 해결하기 위해 사용하는 대표적인 기술이 **로그 중앙화(Log Centralization)**다.
이번 실습에서는 다음 구조를 구축한다.
Kubernetes Cluster
|
+------------------+------------------+
| | |
FastAPI Pod Nginx Pod Other Pods
| | |
+------------------+------------------+
|
Container Logs
|
Node Log Collector
|
Grafana Alloy
|
v
+-------------+
| Loki |
| Log Storage |
+-------------+
|
v
+-------------+
| Grafana |
| Log Search |
+-------------+
|
v
장애 원인 분석
먼저 Kubernetes 로그 구조를 이해해야 한다.
컨테이너가 stdout 또는 stderr로 로그를 출력하면 Kubernetes가 해당 로그를 관리한다.
예를 들어 FastAPI에서 다음과 같이 로그를 출력한다고 하자.
print("API request received")
또는 Python logging을 사용한다.
import logging
logger = logging.getLogger(__name__)
logger.info("API request received")
logger.error("Database connection failed")
컨테이너에서 출력된 로그는 일반적으로 Node의 컨테이너 로그 파일로 저장된다.
대표적으로 다음과 같은 형태를 생각할 수 있다.
/var/log/containers/
그리고 Kubernetes의 Pod 로그는 다음 명령으로 확인할 수 있다.
kubectl logs <pod-name>
예:
kubectl logs backend-7d8f6c9f5-abcde
실시간 로그는 다음과 같이 확인한다.
kubectl logs -f backend-7d8f6c9f5-abcde
문제는 이 로그가 기본적으로 중앙 로그 시스템이 아니라 해당 Kubernetes 환경의 Node와 컨테이너 로그 구조에 의존한다는 것이다.
따라서 운영 환경에서는 별도의 로그 수집 시스템을 구축하는 것이 좋다.
운영 서버에서 장애가 발생했다고 생각해보자.
사용자가 다음과 같은 오류를 보고한다.
로그인 버튼을 눌렀는데 500 오류가 발생합니다.
운영자는 다음을 확인해야 한다.
Nginx
↓
Ingress
↓
FastAPI
↓
Redis
↓
PostgreSQL
문제가 어디에서 발생했는지 확인해야 한다.
기존 방식이라면 다음처럼 각각 확인해야 한다.
kubectl logs nginx-pod
kubectl logs backend-pod-1
kubectl logs backend-pod-2
kubectl logs backend-pod-3
kubectl logs redis-pod
로그 중앙화 환경에서는 Grafana에서 한 번에 검색할 수 있다.
namespace="tax-prod"
app="backend"
level="error"
결과:
07:21:31 Database connection failed
07:21:31 sqlalchemy.exc.OperationalError
07:21:32 POST /api/login 500
즉,
문제 발생
↓
로그 검색
↓
오류 로그 확인
↓
관련 Pod 확인
↓
원인 분석
과정을 하나의 화면에서 수행할 수 있다.
Loki는 Grafana 생태계에서 사용하는 로그 집계 시스템이다.
Prometheus와 비교하면 이해하기 쉽다.
| 시스템 | 목적 |
|---|---|
| Prometheus | Metrics 저장 |
| Loki | Logs 저장 |
| Grafana | Metrics/Logs 시각화 |
| Alertmanager | Alert 전달 |
구조적으로 보면 다음과 같다.
Metrics
Application
|
v
Prometheus
|
v
Grafana
Logs
Application
|
v
Alloy
|
v
Loki
|
v
Grafana
Loki의 중요한 특징은 로그 전체를 무겁게 인덱싱하기보다는 라벨(label)을 중심으로 로그를 찾는 구조라는 점이다.
예를 들어 다음과 같은 라벨을 사용할 수 있다.
namespace="tax-prod"
app="backend"
container="backend"
pod="backend-xxx"
그리고 실제 로그 내용은 Loki에서 조회한다.
로그 중앙화라고 하면 Elasticsearch도 많이 사용한다.
대표적인 구성은 다음과 같다.
Fluent Bit
↓
Elasticsearch
↓
Kibana
또 다른 대표적인 구성은 다음과 같다.
Grafana Alloy
↓
Loki
↓
Grafana
간단하게 비교하면 다음과 같다.
| 항목 | Loki | Elasticsearch |
| Grafana 연동 | 매우 좋음 | 가능 |
| 구조 | Label 중심 | 검색/인덱스 중심 |
| 운영 난이도 | 상대적으로 낮음 | 상대적으로 높음 |
| Kubernetes 로그 | 적합 | 적합 |
| Metrics 연계 | Prometheus와 매우 자연스러움 | 별도 구성 |
| 리소스 사용 | 비교적 가벼움 | 상대적으로 큼 |
소규모 또는 중간 규모 Kubernetes 환경에서 Prometheus + Grafana를 이미 사용하고 있다면 Loki를 추가하는 방식이 매우 자연스럽다.
Loki에 로그를 보내기 위해 로그 수집기가 필요하다.
과거에는 Promtail이 대표적인 선택이었다.
구조는 다음과 같다.
Node
|
+-- /var/log/containers
|
v
Promtail
|
v
Loki
하지만 현재 Grafana 생태계에서는 Grafana Alloy를 새로운 텔레메트리 수집기로 사용하는 방향이 중요하다.
따라서 새로운 환경을 구축한다면 Alloy 중심으로 설계하는 것을 권장할 수 있다.
개념적으로는 다음과 같다.
Kubernetes Node
|
| Container Logs
v
Grafana Alloy
|
| Push
v
Loki
|
v
Grafana
기존 환경에서 Promtail을 이미 사용하고 있다면 당장 모두 변경해야 한다는 의미는 아니다.
다만 신규 구축에서는 Alloy를 우선 검토하는 것이 좋다.
이번 실습에서는 다음과 같은 환경을 가정한다.
Kubernetes Cluster
namespace: tax-prod
Internet
|
v
Ingress
|
v
Nginx
|
v
FastAPI Service
|
+------+------+
| |
v v
FastAPI-1 FastAPI-2
| |
+------+------+
|
PostgreSQL
|
Redis
Logging
FastAPI/Nginx/PostgreSQL/Redis
|
v
Container stdout
|
v
Kubernetes Node
|
v
Grafana Alloy
|
v
Loki
|
v
Grafana
Kubernetes 환경에서는 Helm을 사용하는 것이 편리하다.
먼저 Grafana Helm Repository를 등록한다.
helm repo add grafana https://grafana.github.io/helm-charts
Repository 정보를 업데이트한다.
helm repo update
Loki용 Namespace를 생성한다.
kubectl create namespace logging
Namespace를 확인한다.
kubectl get namespace
실제 운영 환경에서는 Loki의 저장 방식과 복제 구조를 충분히 검토해야 한다.
학습 및 테스트 환경에서는 비교적 간단한 구성으로 시작할 수 있다.
예를 들어 Helm values 파일을 만든다.
vi loki-values.yaml
예제:
deploymentMode: SingleBinary
loki:
auth_enabled: false
singleBinary:
replicas: 1
persistence:
enabled: true
size: 20Gi
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 1Gi
read:
replicas: 0
write:
replicas: 0
backend:
replicas: 0
설치한다.
helm upgrade --install loki grafana/loki \
-n logging \
-f loki-values.yaml
설치 상태를 확인한다.
kubectl get pods -n logging
예:
NAME READY STATUS
loki-0 1/1 Running
Service도 확인한다.
kubectl get svc -n logging
Loki는 로그를 저장해야 하기 때문에 운영 환경에서는 Storage 설계가 중요하다.
개념적으로 다음과 같다.
Loki
|
+-- Index
|
+-- Chunks
|
+-- Object Storage
개발 환경에서는 로컬 Persistent Volume을 사용할 수 있지만 운영 환경에서는 Object Storage 기반 구성을 고려하는 것이 일반적이다.
예:
Loki
|
+---- Object Storage
|
+-- S3
+-- MinIO
+-- Cloud Storage
따라서 운영 환경에서는 다음을 고려해야 한다.
로그 보존 기간
로그 용량
스토리지 비용
복제
백업
장애 복구
앞서 Prometheus + Grafana를 구축했다면 Grafana에 Loki Data Source를 추가한다.
Grafana에서:
Connections
↓
Data Sources
↓
Add data source
↓
Loki
Loki Service 주소를 입력한다.
예:
http://loki.logging.svc.cluster.local:3100
Kubernetes DNS 구조는 다음과 같다.
loki
|
+-- logging namespace
|
+-- port 3100
따라서 클러스터 내부에서는 다음 주소로 접근할 수 있다.
http://loki.logging.svc.cluster.local:3100
연결 테스트 후 정상적으로 연결되면 Grafana에서 Loki 로그를 조회할 수 있다.
이제 로그 수집기를 구성한다.
Alloy는 Kubernetes Node의 컨테이너 로그를 읽어서 Loki로 전달하는 역할을 한다.
구조는 다음과 같다.
/var/log/containers/*.log
|
v
Grafana Alloy
|
v
Loki
Alloy는 모든 Node에서 로그를 수집해야 하기 때문에 일반적으로 DaemonSet 형태로 실행한다.
Node 1
└── Alloy
Node 2
└── Alloy
Node 3
└── Alloy
Helm을 이용하여 설치할 수 있다.
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
Namespace를 준비한다.
kubectl create namespace logging
환경에 맞는 Alloy Helm values를 구성한 뒤 설치한다.
helm upgrade --install alloy grafana/alloy \
-n logging \
-f alloy-values.yaml
확인한다.
kubectl get pods -n logging -o wide
정상이라면 각 Node에 Alloy Pod가 실행된다.
NAME NODE
alloy-abc node-01
alloy-def node-02
alloy-ghi node-03
Alloy가 수행하는 역할을 단계별로 보면 다음과 같다.
1. Node 로그 파일 탐색
↓
2. Kubernetes metadata 확인
↓
3. namespace 확인
↓
4. pod 확인
↓
5. container 확인
↓
6. label 생성
↓
7. Loki로 전송
예를 들어 실제 로그가 다음과 같다고 하자.
2026-09-10 07:20:31 Database connection failed
Alloy가 Kubernetes metadata를 붙인다.
namespace=tax-prod
pod=backend-7d8f6c9f5-abcde
container=backend
app=backend
그러면 Grafana에서 다음처럼 검색할 수 있다.
namespace="tax-prod"
app="backend"
FastAPI는 로그를 stdout으로 출력하는 구조로 만드는 것이 중요하다.
예:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s"
)
logger = logging.getLogger(__name__)
API에서 로그를 출력한다.
@app.get("/health")
def health():
logger.info("health check")
return {"status": "ok"}
오류 발생 시:
logger.error("Database connection failed")
중요한 점은 로그를 컨테이너 내부 파일에만 저장하지 않는 것이다.
좋은 Kubernetes 애플리케이션 구조는 다음과 같다.
FastAPI
|
+-- stdout
|
v
Kubernetes
|
v
Alloy
|
v
Loki
Nginx에서도 access log와 error log를 확인할 수 있다.
예:
10.0.0.15 - - [10/Sep/2026:07:21:31 +0900]
"GET /api/users HTTP/1.1" 200
오류가 발생하면:
"GET /api/users HTTP/1.1" 502
이런 로그는 장애 분석에서 매우 중요하다.
예를 들어:
Nginx
|
+-- 502 Bad Gateway
라면 다음 단계로 FastAPI Pod 상태를 확인해야 한다.
Grafana에서:
Explore
↓
Loki
를 선택한다.
가장 기본적인 검색은 다음과 같다.
{namespace="tax-prod"}
특정 애플리케이션:
{namespace="tax-prod", app="backend"}
특정 Pod:
{pod="backend-7d8f6c9f5-abcde"}
Nginx:
{app="nginx"}
로그 내용에 error가 포함된 로그를 검색할 수도 있다.
{namespace="tax-prod"} |= "error"
대소문자를 고려하여 애플리케이션 로그 구조에 맞게 검색하는 것이 좋다.
예:
{app="backend"} |= "ERROR"
Database 오류:
{app="backend"} |= "Database"
HTTP 500:
{app="backend"} |= "500"
Nginx 로그를 검색한다고 하자.
{app="nginx"} |= "500"
또는:
{app="nginx"} |= "502"
다음과 같이 장애 상황을 빠르게 확인할 수 있다.
Nginx
|
+-- 200
+-- 200
+-- 200
+-- 502
+-- 502
+-- 502
502가 갑자기 증가했다면 Backend 문제일 가능성이 높다.
FastAPI에서 다음과 같은 로그가 발생했다고 가정한다.
ERROR Database connection failed
ERROR sqlalchemy.exc.OperationalError
ERROR POST /api/users 500
Grafana에서:
{app="backend"} |= "500"
으로 검색한다.
그다음 같은 시간대의 로그를 확인한다.
07:21:31 Database connection failed
07:21:31 OperationalError
07:21:31 POST /api/users 500
여기에서 단순히 HTTP 500이라는 결과만 보는 것이 아니라 그 앞에서 발생한 데이터베이스 오류까지 추적할 수 있다.
로그 중앙화의 핵심은 Kubernetes metadata다.
예를 들어 다음과 같은 정보가 있다고 하자.
namespace = tax-prod
deployment = backend
pod = backend-xxx
container = backend
node = worker-01
장애가 발생했을 때:
Grafana
|
+-- namespace
+-- deployment
+-- pod
+-- container
+-- node
를 기준으로 로그를 좁혀갈 수 있다.
예:
{namespace="tax-prod", container="backend"}
특정 Node:
{node="worker-01"}
특정 Pod:
{pod="backend-xxx"}
이 부분이 Kubernetes 모니터링에서 매우 중요하다.
Prometheus는 숫자를 본다.
CPU
Memory
Request
Error Rate
Pod Count
Loki는 로그를 본다.
ERROR
Exception
Connection failed
Timeout
Grafana에서는 이 둘을 함께 사용할 수 있다.
예를 들어 Prometheus에서:
FastAPI HTTP 500 증가
가 확인되었다고 하자.
그렇다면 Loki에서 같은 시간대를 검색한다.
{app="backend"} |= "500"
결과:
Database connection timeout
다시 PostgreSQL 로그를 검색한다.
{app="postgres"} |= "connection"
결과:
too many connections
장애 원인이 연결 수 부족이라는 것을 확인할 수 있다.
즉,
Prometheus
|
| "500 오류 증가"
v
Grafana
|
v
Loki
|
| "Database timeout"
v
PostgreSQL 로그
|
v
장애 원인 확인
이것이 Observability의 핵심이다.
실제 장애 상황을 만들어보자.
먼저 Pod를 확인한다.
kubectl get pods -n tax-prod
예:
backend-abc Running
backend-def Running
Pod 하나를 삭제한다.
kubectl delete pod backend-abc -n tax-prod
Deployment가 새로운 Pod를 생성한다.
kubectl get pods -n tax-prod -w
이때 Loki에서 해당 Pod 로그를 확인한다.
{namespace="tax-prod", app="backend"}
기존 Pod와 새 Pod의 로그를 비교할 수 있다.
의도적으로 애플리케이션을 종료하도록 만들어보자.
예:
raise Exception("Intentional failure")
Pod 상태가 다음과 같이 변경될 수 있다.
CrashLoopBackOff
확인:
kubectl get pods -n tax-prod
그리고:
kubectl logs <pod-name> -n tax-prod
Grafana Loki에서도 오류 로그를 확인한다.
{app="backend"} |= "Intentional failure"
이렇게 Kubernetes 상태와 실제 애플리케이션 로그를 연결할 수 있다.
Backend가 정상적으로 응답하지 않는 상황을 만들어보자.
구조:
Client
|
v
Nginx
|
X
FastAPI
Nginx에서는:
502 Bad Gateway
가 발생할 수 있다.
Grafana에서:
{app="nginx"} |= "502"
검색한다.
그리고 Backend 로그:
{app="backend"}
를 같은 시간대에 확인한다.
이렇게 하면:
Nginx 502
↓
Backend connection failure
↓
FastAPI Pod 장애
와 같은 흐름을 확인할 수 있다.
PostgreSQL Pod를 삭제해본다.
kubectl delete pod <postgres-pod> -n tax-prod
PostgreSQL이 재기동되는 동안 FastAPI에서 DB 요청을 발생시킨다.
FastAPI 로그:
Database connection failed
connection refused
timeout
Grafana에서:
{app="backend"} |= "connection"
을 검색한다.
PostgreSQL 로그도 확인한다.
{app="postgres"}
이렇게 하면 하나의 장애를 여러 컴포넌트의 로그를 통해 추적할 수 있다.
운영자가 가장 많이 사용하는 방식으로 정리하면 다음과 같다.
① 사용자 장애 신고
|
v
② Grafana Dashboard
|
v
③ Prometheus Metrics 확인
|
v
④ Error Rate 증가 확인
|
v
⑤ Loki 로그 검색
|
v
⑥ 오류 발생 Pod 확인
|
v
⑦ Kubernetes 상태 확인
|
v
⑧ 관련 서비스 로그 확인
|
v
⑨ 근본 원인 분석
예를 들어:
500 증가
↓
FastAPI 로그 검색
↓
DB Timeout
↓
PostgreSQL 로그 확인
↓
Connection Limit 초과
↓
DB 연결 관리 설정 확인
이런 방식으로 장애를 좁혀간다.
로그 중앙화 시스템을 구축하는 것만으로 좋은 운영 환경이 만들어지는 것은 아니다.
애플리케이션 로그 자체가 잘 설계되어야 한다.
좋은 로그는 최소한 다음 정보를 포함하는 것이 좋다.
시간
로그 레벨
서비스
요청 정보
사용자 요청
처리 결과
오류 정보
Trace ID
예:
2026-09-10T07:21:31
ERROR
backend
POST /api/users
request_id=abc123
Database connection failed
가능하다면 JSON 로그가 더 좋다.
{
"timestamp": "2026-09-10T07:21:31+09:00",
"level": "ERROR",
"service": "backend",
"method": "POST",
"path": "/api/users",
"request_id": "abc123",
"message": "Database connection failed"
}
이렇게 하면 로그 검색과 분석이 훨씬 쉬워진다.
대표적인 로그 레벨은 다음과 같다.
| Level | 의미 |
| DEBUG | 상세 디버깅 |
| INFO | 정상적인 주요 동작 |
| WARNING | 주의가 필요한 상황 |
| ERROR | 오류 발생 |
| CRITICAL | 심각한 장애 |
운영 환경에서 모든 로그를 DEBUG로 남기면 로그 양이 지나치게 증가할 수 있다.
일반적으로:
개발
DEBUG
운영
INFO / WARNING / ERROR
와 같이 환경별로 로그 레벨을 조절하는 방법을 고려할 수 있다.
로그는 계속 쌓인다.
예를 들어 하루에 10GB의 로그가 발생한다면:
1일 = 10GB
7일 = 70GB
30일 = 300GB
따라서 반드시 보존 정책을 정해야 한다.
예:
Application Log
↓
7일 Hot Log
↓
30일 Archive
↓
90일 삭제
운영 환경에서는 업무와 보안 요구사항에 따라 보존 기간을 결정해야 한다.
로그 중앙화 시스템에서는 불필요한 로그를 줄이는 것도 중요하다.
예를 들어 다음과 같은 로그가 하루 수백만 건 발생한다고 하자.
GET /health 200
Kubernetes readiness/liveness probe 때문에 발생하는 로그라면 실제 장애 분석에는 큰 도움이 되지 않을 수 있다.
따라서 다음과 같은 방법을 고려할 수 있다.
불필요한 DEBUG 로그 제거
Health Check 로그 제외
로그 레벨 조정
Label 개수 최소화
보존 기간 설정
특히 Loki에서는 label을 지나치게 많이 생성하지 않는 것이 중요하다.
예를 들어 다음처럼 사용자 ID를 Label로 사용하는 것은 좋지 않다.
user_id=123456
user_id=123457
user_id=123458
...
값의 종류가 무한히 증가하는 Label을 사용하면 문제가 발생할 수 있다.
이런 값을 흔히 High Cardinality 문제라고 한다.
따라서 Label은 비교적 제한된 값을 사용하는 것이 좋다.
좋은 예:
namespace
app
container
node
environment
주의해야 할 예:
user_id
request_id
order_id
session_id
이런 정보는 로그 본문에 넣고 필요할 때 검색하는 방식을 고려한다.
이제 지금까지 구축한 모니터링 시스템을 하나로 합쳐보자.
Kubernetes
|
+-------------+-------------+
| |
v v
Application Kubernetes
| Metrics
| |
v v
stdout Prometheus
|
v
Alloy
|
v
Loki
| |
+-------------+-------------+
|
v
Grafana
|
+----------+----------+
| | |
v v v
Metrics Logs Alerts
여기에 Alertmanager까지 연결한다.
Prometheus
|
v
Alertmanager
|
+-- Email
+-- Slack
+-- Webhook
+-- 기타 알림
이렇게 구성하면 Kubernetes의 관측성이 크게 향상된다.
운영 환경에서는 Grafana Dashboard를 다음처럼 구성할 수 있다.
+------------------------------------------------+
| Kubernetes Overview |
+------------------------------------------------+
| CPU | Memory | Pod Status |
| 62% | 71% | 20/20 |
+------------------------------------------------+
| Request Rate | Error Rate | Latency |
| 320 req/s | 0.8% | 120ms |
+------------------------------------------------+
| Recent Errors |
| Database timeout |
| Redis connection refused |
| HTTP 500 |
+------------------------------------------------+
| Logs |
| 07:21:31 ERROR Database connection failed |
| 07:21:32 ERROR POST /api/users 500 |
+------------------------------------------------+
운영자는 Dashboard 하나에서 다음을 확인할 수 있다.
서버 상태
Pod 상태
CPU
Memory
Request
Error
Latency
Logs
예를 들어 CPU가 95%라고 하자.
Metrics만 보면:
CPU = 95%
까지만 알 수 있다.
하지만 Loki에서 로그를 확인하면:
ERROR
Worker timeout
Request processing delayed
를 확인할 수 있다.
반대로 로그에 다음이 있다고 하자.
Database connection timeout
Metrics를 보면:
PostgreSQL Connection
95%
일 수 있다.
즉,
Metrics = 무슨 문제가 발생했는가?
Logs = 왜 문제가 발생했는가?
라는 관점으로 생각하면 이해하기 쉽다.
실제 운영에서는 둘을 함께 사용하는 것이 중요하다.
지금까지 배운 내용을 정리하면 Kubernetes Observability는 크게 세 가지로 볼 수 있다.
Observability
|
+----------+----------+
| | |
v v v
Metrics Logs Traces
| | |
Prometheus Loki Tempo
| | |
+----------+----------+
|
Grafana
CPU
Memory
Request
Error Rate
Latency
Pod Count
ERROR
Exception
Timeout
Connection refused
Request
↓
Nginx
↓
FastAPI
↓
Redis
↓
PostgreSQL
이번 실습에서는 Metrics와 Logs를 구축했다.
다음 단계에서는 Distributed Tracing을 추가할 수 있다.
이번 실습의 최종적인 Kubernetes 운영 구조를 정리하면 다음과 같다.
Internet
|
v
Kubernetes
|
+-------------+-------------+
| |
v v
Ingress Applications
| |
v +------+------+
Nginx | |
| FastAPI Redis
v
FastAPI
|
v
PostgreSQL
Metrics
--------
Node / Pod / Application
|
v
Prometheus
|
v
Grafana
Logs
--------
Containers
|
v
Grafana Alloy
|
v
Loki
|
v
Grafana
Alerts
--------
Prometheus
|
v
Alertmanager
|
+---- Email
+---- Slack
+---- Webhook
이제 단순히 Kubernetes에 애플리케이션을 배포하는 단계에서 벗어나 운영 상태를 관측하고 장애를 분석할 수 있는 환경이 만들어진다.
Kubernetes 로그 중앙화를 구축했다면 다음 항목을 확인해야 한다.
kubectl get pods -n logging
Alloy가 모든 Node에서 실행되는지 확인한다.
kubectl get pods -n logging
kubectl get svc -n logging
Loki Data Source가 정상인지 확인한다.
{namespace="tax-prod"}
{namespace="tax-prod"} |= "ERROR"
{pod="backend-xxx"}
kubectl delete pod <backend-pod> -n tax-prod
Pod 재생성 이후 Grafana에서 새로운 Pod 로그가 정상적으로 나타나는지 확인한다.
이번 실습에서는 Kubernetes 로그 중앙화의 기본 구조를 살펴봤다.
핵심 구성 요소는 다음과 같다.
Grafana Alloy
↓
Loki
↓
Grafana
각각의 역할은 명확하다.
Alloy
= 로그 수집
Loki
= 로그 저장 및 조회
Grafana
= 로그 시각화 및 검색
그리고 이전에 구축한 시스템과 연결하면:
Prometheus
= Metrics
Loki
= Logs
Alertmanager
= Alerts
Grafana
= 통합 Dashboard
구조가 된다.
가장 중요한 것은 단순히 로그를 저장하는 것이 아니다.
실제 운영에서 중요한 것은 다음과 같은 장애 분석 과정이다.
사용자 오류 발생
↓
Grafana 확인
↓
Error Rate 증가
↓
Loki 로그 검색
↓
FastAPI 오류 확인
↓
PostgreSQL 로그 확인
↓
DB Connection 문제 발견
↓
Kubernetes 상태 확인
↓
근본 원인 해결
이 과정을 자동화하고 체계화하면 Kubernetes 환경에서 장애가 발생했을 때 단순히 "Pod가 죽었습니다"라고 확인하는 수준을 넘어 언제, 어떤 서비스에서, 어떤 요청이, 어떤 이유로 실패했는지 추적할 수 있게 된다.
특히 시스템 엔지니어에게 중요한 것은 다음 세 가지를 연결해서 보는 능력이다.
Metrics
|
v
이상 현상 발견
|
v
Logs
|
v
오류 원인 확인
|
v
Kubernetes
|
v
실제 장애 조치
여기에 다음 단계로 Distributed Tracing을 추가하면 다음과 같은 완성된 Observability 구조를 만들 수 있다.
Kubernetes
|
+------------+------------+
| | |
v v v
Metrics Logs Traces
| | |
Prometheus Loki Tempo
| | |
+------------+------------+
|
v
Grafana
|
v
장애 원인 분석