Skip to content

필사 모드: Kubernetes HPA·VPA·KEDA 오토스케일링 전략과 실전 튜닝 가이드

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

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 핵심 제한사항

1. **HPA와의 동시 사용 제한**: VPA는 동일한 메트릭(CPU/메모리)으로 HPA와 함께 사용하면 충돌이 발생합니다. HPA가 CPU 사용률 기반으로 Pod 수를 늘리는 동시에 VPA가 CPU 요청량을 늘리면 예측 불가능한 동작이 됩니다.

2. **최대 1,000 Pod 제한**: VPA가 관리하는 Pod 수는 클러스터당 1,000개를 초과하지 않는 것이 권장됩니다.

3. **CronJob/Job 미지원**: VPA는 장기 실행 워크로드(Deployment, StatefulSet)에서만 동작합니다.

4. **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 &gt;= 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

**원인과 해결**:

1. **metrics-server 미설치**: `kubectl get deployment metrics-server -n kube-system`으로 확인

2. **리소스 요청 미설정**: Pod에 `resources.requests`가 없으면 Utilization 계산 불가. 반드시 request 설정 필요

3. **maxReplicas 도달**: `kubectl get hpa`에서 MAXPODS에 도달했는지 확인

4. **알 수 없는 메트릭**: `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 전략이 중요합니다. 이 글에서 다룬 설정 예시와 트러블슈팅 사례를 참고하여 안정적이면서도 비용 효율적인 오토스케일링 전략을 구축하시기 바랍니다.

참고자료

1. [Kubernetes 공식 문서 - Horizontal Pod Autoscaling](https://kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autoscale/)

2. [Kubernetes 공식 문서 - Vertical Pod Autoscaling](https://kubernetes.io/docs/concepts/workloads/autoscaling/vertical-pod-autoscale/)

3. [KEDA 공식 문서 - ScaledObject Specification](https://keda.sh/docs/2.18/concepts/scaling-deployments/)

4. [KEDA 공식 문서 - Authentication](https://keda.sh/docs/2.18/concepts/authentication/)

5. [Kubernetes Autoscaler GitHub - VPA](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler)

6. [KEDA Apache Kafka Scaler](https://keda.sh/docs/2.19/scalers/apache-kafka-go/)

7. [Kubernetes HPA Walkthrough](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/)

현재 단락 (1/411)

Kubernetes 워크로드를 운영하다 보면 가장 까다로운 문제 중 하나가 **리소스 스케일링**입니다. 트래픽이 급증하면 Pod가 부족해 장애가 발생하고, 트래픽이 줄면 과잉 프...

작성 글자: 0원문 글자: 11,685작성 단락: 0/411