Prometheus Grafana 실습
- Docker Desktop 설치 (Windows/macOS)
- 텍스트 에디터 (VS Code 권장)
- 기존 Loki 스택 실습 환경 (기존 디렉토리 계속 사용)
- Day7 실습했던 폴더에 이어서 진행한다.
1. 프로젝트를 구성하는 폴더 및 파일 구조
파일구조는 다음과 같다. loki-stack-demo 폴더 안에 새로 추가할 파일들이 몇개 있다.
loki-stack-demo/
├── fastapi/ # 기존 폴더
├── load-test-app/ # 새로 추가
│ ├── Dockerfile
│ ├── requirements.txt
│ └── app.py
├── prometheus/ # 새로 추가
│ ├── prometheus.yml
│ └── rules.yml
├── alertmanager/ # 새로 추가
│ └── alertmanager.yml
├── docker-compose.yml # 기존 파일 수정
└── ... (기존 파일들)
2. 소스코드 작성
2-1. 부하 테스트용 애플리케이션 (신규 파일 작성)
■ load-test-app/Dockerfile
# ✅ Python 3.9의 슬림 버전 이미지를 베이스로 사용
# 용량 작고 불필요한 패키지가 제거되어있는 미니멀 이미지
FROM python:3.9-slim
# ✅ 컨테이너 안에서 작업할 기본 디렉토리 지정
# 이후 모든 명령은 /app 경로 기준으로 실행됨
WORKDIR /app
# ✅ 로컬의 requirements.txt (의존성 목록 파일) -> 컨테이너의 현재 디렉토리인 /app로 복사
COPY requirements.txt .
# ✅ 의존성 설치 (캐시 없이)
# --no-cache-dir 옵션 : pip 캐시를 남기지 않음 → 이미지 용량 최적화
RUN pip install --no-cache-dir -r requirements.txt
# ✅ Flask 애플리케이션 메인 파일인 app.py를 컨테이너에 복사
COPY app.py .
# ✅ 컨테이너 외부에서 접근할 포트 지정 (Flask 기본 포트)
EXPOSE 5000
# ✅ 컨테이너 시작 시 실행할 명령어
# → Flask 앱 실행
CMD ["python", "app.py"]
✅ 요약
| 명령어 | 설명 |
| FROM | Python 런타임 기반 이미지 |
| WORKDIR | 작업 디렉토리 설정 |
| COPY | 코드 및 설정 파일 복사 |
| RUN | 라이브러리 설치 |
| EXPOSE | 외부 접근 포트 열기 (5000) |
| CMD | 앱 실행 (Flask 서버 구동) |
■ load-test-app/requirements.txt
# ✅ Flask: 간단한 Python의 웹 서버/REST API 프레임워크
# - 부하 테스트용 HTTP 서버(app.py) 구현에 사용
flask==2.0.1
# ✅ Werkzeug: Flask 내부에서 사용하는 WSGI 서버 및 유틸리티
# - 요청/응답 처리, 라우팅, 예외 처리 등 기본 기능 제공
werkzeug==2.0.1
# ✅ Prometheus 메트릭 수집 및 노출용 라이브러리
# - Prometheus가 수집할 수 있는 `/metrics` 엔드포인트 제공
# - request count, latency 등 모니터링 가능
# - 사용 예) load_app_request_total, load_app_request_latency_seconds...
prometheus-client==0.11.0
# ✅ psutil: 시스템 자원 사용량(CPU, 메모리 등)을 모니터링하는 라이브러리
# - `load_app_memory_usage_bytes`, `cpu_usage_percent` 계산에 사용
psutil==5.8.0
✅ 요약
| 패키지 | 설명 | 용도 |
| flask | 웹 서버 프레임워크 | 엔드포인트 처리 |
| werkzeug | *WSGI 서버 핵심 도구 | Flask 내부 핵심 구성 요소 |
| prometheus-client | Prometheus용 메트릭 라이브러리 | /metrics 노출 |
| psutil | 시스템 리소스 측정 도구 | CPU/메모리 모니터링 |
🧩 WSGI 서버에 대해서 아래 글에 정리해 놓았다.
■ load-test-app/app.py
localhost:5000
import time
import random
import threading
# ✅ psutil: 시스템 자원 (CPU, 메모리) 정보를 얻는 모듈
import psutil
import gc # 가비지 컬렉션을 위한 모듈 추가
# ✅ Flask: 웹 서버 프레임워크
from flask import Flask, request, Response, jsonify
# ✅ Prometheus 메트릭을 수집하고 노출하는 라이브러리
from prometheus_client import Counter, Histogram, Gauge, generate_latest, REGISTRY
# ✅ Flask 앱 인스턴스 생성
app = Flask(__name__)
# 메트릭 정의 - Prometheus 메트릭 타입별 용도
# Counter: 계속 증가하는 값 (요청 수 등)
# Histogram: 값의 분포 측정 (응답 시간 등)
# Gauge: 증가하거나 감소할 수 있는 값 (메모리, CPU 사용량 등)
# ✅ 요청 수 카운터: 총 요청 수를 상태 코드 및 엔드포인트별로 구분
REQUEST_COUNT = Counter('load_app_requests_total', 'Total app HTTP requests', ['method', 'endpoint', 'status'])
# ✅ 요청 지연 시간 측정용 히스토그램 (초 단위)
REQUEST_LATENCY = Histogram('load_app_request_latency_seconds', 'Request latency in seconds', ['method', 'endpoint'])
# ✅ 메모리 사용량 게이지 (bytes)
MEMORY_USAGE = Gauge('load_app_memory_usage_bytes', 'Memory usage in bytes')
# ✅ CPU 사용률 게이지 (퍼센트)
CPU_USAGE = Gauge('load_app_cpu_usage_percent', 'CPU usage in percent')
# 스트레스 테스트 제어를 위한 글로벌 변수
stress_memory = False # 메모리 부하 테스트 활성화 플래그
stress_cpu = False # CPU 부하 테스트 활성화 플래그
memory_chunks = [] # 메모리 할당을 위한 리스트 (참조 유지 목적)
# 백그라운드 작업을 관리하기 위한 스레드 객체 저장
background_threads = []
def update_metrics():
"""백그라운드 스레드에서 주기적으로 시스템 메트릭 업데이트
1초마다 현재 프로세스의 메모리 사용량과 CPU 사용률을 측정하여
Prometheus 게이지에 업데이트합니다.
"""
while True:
# 메모리 사용량 업데이트 - 바이트 단위
MEMORY_USAGE.set(psutil.Process().memory_info().rss)
# CPU 사용량 업데이트 - 퍼센트 단위
CPU_USAGE.set(psutil.Process().cpu_percent(interval=0.1))
time.sleep(0.9) # 약 1초마다 업데이트 (interval 포함)
# 백그라운드 메트릭 업데이트 스레드 시작 - 앱 시작 시 실행
metrics_thread = threading.Thread(target=update_metrics, daemon=True)
metrics_thread.start()
print("메트릭 모니터링 스레드 시작됨")
def cpu_stress():
"""CPU에 부하를 주는 함수
무작위 숫자 생성을 반복하여 CPU 사용률을 높입니다.
stress_cpu 플래그가 False가 되면 스레드가 종료됩니다.
"""
print("CPU 부하 테스트 시작")
global stress_cpu
while stress_cpu:
# 백만 개의 랜덤 숫자 생성 - CPU 집약적 작업
_ = [random.random() for _ in range(1000000)]
time.sleep(0.01) # 짧은 대기로 CPU 100% 점유 방지
print("CPU 부하 테스트 종료")
def memory_stress():
"""메모리에 부하를 주는 함수
10MB씩 메모리를 할당하여 memory_chunks 리스트에 저장합니다.
최대 100MB까지 할당하거나 stress_memory 플래그가 False가 되면 종료됩니다.
"""
print("메모리 부하 테스트 시작")
global stress_memory, memory_chunks
try:
# 메모리 초기화 (이전 테스트에서 남은 메모리 해제)
memory_chunks = []
allocated_mb = 0
while stress_memory and allocated_mb < 100: # 최대 100MB 제한
# 10MB 크기의 문자열 할당
memory_chunks.append(' ' * 10 * 1024 * 1024) # 10MB
allocated_mb += 10
print(f"메모리 할당: {allocated_mb}MB")
time.sleep(1) # 1초마다 10MB씩 증가
except Exception as e:
print(f"메모리 부하 테스트 중 오류 발생: {e}")
print("메모리 부하 테스트 종료")
@app.route('/')
def home():
"""메인 페이지
간단한 응답을 반환하며, 약간의 무작위 지연을 추가합니다.
요청 수와 응답 시간을 Prometheus 메트릭으로 기록합니다.
"""
start_time = time.time() # 요청 시작 시간
status_code = 200
# 랜덤 지연 추가 (0-0.5초) - 실제 환경의 변동성 시뮬레이션
time.sleep(random.random() * 0.5)
# 요청 카운터 증가 및 응답 시간 기록
REQUEST_COUNT.labels('get', '/', status_code).inc()
REQUEST_LATENCY.labels('get', '/').observe(time.time() - start_time)
return 'Load Testing Application\n'
@app.route('/metrics')
def metrics():
"""Prometheus 메트릭 엔드포인트
Prometheus가 주기적으로 이 엔드포인트를 스크랩하여
모든 정의된 메트릭(요청 수, 응답 시간, 메모리, CPU 등)을 수집합니다.
"""
return Response(generate_latest(REGISTRY), mimetype='text/plain')
@app.route('/error')
def error():
"""에러 응답을 시뮬레이션하는 엔드포인트
500 상태 코드를 반환하여 서버 오류 상황을 시뮬레이션합니다.
에러 요청 수와 응답 시간을 Prometheus 메트릭으로 기록합니다.
"""
start_time = time.time()
status_code = 500 # 서버 오류 상태 코드
# 에러 요청 카운터 증가 및 응답 시간 기록
REQUEST_COUNT.labels('get', '/error', status_code).inc()
REQUEST_LATENCY.labels('get', '/error').observe(time.time() - start_time)
return 'Error occurred', 500
@app.route('/slow')
def slow():
"""느린 응답을 시뮬레이션하는 엔드포인트
1-3초 사이의 지연을 추가하여 느린 응답을 시뮬레이션합니다.
느린 요청 수와 응답 시간을 Prometheus 메트릭으로 기록합니다.
"""
start_time = time.time()
status_code = 200
# 1-3초 사이 랜덤 지연 - 느린 응답 시뮬레이션
delay = 1 + random.random() * 2
time.sleep(delay)
# 요청 카운터 증가 및 응답 시간 기록
REQUEST_COUNT.labels('get', '/slow', status_code).inc()
REQUEST_LATENCY.labels('get', '/slow').observe(time.time() - start_time)
return f'Slow response (delay: {delay:.2f}s)\n'
@app.route('/status')
def status():
"""현재 부하 테스트 상태를 반환하는 엔드포인트
현재 메모리 및 CPU 부하 테스트 상태와 실제 리소스 사용량을 JSON 형식으로 반환합니다.
"""
global stress_memory, stress_cpu, memory_chunks
# 현재 리소스 사용량 및 부하 테스트 상태 수집
status_info = {
"memory_stress_active": stress_memory,
"cpu_stress_active": stress_cpu,
"allocated_memory_mb": len(memory_chunks) * 10, # 10MB 단위로 할당
"memory_usage_mb": round(psutil.Process().memory_info().rss / (1024 * 1024), 2),
# ✅ CPU 사용률 게이지 (퍼센트)
"cpu_usage_percent": round(psutil.Process().cpu_percent(interval=0.1), 2)
}
return jsonify({
"status": "running",
"stress_tests": status_info,
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
})
@app.route('/stress/memory/<action>')
def stress_memory_endpoint(action):
"""메모리 부하 테스트 엔드포인트
메모리 사용량을 인위적으로 증가시키거나 해제합니다.
'start': 메모리 사용량을 점진적으로 증가 (최대 100MB)
'stop': 할당된 메모리를 해제하고 가비지 컬렉션 실행
"""
global stress_memory, memory_chunks, background_threads
if action == 'start':
if not stress_memory:
stress_memory = True
print("메모리 부하 테스트 시작 명령 수신")
# 별도 스레드에서 메모리 부하 생성
memory_thread = threading.Thread(target=memory_stress, daemon=True)
memory_thread.start()
background_threads.append(memory_thread)
return 'Memory stress started\n'
else:
return 'Memory stress already running\n'
elif action == 'stop':
stress_memory = False
print("메모리 부하 테스트 중지 명령 수신")
# 메모리 명시적 해제
memory_chunks = []
# 가비지 컬렉션 강제 실행
gc.collect()
print("메모리 해제 및 가비지 컬렉션 실행")
return 'Memory stress stopped\n'
else:
return 'Invalid action\n', 400
@app.route('/stress/cpu/<action>')
def stress_cpu_endpoint(action):
"""CPU 부하 테스트 엔드포인트
CPU 사용률을 인위적으로 증가시키거나 정상화합니다.
'start': CPU 집약적인 작업을 통해 CPU 사용률 증가
'stop': CPU 부하 생성 중단
"""
global stress_cpu, background_threads
if action == 'start':
if not stress_cpu:
stress_cpu = True
print("CPU 부하 테스트 시작 명령 수신")
# 별도 스레드에서 CPU 부하 생성
cpu_thread = threading.Thread(target=cpu_stress, daemon=True)
cpu_thread.start()
background_threads.append(cpu_thread)
return 'CPU stress started\n'
else:
return 'CPU stress already running\n'
elif action == 'stop':
stress_cpu = False
print("CPU 부하 테스트 중지 명령 수신")
return 'CPU stress stopped\n'
else:
return 'Invalid action\n', 400
if __name__ == '__main__':
# 앱이 시작될 때 출력
print("부하 테스트 애플리케이션 시작")
print("사용 가능한 엔드포인트:")
print("- /: 기본 페이지")
print("- /metrics: Prometheus 메트릭")
print("- /slow: 느린 응답 시뮬레이션")
print("- /error: 오류 응답 시뮬레이션")
print("- /status: 현재 부하 테스트 상태")
# 🔹 메모리 부하 테스트 시작
print("- /stress/memory/start|stop: 메모리 부하 테스트")
# 🔹 CPU 부하 테스트 시작
print("- /stress/cpu/start|stop: CPU 부하 테스트")
# 모든 인터페이스에서 5000번 포트로 서비스 시작
app.run(host='0.0.0.0', port=5000)
✅ 요약
| 엔드포인트 | 설명 |
| / | 기본 응답 (약간의 지연 포함) |
| /slow | 느린 응답 시뮬레이션 (1~3초 지연) |
| /error | 500 에러 응답 |
| /metrics | Prometheus 메트릭 제공 |
| /status | 현재 메모리/CPU 상태 |
| /stress/memory/start | 메모리 부하 시작 (100MB 할당) |
| /stress/memory/stop | 메모리 부하 해제 |
| /stress/cpu/start | CPU 부하 시작 (무한 루프) |
| /stress/cpu/stop | CPU 부하 중단 |
🔍 Prometheus 메트릭
| 메트릭 이름 | *타입 | 설명 |
| load_app_requests_total | Counter | 요청 수 (method, endpoint, status별 구분) |
| load_app_request_latency_seconds | Histogram | 요청 지연 시간 분포 |
| load_app_memory_usage_bytes | Gauge | 현재 메모리 사용량 |
| load_app_cpu_usage_percent | Gauge | 현재 CPU 사용률 |
🧩 Prometheus의 타입에 대해서 아래 글에 정리해 놓았다.
2-2. Prometheus 및 AlertManager 설정 (신규 파일 작성)
■ prometheus/prometheus.yml
global:
scrape_interval: 15s # 모든 타겟의 메트릭을 15초 간격으로 수집
evaluation_interval: 15s # 알림 규칙을 15초마다 평가
# Alertmanager 설정
alerting:
alertmanagers:
- static_configs:
- targets: # Prometheus가 조건을 만족하는 메트릭에 대해 Alert를 전송할 대상 지정
- alertmanager:9093 # AlertManager 컨테이너 주소 (도커 내부)
# 알림 조건을 정의한 외부 파일 로드
rule_files:
- "rules.yml" # 알림 조건(예: 에러율이 일정 이상 등)들이 들어 있는 규칙 파일
# 메트릭 수집 대상 정의
scrape_configs:
# FastAPI 앱의 메트릭 수집 설정
- job_name: "fastapi"
metrics_path: "/metrics" # FastAPI가 제공하는 엔드포인트(/metrics)에서 메트릭 수집
static_configs:
- targets: ["fastapi:8000"] # 도커 네트워크상의 서비스명:포트
# 부하 테스트용 Flask앱의 메트릭 수집 설정
- job_name: "load-test-app"
static_configs:
- targets: ["load-test-app:5000"] # Flask 앱
# Prometheus 자체 메트릭 (자기 자신도 모니터링)
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
# cAdvisor 컨테이너 메트릭 (Docker 컨테이너의 CPU, 메모리, 디스크 사용량 추적)
- job_name: "cadvisor"
static_configs:
- targets: ["cadvisor:8080"]
# Node Exporter 메트릭 (호스트 시스템의 메트릭 수집)
- job_name: "node"
static_configs:
- targets: ["node-exporter:9100"]
✅ 요약
| 설정 | 설명 |
| scrape_interval | 모든 대상에서 메트릭 수집 주기 설정 |
| alertmanagers | 알림을 보낼 대상 설정 (AlertManager) |
| rule_files | 알림 조건을 정의한 외부 파일 지정 |
| scrape_configs | 메트릭을 수집할 대상들 정의 (FastAPI, Prometheus 등) |
■ prometheus/rules.yml
groups:
- name: app_alerts # 알림 그룹 이름(app_alerts)
rules:
# ✅ 높은 메모리 사용량 알림
- alert: HighMemoryUsage # 알림 이름(HighMemoryUsage)
expr: load_app_memory_usage_bytes > 100000000 # 메모리 사용량이 100MB 초과 시 알림
for: 1m # 1분간 지속 시 경고
labels:
severity: warning # 경고 등급 (warning)
annotations:
summary: "High memory usage detected" # 경고 제목
description: "App memory usage is {{ $value | humanize }} bytes" # 상세 설명 (실제 메모리 사용량 표시)
# ✅ 높은 CPU 사용량 알림
- alert: HighCpuUsage
expr: load_app_cpu_usage_percent > 80 # CPU 사용률 80% 초과 시 알림
for: 1m # 1분간 지속 시 경고
labels:
severity: warning
annotations:
summary: "High CPU usage detected"
description: "App CPU usage is {{ $value }}%" # 상세 설명 (실제 CPU 사용량 표시)
# ✅ 느린 응답 시간 알림
- alert: SlowResponseTime
expr: rate(load_app_request_latency_seconds_sum[5m])
/ rate(load_app_request_latency_seconds_count[5m]) > 1 # 5분 동안의 평균 응답 시간이 1초 초과 시 알림
for: 2m # 2분간 지속 시 경고
labels:
severity: warning
annotations:
summary: "Slow response time detected"
description: "Average response time is {{ $value }} seconds" # 상세 설명 (평균 응답 시간 표시)
✅ 알림 규칙 요약
| 알림 이름 | 조건 | 지속 시간 | 의미 |
| HighMemoryUsage | 메모리 사용량 > 100MB | 1분 | 과도한 메모리 사용 감지 |
| HighCpuUsage | CPU 사용률 > 80% | 1분 | CPU 부하 감지 |
| SlowResponseTime | 평균 응답 시간 > 1초 | 2분 | 서비스 지연 경고 |
📦 사용 목적
- 시스템 또는 앱이 지속적으로 비정상 상태일 때만 알림
- AlertManager를 통해 Slack, Email 등으로 전송 가능
- Grafana에서도 상태를 시각적으로 확인 가능
■ alertmanager/alertmanager.yml
global: # 경고 처리 타이밍 설정
resolve_timeout: 5m # 알림이 해결된 후 '해결됨' 상태를 전파하는 데 걸리는 시간 (5분)
route: # 알림 라우팅 정책 - 어떤 알림을 누구에게 언제 보낼지 정의
group_by: ['alertname'] # 동일 alertname을 가진 알림끼리 그룹화
group_wait: 10s # 첫 알림이 발생하고 그룹 알림을 전송하기까지 대기 시간
group_interval: 10s # 동일 그룹 내 추가 알림이 다시 전송되는 최소 간격
repeat_interval: 1h # 동일 알림을 반복 전송하는 간격 (1시간)
receiver: 'web.hook' # 기본 수신 대상 (아래 receivers에서 정의됨)
receivers: # 알림 전송 대상 (Webhook, Slack 등)
- name: 'web.hook' # 수신자 이름 (web.hook)
webhook_configs:
- url: 'http://localhost:5001/' # 알림이 전송될 Webhook 주소 (실제 환경에서는 Slack, Discord 등 외부 서비스 사용 가능)
inhibit_rules: # 덜 중요한 알림을 자동으로 억제
- source_match: # 심각한(critical) 알림이 발생한 경우
severity: 'critical'
target_match: # 동일 alertname을 가진 경고(warning)는 전송 억제
severity: 'warning' # 예: HighMemoryUsage가 critical 수준으로 발생하면,
equal: ['alertname'] # 같은 alertname의 warning 수준 알림은 따로 전송하지 않음.
✅ 요약 정리
| 항목 | 설명 |
| global.resolve_timeout | 알림이 해결된 후 상태 전파까지 대기 시간 |
| route.group_by | 알림을 그룹화할 기준 (alertname) |
| group_wait | 알림 발생 후 묶어서 전송할 때까지 대기 시간 |
| group_interval | 같은 그룹 알림이 다시 전송되는 최소 간격 |
| repeat_interval | 같은 알림이 다시 전송되는 간격 (기본: 1시간) |
| receiver | 알림이 전송될 대상 이름 |
| webhook_configs.url | 알림 전송 대상의 주소 (테스트용 서버 등) |
| inhibit_rules | 경고가 심각(Critical) 경고에 의해 억제되는 조건 |
2-3. 기존 FastAPI 애플리케이션에 Prometheus 지원 추가 (기존 파일 수정)
※ 기존 파일을 수정하는 파트이므로, 무슨 코드를 추가했고 왜 추가했는지에 대해 중점적으로 작성했다.
■ fastapi/requirements.txt 파일 수정
prometheus-client==0.11.0

