들어가며: 관측성이 중요한 이유
"모니터링은 시스템이 잘 동작하는지 확인하는 것이고, 관측성은 시스템이 왜 잘못 동작하는지 이해하는 것이다."
현대의 분산 시스템에서는 단순히 CPU 사용률이나 메모리를 모니터링하는 것만으로는 부족합니다. 마이크로서비스 아키텍처, 컨테이너, 서버리스 환경에서는 하나의 요청이 수십 개의 서비스를 거치며, 문제의 원인을 파악하려면 시스템 내부를 들여다볼 수 있는 **관측성(Observability)**이 필요합니다.
1. 관측성의 3가지 축 (Three Pillars)
메트릭 (Metrics)
숫자로 표현되는 시계열 데이터입니다. 시스템 상태의 집계(aggregated) 뷰를 제공합니다.
- **Counter**: 단조 증가 값 (예: 총 요청 수)
- **Gauge**: 올라가고 내려가는 값 (예: 현재 메모리 사용량)
- **Histogram**: 값의 분포 (예: 응답 시간 분포)
- **Summary**: 클라이언트 측에서 계산된 분위수
Counter 예시
http_requests_total{method="GET", path="/api/users", status="200"} 15234
Gauge 예시
node_memory_usage_bytes{instance="web-01"} 1073741824
Histogram 예시
http_request_duration_seconds_bucket{le="0.1"} 24054
http_request_duration_seconds_bucket{le="0.5"} 33444
http_request_duration_seconds_bucket{le="1.0"} 34055
로그 (Logs)
이벤트의 텍스트 기록입니다. 개별 이벤트에 대한 상세 정보를 제공합니다.
{
"timestamp": "2025-03-15T10:30:45.123Z",
"level": "ERROR",
"service": "payment-service",
"traceId": "abc123def456",
"spanId": "span789",
"message": "Payment processing failed",
"userId": "user-42",
"orderId": "order-1234",
"error": "Timeout connecting to payment gateway",
"duration_ms": 5000
}
**구조화 로깅(Structured Logging)**을 사용하면 검색과 분석이 훨씬 쉬워집니다.
트레이스 (Traces)
요청이 여러 서비스를 거치는 전체 경로를 추적합니다.
[Trace: abc123def456]
├── [Span: API Gateway] 2ms
│ ├── [Span: Auth Service] 5ms
│ │ └── [Span: Redis Cache Lookup] 1ms
│ ├── [Span: User Service] 15ms
│ │ └── [Span: PostgreSQL Query] 8ms
│ └── [Span: Payment Service] 5003ms ← 병목!
│ └── [Span: External Payment API] 5000ms (TIMEOUT)
└── Total: 5025ms
세 가지 축이 결합되면 "무엇이(What) 잘못되었고, 왜(Why) 잘못되었으며, 어디서(Where) 잘못되었는지"를 모두 파악할 수 있습니다.
2. Prometheus
아키텍처
Prometheus는 Pull 기반 모니터링 시스템입니다.
┌─────────────┐ ┌──────────────┐ ┌───────────┐
│ Targets │────>│ Prometheus │────>│ Grafana │
│ (exporters) │pull │ Server │query│ │
└─────────────┘ │ - TSDB │ └───────────┘
│ - Rules │
│ - AlertMgr │
└──────────────┘
│
┌─────▼─────┐
│ AlertMgr │
│ - Routing │
│ - Silence │
└───────────┘
Prometheus 설정
prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "alert_rules.yml"
- "recording_rules.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
- job_name: 'app-service'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: (.+)
PromQL 핵심 쿼리
1. 현재 초당 요청 수 (rate)
rate(http_requests_total[5m])
2. 서비스별 에러율
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
3. 95번째 백분위 응답 시간
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
)
4. 메모리 사용률 (%)
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
/ node_memory_MemTotal_bytes * 100
5. CPU 사용률
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
6. 디스크 여유 공간이 10% 미만인 노드
node_filesystem_avail_bytes / node_filesystem_size_bytes * 100 < 10
7. Pod 재시작 횟수 (지난 1시간)
increase(kube_pod_container_status_restarts_total[1h]) > 3
8. 서비스 가용성 (지난 30일)
1 - (
sum(increase(http_requests_total{status=~"5.."}[30d]))
/
sum(increase(http_requests_total[30d]))
)
Recording Rules (성능 최적화)
recording_rules.yml
groups:
- name: service_metrics
interval: 30s
rules:
- record: service:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (service)
- record: service:http_errors:rate5m
expr: sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
- record: service:http_error_rate:ratio
expr: service:http_errors:rate5m / service:http_requests:rate5m
- record: service:http_latency:p95
expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service))
Alert Rules
alert_rules.yml
groups:
- name: service_alerts
rules:
- alert: HighErrorRate
expr: service:http_error_rate:ratio > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate on service {{ $labels.service }}"
description: "Error rate is {{ $value | humanizePercentage }} for 5+ minutes"
- alert: HighLatency
expr: service:http_latency:p95 > 2.0
for: 5m
labels:
severity: warning
annotations:
summary: "High p95 latency on {{ $labels.service }}"
description: "P95 latency is {{ $value }}s (threshold: 2s)"
- alert: PodCrashLooping
expr: increase(kube_pod_container_status_restarts_total[1h]) > 5
for: 10m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.pod }} is crash looping"
3. Grafana
대시보드 설계 원칙
**USE 방법론**: Utilization(사용률), Saturation(포화도), Errors(에러)
**RED 방법론**: Rate(비율), Errors(에러), Duration(지속시간)
Grafana 대시보드 JSON 구조
{
"dashboard": {
"title": "Service Overview",
"panels": [
{
"title": "Request Rate",
"type": "timeseries",
"datasource": "Prometheus",
"targets": [
{
"expr": "sum(rate(http_requests_total[5m])) by (service)",
"legendFormat": "{{ service }}"
}
]
},
{
"title": "Error Rate",
"type": "stat",
"datasource": "Prometheus",
"targets": [
{
"expr": "sum(rate(http_requests_total{status=~\"5..\"}[5m])) / sum(rate(http_requests_total[5m])) * 100"
}
],
"fieldConfig": {
"defaults": {
"thresholds": {
"steps": [
{ "value": 0, "color": "green" },
{ "value": 1, "color": "yellow" },
{ "value": 5, "color": "red" }
]
},
"unit": "percent"
}
}
}
],
"templating": {
"list": [
{
"name": "service",
"type": "query",
"query": "label_values(http_requests_total, service)",
"refresh": 2
},
{
"name": "environment",
"type": "custom",
"options": ["production", "staging", "development"]
}
]
}
}
}
Grafana Alerting
Grafana Alert Rule (provisioning)
apiVersion: 1
groups:
- orgId: 1
name: service_alerts
folder: Production
interval: 1m
rules:
- uid: high-error-rate
title: High Error Rate
condition: C
data:
- refId: A
datasourceUid: prometheus
model:
expr: sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
- refId: B
datasourceUid: prometheus
model:
expr: sum(rate(http_requests_total[5m])) by (service)
- refId: C
datasourceUid: __expr__
model:
type: math
expression: "$A / $B > 0.05"
for: 5m
labels:
severity: critical
annotations:
summary: "Error rate exceeds 5%"
4. OpenTelemetry
OpenTelemetry 개요
OpenTelemetry(OTel)는 메트릭, 로그, 트레이스를 수집하는 벤더 중립적인 표준입니다.
┌──────────────┐ ┌────────────────┐ ┌─────────────┐
│ Application │────>│ OTel │────>│ Backend │
│ + OTel SDK │ │ Collector │ │ - Jaeger │
│ │ │ - Receivers │ │ - Tempo │
│ │ │ - Processors │ │ - Prometheus│
│ │ │ - Exporters │ │ - Loki │
└──────────────┘ └────────────────┘ └─────────────┘
SDK 계측 (Node.js)
// tracing.ts - 애플리케이션 시작 시 가장 먼저 import
const sdk = new NodeSDK({
resource: new Resource({
[ATTR_SERVICE_NAME]: 'payment-service',
[ATTR_SERVICE_VERSION]: '1.2.0',
environment: 'production',
}),
traceExporter: new OTLPTraceExporter({
url: 'http://otel-collector:4317',
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: 'http://otel-collector:4317',
}),
exportIntervalMillis: 30000,
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': { enabled: true },
'@opentelemetry/instrumentation-express': { enabled: true },
'@opentelemetry/instrumentation-pg': { enabled: true },
'@opentelemetry/instrumentation-redis': { enabled: true },
}),
],
});
sdk.start();
수동 계측 (Custom Spans)
const tracer = trace.getTracer('payment-service');
async function processPayment(orderId: string, amount: number) {
return tracer.startActiveSpan('processPayment', async (span) => {
try {
span.setAttribute('order.id', orderId);
span.setAttribute('payment.amount', amount);
span.setAttribute('payment.currency', 'USD');
// 하위 span 생성
const validationResult = await tracer.startActiveSpan(
'validatePayment',
async (validationSpan) => {
const result = await validatePaymentDetails(orderId);
validationSpan.setAttribute('validation.result', result.valid);
validationSpan.end();
return result;
}
);
if (!validationResult.valid) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: 'Payment validation failed',
});
throw new Error('Invalid payment');
}
const result = await chargePayment(orderId, amount);
span.setAttribute('payment.transactionId', result.transactionId);
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error) {
span.recordException(error);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
throw error;
} finally {
span.end();
}
});
}
OTel Collector 설정
otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
prometheus:
config:
scrape_configs:
- job_name: 'otel-collector'
scrape_interval: 10s
static_configs:
- targets: ['0.0.0.0:8888']
processors:
batch:
timeout: 5s
send_batch_size: 1000
memory_limiter:
check_interval: 1s
limit_mib: 512
attributes:
actions:
- key: environment
value: production
action: upsert
exporters:
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
prometheusremotewrite:
endpoint: http://prometheus:9090/api/v1/write
loki:
endpoint: http://loki:3100/loki/api/v1/push
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch, attributes]
exporters: [otlp/jaeger]
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, batch]
exporters: [prometheusremotewrite]
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [loki]
5. 분산 추적 (Distributed Tracing)
Jaeger와 Grafana Tempo
**Jaeger**: 독립형 분산 추적 시스템. UI가 내장되어 있어 빠르게 시작할 수 있습니다.
**Grafana Tempo**: Grafana 생태계에 통합된 추적 백엔드. 인덱스가 없어 저장 비용이 낮습니다.
Docker Compose로 추적 스택 구성
docker-compose.yaml
version: '3.8'
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
environment:
- COLLECTOR_OTLP_ENABLED=true
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
ports:
- "4317:4317"
- "4318:4318"
depends_on:
- jaeger
트레이스 분석 팁
1. **느린 Span 찾기**: 전체 트레이스에서 가장 긴 span을 식별합니다
2. **에러 Span 필터**: status=ERROR로 필터링하여 실패 지점을 파악합니다
3. **서비스 맵**: 서비스 간 의존성과 호출 패턴을 시각화합니다
4. **비교 분석**: 정상 트레이스와 문제 트레이스를 나란히 비교합니다
6. 로깅 (Logging)
ELK vs Loki vs CloudWatch
| 구분 | ELK Stack | Grafana Loki | CloudWatch Logs |
|------|-----------|--------------|-----------------|
| 인덱싱 | 전문 인덱스 | 라벨 기반 | 로그 그룹 |
| 스토리지 비용 | 높음 | 낮음 | 중간 |
| 쿼리 언어 | KQL/Lucene | LogQL | Insights |
| Grafana 통합 | 플러그인 | 네이티브 | 플러그인 |
| 적합한 규모 | 대규모 | 중소규모 | AWS 네이티브 |
Grafana Loki + LogQL
서비스별 에러 로그
{service="payment-service"} |= "ERROR"
JSON 파싱 후 필터
{service="api-gateway"} | json | status >= 500
에러 발생 빈도 (1분당)
count_over_time({service="payment-service"} |= "ERROR" [1m])
느린 요청 필터 (1초 이상)
{service="api-gateway"} | json | duration > 1000
특정 트레이스 ID로 전체 로그 검색
{service=~".+"} |= "trace_id=abc123def456"
구조화 로깅 구현 (Node.js)
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level(label) {
return { level: label };
},
},
timestamp: pino.stdTimeFunctions.isoTime,
base: {
service: 'payment-service',
version: '1.2.0',
environment: process.env.NODE_ENV,
},
});
// 요청별 컨텍스트 포함
function createRequestLogger(req) {
return logger.child({
requestId: req.id,
traceId: req.headers['x-trace-id'],
userId: req.user?.id,
method: req.method,
path: req.url,
});
}
// 사용 예시
app.use((req, res, next) => {
req.log = createRequestLogger(req);
req.log.info('Request received');
res.on('finish', () => {
req.log.info({
statusCode: res.statusCode,
duration: Date.now() - req.startTime,
}, 'Request completed');
});
next();
});
7. SRE 핵심 개념
SLI (Service Level Indicator)
서비스 품질을 측정하는 구체적인 지표입니다.
가용성 SLI: 성공 요청 비율
sum(rate(http_requests_total{status!~"5.."}[30d]))
/
sum(rate(http_requests_total[30d]))
지연 SLI: P99 < 300ms인 요청 비율
sum(rate(http_request_duration_seconds_bucket{le="0.3"}[30d]))
/
sum(rate(http_request_duration_seconds_count[30d]))
SLO (Service Level Objective)
SLI의 목표값입니다.
- **가용성 SLO**: 99.9% (월간 다운타임 43분)
- **지연 SLO**: P99 응답 시간 300ms 미만
SLO 정의 (Sloth 형식)
version: "prometheus/v1"
service: "payment-service"
labels:
team: "platform"
slos:
- name: "availability"
objective: 99.9
sli:
events:
error_query: sum(rate(http_requests_total{status=~"5..",service="payment"}[{{.window}}]))
total_query: sum(rate(http_requests_total{service="payment"}[{{.window}}]))
alerting:
page_alert:
labels:
severity: critical
ticket_alert:
labels:
severity: warning
Error Budget (에러 예산)
SLO 99.9%라면, 에러 예산은 0.1%입니다.
- 30일 기준: 43.2분의 다운타임 허용
- 에러 예산이 남으면: 새 기능 배포, 실험 가능
- 에러 예산을 소진하면: 안정화에 집중, 배포 동결
남은 에러 예산 (%)
1 - (
(1 - service:availability:ratio30d)
/
(1 - 0.999)
)
SLA (Service Level Agreement)
고객과의 계약입니다. SLO보다 느슨하게 설정합니다.
SLA > SLO > SLI (측정)
예시:
- SLA: 99.9% (계약, 위반 시 환불)
- SLO: 99.95% (내부 목표, SLA보다 엄격)
- SLI: 99.97% (실제 측정값)
8. 알림 전략 (Alerting Strategy)
알림 피라미드
/ P1: Page \ → 즉시 대응 (PagerDuty)
/ (심각, 고객 영향) \
/──────────────────\
/ P2: Ticket \ → 업무 시간 내 처리 (Jira)
/ (성능 저하, 잠재 위험) \
/──────────────────────\
/ P3: Notification \ → 인지만 필요 (Slack)
/ (경고, 트렌드 변화) \
/──────────────────────────\
/ P4: Dashboard only \ → 대시보드 확인
/ (참고 지표, 자동 복구 가능) \
AlertManager 라우팅
alertmanager.yml
global:
resolve_timeout: 5m
route:
receiver: 'default-slack'
group_by: ['alertname', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- match:
severity: critical
receiver: 'pagerduty-critical'
group_wait: 10s
repeat_interval: 1h
- match:
severity: warning
receiver: 'slack-warnings'
repeat_interval: 4h
- match:
severity: info
receiver: 'slack-info'
repeat_interval: 12h
receivers:
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key: 'your-pagerduty-key'
severity: critical
- name: 'slack-warnings'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#alerts-warning'
title: '[WARNING] {{ .GroupLabels.alertname }}'
- name: 'slack-info'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#alerts-info'
- name: 'default-slack'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#alerts'
inhibit_rules:
- source_match:
severity: critical
target_match:
severity: warning
equal: ['alertname', 'service']
좋은 알림의 조건
1. **실행 가능(Actionable)**: 알림을 받으면 할 수 있는 일이 있어야 합니다
2. **긴급도 구분**: 진짜 긴급한 것만 페이지(호출)합니다
3. **컨텍스트 포함**: Runbook 링크, 관련 대시보드 링크를 포함합니다
4. **알림 피로 방지**: 너무 많은 알림은 모든 알림을 무시하게 만듭니다
5. **자동 복구 우선**: 가능하면 자동 복구 후 알림을 줍니다
9. 온콜(On-Call) 문화
온콜 로테이션 설계
주간 로테이션 예시:
- Primary: 첫 번째 대응자 (5분 내 응답)
- Secondary: 백업 대응자 (Primary 미응답 시 10분 후 에스컬레이션)
- 관리자: 30분 이상 미해결 시 에스컬레이션
교대 주기: 1주일
핸드오프: 매주 월요일 오전 10시
보상: 온콜 수당, 대체 휴무
인시던트 대응 프로세스
1. 감지(Detect)
└── 알림 수신, 영향 범위 초기 파악
2. 대응(Respond)
└── 인시던트 채널 생성, 역할 할당
- IC (Incident Commander): 조율
- Tech Lead: 기술 조사
- Comms: 고객/이해관계자 소통
3. 완화(Mitigate)
└── 즉각적인 조치 (롤백, 스케일 아웃 등)
4. 해결(Resolve)
└── 근본 원인 수정, 서비스 복구 확인
5. 사후 분석(Postmortem)
└── 비난 없는 회고, 재발 방지 액션 아이템 도출
10. 프로덕션 모니터링 스택 아키텍처
권장 스택 구성
┌─────────────────────────────────────────────┐
│ Grafana │
│ (대시보드, 알림, 탐색) │
└───────┬─────────────┬──────────────┬─────────┘
│ │ │
┌────▼────┐ ┌─────▼─────┐ ┌────▼────┐
│Prometheus│ │ Loki │ │ Tempo │
│(Metrics) │ │ (Logs) │ │(Traces) │
└────▲────┘ └─────▲─────┘ └────▲────┘
│ │ │
┌────┴─────────────┴──────────────┴────┐
│ OpenTelemetry Collector │
│ (수집, 처리, 라우팅) │
└────▲─────────────▲──────────────▲────┘
│ │ │
┌────┴────┐ ┌─────┴─────┐ ┌────┴────┐
│Service A│ │Service B │ │Service C│
│+OTel SDK│ │+OTel SDK │ │+OTel SDK│
└─────────┘ └───────────┘ └─────────┘
Kubernetes 환경 모니터링
kube-prometheus-stack values.yaml (Helm)
prometheus:
prometheusSpec:
retention: 15d
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: gp3
resources:
requests:
storage: 100Gi
grafana:
dashboardProviders:
dashboardproviders.yaml:
apiVersion: 1
providers:
- name: 'default'
folder: ''
type: file
options:
path: /var/lib/grafana/dashboards
alertmanager:
config:
route:
receiver: 'slack'
group_by: ['alertname', 'namespace']
receivers:
- name: 'slack'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#k8s-alerts'
11. 실무 면접 질문 15선
기초 (1-5)
**Q1. 관측성의 3가지 축을 설명하세요.**
Metrics(메트릭), Logs(로그), Traces(트레이스)입니다. 메트릭은 숫자로 된 시계열 데이터로 시스템 상태의 집계 뷰를, 로그는 이벤트의 상세 텍스트 기록을, 트레이스는 요청이 여러 서비스를 거치는 경로를 보여줍니다.
**Q2. Prometheus의 Pull 모델을 설명하세요.**
Prometheus가 직접 타겟 서비스의 /metrics 엔드포인트를 주기적으로 스크래핑합니다. Push 모델과 달리 서버가 수집 대상을 제어하며, 서비스 디스커버리와 결합하여 동적 환경을 지원합니다.
**Q3. Counter, Gauge, Histogram의 차이를 설명하세요.**
Counter는 단조 증가하는 값(총 요청 수), Gauge는 오르내리는 현재값(메모리 사용량), Histogram은 값의 분포를 버킷으로 관측하는 타입(응답 시간 분포)입니다.
**Q4. 구조화 로깅이 왜 중요한가요?**
JSON 등 일관된 형식으로 로그를 남기면 자동 파싱, 필터링, 검색이 가능합니다. traceId를 포함하면 분산 시스템에서 로그와 트레이스를 연결하여 디버깅이 훨씬 빨라집니다.
**Q5. SLI, SLO, SLA의 차이를 설명하세요.**
SLI(Service Level Indicator)는 실제 측정 지표, SLO(Service Level Objective)는 내부 목표값, SLA(Service Level Agreement)는 고객과의 법적 계약입니다. SLA는 SLO보다 느슨하게 설정합니다.
중급 (6-10)
**Q6. PromQL의 rate()와 increase()의 차이는?**
rate()는 초당 평균 증가율을 반환하고, increase()는 주어진 시간 범위 동안의 총 증가량을 반환합니다. rate()는 그래프에, increase()는 총 카운트에 적합합니다.
**Q7. OpenTelemetry Collector의 역할을 설명하세요.**
텔레메트리 데이터(메트릭, 로그, 트레이스)를 수신(Receiver)하고, 처리(Processor, 배치, 필터링)한 뒤, 여러 백엔드(Exporter)로 전송합니다. 애플리케이션과 백엔드 사이의 중간 계층으로 벤더 종속을 방지합니다.
**Q8. Error Budget의 개념과 활용법을 설명하세요.**
SLO에서 허용하는 에러 비율입니다. 99.9% SLO라면 에러 예산은 0.1%(월 43분). 예산이 남으면 새 기능을 배포하고, 소진하면 안정화에 집중합니다. 개발 속도와 신뢰성 사이의 균형을 수치로 관리합니다.
**Q9. Distributed Tracing에서 Span과 Trace의 관계는?**
Trace는 하나의 요청이 시스템을 통과하는 전체 여정이고, Span은 그 여정 내 개별 작업 단위입니다. Span은 부모-자식 관계로 트리를 형성하며, 각 span에는 시작/종료 시간, 속성, 상태가 있습니다.
**Q10. Grafana Loki와 ELK의 주요 차이점은?**
ELK는 로그 텍스트를 전문 인덱싱하여 강력한 검색을 제공하지만 스토리지 비용이 높습니다. Loki는 라벨만 인덱싱하고 로그 텍스트는 압축 저장하여 비용이 낮지만, 라벨 기반 필터링 후 텍스트 검색이 필요합니다.
고급 (11-15)
**Q11. 알림 피로(Alert Fatigue)를 어떻게 방지하나요?**
실행 가능한 알림만 설정하고, 심각도를 명확히 구분합니다. inhibit rules로 중복 알림을 억제하고, grouping으로 유사 알림을 묶습니다. 정기적으로 알림을 리뷰하여 노이즈를 제거합니다.
**Q12. Prometheus의 Recording Rules은 왜 필요한가요?**
복잡한 PromQL 쿼리를 미리 계산하여 새로운 시계열로 저장합니다. 대시보드 로딩 시간을 줄이고, 같은 쿼리의 반복 실행을 방지합니다. 특히 SLO 대시보드처럼 장기 범위 쿼리에 효과적입니다.
**Q13. OpenTelemetry에서 Context Propagation이란?**
트레이스 컨텍스트(trace ID, span ID)를 서비스 간 전파하는 메커니즘입니다. HTTP 헤더(W3C Trace Context)나 메시지 큐의 메타데이터를 통해 전파하며, 이를 통해 분산 시스템에서 하나의 요청을 엔드투엔드로 추적합니다.
**Q14. Golden Signals과 RED/USE 방법론을 비교하세요.**
Google의 Golden Signals는 Latency, Traffic, Errors, Saturation입니다. RED(Rate, Errors, Duration)는 서비스 관점, USE(Utilization, Saturation, Errors)는 인프라 관점에 적합합니다. 서비스에는 RED, 인프라에는 USE를 적용하는 것이 일반적입니다.
**Q15. 비난 없는(Blameless) Postmortem의 핵심 원칙은?**
개인을 비난하지 않고 시스템의 실패에 초점을 맞춥니다. 타임라인을 재구성하고, 기여 요인을 분석하며, 구체적이고 측정 가능한 액션 아이템을 도출합니다. 목표는 같은 문제가 재발하지 않도록 시스템을 개선하는 것입니다.
12. 실전 퀴즈 5문제
**정답: Gauge**
Gauge는 올라갔다 내려갔다 하는 순간 값을 나타냅니다. Counter는 단조 증가만 하므로 메모리 사용량처럼 감소할 수 있는 값에는 적합하지 않습니다. Histogram은 분포를 측정하는 데 사용합니다.
**정답: 약 43분**
30일 = 43,200분. 에러 예산 = 43,200 x 0.001 = 43.2분. 이 시간 내의 장애는 SLO를 위반하지 않습니다.
**정답: Receivers, Processors, Exporters**
Receivers는 데이터를 수신하고(OTLP, Prometheus 등), Processors는 데이터를 처리하며(배치, 필터링, 속성 추가 등), Exporters는 데이터를 백엔드로 전송합니다(Jaeger, Prometheus, Loki 등).
**정답: 최근 5분간 HTTP 요청 응답 시간의 95번째 백분위수(P95)**
histogram_quantile은 히스토그램 버킷에서 분위수를 계산합니다. 0.95는 95%를 의미하며, le(less than or equal) 라벨별로 그룹화된 버킷 데이터에서 P95를 추출합니다.
**정답: 서비스 간 요청을 하나의 트레이스로 연결할 수 없게 됩니다.**
Context Propagation이 없으면 각 서비스가 독립적인 트레이스를 생성합니다. 하나의 사용자 요청이 여러 서비스를 거칠 때 전체 경로를 파악할 수 없어, 분산 시스템에서의 디버깅이 극히 어려워집니다.
참고 자료 (References)
1. [Prometheus 공식 문서](https://prometheus.io/docs/)
2. [Grafana 공식 문서](https://grafana.com/docs/)
3. [OpenTelemetry 공식 문서](https://opentelemetry.io/docs/)
4. [Jaeger 공식 문서](https://www.jaegertracing.io/docs/)
5. [Grafana Loki 문서](https://grafana.com/docs/loki/)
6. [Grafana Tempo 문서](https://grafana.com/docs/tempo/)
7. [Google SRE Book](https://sre.google/sre-book/table-of-contents/)
8. [Google SRE Workbook](https://sre.google/workbook/table-of-contents/)
9. [PromQL 치트시트](https://promlabs.com/promql-cheat-sheet/)
10. [Sloth - SLO Generator](https://github.com/slok/sloth)
11. [OpenTelemetry Collector 설정](https://opentelemetry.io/docs/collector/configuration/)
12. [Alertmanager 라우팅 트리](https://prometheus.io/docs/alerting/latest/configuration/)
13. [LogQL 문서](https://grafana.com/docs/loki/latest/logql/)
14. [Pino Logger (Node.js)](https://getpino.io/)
15. [kube-prometheus-stack Helm Chart](https://github.com/prometheus-community/helm-charts)
16. [DORA Metrics 가이드](https://dora.dev/guides/)
현재 단락 (1/647)
"모니터링은 시스템이 잘 동작하는지 확인하는 것이고, 관측성은 시스템이 왜 잘못 동작하는지 이해하는 것이다."