이번 글에서는 쿠버네티스 환경에서 운영되는 웹 애플리케이션, 특히 Airflow와 같은 워크플로우 관리 시스템을 모니터링하는 방법에 대해 알아보겠습니다. 웹 애플리케이션은 사용자에게 직접적인 서비스를 제공하는 핵심 구성 요소로, 효과적인 모니터링은 서비스 안정성과 사용자 경험을 보장하는 데 필수적입니다. 이 글에서는 Prometheus와 Grafana를 활용한 웹 애플리케이션 메트릭 수집, 대시보드 구성, 성능 병목 감지, 그리고 Airflow와 같은 특수 애플리케이션의 모니터링 전략까지 실무에 바로 적용할 수 있는 내용을 다루겠습니다.
📌 웹 애플리케이션 모니터링의 핵심 개념
웹 애플리케이션 모니터링은 인프라 모니터링보다 더 복잡하고, 애플리케이션에 특화된 접근이 필요합니다.
✅ 웹 애플리케이션 모니터링의 특수성
웹 애플리케이션 모니터링은 몇 가지 중요한 차별점을 갖고 있습니다.
- 다층 아키텍처
- 프론트엔드, 백엔드, 데이터베이스 등 여러 계층의 통합 모니터링
- 각 계층 간 의존성 및 상호작용 추적
- 엔드-투-엔드 사용자 경험 측정
- HTTP/HTTPS 기반 통신
- 요청/응답 패턴 모니터링
- 상태 코드, 응답 시간, 요청 볼륨 추적
- API 엔드포인트별 성능 분석
- 세션 및 사용자 관리
- 활성 사용자 및 세션 모니터링
- 사용자 행동 패턴 분석
- 인증 및 권한 관련 이슈 추적
# 웹 애플리케이션 모니터링을 위한 일반적인 Prometheus 설정
# 다양한 계층의 메트릭을 수집하기 위한 스크래핑 설정
scrape_configs:
# 웹 서버 (nginx, apache 등) 메트릭 수집
# 이 부분은 웹 서버가 노출하는 메트릭을 수집하여 요청 처리, 연결, 성능 등을 모니터링
- job_name: 'web-servers' # 작업 이름 - 메트릭 레이블에 job="web-servers"로 추가됨
kubernetes_sd_configs: # 쿠버네티스 서비스 디스커버리 설정
- role: pod # Pod 기반 디스커버리 사용
relabel_configs: # 레이블 재정의 설정
- source_labels: [__meta_kubernetes_pod_label_app] # Pod의 app 레이블 확인
regex: web-server # app=web-server 레이블이 있는 Pod만 선택
action: keep # 조건 충족 시 타겟 유지, 아니면 제외
# 백엔드 애플리케이션 (api, services 등) 메트릭 수집
# 이 부분은 백엔드 서비스의 API 성능, 오류율, 처리 시간 등을 추적
- job_name: 'backend-services' # 작업 이름
kubernetes_sd_configs:
- role: pod # Pod 기반 서비스 디스커버리
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
regex: backend # app=backend 레이블이 있는 Pod 선택
action: keep
# 프론트엔드 애플리케이션 메트릭 수집 (JavaScript/브라우저 메트릭)
# 이 부분은 프론트엔드 메트릭 수집기를 통해 사용자 경험, 페이지 로드 시간 등을 추적
- job_name: 'frontend-metrics' # 작업 이름
static_configs: # 정적 타겟 설정 (서비스 디스커버리 없이 직접 지정)
- targets: ['frontend-metrics-collector:9090'] # 메트릭 수집기 서비스 주소
# 데이터베이스 관련 메트릭 수집
# 이 부분은 다양한 유형의 데이터 스토어(RDBMS, Redis, 캐시 등)의 성능 메트릭을 수집
- job_name: 'database-metrics' # 작업 이름
kubernetes_sd_configs:
- role: pod # Pod 기반 서비스 디스커버리
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
regex: 'database|redis|cache' # 데이터베이스, Redis, 캐시 관련 Pod 선택 (정규식 사용)
action: keep
✅ RED 메서드와 웹 애플리케이션 모니터링
RED(Rate, Errors, Duration) 메서드는 웹 애플리케이션 모니터링의 핵심 접근법입니다.
- Rate (요청 비율)
- 초당 처리하는 요청 수
- 엔드포인트별 트래픽 패턴
- 시간대별 부하 변화
- Errors (오류)
- HTTP 오류율 (4xx, 5xx 상태 코드)
- 애플리케이션 예외 및 오류
- 클라이언트 vs 서버 오류 구분
- Duration (지속 시간)
- 요청 처리 지연 시간
- 백분위수 기반 지연 시간 (P95, P99)
- 엔드포인트별 성능 분포
# RED 메서드를 적용한 PromQL 쿼리 예시
# 이 쿼리들은 웹 애플리케이션의 핵심 성능 지표를 추적합니다
# 요청 비율 (Rate): 지난 5분간 초당 HTTP 요청 수
# rate(): 시간 범위 동안의 초당 평균 증가율 계산
# http_requests_total: HTTP 요청을 카운트하는 메트릭 (계측 코드에서 정의)
# [5m]: 5분 간격의 데이터 사용
sum(rate(http_requests_total[5m]))
# 오류율 (Errors): 지난 5분간 HTTP 오류 비율 (%)
# status=~"5..": 500-599 상태 코드 (서버 오류)에 대한 정규식 매칭
# sum(...) / sum(...): 전체 요청 중 오류 요청의 비율 계산
# * 100: 백분율 변환
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) * 100
# 지속 시간 (Duration): HTTP 요청 처리 시간 (밀리초 단위, 95번째 백분위수)
# histogram_quantile(): 히스토그램 데이터에서 지정된 분위수(여기서는 0.95) 값 계산
# le: 히스토그램 버킷 상한값을 나타내는 레이블
# by (le, endpoint): 버킷(le)과 엔드포인트별로 그룹화하여 각 엔드포인트의 성능 개별 추적
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, endpoint))
▶️ RED 메서드 활용 사례: 한 전자상거래 플랫폼은 RED 메서드를 적용하여 결제 프로세스의 병목 현상을 발견했습니다. 요청 비율이 일정한 상태에서 특정 시간대에 지속 시간이 급증하고 오류율이 증가하는 패턴을 확인했고, 외부 결제 게이트웨이 연결 문제를 조기에 발견하여 해결할 수 있었습니다.
📌 웹 애플리케이션 메트릭 수집 방법
효과적인 모니터링을 위해 웹 애플리케이션에서 유용한 메트릭을 수집하는 방법을 알아보겠습니다.
✅ 서버 사이드 메트릭 수집
백엔드 및 서버 측 컴포넌트에서 메트릭을 수집하는 방법입니다.
- 애플리케이션 코드 계측
- 프레임워크별 Prometheus 클라이언트 라이브러리 활용
- 키 엔드포인트 및 함수 계측
- 비즈니스 로직 관련 메트릭 추가
# Python Flask 애플리케이션에서 Prometheus 메트릭 노출 예시
# 이 코드는 Flask 웹 애플리케이션에 Prometheus 메트릭을 통합하는 방법을 보여줍니다
from flask import Flask, request
from prometheus_client import Counter, Histogram, generate_latest
import time
app = Flask(__name__)
# 요청 카운터 정의 - HTTP 메서드와 엔드포인트로 레이블 지정
# Counter: 단순히 증가만 하는 메트릭 타입 (요청 수, 오류 수 등 누적 값에 적합)
REQUEST_COUNT = Counter(
'app_http_requests_total', # 메트릭 이름 - 규칙: 네임스페이스_메트릭내용_단위
'Total HTTP Requests', # 메트릭 설명 - 메트릭의 의미 설명
['method', 'endpoint', 'status'] # 레이블: HTTP 메서드, 엔드포인트, 상태 코드
# 이 레이블로 메트릭을 세분화하여 분석 가능
)
# 요청 지연 시간 히스토그램 - 요청 처리 시간을 측정
# Histogram: 값의 분포를 측정하는 메트릭 타입 (지연 시간, 요청 크기 등에 적합)
REQUEST_LATENCY = Histogram(
'app_http_request_duration_seconds', # 메트릭 이름 - 단위(seconds)를 포함시킴
'HTTP Request Duration in seconds', # 메트릭 설명
['method', 'endpoint'], # 레이블: HTTP 메서드, 엔드포인트
buckets=[0.0001, 0.001, 0.01, 0.1, 0.5, 1.0, 2.0, 5.0, 10.0] # 지연 시간 구간 (초)
# 버킷: 값의 구간을 나누어 분포 측정
# ex) 0.1초 이하의 요청이 몇 개인지 등
)
# 활성 요청 수를 추적하는 카운터 정의
# 활성 요청은 들어왔지만 아직 완료되지 않은 요청을 의미
ACTIVE_REQUESTS = Counter(
'app_http_active_requests', # 메트릭 이름
'Active HTTP requests', # 메트릭 설명
['method', 'endpoint'] # 레이블: HTTP 메서드, 엔드포인트
)
# 모든 요청에 대한 메트릭 수집을 위한 미들웨어
# Flask의 before_request 데코레이터: 모든 요청 처리 전에 실행됨
@app.before_request
def before_request():
request.start_time = time.time() # 요청 시작 시간 기록 (지연 시간 계산용)
# 활성 요청 카운터 증가
ACTIVE_REQUESTS.labels(
method=request.method, # HTTP 메서드 (GET, POST 등)
endpoint=request.path # 요청 경로 (/api/users 등)
).inc() # 카운터 값 증가
# Flask의 after_request 데코레이터: 모든 요청 처리 후, 응답 전에 실행됨
@app.after_request
def after_request(response):
# 요청 처리 완료 후 지연 시간 기록
request_latency = time.time() - request.start_time # 요청 처리 시간 계산
REQUEST_LATENCY.labels(
method=request.method,
endpoint=request.path
).observe(request_latency) # 히스토그램에 지연 시간 기록
# 요청 카운터 증가
REQUEST_COUNT.labels(
method=request.method,
endpoint=request.path,
status=response.status_code # HTTP 상태 코드 (200, 404, 500 등)
).inc() # 카운터 값 증가
return response # 원래 응답 반환
# Prometheus 메트릭 노출 엔드포인트
# Prometheus 서버가 이 엔드포인트를 스크래핑하여 메트릭 수집
@app.route('/metrics')
def metrics():
return generate_latest() # Prometheus 형식으로 모든 등록된 메트릭 생성
# 애플리케이션 라우트 정의
@app.route('/')
def index():
return "Hello World!"
# 더 많은 엔드포인트 및 비즈니스 로직 정의...
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000) # 애플리케이션 실행
- 웹 서버 및 애플리케이션 서버 메트릭
- Nginx, Apache, Tomcat 등의 익스포터 활용
- 연결, 요청, 처리량 메트릭 수집
- 서버 상태 및 성능 지표 모니터링
# Nginx 익스포터 설정 예시
# 이 매니페스트는 Nginx 웹 서버의 메트릭을 수집하기 위한 설정입니다
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-exporter # 배포 이름
namespace: monitoring # 배포할 네임스페이스
spec:
replicas: 1 # 익스포터 복제본 수 (일반적으로 1개면 충분)
selector:
matchLabels:
app: nginx-exporter # Pod 선택기 레이블
template:
metadata:
labels:
app: nginx-exporter # Pod 레이블
spec:
containers:
- name: nginx-exporter
image: nginx/nginx-prometheus-exporter:0.10.0 # Nginx 공식 익스포터 이미지
args:
- -nginx.scrape-uri=http://nginx-service:8080/stub_status # Nginx 상태 페이지 URL
# stub_status는 Nginx의 기본 상태 모듈로, 연결 및 요청 통계를 제공
ports:
- containerPort: 9113 # 익스포터가 메트릭을 노출하는 포트
name: metrics # 포트 이름
resources: # 리소스 요청 및 제한 설정
limits:
cpu: 100m # CPU 제한 (최대 0.1 코어)
memory: 128Mi # 메모리 제한 (최대 128MB)
requests:
cpu: 50m # CPU 요청 (최소 0.05 코어)
memory: 64Mi # 메모리 요청 (최소 64MB)
livenessProbe: # 컨테이너 건강 상태 확인
httpGet:
path: / # 상태 확인 경로
port: 9113 # 상태 확인 포트
initialDelaySeconds: 30 # 시작 후 첫 검사까지의 대기 시간
timeoutSeconds: 5 # 검사 타임아웃
---
# 익스포터 서비스 정의 - Prometheus가 익스포터에 접근하기 위한 서비스
apiVersion: v1
kind: Service
metadata:
name: nginx-exporter # 서비스 이름
namespace: monitoring # 서비스 네임스페이스
labels:
app: nginx-exporter # 서비스 레이블
spec:
selector:
app: nginx-exporter # 이 서비스가 선택할 Pod
ports:
- port: 9113 # 서비스 포트
targetPort: 9113 # 대상 Pod 포트
name: metrics # 포트 이름
---
# Nginx 설정의 stub_status 모듈 활성화 ConfigMap (참고용)
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf # ConfigMap 이름
namespace: default # ConfigMap 네임스페이스
data:
nginx.conf: | # Nginx 설정 파일
user nginx;
worker_processes auto; # 워커 프로세스 수 자동 설정
error_log /var/log/nginx/error.log warn; # 오류 로그 경로
pid /var/run/nginx.pid; # PID 파일 경로
events {
worker_connections 1024; # 워커당 최대 연결 수
}
http {
server {
listen 8080; # 서버가 수신할 포트
# Nginx 상태 정보를 위한 stub_status 모듈 활성화
location /stub_status { # 상태 모듈 접근 경로
stub_status; # 이 설정이 메트릭 수집을 가능하게 함
allow 127.0.0.1; # localhost 접근 허용
allow 10.0.0.0/8; # 쿠버네티스 내부 네트워크 접근 허용
deny all; # 그 외 접근 차단
}
# 나머지 서버 설정...
}
}
- 데이터베이스 및 백엔드 서비스 메트릭
- 데이터베이스 연결 풀 상태
- 쿼리 성능 및 캐시 효율성
- 외부 API 연동 상태
✅ 클라이언트 사이드 메트릭 수집
사용자 경험과 프론트엔드 성능을 모니터링하기 위한 방법입니다.
- 브라우저 성능 메트릭
- 페이지 로드 시간 (First Contentful Paint, Time to Interactive)
- JavaScript 오류 및 예외
- 네트워크 요청 성능 (XHR, fetch)
- 사용자 경험 메트릭
- 사용자 인터랙션 (클릭, 양식 제출)
- 페이지 이탈률 및 체류 시간
- 사용자 흐름 및 전환율
// 프론트엔드 성능 메트릭 수집 예시
// 브라우저에서 성능 데이터를 수집하여 백엔드로 전송하는 코드
// 성능 관련 메트릭 수집 함수
function collectPerformanceMetrics() {
// Performance API를 사용한 타이밍 메트릭 수집
// PerformanceNavigationTiming: 페이지 탐색 및 로드 타이밍 정보 제공
const navigationPerf = performance.getEntriesByType('navigation')[0];
// 다양한 로드 타이밍 계산
const metrics = {
// DNS 조회에 걸린 시간
dnsTime: navigationPerf.domainLookupEnd - navigationPerf.domainLookupStart,
// TCP 연결 시간
tcpTime: navigationPerf.connectEnd - navigationPerf.connectStart,
// 서버 응답 시간 (Time to First Byte)
ttfb: navigationPerf.responseStart - navigationPerf.requestStart,
// DOM 처리 시간
domProcessingTime: navigationPerf.domComplete - navigationPerf.responseEnd,
// 전체 페이지 로드 시간
pageLoadTime: navigationPerf.loadEventEnd - navigationPerf.startTime,
// 사용자 에이전트 (브라우저 정보)
userAgent: navigator.userAgent,
// 페이지 URL
url: window.location.href,
// 앱 버전 또는 빌드 번호 (앱에서 정의한 변수)
appVersion: window.APP_VERSION || 'unknown',
// 화면 크기 (반응형 디자인 문제 진단에 유용)
screenWidth: window.innerWidth,
screenHeight: window.innerHeight
};
// Performance API의 리소스 타이밍 정보 수집
// PerformanceResourceTiming: 페이지 내에서 로드된 각 리소스의 타이밍 정보
const resourceMetrics = performance.getEntriesByType('resource').map(resource => ({
name: resource.name, // 리소스 URL
initiatorType: resource.initiatorType, // 리소스 요청 주체 (img, script, css 등)
duration: resource.duration, // 로드 시간 (ms)
size: resource.transferSize, // 전송 크기 (bytes)
protocol: resource.nextHopProtocol // 사용된 프로토콜 (h2, http/1.1 등)
}));
// 백엔드로 메트릭 전송
sendMetricsToBackend({ pageMetrics: metrics, resourceMetrics });
}
// 사용자 인터랙션 메트릭 수집 함수
function trackUserInteractions() {
// 클릭 이벤트 추적
document.addEventListener('click', event => {
// 클릭된 요소 정보 수집
const targetElement = event.target;
const metrics = {
eventType: 'click',
elementType: targetElement.tagName, // 요소 타입 (BUTTON, A, DIV 등)
elementId: targetElement.id || 'unknown', // 요소 ID
elementClass: targetElement.className || 'unknown', // 요소 클래스
timestamp: new Date().toISOString(), // 이벤트 발생 시간
url: window.location.href // 현재 페이지 URL
};
// 특정 클래스나 데이터 속성을 가진 요소만 추적 (예: 중요 버튼)
if (targetElement.classList.contains('track-click') ||
targetElement.dataset.trackClick) {
sendMetricsToBackend({ userInteraction: metrics });
}
});
// 양식 제출 추적
document.addEventListener('submit', event => {
const form = event.target;
const metrics = {
eventType: 'form_submit',
formId: form.id || 'unknown', // 양식 ID
formName: form.name || 'unknown', // 양식 이름
timestamp: new Date().toISOString(), // 이벤트 발생 시간
url: window.location.href // 현재 페이지 URL
};
sendMetricsToBackend({ userInteraction: metrics });
});
}
// 에러 추적 함수
function trackErrors() {
// JavaScript 오류 추적
window.addEventListener('error', event => {
const errorMetrics = {
type: 'js_error',
message: event.message, // 오류 메시지
source: event.filename, // 오류 발생 파일
lineno: event.lineno, // 오류 발생 줄 번호
colno: event.colno, // 오류 발생 열 번호
stack: event.error ? event.error.stack : null, // 스택 트레이스
timestamp: new Date().toISOString(), // 오류 발생 시간
url: window.location.href // 현재 페이지 URL
};
sendMetricsToBackend({ errorMetrics });
});
// 캐치되지 않은 Promise 오류 추적
window.addEventListener('unhandledrejection', event => {
const promiseErrorMetrics = {
type: 'unhandled_promise_rejection',
message: event.reason ? event.reason.message : 'Unknown Promise error',
stack: event.reason ? event.reason.stack : null,
timestamp: new Date().toISOString(),
url: window.location.href
};
sendMetricsToBackend({ errorMetrics: promiseErrorMetrics });
});
}
// 수집된 메트릭을 백엔드로 전송하는 함수
function sendMetricsToBackend(data) {
// 비동기로 데이터 전송 (네비게이션에 영향 없도록)
// Beacon API: 페이지 언로드 중에도 데이터 전송을 보장
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/metrics', JSON.stringify(data));
} else {
// Beacon API 미지원 시 대체 방법
fetch('/api/metrics', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
// 페이지 이동과 무관하게 요청 진행
keepalive: true
}).catch(err => console.error('Failed to send metrics:', err));
}
}
// 페이지 로드 완료 시 성능 메트릭 수집
window.addEventListener('load', collectPerformanceMetrics);
// 사용자 인터랙션 추적 시작
trackUserInteractions();
// 오류 추적 시작
trackErrors();
- 통합 프론트엔드 모니터링 서비스
- 오픈 소스 도구 (OpenTelemetry Browser SDK)
- 상용 서비스 (New Relic Browser, Datadog RUM)
- 커스텀 솔루션 구축
✅ 분산 추적 통합
웹 애플리케이션에서 복잡한 요청 흐름을 추적하기 위한 방법입니다.
- 분산 추적의 중요성
- 마이크로서비스 아키텍처에서의 요청 흐름 추적
- 서비스 간 지연 시간 및 병목 식별
- 오류 전파 경로 분석
- 추적 구현 방법
- OpenTelemetry 통합
- 서비스 간 컨텍스트 전파
- Prometheus와 추적 데이터 연계
# OpenTelemetry 수집기(collector) 설정 예시
# 이 설정은 분산 추적 데이터를 수집하고 처리하기 위한 것입니다
apiVersion: v1
kind: ConfigMap
metadata:
name: otel-collector-config # ConfigMap 이름
namespace: monitoring # 네임스페이스
data:
otel-collector-config.yaml: | # OpenTelemetry Collector 설정 파일
receivers: # 데이터 수신 설정
otlp: # OTLP(OpenTelemetry Protocol) 수신기
protocols: # 지원 프로토콜
grpc: # gRPC 프로토콜
endpoint: 0.0.0.0:4317 # 수신할 엔드포인트 (IP:포트)
http: # HTTP 프로토콜
endpoint: 0.0.0.0:4318 # 수신할 엔드포인트 (IP:포트)
# Prometheus 메트릭을 함께 수집 (선택적)
prometheus: # Prometheus 수신기
config: # Prometheus 설정
scrape_configs: # 스크래핑 설정
- job_name: 'otel-collector' # 작업 이름
scrape_interval: 10s # 스크래핑 간격
static_configs: # 정적 타겟 설정
- targets: ['0.0.0.0:8888'] # 수집기 자체 메트릭
processors: # 데이터 처리기 설정
batch: # 배치 처리기 - 데이터를 묶어서 처리
timeout: 1s # 최대 배치 대기 시간
send_batch_size: 1024 # 배치당 최대 데이터 수
# 메모리 리미터 - 메모리 사용량 제한
memory_limiter: # 메모리 제한 처리기
check_interval: 1s # 체크 간격
limit_mib: 1000 # 최대 메모리 사용량 (MiB)
# 요청 경로 추적 정보 추가 처리기
attributes: # 속성 처리기
actions: # 속성 변환 작업
- key: http.url # HTTP URL 속성
action: extract # 값 추출 작업
pattern: ^(?P<scheme>https?)://[^/]+(?P<path>/[^\?]*) # 정규식 패턴
exporters: # 데이터 전송 설정
# Jaeger로 트레이스 데이터 전송
jaeger: # Jaeger 내보내기
endpoint: jaeger-collector.monitoring:14250 # Jaeger 컬렉터 엔드포인트
tls: # TLS 설정
insecure: true # 비보안 연결 허용 (개발 환경용)
# Prometheus로 메트릭 데이터 전송
prometheus: # Prometheus 내보내기
endpoint: 0.0.0.0:8889 # 메트릭 노출 엔드포인트
namespace: otel # 메트릭 네임스페이스 접두사
# 로깅 내보내기 (디버깅용)
logging: # 로깅 내보내기
loglevel: info # 로그 레벨
service: # 서비스 파이프라인 설정
pipelines: # 데이터 파이프라인
traces: # 트레이스 파이프라인
receivers: [otlp] # 사용할 수신기
processors: [memory_limiter, batch, attributes] # 사용할 처리기
exporters: [jaeger, logging] # 사용할 내보내기
metrics: # 메트릭 파이프라인
receivers: [otlp, prometheus] # 사용할 수신기
processors: [memory_limiter, batch] # 사용할 처리기
exporters: [prometheus, logging] # 사용할 내보내기
---
# OpenTelemetry Collector 배포
apiVersion: apps/v1
kind: Deployment
metadata:
name: otel-collector # 배포 이름
namespace: monitoring # 네임스페이스
spec:
selector:
matchLabels:
app: otel-collector # 선택기 레이블
replicas: 1 # 복제본 수
template:
metadata:
labels:
app: otel-collector # Pod 레이블
spec:
containers:
- name: otel-collector
image: otel/opentelemetry-collector:0.52.0 # OpenTelemetry Collector 이미지
args: # 실행 인자
- --config=/conf/otel-collector-config.yaml # 설정 파일 경로
volumeMounts: # 볼륨 마운트
- name: otel-collector-config-vol # 볼륨 이름
mountPath: /conf # 마운트 경로
ports: # 포트 설정
- containerPort: 4317 # OTLP gRPC 수신 포트
name: otlp-grpc
- containerPort: 4318 # OTLP HTTP 수신 포트
name: otlp-http
- containerPort: 8888 # 메트릭 수집 포트
name: metrics
- containerPort: 8889 # Prometheus 내보내기 포트
name: prom-export
volumes: # 볼륨 정의
- name: otel-collector-config-vol # 볼륨 이름
configMap: # ConfigMap 기반 볼륨
name: otel-collector-config # 참조할 ConfigMap
items: # 특정 항목만 마운트
- key: otel-collector-config.yaml # ConfigMap의 키
path: otel-collector-config.yaml # 파일 경로
---
# OpenTelemetry Collector 서비스
apiVersion: v1
kind: Service
metadata:
name: otel-collector # 서비스 이름
namespace: monitoring # 네임스페이스
spec:
ports:
- name: otlp-grpc # OTLP gRPC 포트
port: 4317
targetPort: 4317
- name: otlp-http # OTLP HTTP 포트
port: 4318
targetPort: 4318
- name: metrics # 메트릭 포트
port: 8888
targetPort: 8888
- name: prom-export # Prometheus 내보내기 포트
port: 8889
targetPort: 8889
selector:
app: otel-collector # 라우팅할 Pod 선택기
📌 Airflow 모니터링 설정
Apache Airflow는 복잡한 워크플로우를 관리하는 대표적인 웹 애플리케이션으로, 효과적인 모니터링 설정이 중요합니다.
✅ Airflow 아키텍처와 모니터링 포인트
Airflow의 아키텍처와 주요 모니터링 지점을 이해하는 것이 중요합니다.
- Airflow 구성 요소
- 웹 서버: UI 및 REST API 제공
- 스케줄러: DAG 실행 관리
- 워커: 태스크 실행
- 메타데이터 데이터베이스: 상태 및 이력 저장
- 주요 모니터링 지점
- 작업 성공률 및 지연 시간
- 스케줄러 성능 및 큐 상태
- 워커 리소스 사용량
- 데이터베이스 성능
# Airflow StatefulSet에 StatsD/Prometheus 설정 추가 예시
# 이 설정은 Airflow 메트릭을 수집하기 위한 것입니다
apiVersion: v1
kind: ConfigMap
metadata:
name: airflow-config # ConfigMap 이름
namespace: airflow # 네임스페이스
data:
airflow.cfg: | # Airflow 설정 파일
[metrics]
# StatsD 메트릭 설정
statsd_on = True # StatsD 메트릭 활성화
statsd_host = statsd-exporter # StatsD 수신기 호스트
statsd_port = 9125 # StatsD 수신기 포트
statsd_prefix = airflow # 메트릭 접두사
# Prometheus 메트릭 설정 (Airflow 2.0+)
statsd_allow_list = # 모든 메트릭 허용
# DAG 처리 메트릭 설정
dag_processor_manager_log_location = /opt/airflow/logs/dag_processor_manager/dag_processor_manager.log
[scheduler]
# 스케줄러 설정
scheduler_heartbeat_sec = 5 # 스케줄러 하트비트 간격 (초)
min_file_process_interval = 30 # 파일 처리 최소 간격 (초)
print_stats_interval = 30 # 통계 출력 간격 (초)
[webserver]
# 웹서버 설정
web_server_host = 0.0.0.0 # 웹서버 호스트
web_server_port = 8080 # 웹서버 포트
access_logfile = /opt/airflow/logs/webserver/access.log # 접근 로그 경로
error_logfile = /opt/airflow/logs/webserver/error.log # 오류 로그 경로
---
# StatsD Exporter 배포 - Airflow StatsD 메트릭을 Prometheus 형식으로 변환
apiVersion: apps/v1
kind: Deployment
metadata:
name: statsd-exporter # 배포 이름
namespace: airflow # 네임스페이스
spec:
replicas: 1 # 복제본 수
selector:
matchLabels:
app: statsd-exporter # 선택기 레이블
template:
metadata:
labels:
app: statsd-exporter # Pod 레이블
annotations:
prometheus.io/scrape: "true" # Prometheus 자동 스크래핑 활성화
prometheus.io/port: "9102" # Prometheus 스크래핑 포트
spec:
containers:
- name: statsd-exporter
image: prom/statsd-exporter:v0.22.7 # StatsD Exporter 이미지
args: # 인자
- --statsd.listen-udp=:9125 # StatsD UDP 리스닝 포트
- --statsd.listen-tcp=:9125 # StatsD TCP 리스닝 포트
- --web.listen-address=:9102 # Prometheus 메트릭 노출 포트
ports:
- containerPort: 9125 # StatsD 수신 포트
name: statsd
protocol: UDP
- containerPort: 9125 # StatsD 수신 포트 (TCP)
name: statsd-tcp
protocol: TCP
- containerPort: 9102 # Prometheus 메트릭 포트
name: metrics
resources:
limits:
cpu: 200m # CPU 제한
memory: 256Mi # 메모리 제한
requests:
cpu: 100m # CPU 요청
memory: 128Mi # 메모리 요청
---
# StatsD Exporter 서비스 - Airflow로부터 메트릭을 수신하고 Prometheus에 노출
apiVersion: v1
kind: Service
metadata:
name: statsd-exporter # 서비스 이름
namespace: airflow # 네임스페이스
labels:
app: statsd-exporter # 서비스 레이블
spec:
selector:
app: statsd-exporter # 라우팅할 Pod 선택기
ports:
- port: 9125 # StatsD 포트
targetPort: 9125
protocol: UDP
name: statsd
- port: 9125 # StatsD TCP 포트
targetPort: 9125
protocol: TCP
name: statsd-tcp
- port: 9102 # Prometheus 메트릭 포트
targetPort: 9102
name: metrics
✅ 핵심 Airflow 메트릭
Airflow 모니터링에서 특히 중요한 메트릭들입니다.
- DAG 및 태스크 메트릭
- 태스크 성공/실패율
- 태스크 지연 시간
- DAG 실행 시간 및 주기성
- 시스템 성능 메트릭
- 스케줄러 루프 지연
- 태스크 큐 길이
- 워커 상태 및 병목
- 리소스 사용량 메트릭
- 프로세스별 CPU/메모리 사용량
- 데이터베이스 연결 상태
- 디스크 I/O 및 네트워크 사용량
# Airflow 모니터링을 위한 핵심 PromQL 쿼리 예시
# 이 쿼리들은 Airflow의 중요한 성능 지표를 추적합니다
# 1. DAG 성공률 (%) - 지난 24시간 동안의 DAG 성공 비율
# airflow_dag_success_count: 성공한 DAG 실행 수를 카운트하는 메트릭
# airflow_dag_failure_count: 실패한 DAG 실행 수를 카운트하는 메트릭
sum(increase(airflow_dag_success_count[24h])) /
(sum(increase(airflow_dag_success_count[24h])) + sum(increase(airflow_dag_failure_count[24h]))) * 100
# 2. 태스크 성공률 (%) - 태스크별, DAG별 성공 비율
# airflow_task_success_count: 성공한 태스크 수를 카운트하는 메트릭
# airflow_task_failure_count: 실패한 태스크 수를 카운트하는 메트릭
# by (dag_id, task_id): DAG ID와 태스크 ID별로 그룹화
sum(increase(airflow_task_success_count[24h])) by (dag_id, task_id) /
(sum(increase(airflow_task_success_count[24h])) by (dag_id, task_id) +
sum(increase(airflow_task_failure_count[24h])) by (dag_id, task_id)) * 100
# 3. 태스크 지연 시간 (초) - 95번째 백분위수
# airflow_task_duration_seconds: 태스크 실행 시간을 측정하는 히스토그램 메트릭
# histogram_quantile(0.95, ...): 95번째 백분위수 계산
histogram_quantile(0.95, sum(rate(airflow_task_duration_seconds_bucket[1h])) by (le, dag_id, task_id))
# 4. 스케줄러 효율성 - 스케줄러의 작업 처리량 (시간당 처리된 작업 수)
# airflow_scheduler_tasks_processed: 스케줄러가 처리한 태스크 수
sum(rate(airflow_scheduler_tasks_processed[1h]) * 3600) by (instance)
# 5. 실행 큐 깊이 - 대기 중인 태스크 수
# airflow_executor_queued_tasks: 큐에 대기 중인 태스크 수
sum(airflow_executor_queued_tasks) by (queue)
# 6. 워커 활용도 - 활성 태스크 대비 총 슬롯 비율 (%)
# airflow_executor_running_tasks: 현재 실행 중인 태스크 수
# airflow_executor_open_slots: 사용 가능한 워커 슬롯 수
sum(airflow_executor_running_tasks) / (sum(airflow_executor_running_tasks) + sum(airflow_executor_open_slots)) * 100
# 7. 데이터베이스 연결 상태 - 활성 DB 연결 수
# airflow_pool_open_slots: 풀의 사용 가능한 슬롯 수
# airflow_pool_used_slots: 풀의 사용 중인 슬롯 수
sum(airflow_pool_used_slots) by (pool)
# 8. 태스크 오류율 추세 - 시간당 실패한 태스크 수
# 시간 경과에 따른 오류 패턴 식별에 유용
sum(increase(airflow_task_failure_count[1h])) by (dag_id)
✅ Airflow 대시보드 구성
Airflow 모니터링을 위한 효과적인 Grafana 대시보드 구성 방법입니다.
- 개요 대시보드
- 전체 시스템 상태 요약
- 주요 DAG 성공/실패율
- 시스템 리소스 사용량
- DAG 성능 대시보드
- DAG별 실행 시간 및 성공률
- 태스크 지연 시간 분포
- DAG 간 상관관계 분석
- 시스템 성능 대시보드
- 스케줄러 성능 지표
- 워커 상태 및 큐 깊이
- 데이터베이스 연결 상태
- 운영자 대시보드
- 중요 DAG 실시간 모니터링
- 알림 상태 및 이력
- 문제 해결 및 진단 지표
▶️ Airflow 구성 추천: Airflow를 쿠버네티스에 배포할 때는 KubernetesPodOperator를 활용하여 각 태스크를 별도의 포드로 실행하는 것이 메트릭 격리와 리소스 관리에 유리합니다. 또한 AirflowClusterPolicy을 사용하여 태스크 포드에 Prometheus 주석을 자동으로 추가하면 개별 태스크 수준의 세부 모니터링이 가능합니다.
📌 웹 애플리케이션 모니터링 최적화 전략
효과적인 웹 애플리케이션 모니터링을 위한 최적화 전략을 알아보겠습니다.
✅ 메트릭 카디널리티 관리
메트릭 카디널리티가 증가하면 Prometheus 성능에 영향을 미칠 수 있습니다.
- 카디널리티 증가 요인
- 과도한 레이블 사용
- 고유 값이 많은 레이블 (사용자 ID, 세션 ID 등)
- 동적 레이블 값 사용
- 카디널리티 관리 전략
- 레이블 수 최소화
- 레이블 값 그룹화
- 고카디널리티 데이터 별도 처리
# Prometheus 메트릭 재레이블링 설정 예시
# 이 설정은 메트릭 카디널리티를 관리하기 위한 것입니다
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: prometheus # Prometheus 인스턴스 이름
namespace: monitoring # 네임스페이스
spec:
# 다른 Prometheus 설정...
# 글로벌 메트릭 재레이블링 설정
metricRelabelings: # 메트릭 재레이블링 규칙
# 1. 불필요한 고카디널리티 레이블 제거
- sourceLabels: [http_path] # 소스 레이블
regex: '(/api/v1/users/)[0-9]+(.*)' # 사용자 ID를 포함하는 경로 매칭
targetLabel: http_path # 대상 레이블
replacement: '$1<user_id>$2' # 사용자 ID를 상수로 대체
action: replace # 레이블 값 대체 작업
# 2. 특정 메트릭 필터링 (선택적으로 수집)
- sourceLabels: [__name__] # 메트릭 이름 레이블
regex: 'http_requests_total|http_request_duration_seconds_.*|process_.*'
action: keep # 매칭되는 메트릭만 유지
# 3. 개발용 엔드포인트 제외
- sourceLabels: [http_path] # 소스 레이블
regex: '.*(metrics|healthz|readiness|liveness).*' # 제외할 경로 패턴
action: drop # 매칭되는 메트릭 제외
# 4. 테스트 네임스페이스 데이터 제외
- sourceLabels: [namespace] # 소스 레이블
regex: '.*-test|.*-dev' # 테스트/개발 네임스페이스 패턴
action: drop # 매칭되는 메트릭 제외
# 5. URL 경로 정규화 (동적 ID 부분 상수화)
- sourceLabels: [http_url] # 소스 레이블
regex: '(.*)/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(/.*)?'
replacement: '$1/<uuid>$2' # UUID를 상수로 대체
targetLabel: http_url # 대상 레이블
action: replace # 레이블 값 대체 작업
✅ 알림 설정 최적화
효과적인 알림은 문제를 조기에 감지하고 대응할 수 있게 해줍니다.
- 알림 임계값 설정
- 역사적 데이터 기반 임계값 설정
- 주요 사용자 경험 지표 중심 알림
- 단계별 심각도 구분
- 알림 그룹화 및 필터링
- 관련 알림 그룹화
- 중복 알림 제거
- 서비스 영향도 기반 우선순위화
# 웹 애플리케이션을 위한 Prometheus 알림 규칙 예시
# 이 설정은 웹 애플리케이션의 문제를 감지하기 위한 알림 규칙을 정의합니다
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: web-app-alerts # 규칙 이름
namespace: monitoring # 네임스페이스
spec:
groups:
- name: web-application.rules # 규칙 그룹 이름
rules:
# 1. 높은 오류율 감지 (5xx 오류가 10% 이상일 때)
- alert: HighErrorRate # 알림 이름
expr: sum(rate(http_requests_total{status=~"5.."}[5m])) by (service, handler) / sum(rate(http_requests_total[5m])) by (service, handler) > 0.1 # 알림 조건
for: 5m # 지속 시간 (5분 동안 조건 충족시 알림)
labels:
severity: critical # 심각도 레이블
category: availability # 카테고리 레이블
annotations:
summary: "높은 오류율 감지: {{ $labels.service }}" # 알림 요약
description: "서비스 {{ $labels.service }}의 엔드포인트 {{ $labels.handler }}에서 5분 동안 {{ $value | humanizePercentage }} 오류율이 감지되었습니다." # 상세 설명
runbook_url: "https://wiki.example.com/runbooks/high-error-rate" # 대응 매뉴얼 링크
# 2. 높은 응답 지연 시간 (P95 응답 시간이 2초 초과)
- alert: HighResponseLatency
expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service, handler)) > 2
for: 10m # 지속 시간 (10분 동안 조건 충족시 알림)
labels:
severity: warning # 심각도 레이블
category: performance # 카테고리 레이블
annotations:
summary: "높은 응답 지연: {{ $labels.service }}"
description: "서비스 {{ $labels.service }}의 엔드포인트 {{ $labels.handler }}에서 P95 응답 시간이 {{ $value | humanizeDuration }} 입니다 (10분 동안 평균)."
dashboard_url: "https://grafana.example.com/d/web-app-dashboard" # 대시보드 링크
# 3. 트래픽 급증 감지 (5분 전 대비 3배 이상 증가)
- alert: TrafficSpike
expr: sum(rate(http_requests_total[5m])) by (service) / sum(rate(http_requests_total[5m] offset 5m)) by (service) > 3
for: 5m
labels:
severity: warning
category: traffic
annotations:
summary: "트래픽 급증: {{ $labels.service }}"
description: "서비스 {{ $labels.service }}에서 5분 전 대비 {{ $value | humanizePercentage }} 트래픽이 증가했습니다."
# 4. 낮은 가용성 (성공률 95% 미만)
- alert: LowAvailability
expr: sum(rate(http_requests_total{status=~"2.."}[5m])) by (service) / sum(rate(http_requests_total[5m])) by (service) < 0.95
for: 10m
labels:
severity: critical
category: availability
annotations:
summary: "낮은 가용성: {{ $labels.service }}"
description: "서비스 {{ $labels.service }}의 성공률이 {{ $value | humanizePercentage }}로 떨어졌습니다 (10분 동안 평균)."
# 5. 프로세스 메모리 사용량 높음 (85% 이상)
- alert: HighMemoryUsage
expr: process_resident_memory_bytes / container_memory_limit_bytes > 0.85
for: 15m # 지속 시간 (15분 동안 조건 충족시 알림)
labels:
severity: warning
category: resources
annotations:
summary: "높은 메모리 사용량: {{ $labels.pod }}"
description: "Pod {{ $labels.pod }}의 메모리 사용률이 {{ $value | humanizePercentage }}입니다 (15분 동안 평균)."
# 6. 비정상적인 HTTP 상태 코드 분포
- alert: AbnormalStatusCodeDistribution
expr: abs(sum(rate(http_requests_total{status=~"4.."}[1h])) by (service) / sum(rate(http_requests_total[1h])) by (service) - avg_over_time((sum(rate(http_requests_total{status=~"4.."}[1h])) by (service) / sum(rate(http_requests_total[1h])) by (service))[1d:1h])) > 0.1
for: 30m # 지속 시간 (30분 동안 조건 충족시 알림)
labels:
severity: warning
category: anomaly
annotations:
summary: "비정상적인 HTTP 상태 코드 분포: {{ $labels.service }}"
description: "서비스 {{ $labels.service }}의 4xx 오류 비율이 일반적인 패턴에서 {{ $value | humanizePercentage }} 이상 벗어났습니다."
✅ 자동화된 문제 진단
자동화된 진단 도구와 절차를 통해 문제 해결 시간을 단축할 수 있습니다.
- 진단 규칙 정의
- 일반적인 문제 패턴 정의
- 증상과 원인 매핑
- 자동화된 진단 스크립트 개발
- 절차적 대응 방안
- 문제 유형별 대응 매뉴얼
- 에스컬레이션 경로 및 조건
- 자동 복구 옵션 설정
# 웹 애플리케이션 문제 자동 진단 스크립트 예시
# 이 스크립트는 Prometheus API를 사용하여 웹 애플리케이션의 성능 문제를 자동으로 진단합니다.
# 지연 시간, 오류율, 리소스 사용량 등 다양한 측면을 분석하여 문제의 원인을 파악하고 해결 방안을 제시합니다.
import requests # HTTP 요청을 위한 라이브러리
import json # JSON 데이터 처리를 위한 라이브러리
import logging # 로깅을 위한 라이브러리
import time # 시간 관련 기능을 위한 라이브러리
from datetime import datetime, timedelta # 날짜/시간 처리를 위한 라이브러리
# 로깅 설정 - 스크립트 실행 과정과 결과를 기록하기 위한 설정
# level=logging.INFO: 정보성 메시지부터 기록 (DEBUG, INFO, WARNING, ERROR, CRITICAL 중)
# format: 로그 메시지의 형식 지정 (시간, 로그 레벨, 메시지 포함)
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__) # 현재 모듈의 로거 객체 생성
# Prometheus 서버 정보 - 실제 환경에서는 환경 변수나 설정 파일에서 로드하는 것이 좋음
PROMETHEUS_URL = "http://prometheus-server:9090" # Prometheus API 엔드포인트
# 문제 진단 함수
def diagnose_high_latency(service_name, handler=None, lookback_minutes=30):
"""
높은 지연 시간 문제를 진단하는 함수
이 함수는 특정 서비스의 성능 문제를 다각도로 분석하여 지연 시간의 원인을 파악합니다.
CPU 사용량, 데이터베이스 쿼리 지연, 외부 API 지연, 메모리 사용량, 연결 풀 상태 등을
종합적으로 조사하여 문제 원인과 해결 방안을 제시합니다.
Args:
service_name (str): 진단할 서비스의 이름 (예: "web-frontend", "api-server" 등)
handler (str, optional): 특정 핸들러/엔드포인트 경로 (예: "/api/users")
lookback_minutes (int): 과거 몇 분 동안의 데이터를 분석할지 지정 (기본값: 30분)
Returns:
dict: 진단 결과를 담은 딕셔너리 (발견 사항, 권장 사항 등 포함)
"""
# 조회 시간 범위 설정
end_time = datetime.now() # 현재 시간을 끝 시간으로 설정
start_time = end_time - timedelta(minutes=lookback_minutes) # 지정된 시간만큼 이전을 시작 시간으로 설정
# Unix 타임스탬프로 변환 (Prometheus API 요구 형식)
end_timestamp = int(end_time.timestamp()) # 끝 시간을 Unix 타임스탬프로 변환
start_timestamp = int(start_time.timestamp()) # 시작 시간을 Unix 타임스탬프로 변환
# 진단 결과를 저장할 딕셔너리 초기화
diagnostics = {
"service": service_name, # 진단 대상 서비스 이름
"handler": handler, # 진단 대상 핸들러 (있는 경우)
"timestamp": end_time.isoformat(), # ISO 형식 타임스탬프 (예: 2023-01-01T12:34:56)
"lookback_minutes": lookback_minutes, # 분석 기간
"findings": [], # 발견 사항을 저장할 리스트
"recommendations": [] # 권장 사항을 저장할 리스트
}
# 1. 지연 시간 분포 확인 - P95(95번째 백분위수) 지연 시간 분석
# 특정 핸들러가 지정된 경우 해당 핸들러만 필터링, 아니면 전체 서비스 기준
handler_filter = f', handler="{handler}"' if handler else ""
# PromQL 쿼리: 95번째 백분위수 요청 지연 시간 계산
# histogram_quantile: 히스토그램 메트릭에서 특정 백분위수 값을 계산하는 함수
# 0.95: 95번째 백분위수 (상위 5%의 지연 시간 경계값)
# rate(): 지정 기간 동안의 초당 평균 증가율
# 5m: 5분 단위로 추세 계산
# by (le): le(less than or equal) 버킷별로 데이터 그룹화
latency_query = f"""
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{{service="{service_name}"{handler_filter}}}[5m])) by (le))
"""
latency_result = query_prometheus(latency_query, end_timestamp) # Prometheus API 호출
# 지연 시간 결과가 있는 경우 분석
if latency_result and len(latency_result) > 0:
# 쿼리 결과에서 P95 지연 시간 값 추출
# latency_result[0]["value"][1]: Prometheus 응답 형식에서 실제 값은 [타임스탬프, 값] 형태의 배열 중 두번째 요소
p95_latency = float(latency_result[0]["value"][1]) # 문자열을 float으로 변환
diagnostics["p95_latency"] = p95_latency # 진단 결과에 P95 지연 시간 추가
# 지연 시간이 1초를 초과하는 경우 성능 문제로 판단
if p95_latency > 1.0:
diagnostics["findings"].append(f"P95 지연 시간이 높음: {p95_latency:.2f}초")
# 추가 진단: CPU 사용량 확인 - 높은 CPU 사용량이 지연의 원인일 수 있음
cpu_query = f"""
sum(rate(process_cpu_seconds_total{{service="{service_name}"}}[5m])) by (instance)
"""
cpu_result = query_prometheus(cpu_query, end_timestamp)
if cpu_result and len(cpu_result) > 0:
cpu_usage = float(cpu_result[0]["value"][1]) # CPU 사용량 (1.0 = 100% 1 코어)
diagnostics["cpu_usage"] = cpu_usage # 진단 결과에 CPU 사용량 추가
# CPU 사용량이 80% 이상인 경우 리소스 부족으로 판단
if cpu_usage > 0.8:
diagnostics["findings"].append(f"CPU 사용량이 높음: {cpu_usage * 100:.2f}%")
diagnostics["recommendations"].append("CPU 리소스 증가 또는 수평 확장 고려")
# 데이터베이스 지연 시간 확인 - DB 쿼리가 성능 병목일 수 있음
db_latency_query = f"""
histogram_quantile(0.95, sum(rate(database_query_duration_seconds_bucket{{service="{service_name}"}}[5m])) by (le))
"""
db_result = query_prometheus(db_latency_query, end_timestamp)
if db_result and len(db_result) > 0:
db_latency = float(db_result[0]["value"][1]) # DB 쿼리 지연 시간 (초)
diagnostics["db_latency"] = db_latency # 진단 결과에 DB 지연 시간 추가
# DB 쿼리 지연이 0.5초를 초과하는 경우 DB 성능 문제로 판단
if db_latency > 0.5:
diagnostics["findings"].append(f"데이터베이스 쿼리 지연 높음: {db_latency:.2f}초")
diagnostics["recommendations"].append("데이터베이스 쿼리 최적화 또는 인덱스 확인")
# 외부 API 호출 지연 확인 - 외부 서비스 의존성이 병목일 수 있음
api_latency_query = f"""
histogram_quantile(0.95, sum(rate(http_client_request_duration_seconds_bucket{{service="{service_name}"}}[5m])) by (le, target_service))
"""
api_result = query_prometheus(api_latency_query, end_timestamp)
if api_result and len(api_result) > 0:
# 각 외부 서비스별 지연 시간 분석
for result in api_result:
# 대상 서비스 이름 (없으면 "unknown"으로 표시)
target_service = result["metric"].get("target_service", "unknown")
api_latency = float(result["value"][1]) # API 호출 지연 시간 (초)
# 외부 API 호출 지연이 0.5초를 초과하는 경우 외부 서비스 문제로 판단
if api_latency > 0.5:
diagnostics["findings"].append(f"외부 서비스 '{target_service}' 호출 지연: {api_latency:.2f}초")
diagnostics["recommendations"].append(f"'{target_service}' 서비스의 성능 확인 또는 타임아웃 설정 검토")
# 2. 오류율 확인 - 높은 오류율이 성능 저하의 원인일 수 있음
# PromQL 쿼리: HTTP 5xx 오류 비율 계산 (백분율)
# status=~"5..": 5xx 상태 코드만 필터링 (정규식 사용)
error_query = f"""
sum(rate(http_requests_total{{service="{service_name}", status=~"5.."}}[5m])) / sum(rate(http_requests_total{{service="{service_name}"}}[5m])) * 100
"""
error_result = query_prometheus(error_query, end_timestamp)
if error_result and len(error_result) > 0:
error_rate = float(error_result[0]["value"][1]) # 오류율 (%)
diagnostics["error_rate"] = error_rate # 진단 결과에 오류율 추가
# 오류율이 1%를 초과하는 경우 문제로 판단
if error_rate > 1.0:
diagnostics["findings"].append(f"오류율 높음: {error_rate:.2f}%")
diagnostics["recommendations"].append("오류 로그 분석 및 예외 처리 확인")
# 3. 메모리 사용량 확인 - 메모리 누수나 부족이 성능 저하의 원인일 수 있음
# PromQL 쿼리: 메모리 사용량 백분율 계산
memory_query = f"""
process_resident_memory_bytes{{service="{service_name}"}} / container_memory_limit_bytes{{service="{service_name}"}} * 100
"""
memory_result = query_prometheus(memory_query, end_timestamp)
if memory_result and len(memory_result) > 0:
memory_usage = float(memory_result[0]["value"][1]) # 메모리 사용률 (%)
diagnostics["memory_usage"] = memory_usage # 진단 결과에 메모리 사용률 추가
# 메모리 사용량이 80%를 초과하는 경우 문제로 판단
if memory_usage > 80:
diagnostics["findings"].append(f"메모리 사용량 높음: {memory_usage:.2f}%")
diagnostics["recommendations"].append("메모리 누수 확인 또는 메모리 한도 증가 고려")
# 4. 연결 풀 상태 확인 - 연결 풀 고갈이 성능 저하의 원인일 수 있음
# PromQL 쿼리: 연결 풀 사용률 계산 (백분율)
pool_query = f"""
connection_pool_used{{service="{service_name}"}} / connection_pool_max{{service="{service_name}"}} * 100
"""
pool_result = query_prometheus(pool_query, end_timestamp)
if pool_result and len(pool_result) > 0:
pool_usage = float(pool_result[0]["value"][1]) # 연결 풀 사용률 (%)
diagnostics["connection_pool_usage"] = pool_usage # 진단 결과에 연결 풀 사용률 추가
# 연결 풀 사용량이 80%를 초과하는 경우 문제로 판단
if pool_usage > 80:
diagnostics["findings"].append(f"연결 풀 고갈 위험: {pool_usage:.2f}%")
diagnostics["recommendations"].append("연결 풀 설정 조정 또는 연결 관리 최적화")
# 진단 결과 요약 - 발견된 문제가 없는 경우 기본 메시지 추가
if not diagnostics["findings"]:
diagnostics["findings"].append("명확한 성능 문제가 감지되지 않음")
diagnostics["recommendations"].append("추가 모니터링 및 사용자 경험 피드백 확인")
return diagnostics # 최종 진단 결과 반환
# Prometheus 쿼리 함수 - Prometheus API를 호출하여 메트릭 데이터를 가져옴
def query_prometheus(query, timestamp=None):
"""
Prometheus API를 사용하여 PromQL 쿼리를 실행하는 함수
Args:
query (str): 실행할 PromQL 쿼리 문자열
timestamp (int, optional): 쿼리할 시점의 Unix 타임스탬프
Returns:
list or None: 쿼리 결과 리스트 또는 오류 발생 시 None
"""
# API 요청 파라미터 설정
params = {"query": query} # 기본 쿼리 파라미터
# 타임스탬프가 제공된 경우 파라미터에 추가
if timestamp:
params["time"] = timestamp
try:
# Prometheus API 호출 (/api/v1/query 엔드포인트)
response = requests.get(f"{PROMETHEUS_URL}/api/v1/query", params=params)
response.raise_for_status() # HTTP 오류 발생 시 예외 발생
# 응답 JSON 파싱 및 결과 확인
result = response.json()
# 성공적인 쿼리이고 결과가 있는 경우 결과 반환
if result["status"] == "success" and result["data"]["result"]:
return result["data"]["result"]
except Exception as e:
# 쿼리 실행 중 오류 발생 시 로깅 후 None 반환
logger.error(f"Prometheus 쿼리 오류: {str(e)}")
return None # 결과가 없거나 오류 발생 시 None 반환
# 샘플 실행 코드 - 이 스크립트를 직접 실행할 때만 실행됨
if __name__ == "__main__":
# 샘플 서비스에 대한 진단 실행
service_name = "web-frontend" # 진단할 서비스 이름
logger.info(f"서비스 '{service_name}'에 대한 성능 진단 시작...")
diagnosis = diagnose_high_latency(service_name, lookback_minutes=30) # 30분 동안의 데이터로 진단
# 결과 출력 - JSON 형식으로 보기 좋게 출력
logger.info("진단 결과:")
logger.info(json.dumps(diagnosis, indent=2)) # 들여쓰기된 JSON 형식으로 출력
# 심각한 발견 사항이 있는 경우 알림 전송 (예시)
# "높음"이라는 단어가 발견 사항에 포함된 경우를 심각한 문제로 간주
if diagnosis["findings"] and any("높음" in finding for finding in diagnosis["findings"]):
# 실제 구현에서는 이 부분에 알림 전송 로직 구현 (이메일, Slack 등)
logger.warning(f"성능 문제 감지: {diagnosis['findings']}")
📌 Summary
이번 포스트에서는 웹 애플리케이션, 특히 Apache Airflow와 같은 워크플로우 관리 시스템을 효과적으로 모니터링하는 방법에 대해 알아보았습니다. 다음과 같은 내용을 다루었습니다:
- 웹 애플리케이션 모니터링의 핵심 개념: 웹 애플리케이션의 다층 아키텍처와 HTTP 기반 통신 특성을 고려한 모니터링 접근법과 RED 메서드(Rate, Errors, Duration)를 활용한 핵심 지표 정의에 대해 살펴보았습니다.
- 서버 사이드 메트릭 수집: Python Flask와 같은 백엔드 프레임워크에서 Prometheus 클라이언트 라이브러리를 활용한 코드 계측 방법과 Nginx와 같은 웹 서버의 메트릭 수집 방법을 구체적인 예제와 함께 확인했습니다.
- 클라이언트 사이드 메트릭 수집: JavaScript를 활용한 프론트엔드 성능 메트릭 수집 방법과 사용자 경험 데이터를 Prometheus로 전송하는 방법을 코드 예제를 통해 알아보았습니다.
- 분산 추적 통합: OpenTelemetry를 활용한 분산 추적 설정 방법과 마이크로서비스 아키텍처에서의 요청 흐름 추적 방법을 살펴보았습니다.
- Airflow 모니터링 설정: Airflow의 아키텍처에 맞는 모니터링 포인트 식별과 StatsD/Prometheus 통합 설정, 그리고 DAG 및 태스크 성능, 스케줄러 효율성 등의 핵심 메트릭 수집 방법을 다루었습니다.
- 메트릭 카디널리티 관리: 효과적인 레이블 사용 전략과 Prometheus 재레이블링 설정을 통한 카디널리티 최적화 방법을 알아보았습니다.
- 알림 설정 및 자동 진단: 웹 애플리케이션에 특화된 알림 규칙 설정과 Python을 활용한 자동화된 문제 진단 스크립트 개발 방법을 살펴보았습니다.
웹 애플리케이션 모니터링은 단순한 시스템 메트릭을 넘어, 실제 사용자 경험과 비즈니스 가치에 직접적인 영향을 미치는 성능 지표를 추적하는 것이 중요합니다. 효과적인 모니터링 전략을 통해 문제를 조기에 감지하고, 사용자 경험을 지속적으로 개선할 수 있습니다.
'Observability > Prometheus' 카테고리의 다른 글
EP17 [Part 6: 고급 모니터링 전략] 고성능 모니터링 설정 (0) | 2025.03.25 |
---|---|
EP16 [Part 6: 고급 모니터링 전략] 메트릭 보존 정책 (0) | 2025.03.25 |
EP14 [Part 5: 애플리케이션 레벨 모니터링] 데이터베이스 모니터링 (MySQL, PostgreSQL) (0) | 2025.03.24 |
EP13 [Part 5: 애플리케이션 레벨 모니터링] 다양한 애플리케이션 익스포터 소개 (0) | 2025.03.24 |
EP12 [Part 4: Grafana 대시보드 마스터하기] 대시보드 베스트 프랙티스 (0) | 2025.03.24 |