🔍 왜 수정이 필요할까?
✅ FastAPI 애플리케이션에 Prometheus 메트릭을 노출하기 위해.
Prometheus는 앱 내부의 상태를 주기적으로 수집해서 모니터링하는 시스템.
이를 위해 앱은 반드시 /metrics 같은 엔드포인트에서 Prometheus가 이해할 수 있는 형식의 메트릭을 노출해야 한다.
이를 가능하게 해주는 Python 라이브러리가 바로 prometheus-client 다.
■ fastapi/main.py 파일 수정
1️⃣ 메트릭 정의 추가 및 /metrics 엔드 포인트 추가
- Counter: 요청 수를 상태코드, 메서드, 경로별로 기록
- Histogram: 요청 시간 분포 기록
- Prometheus는 /metrics 경로를 폴링하여 메트릭 수집
- FastAPI 애플리케이션과 분리된 ASGI 앱으로 구성

2️⃣ 요청 처리 시 메트릭 기록 추가
- /metrics 요청은 자기 자신이므로 무시
- 그 외 모든 요청은 지표로 기록

2-4. Docker Compose 파일 수정 (기존 파일 수정)
■ docker-compose.yml 파일 수정
1️⃣ load-test-app 추가
Flask 기반 테스트 앱
Prometheus 메트릭 노출, 요청 지연, CPU/메모리 부하 테스트 가능
✅ 목적: 실제 요청 시뮬레이션을 위한 독립 테스트 환경
2️⃣ nginx 포트 변경 (8080 → 8081)
cAdvisor와 포트 충돌을 피하기 위해 조정

