- Authors
- Name
- 들어가며
- HPA v2 동작 원리와 알고리즘
- VPA 모드별 차이와 제한사항
- KEDA: 이벤트 드리븐 오토스케일링
- HPA vs VPA vs KEDA 비교
- 혼합 전략: HPA + VPA + KEDA
- 트러블슈팅: 실패 사례와 복구
- 운영 체크리스트
- 마치며
- 참고자료

들어가며
Kubernetes 워크로드를 운영하다 보면 가장 까다로운 문제 중 하나가 리소스 스케일링입니다. 트래픽이 급증하면 Pod가 부족해 장애가 발생하고, 트래픽이 줄면 과잉 프로비저닝으로 비용이 낭비됩니다. Kubernetes는 이 문제를 해결하기 위해 세 가지 핵심 오토스케일러를 제공합니다.
- HPA (Horizontal Pod Autoscaler): Pod 수를 수평으로 조절
- VPA (Vertical Pod Autoscaler): 개별 Pod의 CPU/메모리 요청량을 수직으로 조절
- KEDA (Kubernetes Event-Driven Autoscaling): 외부 이벤트 소스 기반의 확장된 오토스케일링
이 글에서는 각 오토스케일러의 동작 원리, 설정 방법, 그리고 실전에서 겪는 트러블슈팅 사례까지 종합적으로 다룹니다.
HPA v2 동작 원리와 알고리즘
스케일링 알고리즘
HPA는 autoscaling/v2 API를 사용하며, 다음 공식으로 원하는 레플리카 수를 계산합니다:
desiredReplicas = ceil(currentReplicas × (currentMetricValue / desiredMetricValue))
예를 들어 현재 4개의 Pod가 평균 CPU 사용률 80%이고 목표가 50%라면, ceil(4 × (80/50)) = ceil(6.4) = 7개의 Pod로 스케일 아웃됩니다.
HPA 컨트롤러는 기본적으로 15초 주기로 메트릭을 수집하며, --horizontal-pod-autoscaler-sync-period 플래그로 조절할 수 있습니다.
기본 HPA 설정 - CPU/메모리 기반
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 75
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: Max
이 설정에서 핵심은 behavior 블록입니다. 스케일 업은 즉시(stabilizationWindowSeconds: 0) 수행되지만, 스케일 다운은 5분 동안 안정화 기간을 거치며 60초마다 최대 10%씩만 줄입니다. 이를 통해 급격한 축소로 인한 서비스 영향을 방지합니다.
커스텀 메트릭 기반 HPA
실제 운영에서는 CPU/메모리보다 애플리케이션 메트릭(RPS, 큐 깊이, 활성 커넥션 등)이 더 정확한 스케일링 지표입니다. Prometheus Adapter를 활용한 커스텀 메트릭 HPA를 설정해봅니다.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 2
maxReplicas: 30
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: '100'
- type: Object
object:
metric:
name: rabbitmq_queue_messages
describedObject:
apiVersion: v1
kind: Service
name: rabbitmq
target:
type: Value
value: '500'
이 설정은 두 가지 커스텀 메트릭을 사용합니다. Pod당 초당 HTTP 요청이 100을 넘거나, RabbitMQ 큐에 대기 중인 메시지가 500개를 넘으면 스케일 아웃합니다. 복수 메트릭이 설정되면 HPA는 각 메트릭별 원하는 레플리카 수를 계산한 뒤 최대값을 선택합니다.
Prometheus Adapter 설정은 다음과 같습니다:
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-adapter-config
namespace: monitoring
data:
config.yaml: |
rules:
- seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
resources:
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
name:
matches: "^(.*)_total$"
as: "${1}_per_second"
metricsQuery: 'rate(<<.Series>>{<<.LabelMatchers>>}[2m])'
- seriesQuery: 'rabbitmq_queue_messages{namespace!=""}'
resources:
overrides:
namespace: {resource: "namespace"}
name:
matches: "^(.*)$"
as: "$1"
metricsQuery: '<<.Series>>{<<.LabelMatchers>>}'
VPA 모드별 차이와 제한사항
VPA 아키텍처
VPA는 세 가지 컴포넌트로 구성됩니다:
- Recommender: 현재 및 과거 리소스 사용량을 분석하여 최적 요청량 추천
- Updater: 잘못된 리소스 요청을 가진 Pod를 축출(Evict)
- Admission Controller: 새로 생성되는 Pod에 올바른 리소스 요청 주입
VPA 모드
| 모드 | 동작 | Pod 재시작 | 사용 시나리오 |
|---|---|---|---|
| Off | 추천만 제공, 자동 적용 없음 | 없음 | 리소스 분석, 초기 도입 단계 |
| Initial | Pod 생성 시에만 추천값 적용 | 없음 (신규 Pod만) | 안정성 우선 워크로드 |
| Auto | 추천값을 기존 Pod에도 적용 (재시작) | 있음 | 비상태 워크로드, 개발 환경 |
| Recreate | Auto와 동일하나 재시작 보장 | 있음 | 명시적 재시작 필요 시 |
Kubernetes 1.32부터 InPlaceOrRecreate 모드가 추가되어, 가능한 경우 Pod를 재시작하지 않고 리소스를 변경합니다. 이 기능은 InPlacePodVerticalScaling 피처 게이트가 활성화되어야 합니다.
VPA 설정 예시
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: payment-service-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: payment-service
updatePolicy:
updateMode: 'Initial'
minReplicas: 2
resourcePolicy:
containerPolicies:
- containerName: payment-app
minAllowed:
cpu: '100m'
memory: '128Mi'
maxAllowed:
cpu: '4'
memory: '8Gi'
controlledResources: ['cpu', 'memory']
controlledValues: RequestsOnly
- containerName: sidecar-proxy
mode: 'Off'
이 설정에서 주목할 점은 다음과 같습니다:
updateMode: Initial로 설정하여 기존 Pod를 재시작하지 않고 새 Pod에만 적용minReplicas: 2로 최소 2개 Pod가 항상 실행 중이도록 보장- 사이드카 컨테이너(
sidecar-proxy)는 VPA 대상에서 제외 (mode: Off) controlledValues: RequestsOnly로 limit은 건드리지 않고 request만 조절
VPA 핵심 제한사항
- HPA와의 동시 사용 제한: VPA는 동일한 메트릭(CPU/메모리)으로 HPA와 함께 사용하면 충돌이 발생합니다. HPA가 CPU 사용률 기반으로 Pod 수를 늘리는 동시에 VPA가 CPU 요청량을 늘리면 예측 불가능한 동작이 됩니다.
- 최대 1,000 Pod 제한: VPA가 관리하는 Pod 수는 클러스터당 1,000개를 초과하지 않는 것이 권장됩니다.
- CronJob/Job 미지원: VPA는 장기 실행 워크로드(Deployment, StatefulSet)에서만 동작합니다.
- JVM 워크로드: JVM의 힙 메모리는 시작 시 고정되므로 VPA가 메모리를 줄여도 실제 JVM이 사용하는 메모리는 줄어들지 않습니다.
KEDA: 이벤트 드리븐 오토스케일링
KEDA 아키텍처
KEDA는 HPA를 대체하는 것이 아니라 확장합니다. KEDA는 외부 이벤트 소스(Kafka, Prometheus, Redis, AWS SQS 등)의 메트릭을 가져와 HPA에 주입하는 역할을 합니다.
핵심 CRD는 다음과 같습니다:
- ScaledObject: Deployment/StatefulSet에 대한 스케일링 규칙 정의
- ScaledJob: Job 기반 워크로드에 대한 스케일링 규칙 정의
- TriggerAuthentication: 외부 시스템 인증 정보 관리
- ClusterTriggerAuthentication: 클러스터 범위 인증 정보
Kafka 기반 KEDA 스케일링
apiVersion: v1
kind: Secret
metadata:
name: kafka-credentials
namespace: production
type: Opaque
data:
sasl_username: dXNlcm5hbWU=
sasl_password: cGFzc3dvcmQ=
ca: LS0tLS1CRUdJTi4uLg==
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: kafka-trigger-auth
namespace: production
spec:
secretTargetRef:
- parameter: sasl
name: kafka-credentials
key: sasl_username
- parameter: password
name: kafka-credentials
key: sasl_password
- parameter: ca
name: kafka-credentials
key: ca
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-consumer-scaler
namespace: production
spec:
scaleTargetRef:
name: order-consumer
pollingInterval: 15
cooldownPeriod: 300
idleReplicaCount: 0
minReplicaCount: 1
maxReplicaCount: 50
fallback:
failureThreshold: 3
replicas: 5
triggers:
- type: kafka
metadata:
bootstrapServers: 'kafka-0.kafka:9092,kafka-1.kafka:9092,kafka-2.kafka:9092'
consumerGroup: order-consumer-group
topic: orders
lagThreshold: '100'
activationLagThreshold: '10'
offsetResetPolicy: latest
authenticationRef:
name: kafka-trigger-auth
이 설정의 핵심 파라미터를 분석합니다:
idleReplicaCount: 0— 이벤트가 없으면 Pod를 0으로 줄여 비용 절감 (제로 스케일링)activationLagThreshold: 10— 컨슈머 랙이 10 미만이면 활성화하지 않음lagThreshold: 100— 컨슈머 랙 100당 1개의 Pod 추가fallback— KEDA가 메트릭을 가져오지 못하면(3회 실패 시) 5개의 레플리카로 폴백
Prometheus 기반 KEDA 스케일링
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: web-frontend-scaler
namespace: production
spec:
scaleTargetRef:
name: web-frontend
pollingInterval: 30
cooldownPeriod: 120
minReplicaCount: 2
maxReplicaCount: 100
advanced:
restoreToOriginalReplicaCount: true
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 60
triggers:
- type: prometheus
metadata:
serverAddress: 'http://prometheus.monitoring.svc:9090'
query: |
sum(rate(nginx_ingress_controller_requests{
namespace="production",
service="web-frontend"
}[2m]))
threshold: '500'
activationThreshold: '50'
- type: prometheus
metadata:
serverAddress: 'http://prometheus.monitoring.svc:9090'
query: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket{
namespace="production",
service="web-frontend"
}[5m])) by (le))
threshold: '0.5'
activationThreshold: '0.1'
이 설정은 두 가지 Prometheus 쿼리를 사용합니다. 초당 요청 수가 500을 넘거나 P95 레이턴시가 500ms를 넘으면 스케일 아웃합니다. advanced.horizontalPodAutoscalerConfig를 통해 내부적으로 생성되는 HPA의 behavior도 제어할 수 있습니다.
HPA vs VPA vs KEDA 비교
| 항목 | HPA | VPA | KEDA |
|---|---|---|---|
| 스케일링 방향 | 수평 (Pod 수) | 수직 (리소스 크기) | 수평 (Pod 수) + 제로 스케일 |
| 기본 메트릭 | CPU, 메모리 | CPU, 메모리 | 60+ 외부 이벤트 소스 |
| 커스텀 메트릭 | Adapter 필요 | 미지원 | 네이티브 지원 |
| 제로 스케일링 | 불가 (minReplicas >= 1) | 해당 없음 | 가능 (idleReplicaCount: 0) |
| Pod 재시작 | 없음 | 있음 (Auto/Recreate) | 없음 |
| 설정 복잡도 | 낮음 | 중간 | 중간~높음 |
| Kubernetes 내장 | 예 | 별도 설치 | 별도 설치 |
| CronJob 지원 | 제한적 | 미지원 | ScaledJob으로 지원 |
| 상태 저장 워크로드 | 주의 필요 | 지원 | 주의 필요 |
| 커뮤니티/생태계 | 매우 활발 | 활발 | CNCF Graduated, 매우 활발 |
혼합 전략: HPA + VPA + KEDA
HPA + VPA 혼합
HPA와 VPA를 함께 사용할 때는 메트릭 충돌을 반드시 피해야 합니다. 권장 패턴은 다음과 같습니다:
- HPA: 커스텀 메트릭(RPS, 큐 깊이 등) 기반 수평 스케일링
- VPA: Off 모드로 리소스 추천만 수행하거나, CPU/메모리 기반 수직 조절
# HPA: 커스텀 메트릭만 사용
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 3
maxReplicas: 20
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: '200'
---
# VPA: CPU/메모리 리소스 최적화
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
updatePolicy:
updateMode: 'Auto'
resourcePolicy:
containerPolicies:
- containerName: api
controlledResources: ['cpu', 'memory']
controlledValues: RequestsOnly
HPA + KEDA 혼합
KEDA는 내부적으로 HPA를 생성하므로 동일 Deployment에 별도의 HPA를 만들면 충돌합니다. 대신 KEDA의 ScaledObject에 여러 trigger를 추가하는 방식으로 결합합니다.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: hybrid-scaler
namespace: production
spec:
scaleTargetRef:
name: worker-service
minReplicaCount: 2
maxReplicaCount: 100
triggers:
# CPU 기반 (HPA 역할 대체)
- type: cpu
metricType: Utilization
metadata:
value: '70'
# Kafka 이벤트 기반
- type: kafka
metadata:
bootstrapServers: 'kafka.default:9092'
consumerGroup: worker-group
topic: tasks
lagThreshold: '50'
# Cron 기반 예약 스케일링
- type: cron
metadata:
timezone: Asia/Seoul
start: '0 9 * * 1-5'
end: '0 18 * * 1-5'
desiredReplicas: '10'
이 설정은 세 가지 전략을 결합합니다: 평소에는 CPU 기반, Kafka 메시지가 밀리면 이벤트 기반, 평일 업무 시간(09:00~18:00)에는 Cron 기반으로 최소 10개를 유지합니다. KEDA는 모든 트리거 중 최대값을 사용하므로 안전하게 결합할 수 있습니다.
트러블슈팅: 실패 사례와 복구
사례 1: HPA가 스케일 아웃하지 않음
증상: CPU 사용률이 90%인데 HPA가 반응하지 않음
# HPA 상태 확인
kubectl get hpa api-server-hpa -n production -o yaml
# 이벤트 확인
kubectl describe hpa api-server-hpa -n production
# metrics-server 동작 확인
kubectl top pods -n production
kubectl get apiservices | grep metrics
원인과 해결:
- metrics-server 미설치:
kubectl get deployment metrics-server -n kube-system으로 확인 - 리소스 요청 미설정: Pod에
resources.requests가 없으면 Utilization 계산 불가. 반드시 request 설정 필요 - maxReplicas 도달:
kubectl get hpa에서 MAXPODS에 도달했는지 확인 - 알 수 없는 메트릭:
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1"으로 커스텀 메트릭 API 확인
사례 2: VPA 추천값이 비정상적으로 높음
증상: VPA가 메모리 요청을 32Gi로 추천하는데 실제 사용량은 2Gi
원인: JVM 워크로드에서 -Xmx를 메모리 limit에 가깝게 설정한 경우, JVM이 최대 힙을 할당하므로 VPA가 높은 값을 추천합니다.
해결: maxAllowed를 적절하게 설정하고, JVM 워크로드는 VPA에서 메모리를 제외합니다.
resourcePolicy:
containerPolicies:
- containerName: java-app
controlledResources: ['cpu'] # 메모리는 VPA에서 제외
maxAllowed:
cpu: '4'
사례 3: KEDA가 0으로 스케일 인 후 복구가 느림
증상: Kafka 컨슈머가 0으로 스케일 인 후 메시지가 들어와도 Pod 시작까지 2~3분 소요
해결방안:
activationLagThreshold를 낮게 설정하여 더 빨리 활성화minReplicaCount: 1로 설정하여 최소 1개 Pod 유지- Pod에
readinessProbe시간을 줄여 빠르게 서비스 등록 - 컨테이너 이미지 사전 풀(pre-pull) 전략 사용
사례 4: HPA Flapping (빈번한 스케일 업/다운)
증상: Pod 수가 지속적으로 증가/감소를 반복
# HPA 이벤트 히스토리 확인
kubectl get events --field-selector involvedObject.name=api-hpa -n production --sort-by='.lastTimestamp'
해결:
behavior:
scaleDown:
stabilizationWindowSeconds: 600 # 10분 안정화
policies:
- type: Pods
value: 1
periodSeconds: 300 # 5분에 1개씩만 축소
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 50
periodSeconds: 60
운영 체크리스트
운영 환경에서 오토스케일링을 적용할 때 반드시 확인해야 할 항목들입니다:
배포 전 체크리스트
- 모든 Pod에
resources.requests와resources.limits가 설정되어 있는가 - PodDisruptionBudget(PDB)이 설정되어 있는가 (VPA 사용 시 필수)
- metrics-server 또는 Prometheus Adapter가 정상 동작하는가
- HPA와 VPA가 동일 메트릭으로 충돌하지 않는가
maxReplicas가 클러스터 노드 오토스케일러의 최대 노드 수와 맞는가- KEDA 사용 시
fallback설정이 되어 있는가
모니터링 체크리스트
- HPA 현재 레플리카 수와 목표 레플리카 수 대시보드
- VPA 추천값 vs 실제 사용량 추이
- KEDA 트리거 메트릭 값과 활성화 상태
- 스케일링 이벤트 알림 (Slack/PagerDuty 연동)
- 노드 오토스케일러와의 연동 상태 (Pending Pod 모니터링)
비용 최적화 체크리스트
- 개발/스테이징 환경은 KEDA로 제로 스케일링 적용
- 업무 시간 외 Cron 트리거로 최소 레플리카 축소
- VPA Off 모드로 추천값을 수집하고 주기적으로 request 업데이트
- Spot/Preemptible 인스턴스와 오토스케일링 결합
마치며
Kubernetes 오토스케일링은 단일 도구로 해결되지 않습니다. 워크로드 특성에 따라 HPA, VPA, KEDA를 적절히 조합해야 합니다. CPU/메모리 기반의 단순한 스케일링에는 HPA v2로 충분하고, 리소스 최적화가 필요하면 VPA를 병행하며, 이벤트 드리븐 워크로드나 제로 스케일링이 필요하면 KEDA가 적합합니다.
특히 운영 환경에서는 behavior 설정을 통한 스케일링 속도 제어, PDB와의 연동, 그리고 fallback 전략이 중요합니다. 이 글에서 다룬 설정 예시와 트러블슈팅 사례를 참고하여 안정적이면서도 비용 효율적인 오토스케일링 전략을 구축하시기 바랍니다.