[EKS] kube-prometheus-stack을 활용한 모니터링 시스템 구축
AWS EKS를 운영하다 보면 반드시 마주치는 순간이 있다.
"파드가 죽었는데 왜 죽었지?" "어제 밤에 API 응답이 느렸는데 CPU 문제인가, 메모리 릭인가?"
CloudWatch Container Insights가 있지만, 비용이 만만치 않고 파드 내부의 세밀한 애플리케이션 지표를 보기엔 한계가 있다. 그래서 Prometheus와 Grafana를 찾게 된다.
kube-prometheus-stack을 이용해 EKS 환경에 제대로 된 모니터링 시스템을 구축하는 방법을 정리한다.
1. 왜 "Stack"인가?
Prometheus를 쿠버네티스에 설치하는 방법은 다양하지만, 실무에서는 대부분 Prometheus Operator 패턴을 사용한다.
기존 방식 vs Operator 방식
| 구분 | 기존 방식 | Operator 방식 |
|---|---|---|
| 설정 관리 | prometheus.yml 직접 수정 | CRD로 선언적 관리 |
| 타겟 추가 | 설정 파일 수정 + 재시작 | ServiceMonitor 생성만 하면 자동 반영 |
| 확장성 | 수동 관리 필요 | Operator가 자동 조율 |
kube-prometheus-stack은 이 Operator와 함께 Grafana, Alertmanager, Node-Exporter 등을 한 번에 묶어놓은 Helm 차트다.
설치 시 포함되는 컴포넌트
- Prometheus: 메트릭 수집 및 저장
- Prometheus Operator: CRD 기반 설정 관리
- Grafana: 시각화 대시보드
- Alertmanager: 알람 관리
- Node Exporter: 노드 레벨 메트릭
- kube-state-metrics: 쿠버네티스 오브젝트 상태 메트릭
flowchart TB
subgraph "kube-prometheus-stack"
Helm[Helm Chart] --> Operator[Prometheus Operator]
Operator --> Prometheus
Operator --> Alertmanager
Helm --> Grafana
Helm --> NodeExporter[Node Exporter]
Helm --> KSM[kube-state-metrics]
end
Prometheus --> Grafana
Alertmanager --> Slack/Email2. EKS 환경에 설치하기
EKS에서 가장 중요한 건 데이터 영속성이다. 파드가 재시작되어도 수집한 메트릭이 사라지지 않도록 EBS와 연결해야 한다.
사전 요구사항
# EBS CSI Driver 설치 확인 (필수!)
kubectl get pods -n kube-system | grep ebs-csi
# 없다면 EKS Add-on으로 설치
aws eks create-addon --cluster-name my-cluster --addon-name aws-ebs-csi-driverHelm 설치
# 레포 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update커스텀 values.yaml 작성
기본 설정으로 설치하면 데이터가 날아가거나 필요 없는 컴포넌트까지 설치된다. 실무용 설정을 작성하자.
# my-values.yaml
prometheus:
prometheusSpec:
# 데이터 보존 기간 (기본값이 짧으므로 10~15일 권장)
retention: 15d
# 리소스 설정 (메트릭이 많아지면 메모리 사용량 급증)
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
# EBS 볼륨 연결
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: gp3
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
# 모든 네임스페이스의 ServiceMonitor 수집 (중요!)
serviceMonitorSelectorNilUsesHelmValues: false
podMonitorSelectorNilUsesHelmValues: false
grafana:
adminPassword: "your-secure-password"
# Grafana 설정도 영속화
persistence:
enabled: true
storageClassName: gp3
size: 10Gi
# 기본 대시보드 활성화
defaultDashboardsEnabled: true
alertmanager:
alertmanagerSpec:
storage:
volumeClaimTemplate:
spec:
storageClassName: gp3
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
# EKS에서 접근 불가능한 컴포넌트 비활성화
kubeControllerManager:
enabled: false
kubeScheduler:
enabled: false
kubeEtcd:
enabled: false이 옵션을 false로 설정하지 않으면, 기본적으로 release 라벨이 있는 ServiceMonitor만 인식한다. 다른 네임스페이스의 앱을 모니터링하려면 반드시 false로 설정하자.
배포
# 네임스페이스 생성 및 배포
kubectl create namespace monitoring
helm install prometheus-stack prometheus-community/kube-prometheus-stack \
-f my-values.yaml \
-n monitoring
# 파드 상태 확인
kubectl get pods -n monitoring3. 핵심 개념: ServiceMonitor
설치가 끝났으니 이제 내 앱을 모니터링해보자. 가장 중요한 개념이 ServiceMonitor다.
왜 ServiceMonitor가 필요한가?
과거에는 prometheus.yml에 타겟 IP를 직접 적었다. 하지만 쿠버네티스에서는 파드 IP가 수시로 바뀐다.
그래서:
- Service가 파드들을 묶어주고
- ServiceMonitor가 "이 Service를 스크래핑해!"라고 Prometheus에게 알려준다
flowchart LR
subgraph "Application"
Pod1[Pod /metrics]
Pod2[Pod /metrics]
end
Pod1 --> Service
Pod2 --> Service
ServiceMonitor -->|"selector: app=backend"| Service
Prometheus -->|"reads"| ServiceMonitor
Prometheus -->|"scrapes"| ServiceServiceMonitor 동작 흐름
- App Pod:
/metrics경로로 지표를 노출 - Service: Pod들을 묶어줌 (label selector)
- ServiceMonitor: 특정 라벨을 가진 Service를 찾음
- Prometheus Operator: ServiceMonitor를 감지하고 Prometheus 설정에 반영
- Prometheus: 해당 Service의 엔드포인트를 스크래핑
4. 실전: Spring Boot 앱 연동
Step 1. 애플리케이션 준비
Spring Boot의 경우 Actuator + Micrometer로 메트릭을 노출한다.
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency># application.yml
management:
endpoints:
web:
exposure:
include: prometheus,health
endpoint:
prometheus:
enabled: trueStep 2. Service 배포
apiVersion: v1
kind: Service
metadata:
name: backend-service
namespace: default
labels:
app: backend # ← ServiceMonitor가 이 라벨을 찾는다
spec:
selector:
app: backend
ports:
- name: http-metrics # ← 포트 이름 지정 (중요!)
port: 8080
targetPort: 8080Step 3. ServiceMonitor 생성
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: backend-monitor
namespace: monitoring # Prometheus가 있는 네임스페이스
spec:
# 어떤 네임스페이스의 Service를 찾을 것인가
namespaceSelector:
matchNames:
- default
# 어떤 라벨을 가진 Service를 찾을 것인가
selector:
matchLabels:
app: backend
# 어떻게 스크래핑할 것인가
endpoints:
- port: http-metrics # Service의 포트 이름과 일치
path: /actuator/prometheus
interval: 15s
scrapeTimeout: 10sStep 4. 확인
# ServiceMonitor 확인
kubectl get servicemonitor -n monitoring
# Prometheus 타겟 확인 (포트포워딩 후 웹 UI 접속)
kubectl port-forward -n monitoring svc/prometheus-stack-kube-prom-prometheus 9090
# 브라우저에서 http://localhost:9090 -> Status -> Targets타겟이 UP 상태면 성공이다.
5. Grafana에서 시각화하기
접속
kubectl port-forward -n monitoring svc/prometheus-stack-grafana 3000:80
# 브라우저에서 http://localhost:3000
# ID: admin / PW: values.yaml에서 설정한 값유용한 PromQL 쿼리
초당 요청 수(RPS)
sum(rate(http_server_requests_seconds_count{app="backend"}[1m])) by (uri)응답 시간 p99
histogram_quantile(0.99,
sum(rate(http_server_requests_seconds_bucket{app="backend"}[5m])) by (le, uri)
)에러율
sum(rate(http_server_requests_seconds_count{app="backend", status=~"5.."}[5m]))
/
sum(rate(http_server_requests_seconds_count{app="backend"}[5m])) * 100JVM 힙 메모리 사용량
jvm_memory_used_bytes{area="heap", app="backend"}
/
jvm_memory_max_bytes{area="heap", app="backend"} * 1006. 트러블슈팅
문제 1: ServiceMonitor가 타겟을 못 찾음 (0/0 targets)
체크리스트:
-
라벨 매칭 확인
# Service 라벨 확인 kubectl get svc backend-service -o yaml | grep -A5 labels # ServiceMonitor selector 확인 kubectl get servicemonitor backend-monitor -n monitoring -o yaml | grep -A5 selector -
포트 이름 확인 - 숫자가 아닌 이름으로 지정해야 함
# 잘못된 예 endpoints: - port: 8080 # 올바른 예 endpoints: - port: http-metrics -
네임스페이스 확인 -
namespaceSelector설정 필수
문제 2: Prometheus가 ServiceMonitor 자체를 무시
Prometheus Operator가 ServiceMonitor를 인식하도록 라벨 설정:
# values.yaml에서 이 옵션을 false로 설정했는지 확인
prometheus:
prometheusSpec:
serviceMonitorSelectorNilUsesHelmValues: false문제 3: PVC가 Pending 상태
kubectl get pvc -n monitoring
# Pending이면 EBS CSI Driver 확인
kubectl get pods -n kube-system | grep ebsEBS CSI Driver가 없거나 IAM 권한이 없을 가능성이 높다.
7. 성능 최적화 팁
Label Cardinality 관리
라벨 조합이 많아지면 Prometheus 메모리가 폭발한다.
# metric_relabel_configs로 불필요한 라벨 제거
endpoints:
- port: http-metrics
metricRelabelings:
- action: labeldrop
regex: pod|instanceRecording Rules 활용
자주 사용하는 복잡한 쿼리는 Recording Rule로 미리 계산해두자.
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: backend-rules
namespace: monitoring
spec:
groups:
- name: backend.rules
rules:
- record: backend:http_requests:rate5m
expr: sum(rate(http_server_requests_seconds_count{app="backend"}[5m])) by (uri)리소스 할당
| 환경 | 메모리 | CPU | 스토리지 |
|---|---|---|---|
| 소규모 (pod 50개 이하) | 2Gi | 500m | 30Gi |
| 중규모 (pod 200개) | 4Gi | 1000m | 50Gi |
| 대규모 (pod 500개+) | 8Gi+ | 2000m+ | 100Gi+ |
8. 알람 설정 (Alertmanager)
Slack 연동
# values.yaml
alertmanager:
config:
global:
slack_api_url: 'https://hooks.slack.com/services/xxx/yyy/zzz'
route:
receiver: 'slack-notifications'
group_by: ['alertname']
group_wait: 10s
group_interval: 5m
repeat_interval: 12h
receivers:
- name: 'slack-notifications'
slack_configs:
- channel: '#alerts'
send_resolved: true
title: '{{ .Status | toUpper }}: {{ .CommonLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'기본 알람 예시
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: backend-alerts
namespace: monitoring
spec:
groups:
- name: backend.alerts
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
/ sum(rate(http_server_requests_seconds_count[5m])) > 0.05
for: 5m
labels:
severity: critical
annotations:
description: "에러율이 5%를 초과했습니다"
- alert: HighLatency
expr: |
histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le)) > 1
for: 5m
labels:
severity: warning
annotations:
description: "P99 응답 시간이 1초를 초과했습니다"마무리
kube-prometheus-stack은 방대하지만, 핵심 흐름만 이해하면 커스텀은 어렵지 않다.
핵심 포인트:
- ServiceMonitor가 Service를 가리키고, Prometheus가 ServiceMonitor를 읽는다
- 라벨 매칭이 모든 것의 시작
- serviceMonitorSelectorNilUsesHelmValues: false 설정 잊지 말기
- EBS 영속성 반드시 설정
이제 EKS 클러스터는 더 이상 블랙박스가 아니다. 대시보드를 꾸미고 알람을 설정해서 장애에 빠르게 대응하자.
Loading comments...