3️⃣ prometheus 추가
메트릭 수집 시스템
Loki와 별개로 CPU, 메모리, 요청 지연 등 추적 가능
✅ 목적: FastAPI + load-test-app + 시스템 전체 성능 추적

4️⃣ alertmanager 추가
Prometheus에서 정의한 규칙에 따라 알림 전송 담당Slack, Email 등과 연동 가능
✅ 목적: 응답 지연/에러율 증가 등 이상 징후에 대한 자동 경고
5️⃣ cadvisor 추가
Docker 컨테이너별 CPU, 메모리, 디스크 I/O 등 추적시각화 및 Alert 설정에 사용
✅ 목적: 컨테이너 수준 리소스 모니터링

6️⃣ node-exporter 추가
호스트 시스템 수준 메트릭 수집 (CPU, Load, Filesystem 등)
Prometheus 기본 대상 중 하나
✅ 목적: 시스템 전체 리소스 사용 상태 확인
7️⃣ grafana 서비스 개선
기존엔 Loki만 연결
Prometheus를 추가하면서 시계열 메트릭 시각화 가능
8️⃣ 볼륨 정의 추가
컨테이너 재시작 시에도 메트릭 & 알림 설정 유지

🎯 전체 설계 목적 요약
| 목표 | 관련 서비스 |
| ✅ 로그 수집 | Loki + Promtail |
| ✅ 로그 시각화 | Grafana + Loki |
| ✅ 시스템/앱 성능 측정 | Prometheus, Node Exporter, cAdvisor |
| ✅ 부하 테스트 | load-test-app |
| ✅ 이상 탐지 및 알림 | Prometheus + Alertmanager |
| ✅ 종합 관측 대시보드 | Grafana (Loki + Prometheus 둘 다 사용 가능) |
4. 실행
docker-compose 빌드 및 실행
# 기존 서비스가 실행 중이면 중지
docker-compose down -v
docker-compose build --no-cache
# 컨테이너 빌드 및 실행
docker-compose up -d
# 서비스 상태 확인
docker-compose ps

