K8s VPA란? 왜 필요한가
Kubernetes 클러스터를 운영하다 보면 Pod의 CPU·메모리 requests/limits를 얼마로 설정해야 할지 고민됩니다. 너무 크게 잡으면 리소스 낭비, 너무 작게 잡으면 OOMKill이나 CPU 쓰로틀링이 발생합니다. Vertical Pod Autoscaler(VPA)는 실제 사용량을 관측하여 최적의 requests/limits를 자동으로 추천·적용해주는 컨트롤러입니다.
HPA(Horizontal Pod Autoscaler)가 Pod 수를 늘리고 줄이는 수평 확장이라면, VPA는 개별 Pod의 리소스 할당량을 조정하는 수직 확장입니다. 두 가지를 적절히 조합하면 비용 효율과 안정성을 동시에 달성할 수 있습니다.
VPA 아키텍처: 3가지 컴포넌트
VPA는 세 개의 독립적인 컴포넌트로 구성됩니다:
- Recommender — Metrics Server(또는 Prometheus)에서 과거 사용량 데이터를 수집하고, 히스토그램 기반 알고리즘으로 최적 requests/limits를 계산합니다.
- Updater — 현재 실행 중인 Pod의 requests가 추천 범위를 크게 벗어나면 해당 Pod를 evict하여 새 값이 적용되도록 합니다.
- Admission Controller — 새로 생성되는 Pod에 추천된 requests/limits를 mutating webhook으로 주입합니다.
# VPA 컴포넌트 흐름도
Metrics Server → Recommender → VPA 오브젝트(추천값 저장)
↓
Updater (기존 Pod evict)
Admission Controller (새 Pod에 주입)
VPA 설치 방법
VPA는 Kubernetes 기본 내장이 아니므로 별도 설치가 필요합니다. 공식 autoscaler 레포지토리에서 제공하는 스크립트를 사용합니다:
# 공식 레포 클론
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
# 설치 (Recommender + Updater + Admission Controller)
./hack/vpa-up.sh
# 설치 확인
kubectl get pods -n kube-system | grep vpa
# vpa-admission-controller-xxx 1/1 Running
# vpa-recommender-xxx 1/1 Running
# vpa-updater-xxx 1/1 Running
Helm 차트로도 설치할 수 있습니다:
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm install vpa fairwinds-stable/vpa
--namespace vpa --create-namespace
VPA 오브젝트 작성: updateMode 이해
VPA 리소스의 핵심은 updatePolicy.updateMode입니다. 세 가지 모드를 지원합니다:
| 모드 | 동작 | Pod 재시작 | 사용 시나리오 |
|---|---|---|---|
| Off | 추천만 제공, 적용 안함 | 없음 | 운영 초기 관측용 |
| Initial | Pod 생성 시에만 적용 | 없음(기존 Pod 유지) | 롤링 업데이트 시 반영 |
| Auto | 추천값 자동 적용 | 있음(evict 발생) | 완전 자동화 |
운영 팁: 처음에는 반드시 Off 모드로 시작하여 추천값이 합리적인지 검증한 뒤, Auto로 전환하세요.
실전 예제: Spring Boot 앱에 VPA 적용
다음은 Spring Boot 기반 API 서버에 VPA를 적용하는 전체 매니페스트입니다:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api
image: myregistry/api-server:v2.1.0
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "1Gi"
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-server-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
updatePolicy:
updateMode: "Off" # 먼저 관측 모드로 시작
resourcePolicy:
containerPolicies:
- containerName: api
minAllowed:
cpu: "100m"
memory: "128Mi"
maxAllowed:
cpu: "2000m"
memory: "2Gi"
controlledResources: ["cpu", "memory"]
VPA 추천값 확인하기
VPA를 Off 모드로 배포한 뒤, kubectl describe vpa로 추천값을 확인할 수 있습니다:
kubectl describe vpa api-server-vpa -n production
# 출력 예시
Status:
Recommendation:
Container Recommendations:
- Container Name: api
Lower Bound:
Cpu: 150m
Memory: 200Mi
Target: # ← 이 값이 최적 추천
Cpu: 350m
Memory: 450Mi
Uncapped Target:
Cpu: 350m
Memory: 450Mi
Upper Bound:
Cpu: 800m
Memory: 900Mi
각 필드의 의미:
- Target — VPA가 추천하는 최적 requests 값
- Lower Bound — 최소한 이 정도는 할당해야 안정적
- Upper Bound — 피크 시 필요할 수 있는 상한
- Uncapped Target — minAllowed/maxAllowed 제한 무시한 순수 추천값
VPA + HPA 동시 사용 전략
VPA와 HPA를 같은 CPU 메트릭으로 동시 사용하면 충돌이 발생합니다. VPA가 requests를 올리면 HPA의 사용률(%)이 낮아져 스케일인하고, 다시 부하가 오르면 HPA가 스케일아웃하는 진동 현상이 생깁니다.
해결 전략은 메트릭을 분리하는 것입니다:
# VPA: 메모리만 관리
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-server-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: api
controlledResources: ["memory"] # 메모리만!
minAllowed:
memory: "128Mi"
maxAllowed:
memory: "2Gi"
---
# HPA: CPU 기반 수평 확장
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
이렇게 하면 CPU는 HPA가 수평으로, 메모리는 VPA가 수직으로 각각 독립적으로 관리합니다. HPA 오토스케일링 심화 가이드도 함께 참고하세요.
운영 시 주의사항과 베스트 프랙티스
1. Pod 재시작 관리
Auto 모드에서 VPA는 추천값과 현재 값의 차이가 클 때 Pod를 evict합니다. PodDisruptionBudget(PDB)을 반드시 설정하여 동시에 너무 많은 Pod가 재시작되지 않도록 보호하세요:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-server-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: api-server
2. minAllowed/maxAllowed 반드시 설정
제한 없이 VPA를 사용하면 노드 리소스를 초과하는 추천이 나올 수 있습니다. 반드시 resourcePolicy로 상·하한을 지정하세요.
3. JVM 기반 앱 특별 주의
Java/Spring 앱은 -Xmx 힙 설정이 고정되어 있으면 VPA가 메모리 requests를 줄여도 실제 사용량이 줄지 않습니다. 컨테이너 메모리 인식 JVM 옵션을 사용하세요:
env:
- name: JAVA_OPTS
value: >-
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=75.0
-XX:InitialRAMPercentage=50.0
4. Goldilocks로 VPA 대시보드 구축
Fairwinds의 Goldilocks는 네임스페이스의 모든 Deployment에 자동으로 VPA(Off 모드)를 생성하고, 추천값을 웹 UI로 보여줍니다:
helm install goldilocks fairwinds-stable/goldilocks
--namespace goldilocks --create-namespace
# 네임스페이스 레이블 추가
kubectl label ns production goldilocks.fairwinds.com/enabled=true
# 대시보드 접속
kubectl port-forward -n goldilocks svc/goldilocks-dashboard 8080:80
5. 비용 절감 효과 측정
VPA 도입 전후 리소스 사용량을 비교하여 비용 절감 효과를 정량화하세요:
# 현재 requests 합계 vs 실제 사용량 비교
kubectl top pods -n production --containers
kubectl get vpa -n production -o jsonpath='{.items[*].status.recommendation}'
VPA 도입 로드맵
실제 운영 환경에 VPA를 도입할 때는 단계적으로 접근하세요:
- 1주차: Off 모드로 전체 워크로드에 VPA 배포, Goldilocks 대시보드 설정
- 2주차: 추천값 분석, minAllowed/maxAllowed 튜닝
- 3주차: Staging에서 Auto 모드 테스트, PDB 설정 검증
- 4주차: Production에 Initial 모드 적용 → 안정 확인 후 Auto 전환
마치며
VPA는 “리소스 requests를 얼마로 설정하지?”라는 영원한 고민을 데이터 기반으로 해결해줍니다. Off 모드의 추천값만 활용해도 리소스 최적화에 큰 도움이 되며, HPA와 메트릭을 분리하여 조합하면 수평·수직 확장을 동시에 달성할 수 있습니다. 단, Pod 재시작 영향과 JVM 같은 런타임 특성을 고려한 세심한 설정이 필요합니다.