EKS Pod 리소스 최적화 가이드
📌 기준 환경: EKS 1.33+, Kubernetes 1.33+, Metrics Server v0.7+
개요
Kubernetes 환경에서 Pod 리소스 설정은 클러스터 효율성과 비용에 직접적인 영향을 미칩니다. 컨테이너의 50%가 요청한 CPU의 1/3만 사용하며, 이는 평균 40-60%의 리소스 낭비로 이어집니다. 이 가이드는 Pod 레벨 리소스 최적화를 통해 클러스터 효율성을 극대화하고 비용을 30-50% 절감하는 실전 전략을 제공합니다.
- karpenter-autoscaling.md: 노드 레벨 오토스케일링 (이 문서는 Pod 레벨)
- cost-management.md: 전체 비용 전략 (이 문서는 리소스 설정에 집중)
- eks-resiliency-guide.md: 리소스 설정을 체크리스트 항목으로만 다룸
핵심 내용
- Requests vs Limits 심층 이해: CPU throttling과 OOM Kill 메커니즘
- QoS 클래스 전략: Guaranteed, Burstable, BestEffort의 실전 활용
- VPA 완벽 가이드: 자동 리소스 조정과 HPA 공존 패턴
- Right-Sizing 방법론: P95 기반 리소스 산정 및 Goldilocks 활용
- 비용 영향 분석: 리소스 최적화의 실제 절감 효과
학습 목표
이 가이드를 완료하면 다음을 수행할 수 있습니다:
- CPU와 Memory requests/limits의 정확한 동작 원리 이해
- 워크로드 특성에 맞는 QoS 클래스 선택
- VPA와 HPA를 안전하게 공존시키는 구성
- 실제 사용량 기반 Right-Sizing 수행
- 리소스 효율성 30% 이상 개선
사전 요구사항
필요한 도구
| 도구 | 버전 | 용도 |
|---|---|---|
| kubectl | 1.28+ | Kubernetes 클러스터 관리 |
| helm | 3.12+ | VPA, Goldilocks 설치 |
| metrics-server | 0.7+ | 리소스 메트릭 수집 |
| kubectl-top | 내장 | 리소스 사용량 확인 |
필요한 권한
# RBAC 권한 확인
kubectl auth can-i get pods --all-namespaces
kubectl auth can-i get resourcequotas
kubectl auth can-i create verticalpodautoscaler
선행 지식
- Kubernetes Pod, Deployment 기본 개념
- YAML 매니페스트 작성 경험
- Linux cgroups 기본 이해 (권장)
- Prometheus/Grafana 기본 사용법 (권장)
Resource Requests & Limits 심층 이해
2.1 Requests vs Limits의 정확한 의미
Resource requests와 limits는 Kubernetes 리소스 관리의 핵심 개념입니다.
Requests (요청량)
- 정의: 스케줄러가 Pod 배치 시 보장하는 최소 리소스
- 역할: 노드 선택 기준, QoS 클래스 결정
- 보장: kubelet이 이 양을 항상 확보
Limits (제한량)
- 정의: kubelet이 강제하는 최대 리소스
- 역할: 리소스 고갈 방지, 노이지 네이버(noisy neighbor) 제한
- 강제: CPU는 throttling, Memory는 OOM Kill
핵심 차이점
| 속성 | CPU | Memory |
|---|---|---|
| Requests 초과 시 | 다른 Pod가 사용 안 하면 사용 가능 | 다른 Pod가 사용 안 하면 사용 가능 |
| Limits 초과 시 | Throttling (프로세스 속도 저하) | OOM Kill (프로세스 강제 종료) |
| 압축 가능 여부 | 압축 가능 (Compressible) | 압축 불가 (Incompressible) |
| 초과 사용 위험 | 성능 저하 | 서비스 중단 |
2.2 CPU 리소스 깊이 이해
CPU Millicore 단위
# CPU 표기법
resources:
requests:
cpu: "500m" # 500 millicore = 0.5 CPU core
cpu: "1" # 1000 millicore = 1 CPU core
cpu: "2.5" # 2500 millicore = 2.5 CPU cores
1 CPU core = 1000 millicore
- AWS vCPU, Azure vCore 모두 동일
- 하이퍼스레딩 환경에서도 논리 코어 기준
CFS Bandwidth Throttling
Linux CFS (Completely Fair Scheduler)는 CPU limits를 강제 합니다:
# cgroups v2 기준
/sys/fs/cgroup/cpu.max
# 예시: "100000 100000" = 100ms 주기당 100ms 사용 가능 (100% = 1 CPU)
# 예시: "50000 100000" = 100ms 주기당 50ms 사용 가능 (50% = 0.5 CPU)
Throttling 메커니즘
시간 주기: 100ms
CPU Limit: 500m (0.5 CPU)
→ 100ms 중 50ms만 사용 가능
실제 동작:
[0-50ms] ████████████████████ (실행)
[50-100ms] ...................... (throttled)
[100-150ms] ████████████████████ (실행)
[150-200ms] ...................... (throttled)
Google, Datadog 등 대규모 클러스터 운영 조직은 CPU limits를 설정하지 않습니다:
이유:
- CPU는 압축 가능한 리소스 (다른 Pod가 필요하면 자동 조정)
- Throttling으로 인한 불필요한 성능 저하 방지
- Requests만으로도 스케줄링과 QoS 제어 가능
대신 권장:
- CPU requests는 P95 사용량 기준으로 설정
- HPA로 부하에 따른 수평 확장
- Node-level 리소스 모니터링 강화
예외 (Limits 설정 필요):
- 배치 작업 (CPU 독점 방지)
- 신뢰할 수 없는 워크로드
- 멀티테넌트 환경
CPU 리소스 설정 예시
# 패턴 1: Requests만 설정 (권장)
apiVersion: v1
kind: Pod
metadata:
name: web-server
spec:
containers:
- name: nginx
image: nginx:1.25
resources:
requests:
cpu: "250m" # P95 사용량 기준
memory: "128Mi"
# limits 생략 - CPU 압축 가능 리소스 활용
---
# 패턴 2: 배치 작업 (Limits 설정)
apiVersion: batch/v1
kind: Job
metadata:
name: data-processing
spec:
template:
spec:
containers:
- name: processor
image: data-processor:v1
resources:
requests:
cpu: "1000m"
limits:
cpu: "2000m" # CPU 독점 방지
memory: "4Gi"
restartPolicy: OnFailure
2.3 Memory 리소스 깊이 이해
Memory 단위
# Memory 표기법 (1024 기반 vs 1000 기반)
resources:
requests:
memory: "128Mi" # 128 * 1024^2 bytes = 134,217,728 bytes
memory: "128M" # 128 * 1000^2 bytes = 128,000,000 bytes
memory: "1Gi" # 1 * 1024^3 bytes = 1,073,741,824 bytes
memory: "1G" # 1 * 1000^3 bytes = 1,000,000,000 bytes
권장: Mi, Gi 사용 (1024 기반, Kubernetes 표준)
OOM Kill 메커니즘
Memory limits 초과 시 Linux OOM Killer가 프로세스를 강제 종료합니다:
실제 사용량 > Memory Limit
→ cgroup memory.max 초과
→ Kernel OOM Killer 발동
→ 프로세스 SIGKILL
→ Pod 상태: OOMKilled
→ kubelet이 Pod 재시작 (RestartPolicy 따름)
OOM Score 계산
# 프로세스별 OOM Score 확인
cat /proc/<PID>/oom_score
# OOM Score 계산 요소
# 1. 메모리 사용량 (높을수록 점수 높음)
# 2. oom_score_adj 값 (QoS 클래스별로 다름)
# 3. 루트 프로세스 보호 (-1000 = 절대 Kill 안 함)
Memory는 압축 불가능한 리소스이므로 반드시 limits 설정 필요:
이유:
- Memory 고갈 시 전체 노드 불안정
- Kernel Panic 가능성
- 다른 Pod에 영향 (노드 Eviction)
권장 설정:
requests = limits(Guaranteed QoS)- 또는
limits = requests * 1.5(Burstable QoS) - JVM 애플리케이션: Heap 크기는 limits의 75%로 설정
Memory 리소스 설정 예시
# 패턴 1: Guaranteed QoS (안정성 최우선)
apiVersion: apps/v1
kind: Deployment
metadata:
name: database
spec:
replicas: 3
template:
spec:
containers:
- name: postgres
image: postgres:16
resources:
requests:
cpu: "2000m"
memory: "4Gi"
limits:
cpu: "2000m" # requests와 동일
memory: "4Gi" # requests와 동일 (Guaranteed)
---
# 패턴 2: JVM 애플리케이션
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-app
spec:
template:
spec:
containers:
- name: app
image: java-app:v1
env:
- name: JAVA_OPTS
value: "-Xmx3072m -Xms3072m" # limits의 75% (4Gi * 0.75 = 3Gi)
resources:
requests:
memory: "4Gi"
limits:
memory: "4Gi"
---
# 패턴 3: Node.js 애플리케이션
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-api
spec:
template:
spec:
containers:
- name: api
image: nodejs-api:v2
env:
- name: NODE_OPTIONS
value: "--max-old-space-size=896" # limits의 70% (1280Mi * 0.7 = 896Mi)
resources:
requests:
memory: "1280Mi"
limits:
memory: "1280Mi"
2.4 Ephemeral Storage
컨테이너 로컬 스토리지도 리소스로 관리할 수 있습니다:
apiVersion: v1
kind: Pod
metadata:
name: ephemeral-demo
spec:
containers:
- name: app
image: busybox
resources:
requests:
ephemeral-storage: "2Gi" # 최소 보장
limits:
ephemeral-storage: "4Gi" # 최대 사용량
volumeMounts:
- name: cache
mountPath: /cache
volumes:
- name: cache
emptyDir:
sizeLimit: "4Gi"
Ephemeral Storage 포함 항목:
- 컨테이너 레이어 쓰기
- 로그 파일 (
/var/log) - emptyDir 볼륨
- 임시 파일
노드 Eviction Threshold:
# kubelet 설정
evictionHard:
nodefs.available: "10%" # 노드 전체 디스크 10% 미만 시 eviction
nodefs.inodesFree: "5%" # inode 5% 미만 시 eviction
imagefs.available: "10%" # 이미지 파일시스템 10% 미만 시 eviction
2.5 EKS Auto Mode 리소스 최적화
EKS Auto Mode는 Kubernetes 클러스터 운영의 복잡성을 극적으로 줄이는 완전 관리형 솔루션입니다. 컴퓨팅, 스토리지, 네트워킹의 프로비저닝부터 지속적 유지보수까지 자동화하여 운영팀이 인프라 관리 대신 애플리케이션 개발에 집중할 수 있게 합니다.
2.5.1 Auto Mode 개요
핵심 기능:
- 단일 클릭 활성화: 클러스터 생성 시
--compute-config autoMode플래그만으로 활성화 - 자동 인프라 프로비저닝: Pod 스케줄링 요구사항에 따라 최적 인스턴스 타입 자동 선택
- 지속적 유지보수: OS 패치, 보안 업데이트, 코어 애드온 관리 자동화
- 비용 최적화: Graviton 프로세서와 Spot 인스턴스 자동 활용
- 통합 보안: AWS 보안 서비스 기본 통합
# Auto Mode 클러스터 생성
aws eks create-cluster \
--name my-auto-cluster \
--compute-config autoMode=ENABLED \
--kubernetes-network-config serviceIpv4Cidr=10.100.0.0/16 \
--access-config bootstrapClusterCreatorAdminPermissions=true
Auto Mode는 기존 수동 관리 방식을 완전히 대체하는 것이 아니라, 운영 오버헤드를 최소화하려는 팀을 위한 보완적 선택지입니다. 세밀한 제어가 필요한 경우 여전히 수동 관리 방식을 선택할 수 있습니다.
2.5.2 Auto Mode vs 수동 관리 비교
| 항목 | 수동 관리 | Auto Mode |
|---|---|---|
| 노드 프로비저닝 | Managed Node Group, Self-managed, Karpenter 직접 구성 | 자동 프로비저닝 (EC2 Managed Instances 기반) |
| 인스턴스 타입 선택 | 수동 선택 및 NodePool 구성 | Pod 요구사항 기반 자동 선택 (Graviton 우선) |
| VPA 설정 | 수동 설치 및 구성 필요 | 필요 없음 (자동 리소스 최적화) |
| HPA 설정 | 수동 설정 및 메트릭 구성 | 자동 구성 가능 (개발자는 선언만) |
| OS 패치 | 수동 또는 자동화 스크립트 | 완전 자동 (무중단) |
| 보안 업데이트 | 수동 적용 | 자동 적용 |
| 코어 애드온 관리 | 수동 업그레이드 (CoreDNS, kube-proxy, VPC CNI) | 자동 업그레이드 |
| 비용 최적화 | Spot, Graviton 수동 구성 | 자동 활용 (최대 90% 절감) |
| Request/Limit 설정 | 개발자 책임 (필수) | 개발자 책임 (여전히 필수) |
| 리소스 효율성 | VPA Off 모드 + 수동 적용 | 자동 Right-Sizing (지속적) |
| 학습 곡선 | 높음 (Kubernetes, AWS 전문 지식 필요) | 낮음 (Kubernetes 기본만 필요) |
| 운영 오버헤드 | 높음 | 최소 |
Auto Mode는 인프라를 자동화하지만, Pod-level requests/limits 설정은 여전히 개발자의 책임입니다. 이는 애플리케이션의 실제 리소스 요구사항을 가장 잘 아는 사람이 개발자이기 때문입니다.
2.5.3 Graviton + Spot 조합 최적화
Auto Mode는 AWS Graviton 프로세서와 Spot 인스턴스를 지능적으로 조합하여 비용 효율성을 극대화합니다.
Graviton 프로세서의 장점:
- 40% 향상된 가격 대비 성능 (x86 대비)
- 범용 워크로드, 웹 서버, 컨테이너화된 마이크로서비스에 최적
- Arm64 아키텍처 지원 (대부분의 컨테이너 이미지 호환)
Spot 인스턴스 절감:
- 최대 90% 비용 절감 (On-Demand 대비)
- Auto Mode가 자동으로 Spot 가용성 모니터링 및 Fallback 처리
- 중단 2분 전 알림으로 Graceful Termination 보장
NodePool YAML 예시 (수동 관리 클러스터 - Karpenter 기반):
# Auto Mode는 이러한 NodePool을 자동 생성하지만,
# 참고를 위해 수동 설정 시 Graviton + Spot 패턴을 보여줍니다
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: graviton-spot-pool
spec:
template:
spec:
requirements:
# Graviton 인스턴스 우선
- key: kubernetes.io/arch
operator: In
values: ["arm64"]
# Spot 우선, Fallback으로 On-Demand
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
# 범용 워크로드용 인스턴스 패밀리
- key: node.kubernetes.io/instance-type
operator: In
values: ["m7g.medium", "m7g.large", "m7g.xlarge", "m7g.2xlarge"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
# Spot 중단 처리
disruption:
consolidationPolicy: WhenUnderutilized
expireAfter: 720h
# 리소스 제한
limits:
cpu: "1000"
memory: "1000Gi"
---
# Fallback: x86 On-Demand (Spot 불가 시)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: x86-ondemand-fallback
spec:
weight: 10 # 낮은 우선순위
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m6i.large", "m6i.xlarge", "m6i.2xlarge"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
Auto Mode에서의 자동 처리:
Auto Mode는 위와 같은 NodePool 구성을 수동으로 작성할 필요 없이, Pod의 리소스 요구사항과 워크로드 특성을 분석하여 자동으로 최적 인스턴스를 선택합니다.
# Auto Mode 환경에서 개발자가 작성하는 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
spec:
replicas: 10
template:
spec:
containers:
- name: nginx
image: nginx:1.25-arm64 # Graviton용 이미지
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
memory: "1Gi"
# Auto Mode가 자동으로:
# 1. Graviton Spot 인스턴스 선택 시도
# 2. Spot 불가 시 Graviton On-Demand로 Fallback
# 3. 인스턴스 타입 자동 선택 (m7g.large 등)
# 4. 노드 프로비저닝 및 Pod 배치
Graviton 인스턴스를 활용하려면 arm64 아키텍처 컨테이너 이미지가 필요합니다. 대부분의 공식 이미지는 multi-arch를 지원하므로, 동일한 이미지 태그로 Graviton과 x86 모두에서 실행 가능합니다.
# multi-arch 이미지 확인
docker manifest inspect nginx:1.25 | jq '.manifests[].platform'
# 출력 예시:
# { "architecture": "amd64", "os": "linux" }
# { "architecture": "arm64", "os": "linux" }
실제 비용 절감 예시:
| 시나리오 | 인스턴스 타입 | 시간당 비용 | 월간 비용 (730시간) | 절감률 |
|---|---|---|---|---|
| x86 On-Demand | m6i.2xlarge | $0.384 | $280.32 | - |
| Graviton On-Demand | m7g.2xlarge | $0.3264 | $238.27 | 15% |
| Graviton Spot | m7g.2xlarge | $0.0979 | $71.47 | 75% |
10개 노드 기준:
- x86 On-Demand: $2,803/월
- Graviton On-Demand: $2,383/월 (15% 절감)
- Graviton Spot: $715/월 (75% 절감) ⭐
Graviton4 및 Graviton5 특화 최적화:
Graviton4 (R8g, M8g, C8g) 인스턴스는 Graviton3 대비 30% 향상된 컴퓨팅 성능과 75% 향상된 메모리 대역폭을 제공합니다. Graviton5 (M9g, M9gd)는 2026년 6월 GA되었으며, M8g 대비 약 25% 추가 성능 향상을 제공합니다.
| 세대 | 인스턴스 패밀리 | 성능 개선 | 주요 워크로드 |
|---|---|---|---|
| Graviton3 | m7g, c7g, r7g | 기준 | 범용 웹/API, 컨테이너 |
| Graviton4 | m8g, c8g, r8g (8g 시리즈) | +30% 컴퓨팅, +75% 메모리 | 고성능 데이터베이스, ML 추론, 실시간 분석 |
| Graviton5 | m9g, m9gd | Graviton4 대비 +25% | 최신 고성능 워크로드 |
ARM64 Multi-Arch 빌드 파이프라인:
Graviton 인스턴스를 최대한 활용하려면 ARM64와 AMD64를 모두 지원하는 multi-arch 컨테이너 이미지가 필요합니다.
# Multi-arch Dockerfile 예시
FROM golang:1.22-alpine AS builder
ARG TARGETOS TARGETARCH
WORKDIR /app
COPY . .
# 타겟 아키텍처에 맞게 빌드
RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o app .
# 런타임 이미지
FROM alpine:3.19
COPY /app/app /usr/local/bin/app
ENTRYPOINT ["/usr/local/bin/app"]
GitHub Actions CI/CD에서 multi-arch 빌드:
# .github/workflows/build.yml
name: Build Multi-Arch Image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push multi-arch
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64 # ARM64 포함
push: true
tags: |
${{ secrets.ECR_REGISTRY }}/myapp:${{ github.sha }}
${{ secrets.ECR_REGISTRY }}/myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Graviton3 → Graviton4/5 마이그레이션 벤치마크 포인트:
# Graviton4/5 우선 NodePool 예시 (Karpenter)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: graviton-spot-pool
spec:
template:
spec:
requirements:
# Graviton5 → Graviton4 → Graviton3 우선순위
- key: node.kubernetes.io/instance-type
operator: In
values:
# Graviton5 (최우선, 2026-06 GA)
- "m9g.medium"
- "m9g.large"
- "m9g.xlarge"
- "m9g.2xlarge"
# Graviton4 (8g 시리즈)
- "m8g.medium"
- "m8g.large"
- "m8g.xlarge"
- "m8g.2xlarge"
# Graviton3 (Fallback)
- "m7g.medium"
- "m7g.large"
- "m7g.xlarge"
- "m7g.2xlarge"
- key: kubernetes.io/arch
operator: In
values: ["arm64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
disruption:
consolidationPolicy: WhenUnderutilized
consolidateAfter: 30s
limits:
cpu: "1000"
memory: "2000Gi"
Graviton4 성능 벤치마크 체크포인트:
마이그레이션 시 다음 메트릭을 모니터링하여 성능 개선을 검증합니다:
| 메트릭 | Graviton3 기준 | Graviton4 목표 | 측정 방법 |
|---|---|---|---|
| P99 응답 시간 | 100ms | 70ms (-30%) | Prometheus http_request_duration_seconds |
| 처리량 (RPS) | 1000 req/s | 1300 req/s (+30%) | Load testing (k6, Locust) |
| 메모리 대역폭 | 205 GB/s | 358 GB/s (+75%) | sysbench memory |
| CPU 사용률 | 60% | 45% (-25%) | node_cpu_seconds_total |
# Graviton4 성능 테스트 스크립트
#!/bin/bash
# 1. 메모리 대역폭 테스트
sysbench memory --memory-total-size=100G --memory-oper=write run
# 2. CPU 벤치마크
sysbench cpu --cpu-max-prime=20000 --threads=8 run
# 3. 애플리케이션 부하 테스트 (k6)
k6 run --vus 100 --duration 5m loadtest.js
# 4. Prometheus 메트릭 수집
curl -s http://localhost:9090/api/v1/query?query=rate(http_request_duration_seconds_sum[5m]) | jq .
- 컨테이너 이미지: ARM64 지원 확인 (
docker manifest inspect) - 의존성 라이브러리: ARM64 호환성 검증
- CI/CD 파이프라인: Multi-arch 빌드 활성화
- NodePool 우선순위: Graviton5 → Graviton4(8g 시리즈) → Graviton3 → x86 순서 설정
- 성능 벤치마크: P99 레이턴시, 처리량, CPU 사용률 측정
- 비용 분석: Graviton3 대비 가격/성능 비율 계산
Graviton4 기반 인스턴스(M8g, C8g, R8g)와 Graviton5 인스턴스(M9g, M9gd)는 아직 모든 리전에서 가용하지 않을 수 있습니다. 프로덕션 배포 전 대상 리전의 인스턴스 가용성을 확인하세요: aws ec2 describe-instance-type-offerings --location-type availability-zone --filters Name=instance-type,Values=m8g.*,m9g.* --region <region>
2.5.4 Auto Mode 환경의 리소스 설정 권장사항
Auto Mode는 많은 부분을 자동화하지만, 개발자는 여전히 애플리케이션의 리소스 요구사항을 정확히 설정해야 합니다.
Auto Mode가 자동 처리하는 항목:
| 항목 | 수동 관리 | Auto Mode |
|---|---|---|
| 노드 프로비저닝 | Karpenter, Managed Node Group 설정 | 자동 |
| 인스턴스 타입 선택 | NodePool에서 수동 지정 | Pod requests 기반 자동 선택 |
| Spot/On-Demand 전환 | 수동 또는 Karpenter 설정 | 자동 Fallback |
| 노드 스 케일링 | HPA + Cluster Autoscaler/Karpenter | 자동 |
| OS 패치 | 수동 또는 자동화 스크립트 | 자동 (무중단) |
개발자가 여전히 설정해야 하는 항목:
| 항목 | 이유 | 권장 방법 |
|---|---|---|
| CPU Requests | 스케줄링 결정 기준 | P95 사용량 + 20% |
| Memory Requests | 스케줄링 및 OOM 방지 | P95 사용량 + 20% |
| Memory Limits | OOM Kill 방지 (필수) | Requests × 1.5~2 |
| CPU Limits | 일반 워크로드는 미설정 권장 | 배치 작업만 설정 |
| HPA 메트릭 | 수평 확장 기준 | CPU 70%, Custom Metrics |
Auto Mode 환경에서의 VPA 역할 변화:
Auto Mode에서 VPA는:
- 별도 설치 불필요
- 내장 Right-Sizing 엔진이 지속적으로 워크로드 분석
- 개발자에게 권장사항 제공 (자동 적용 대신)
- 개발자가 검토 후 Deployment 매니페스트에 반영
권장 워크플로우:
# 1. Auto Mode 클러스터에 배포
kubectl apply -f deployment.yaml
# 2. 7-14일 후 Auto Mode 대시보드에서 권장사항 확인
# (AWS Console → EKS → Clusters → <cluster-name> → Insights)
# 3. 권장사항을 Deployment에 반영
kubectl set resources deployment web-app \
--requests=cpu=300m,memory=512Mi \
--limits=memory=1Gi
# 4. GitOps로 매니페스트 업데이트
git add deployment.yaml
git commit -m "chore: apply Auto Mode resource recommendations"
git push
Auto Mode는 다음과 같은 경우에 특히 유용합니다:
- 신규 클러스터: 기존 인프라 없이 빠르게 시작
- 운영 리소스 부족: 소규모 팀에서 Kubernetes 전문가 없이 운영
- 비용 최적화 우선: Graviton + Spot 자동 활용으로 즉시 절감
- 표준화된 워크로드: 일반적인 웹/API 서버, 마이크로서비스
수동 관리 권장 시나리오:
- 세밀한 제어 필요: 특정 인스턴스 타입, AZ 배치, 네트워크 구성
- 기존 Karpenter 투자: 고도화된 NodePool 정책 보유
- 규제 요구사항: 특정 하드웨어, 보안 그룹 강제
Auto Mode + 수동 Right-Sizing 비교:
| 항목 | 수동 Right-Sizing (VPA Off) | Auto Mode |
|---|---|---|
| 초기 설정 복잡도 | 높음 (VPA 설치, Prometheus 구성) | 낮음 (클러스터 생성 시 플래그만) |
| 권장사항 생성 시간 | 7-14일 | 7-14일 (동일) |
| 권장사항 정확도 | 높음 (Prometheus 기반) | 높음 (내장 분석 엔진) |
| 적용 방식 | 수동 (개발자가 매니페스트 수정) | 수동 (개발자가 매니페스트 수정) |
| 지속적 모니터링 | 수동 (주기적 VPA 확인) | 자동 (대시보드 알림) |
| 인프라 최적화 | 수동 (Karpenter 설정) | 자동 (Graviton + Spot) |
| 총 운영 오버헤드 | 높음 | 낮음 |
결론:
Auto Mode는 리소스 최적화의 복잡성을 제거하지만, 리소스 설정의 책임은 제거하지 않습니다. 개발자는 여전히 애플리케이션의 requests/limits를 설정해야 하며, Auto Mode는 이를 기반으로 최적의 인프라를 자동으로 프로비저닝합니다.
이는 **"개발자는 애플리케이션 요구사항 정의, AWS는 인프라 관리"**라는 명확한 책임 분리를 통해, 양측 모두가 자신의 전문 분야에 집중할 수 있게 합니다.
QoS (Quality of Service) 클래스
3.1 세 가지 QoS 클래스
Kubernetes는 리소스 설정에 따라 Pod를 3가지 QoS 클래스로 분류합니다:
Guaranteed (최고 우선순위)
조건:
- 모든 컨테이너에 CPU와 Memory requests와 limits 설정
- requests == limits (동일 값)
apiVersion: v1
kind: Pod
metadata:
name: guaranteed-pod
labels:
qos: guaranteed
spec:
containers:
- name: app
image: nginx:1.25
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "500m" # requests와 동일
memory: "256Mi" # requests와 동일
- name: sidecar
image: fluentd:v1
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "100m"
memory: "128Mi"
특징:
- oom_score_adj: -997 (가장 낮음, OOM Kill 우선순위 최하)
- 노드 압박 시에도 마지막에 Eviction
- CPU 스케줄링 우선순위 높음
Burstable (중간 우선순위)
조건:
- 최소 1개 컨테이너에 CPU 또는 Memory requests 설정
- Guaranteed 조건을 만족하지 않음
apiVersion: v1
kind: Pod
metadata:
name: burstable-pod
labels:
qos: burstable
spec:
containers:
- name: app
image: web-app:v1
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "1000m" # requests보다 큼 (Burstable)
memory: "1Gi" # requests보다 큼
- name: cache
image: redis:7
resources:
requests:
memory: "256Mi" # CPU requests 없음 (Burstable)
limits:
memory: "512Mi"
특징:
- oom_score_adj: min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)
- 사용량에 따라 동적으로 조정
- 여유 있을 때 burst 가능
BestEffort (최저 우선순위)
조건:
- 모든 컨테이너에 requests와 limits 미설정
apiVersion: v1
kind: Pod
metadata:
name: besteffort-pod
labels:
qos: besteffort
spec:
containers:
- name: app
image: test-app:latest
# resources 섹션 없음 또는 비어있음
특징:
- oom_score_adj: 1000 (가장 높음, OOM Kill 최우선)
- 노드 압박 시 가장 먼저 Eviction
- 개발/테스트 환경에서만 사용 권장
3.2 QoS와 Eviction 우선순위
노드 리소스 압박 시 kubelet은 다음 순서로 Pod를 Eviction합니다:
Eviction 순서 요약:
| 순위 | QoS 클래스 | 조건 | oom_score_adj |
|---|---|---|---|
| 1 (최우선) | BestEffort | 모든 Pod | 1000 |
| 2 | Burstable | requests 초과 사용 중 | 2-999 (사용량 비례) |
| 3 | Burstable | requests 이하 사용 중 | 2-999 (사용량 비례) |
| 4 (최후) | Guaranteed | 시스템 중요 Pod 제외 | -997 |
oom_score_adj 확인 방법:
# Pod의 메인 컨테이너 프로세스 찾기
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].containerID}'
# 노드에서 oom_score_adj 확인
docker inspect <container-id> | grep Pid
cat /proc/<pid>/oom_score_adj
# 예시 출력
# BestEffort: 1000
# Burstable: 500 (사용량에 따라 변동)
# Guaranteed: -997
3.3 실전 QoS 전략
워크로드 특성에 맞는 QoS 클래스 선택 가이드:
| 워크로드 유형 | 권장 QoS | 설정 패턴 | 이유 |
|---|---|---|---|
| 프로덕션 API | Guaranteed | requests = limits | 안정성 최우선, Eviction 방지 |
| 데이터베이스 | Guaranteed | requests = limits | 메모리 압박 시에도 보호 |
| 배치 작업 | Burstable | limits > requests | 유휴 시 리소스 활용, 비용 효율 |
| 큐 워커 | Burstable | limits > requests | 부하 변동 대응 |
| 개발/테스트 | BestEffort | 설정 없음 | 리소스 효율 (운영 환경 금지) |
| 모니터링 Agent | Guaranteed | 낮은 값으로 설정 | 시스템 안정성 |
프로덕션 권장 설정:
# 패턴 1: 미션 크리티컬 서비스 (Guaranteed)
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-api
namespace: production
spec:
replicas: 5
template:
metadata:
labels:
app: payment-api
tier: critical
spec:
containers:
- name: api
image: payment-api:v2.1
resources:
requests:
cpu: "1000m"
memory: "2Gi"
limits:
cpu: "1000m"
memory: "2Gi"
priorityClassName: system-cluster-critical # 추가 보호
---
# 패턴 2: 일반 웹 서비스 (Burstable)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-frontend
namespace: production
spec:
replicas: 10
template:
spec:
containers:
- name: frontend
image: web-frontend:v1.5
resources:
requests:
cpu: "200m" # P50 사용량
memory: "256Mi"
limits:
cpu: "500m" # P95 사용량
memory: "512Mi" # OOM 방지
---
# 패턴 3: 배치 워커 (Burstable)
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-report
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: report-generator
image: report-gen:v1
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "4000m" # 야간 시간대 리소스 활용
memory: "8Gi"
restartPolicy: OnFailure
VPA (Vertical Pod Autoscaler) 상세 가이드
4.1 VPA 아키텍처
VPA는 3개의 컴포넌트로 구성됩니다:
컴포넌트 역할:
| 컴포넌트 | 역할 | 데이터 소스 |
|---|---|---|
| Recommender | 과거 사용량 분석, 권장사항 계산 | Metrics Server, Prometheus |
| Updater | Auto 모드에서 Pod 재시작 | VPA CRD 상태 |
| Admission Controller | 새 Pod에 리소스 자동 주입 | VPA CRD 권장사항 |
4.1.4 VPA Recommender ML 알고리즘 상세
VPA Recommender는 단순한 평균 계산이 아닌, 머신러닝 기반의 정교한 알고리즘으로 리소스 추천값을 산출합니다.
지수 가중 히스토그램 (Exponentially-weighted Histogram)
VPA Recommender의 핵심은 시간에 따라 가중치가 감소하는 히스토그램입니다:
최근 데이터 → 높은 가중치
오래된 데이터 → 낮은 가중치 (지수적 감소)
알고리즘 동작:
- 메트릭 수집 주기: 1분마다 Pod 리소스 사용량 수집
- 히스토그램 업데이트: 각 측정값을 히스토그램 버킷에 누적
- 가중치 적용: 오래된 데이터는
e^(-t/decay_half_life)가중치로 감소 - 추천값 계산: 히스토그램에서 백분위수 기반 추천
4가지 추천값 계산 방법
| 추천값 | 계산 방법 | 의미 |
|---|---|---|
| Lower Bound | P5 (5번째 백분위수) | 최소 필요 리소스 - 95% 시간 동안 충분 |
| Target | P95 (95번째 백분위수) | 권장 설정값 - 5% 피크 부하 대응 |
| Upper Bound | P99 (99번째 백분위수) | 최대 관찰 사용량 - Limits 설정 참고 |
| Uncapped Target | maxAllowed 제약 없이 계산한 P95 | 실제 필요량 확인용 |
백분위수 계산 예시:
# 가상의 CPU 사용량 히스토그램 (1일 = 1440분)
cpu_samples = [100m, 150m, 200m, 250m, 300m, 350m, 400m, 450m, 500m, ...]
# 지수 가중치 적용 (decay_half_life = 24시간)
weighted_samples = [
(100m, weight=1.0), # 최근 (1시간 전)
(150m, weight=0.97), # 2시간 전
(200m, weight=0.92), # 5시간 전
(250m, weight=0.71), # 12시간 전
(300m, weight=0.50), # 24시간 전 (반감기)
(350m, weight=0.25), # 48시간 전
...
]
# 백분위수 계산
P5 = 150m # Lower Bound
P95 = 450m # Target ⭐
P99 = 500m # Upper Bound
Confidence Multiplier: 신뢰도 기반 조정
데이터 수집 기간이 짧을수록 안전하게 높은 값을 추천합니다:
Confidence Multiplier = f(데이터_수집_기간)
0-24시간: multiplier = 1.5 (50% 안전 마진)
1-3일: multiplier = 1.3 (30% 안전 마진)
3-7일: multiplier = 1.1 (10% 안전 마진)
7일 이상: multiplier = 1.0 (신뢰도 충분)
실제 적용 예시:
# 데이터 수집 2일차
원본 P95: 450m
Confidence Multiplier: 1.3
최종 Target: 450m × 1.3 = 585m ≈ 600m
# 데이터 수집 10일차
원본 P95: 450m
Confidence Multiplier: 1.0
최종 Target: 450m × 1.0 = 450m
VPA가 정확한 추천을 제공하려면 최소 7일, 권장 14일의 데이터 수집이 필요합니다. 주간 패턴(평일 vs 주말)을 포착하려면 최소 2주 이상의 관찰이 필수적입니다.
Memory 추천: OOM 이벤트 기반 Bump-Up
Memory는 CPU와 다르게 OOM Kill 이벤트를 특별히 고려 합니다:
OOM 이벤트 감지 시:
현재 Memory Target: 500Mi
OOM Kill 발생 시점 메모리: 600Mi
→ 새로운 Target: 600Mi × 1.2 = 720Mi (20% 안전 마진 추가)
OOM Bump-Up 로직:
if oom_kill_detected:
oom_memory = get_memory_at_oom_time()
new_target = max(
current_target,
oom_memory * 1.2 # 20% 안전 마진
)
# 급격한 변경 방지 (최대 2배)
new_target = min(new_target, current_target * 2)
CPU throttling과 달리, OOM Kill 이벤트는 즉시 Memory Target을 상향 조정합니다. 이는 서비스 중단을 방지하기 위한 안전 장치입니다.
CPU 추천: P95/P99 사용량 기반
CPU는 압축 가능한 리소스이므로 보수적으로 접근합니다:
CPU Target = P95 사용량
CPU Upper Bound = P99 사용량
Throttling 발생 시:
→ 추 천값은 변경하지 않음 (HPA로 해결 권장)
CPU Throttling 감지 시:
if cpu_throttling_detected:
throttled_percentage = get_throttled_time_percentage()
if throttled_percentage > 10:
# VPA 자체 추천값은 유지
# 대신 다음을 제안:
# 1. HPA 추가로 수평 확장
# 2. CPU limits 제거 (Google, Datadog 패턴)
# 3. 또는 Target을 P99로 상향 (수동 조정)
pass
VPA는 CPU throttling을 감지하면 추천값을 크게 올리지 않습니다. 대신 HPA로 수평 확장하는 것이 Kubernetes 모범 사례입니다.
VPA와 Prometheus 데이터 소스 통합
VPA Recommender는 Metrics Server만으로도 동작하지만, Prometheus와 통합하면 더욱 정교한 추천이 가능합니다:
Prometheus 메트릭 활용:
# VPA Recommender에 Prometheus 연동 설정
apiVersion: v1
kind: ConfigMap
metadata:
name: vpa-recommender-config
namespace: vpa-system
data:
recommender-config.yaml: |
# Prometheus 메트릭 소스 활성화
metrics-provider: prometheus
prometheus-url: http://prometheus-server.monitoring.svc:9090
# 히스토그램 설정
histogram-decay-half-life: 24h
histogram-bucket-size-growth: 1.05
# CPU 추천 설정
cpu-histogram-decay-half-life: 24h
memory-histogram-decay-half-life: 48h # Memory는 더 긴 관찰
# OOM 이벤트 처리
oom-min-bump-up: 1.2 # 최소 20% 증가
oom-bump-up-ratio: 0.5 # 50% 안전 마진
Prometheus Custom Metrics API 연동:
# Custom Metrics API 어댑터 배포 (Prometheus Adapter)
helm install prometheus-adapter prometheus-community/prometheus-adapter \
--namespace monitoring \
--set prometheus.url=http://prometheus-server.monitoring.svc \
--set rules.default=true
# VPA가 Custom Metrics API 사용하도록 설정
kubectl edit deploy vpa-recommender -n vpa-system
# 환경 변수 추가:
# - PROMETHEUS_ADDRESS=http://prometheus-server.monitoring.svc:9090
# - USE_CUSTOM_METRICS=true
연동 확인:
# VPA Recommender가 Prometheus 메트릭 사용 중인지 확인
kubectl logs -n vpa-system deploy/vpa-recommender | grep prometheus
# 출력 예시:
# I0212 10:15:30.123456 1 metrics_client.go:45] Using Prometheus metrics provider
# I0212 10:15:31.234567 1 prometheus_client.go:78] Connected to Prometheus at http://prometheus-server.monitoring.svc:9090
VPA 추천 품질 검증 방법
추천값이 실제로 적절한지 검증하는 PromQL 쿼리:
1. CPU 추천값 vs 실제 사용량 비교:
# VPA Target vs 실제 P95 사용량 비교
(
kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target{resource="cpu"}
-
quantile_over_time(0.95,
container_cpu_usage_seconds_total{pod=~"web-app-.*"}[7d]
) * 1000
) /
kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target{resource="cpu"} * 100
# 출력: 추천값과 실제 P95 차이 (%)
# 10-20% 범위: 적절 ✅
# >30%: 과다 프로비저닝 ⚠️
# <0%: 과소 프로비저닝 (즉시 조정 필요) 🚨
2. Memory 추천값 검증:
# VPA Target vs 실제 P99 사용량
(
kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target{resource="memory"}
-
quantile_over_time(0.99,
container_memory_working_set_bytes{pod=~"web-app-.*"}[7d]
)
) /
kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target{resource="memory"} * 100
# 20-30% 여유: 이상적 ✅
# <10% 여유: OOM 위험 🚨
3. OOM Kill 빈도 모니터링:
# 최근 7일 OOM Kill 이벤트 수
increase(
kube_pod_container_status_terminated_reason{reason="OOMKilled"}[7d]
)
# 0건: VPA 추천 정확 ✅
# 1-2건: 수용 가능 (피크 부하)
# >3건: VPA Target 수동 상향 필요 🚨
4. CPU Throttling 비율:
# CPU Throttling 시간 비율 (%)
rate(container_cpu_cfs_throttled_seconds_total{pod=~"web-app-.*"}[5m])
/
rate(container_cpu_cfs_periods_total{pod=~"web-app-.*"}[5m]) * 100
# <5%: 정상 ✅
# 5-10%: 모니터링 필요 ⚠️
# >10%: HPA 추가 또는 CPU limits 제거 고려 🚨
Grafana 대시보드 예시:
# VPA 추천 품질 모니터링 대시보드
apiVersion: v1
kind: ConfigMap
metadata:
name: vpa-quality-dashboard
namespace: monitoring
data:
dashboard.json: |
{
"panels": [
{
"title": "CPU: VPA Target vs P95 실제 사용량",
"targets": [
{
"expr": "kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target{resource=\"cpu\"}",
"legendFormat": "VPA Target"
},
{
"expr": "quantile_over_time(0.95, container_cpu_usage_seconds_total[7d]) * 1000",
"legendFormat": "실제 P95"
}
]
},
{
"title": "Memory: VPA Target vs P99 실제 사용량",
"targets": [
{
"expr": "kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target{resource=\"memory\"}",
"legendFormat": "VPA Target"
},
{
"expr": "quantile_over_time(0.99, container_memory_working_set_bytes[7d])",
"legendFormat": "실제 P99"
}
]
},
{
"title": "OOM Kill 이벤트 (7일)",
"targets": [
{
"expr": "increase(kube_pod_container_status_terminated_reason{reason=\"OOMKilled\"}[7d])"
}
]
}
]
}
VPA는 과거 데이터 기반 추천이므로 다음 상황에서는 한계가 있습니다:
- 갑작스러운 트래픽 패턴 변화: 과거에 없던 피크 부하
- 계절성 워크로드: 월말 배치, 연말 결산 등
- 초기 부트스트랩: 애플리케이션 시작 시 높은 메모리 사용
이러한 경우 수동 조정 또는 HPA와의 조합이 필요합니다.
4.2 VPA 설치 및 구성
Helm을 통한 설치
# 1. Metrics Server 설치 (사전 요구사항)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 2. Metrics Server 확인
kubectl get deployment metrics-server -n kube-system
kubectl top nodes
# 3. VPA Helm 레포지토리 추가
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm repo update
# 4. VPA 설치
helm install vpa fairwinds-stable/vpa \
--namespace vpa-system \
--create-namespace \
--set recommender.enabled=true \
--set updater.enabled=true \
--set admissionController.enabled=true
# 5. 설치 확인
kubectl get pods -n vpa-system
# 예상 출력:
# NAME READY STATUS RESTARTS AGE
# vpa-admission-controller-xxx 1/1 Running 0 1m
# vpa-recommender-xxx 1/1 Running 0 1m
# vpa-updater-xxx 1/1 Running 0 1m
수동 설치 (공식 방법)
# VPA 공식 레포지토리 클론
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
# VPA 설치
./hack/vpa-up.sh
# 설치 확인
kubectl get crd | grep verticalpodautoscaler
4.3 VPA 모드
VPA는 3가지 모드로 동작합니다:
Off 모드 (권장사항만 제공)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-app-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
updatePolicy:
updateMode: "Off" # 권장사항만 표시, 자동 적용 안 함
사용 시나리오:
- VPA를 처음 도입할 때
- 프로덕션 워크로드 분석
- 수동 검토 후 적용 원할 때
권장사항 확인:
# VPA 상태 확인
kubectl describe vpa web-app-vpa -n production
# 출력 예시:
# Recommendation:
# Container Recommendations:
# Container Name: web-app
# Lower Bound:
# Cpu: 150m
# Memory: 200Mi
# Target: # ← 이 값 사용 권장
# Cpu: 250m
# Memory: 300Mi
# Uncapped Target:
# Cpu: 350m
# Memory: 400Mi
# Upper Bound:
# Cpu: 500m
# Memory: 600Mi
Initial 모드 (Pod 생성 시에만 적용)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: batch-worker-vpa
namespace: batch
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: batch-worker
updatePolicy:
updateMode: "Initial" # Pod 생성 시에만 리소스 설정
resourcePolicy:
containerPolicies:
- containerName: worker
minAllowed:
cpu: "100m"
memory: "128Mi"
maxAllowed:
cpu: "4000m"
memory: "16Gi"
사용 시나리오:
- CronJob, Job 워크로드
- 재시작이 허용되지 않는 StatefulSet
- 수동 스케일링을 원하는 경우
동작 방식:
- 새 Pod 생성 요청
- VPA Admission Controller가 권장 리소스 주입
- 기존 실행 중인 Pod는 그대로 유지
Auto 모드 (완전 자동화)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-vpa
namespace: development
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
updatePolicy:
updateMode: "Auto" # 자동으로 Pod 재시작 및 리소스 조정
minReplicas: 2 # 최소 2개 Pod 유지
resourcePolicy:
containerPolicies:
- containerName: api
minAllowed:
cpu: "200m"
memory: "256Mi"
maxAllowed:
cpu: "2000m"
memory: "4Gi"
controlledResources:
- cpu
- memory
controlledValues: RequestsAndLimits # requests와 limits 모두 조정
사용 시나리오:
- 개발/스테이징 환경
- Stateless 애플리케이션
- PodDisruptionBudget 설정된 워크로드
Auto 모드는 Pod를 재시작합니다:
- Eviction API를 통한 재시작
- 다운타임 발생 가능
- PodDisruptionBudget (PDB) 필수 설정
- 프로덕션 환경에서는 신중히 사용
권장: 프로덕션에서는 Off 또는 Initial 모드 사용