5. Prometheus 메트릭 확인
1. Prometheus 웹 인터페이스 접속
- 웹 브라우저에서 'http://localhost:9090' 접속
2. 메트릭 확인
- Status > Targets 메뉴에서 모든 대상이 UP 상태인지 확인
- Graph 탭에서 PromQL 쿼리를 실행하여 메트릭 확인

6. Grafana 통합 대시보드 구성
1. Grafana 접속
- 웹 브라우저에서 'http://localhost:3000' 접속
2. Prometheus 데이터 소스 추가



3. Node Exporter 대시보드 가져오기

4. 커스텀 FastAPI 애플리케이션 대시보드 생성
패널을 2개 추가해보자.
- 패널1. FastAPI 요청: fastapi requests
rate(fastapi_requests_total[5m]) - 패널2. FastAPI 응답 시간: fastapi request latency
rate(fastapi_request_latency_seconds_sum[5m]) / rate(fastapi_request_latency_seconds_count[5m])

완성하고 요청을 보내보면 다음과 같이 보인다

5. 부하 테스트 애플리케이션 대시보드 생성
패널을 4개 추가해보자.
- 패널1. 부하 테스트 앱 요청: load app requests
rate(load_app_requests_total[5m]) - 패널2. 부하 테스트 앱 응답 시간: load app request latency
rate(load_app_request_latency_seconds_sum[5m]) / rate(load_app_request_latency_seconds_count[5m]) - 패널3. 부하 테스트 앱 메모리 사용량: load app memory usage
load_app_memory_usage_bytes / 1024 / 1024 - 패널4. 부하 테스트 앱 메모리 사용량: load app memory usage
load_app_cpu_usage_percent

6. 통합 로그와 메트릭 대시보드 생성
하나의 패널에 여러 쿼리를 입력해서 여러 실행 결과를 한번에 볼 수도 있고,
프로메테우스의 Time Series, Loki의 로그를 혼합해서 대시보드를 구성할 수도 있다.

7. 대시보드 저장
대시보드는 꼭 저장을 해줍시다. 저장을 안하고 새로고침하면 날아갑니다.
7. 부하 테스트 및 모니터링
Grafana로 구성한 대시보드의 변화를 확인하기 위해 ChatGPT로 테스트 시나리오를 생성해 요청을 보냈다.
.bat 파일로 저장해서 실행하면 된다.
✅ Windows CMD용 테스트 시나리오
1️⃣ FastAPI 요청 시뮬레이션 (패널1, 패널2)
@echo off
echo === FastAPI 요청 시뮬레이션 시작 ===
:: 빠른 요청 (정상 요청)
for /L %%i in (1,1,20) do (
curl http://localhost:8081/ >nul
timeout /t 1 >nul
)
:: 에러 요청 (500 응답)
for /L %%i in (1,1,5) do (
curl http://localhost:8081/error >nul
timeout /t 1 >nul
)
2️⃣ Load Test App 요청 시뮬레이션 (패널1~4)
@echo off
echo === Load Test App 요청 시뮬레이션 시작 ===
:: 빠른 요청
for /L %%i in (1,1,20) do (
curl http://localhost:5000/ >nul
timeout /t 1 >nul
)
:: 느린 요청
for /L %%i in (1,1,10) do (
curl http://localhost:5000/slow >nul
timeout /t 1 >nul
)
:: 에러 요청
for /L %%i in (1,1,5) do (
curl http://localhost:5000/error >nul
timeout /t 1 >nul
)
3️⃣ 메모리 & CPU 부하 테스트 (패널3, 패널4)
@echo off
echo === Load Test App 요청 시뮬레이션 시작 ===
:: 빠른 요청
for /L %%i in (1,1,20) do (
curl http://localhost:5000/ >nul
timeout /t 1 >nul
)
:: 느린 요청
for /L %%i in (1,1,10) do (
curl http://localhost:5000/slow >nul
timeout /t 1 >nul
)
:: 에러 요청
for /L %%i in (1,1,5) do (
curl http://localhost:5000/error >nul
timeout /t 1 >nul
)
트러블 슈팅
대시보드 구성 중에 Loki 패널을 하나 만들었는데 로그가 나오지 않고 다음과 같은 에러가 떴다.
- Data is missing a number field

원인)
“Data is missing a number field” 에러는 숫자(메트릭)가 필요한 시각화 유형에 로그 데이터를 연결했기 때문에 나오는 에러라고 한다. Loki 패널을 추가할때 [오른쪽 상단 메뉴 (⋮) 클릭 → Add → Visualization] 이런 순서로 추가했는데, 시각화(Visualization)유형이 Time Series로 기본 선택되어 있었다.
해결)
Loki는 로그(logs) 를 다루는 시스템이기 때문에, 시각화 유형을 Logs로 선택해야 한다.

본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.
'학습일지 > K-Digital Traing' 카테고리의 다른 글
| [KDT] AIaaS 마스터클래스 3주차 - 쿠버네티스 실습 (0) | 2025.04.09 |
|---|---|
| [KDT] AIaaS 마스터클래스 2주차 - 3 Tier Application 배포 실습 (0) | 2025.04.04 |
| [KDT] AIaaS 마스터클래스 2주차 - VM 배포 실습 (0) | 2025.04.03 |
| [KDT] AIaaS 마스터클래스 1주차 - 네트워크 프로토콜 (0) | 2025.03.28 |
| [KDT] AIaaS 마스터클래스 1주차 - Docker 실습 (0) | 2025.03.27 |