부하가 늘면 누가, 무엇을, 늘리나 — 세 종류의 autoscaling이 잰 축

블랙 프라이데이 트래픽이 10배로 뛰었다. 팀은 두 가지를 동시에 원했다: Pod 수를 늘리고(트래픽 대응), 노드를 늘리고(부족한 자원 확보). 그런데 Kubernetes엔 autoscaling이 한 종류가 아니다 — Pod 수(HPA), Pod 자원(VPA), 노드 수(Cluster Autoscaler/Karpenter) 세 축이 각기 다른 질문에 답한다. 이 셋을 하나로 뭉개면 "autoscaling 켰는데 안 커진다"는 혼란이 생긴다.

이 글이 푸는 것은: 세 autoscaling이 각각 무슨 질문에 답하고, 언제 어떤 걸 써야 하며, 왜 HPA와 VPA를 동시에 쓰면 곤란한가다.

세 축의 autoscaling — 각기 다른 질문

flowchart TD
    HPA["HPA<br/>'부하가 늘면 Pod 수를 늘리자'"] --> P_COUNT["Pod 복제본 수"]
    VPA["VPA<br/>'Pod가 자원 부족이면 requests를 늘리자'"] --> P_SIZE["Pod 자원(requests/limits)"]
    CA["Cluster Autoscaler / Karpenter<br/>'Pod가 Pending이면 노드를 늘리자'"] --> N_COUNT["노드 수"]
도구 바꾸는 것 언제
수평(Pod 수) HPA replicas CPU/메모리/사용자 메트릭이 임계치 초과
수직(Pod 자원) VPA requests/limits Pod가 부족/과잉 자원 쓰는 것 같을 때
클러스터(노드 수) Cluster Autoscaler / Karpenter 노드 수 Pending Pod가 생겼을 때

핵심: HPA와 Cluster Autoscaler는 협력하지만, HPA와 VPA는 충돌할 수 있다. 이 관계를 모르면 "둘 다 켰더니 이상해진다"는 사태가 생긴다.

HPA — 가장 흔한, 부하 기반 수평 확장

HPA(HorizontalPodAutoscaler)는 메트릭이 임계치를 넘으면 replicas를 늘린다. 가장 흔한 autoscaling.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: {name: web}
spec:
  scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: web}
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource: {name: cpu, target: {type: Utilization, averageUtilization: 70}}

의미: "CPU 평균 사용률이 70%를 넘으면 replicas를 늘려라. 최소 3, 최대 20까지."

HPA가 작동하려면 메트릭 서버(Metrics Server)가 있어야 한다 — 각 Pod의 CPU/메모리 사용량을 수집. 없으면 HPA는 unable to fetch metrics 에러. (커스텀 메트릭 — 예: 초당 요청 수 — 이면 Prometheus Adapter 같은 추가 컴포넌트 필요.)

HPA의 계산은 단순 비례가 아니다. 목표 비율을 향해 수렴하는 공식을 쓴다 — desiredReplicas = currentReplicas × (currentMetric / targetMetric). 예: CPU 140% vs 목표 70%면 replicas를 2배로. 그래서 한 번에 폭발하지 않고 점진 확장.

# Kubernetes 1.36 — HPA 상태 (메트릭 서버 필요)
kubectl get hpa web
NAME   REFERENCE        TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   140%/70%  3         20        6          5m

확인할 것: TARGETS 140%/70% → 목표 초과, REPLICAS 6으로 늘어남. (메트릭 서버 미설치 시 <unknown>.)

VPA — Pod 자원을 수직으로 조정

VPA(VerticalPodAutoscaler)는 Pod의 requests/limits추천하고(필요 시 자동 적용). HPA가 "몇 개"를 바꾸면 VPA는 "Pod 하나당 얼마나 큰가"를 바꾼다.

용도: "이 앱이 실제로 얼마나 자원을 쓰는지 모르겠다" — VPA가 관측해 적정 requests를 추천. 과잉 할당(requests를 너무 크게)을 교정해 자원 낭비를 줄인다.

치명적 제약: VPA가 requests를 바꾸려면 Pod를 재시작해야 한다(전통적). 실행 중인 Pod의 requests는 못 바꾼다(In-place Resize 1.33 GA로 일부 완화되나, VPA와의 통합은 별개). 그래서 VPA 자동 모드는 재시작 비용을 수반한다.

그래서 VPA는 보통 Off(추천만)/Initial(새 Pod에만 적용) 모드로 쓰고, Auto(재시작하며 적용)는 주의해서. 특히 상태ful 워크로드(StatefulSet)엔 재시작 비용이 크다.

HPA와 VPA를 동시에 쓰면 안 되는 이유

둘 다 CPU 메트릭을 본다면 충돌한다:

  • 부하 증가 → HPA가 "replicas 늘리자" vs VPA가 "각 Pod requests 늘리자".
  • VPA가 requests를 늘리면 HPA의 CPU 사용 비율이 떨어져 HPA가 "replicas 줄여도 되겠다"고 반응 — 진동.

그래서 일반적으로 CPU 기반 HPA를 쓰면 VPA는 requests 자동 조정을 끄거나(Off 모드), 다른 축(메모리)만 본다. 둘의 조율이 까다로워 많은 팀이 HPA만 쓰고 VPA는 추천(Off) 모드로 모니터링 용도로만 둔다.

Cluster Autoscaler와 Karpenter — 노드를 늘리는 두 접근

HPA가 replicas를 늘려도, 노드 자원이 부족하면 새 Pod는 Pending(08장). 이때 노드를 늘려야 한다. 두 도구가 이 일을 한다.

Cluster Autoscaler(전통): Pending Pod를 감지하면, 클라우드 API로 노드를 하나 더 추가. 추가된 노드에 Pending Pod가 스케줄된다. 느린 축(노드 부팅에 수 분).

Karpenter(AWS 주도, 더 최신): Pending Pod의 자원 요구*를 보고, 그에 딱 맞는 노드를 *골라서 띄운다. 미리 정의된 노드 그룹이 아니라, "이 Pod들이 CPU 4코어/메모리 16Gi 원하니 그에 맞는 가장 싼 인스턴스를 즉시 띄운다". 빠르고 비용 효율적.

Cluster Autoscaler Karpenter
노드 선택 사전 정의된 노드 그룹에서 요구에 맞춰 즉석 선택
속도 느림(노드 그룹 확장) 빠름(직접 프로비전)
비용 노드 그룹 기반 요구 맞춤(더 저렴 가능)
클라우드 다중 지원 AWS 중심(Azure 등 확장 중)

세 축의 협력 — 실제 부하 증가 시나리오

flowchart LR
    L["부하 10배 증가"] --> H["1. HPA: replicas 3→20"]
    H --> P["새 Pod 17개 생김"]
    P --> SCHED["2. 스케줄러: 자원 부족 → 일부 Pending"]
    SCHED --> CA["3. Cluster Autoscaler/Karpenter: 노드 추가"]
    CA --> READY["4. Pending Pod들 새 노드에 스케줄 → Running"]

이 흐름이 "부하가 늘면 자동으로 확장"의 전체 경로. HPA만 켜고 Cluster Autoscaler 없으면 — replicas는 늘어나지만 Pending에 막혀 실제로는 안 늘어난다. 둘이 짝이어야 한다.

scale-down의 어려움 — 줄이기는 늘리기보다 보수적

확장(scale-up)은 빠르지만 축소(scale-down)는 보수적이다:

  • HPA는 메트릭이 임계치 이하로 일정 시간 유지돼야 replicas를 줄인다(flapping 방지).
  • Cluster Autoscaler/Karpenter는 노드가 충분히 한가한 상태가 일정 시간 지속돼야 노드를 내린다. 그리고 내릴 때 그 노드의 Pod를 다른 곳으로 옮기는 시간도 든다.

그래서 "부하가 떨어졌는데 노드가 안 줄어든다"는 흔한 불만. 의도된 보수성 — 빈도가 잦은 확대/축소 반복(thrashing)을 막기 위함.

직접 확인하기

# Kubernetes 1.36 — HPA로 CPU 기반 autoscaling (메트릭 서버 필요)
kubectl autoscale deploy web --min=3 --max=10 --cpu-percent=70
kubectl get hpa

메트릭 서버 미설치 시 <unknown>으로 표시. kind에 메트릭 서버 추가 설치 필요(kind: ... addon 또는 별도 apply). 미검증 환경은 노트.

# Pending Pod로 인한 노드 확장 감지 (Cluster Autoscaler 로그 — CA 설치 시)
# 일반 kind엔 CA가 없으므로 Pending은 그대로 남음
kubectl get pod <pending-pod>
# FailedScheduling: Insufficient cpu

HPA의 메트릭 종류 — Resource, Pods, External, Object

HPA가 확장 판단을 무엇으로 하느냐에 따라 적용이 달라진다. HPA는 네 종류의 메트릭 소스를 지원:

타입 무엇을 보나 추가 컴포넌트
Resource Pod의 CPU/메모리 사용률 "CPU 평균 70% 초과" Metrics Server만
Pods 커스텀 Pod 메트릭(Pod 단위) "초당 요청 수" Custom Metrics API
Object 클러스터 객체 메트릭 "Ingress의 초당 요청" Custom Metrics API
External 클러스터 외부 메트릭 "SQS 큐 길이" External Metrics API
metrics:
- type: Resource
  resource: {name: cpu, target: {type: Utilization, averageUtilization: 70}}
- type: External
  external: {metric: {name: queue-length, target: {type: AverageValue, averageValue: "30"}}

Resource만 쓰면 Metrics Server면 충분. 하지만 "CPU는 여유인데 지연이 길다"를 잡으려면 Pods/External(커스텀 메트릭)이 필요 — Prometheus Adapter가 그 다리(Kubernetes 커스텀 메트릭 API → Prometheus 쿼리). 이것이 "HPA는 CPU만 본다"는 통념이 틀린 이유 — 적절한 어댑터만 있으면 어떤 메트릭이든.

커스텀 메트릭의 가치 — CPU가 답이 아닌 순간

CPU 기반 HPA의 맹점: CPU가 여유인데 지연이 길어지는 병목(I/O 바운드, 외부 API 대기)을 못 잡는다. "CPU 50%인데 응답 지연 2초" → HPA는 안 늘림(CPU가 목표 이하니). 이때 지연 메트릭으로 HPA를 돌리면 — "지연 1초 초과 시 확장"이 가능. 커스텀 메트릭이 "진짜 부하 신호"를 잡는 도구. 단 Prometheus + Adapter 설정이 추가 부담 — 가치와 비용의 trade-off.

scale-down 안정화 윈도우 — 왜 줄이기가 느리나

HPA의 확장(scale-up)은 빠르지만 축소(scale-down)는 보수적. 이유: 안정화 윈도우(stabilizationWindow). HPA는 메트릭이 임계치 이하로 떨어져도 일정 시간 그 상태가 유지돼야 replicas를 줄인다.

behavior:
  scaleDown:
    stabilizationWindowSeconds: 300    # 5분간 낮아야 축소
    policies:
    - type: Percent
      value: 10                          # 한 번에 최대 10%만 감소
      periodSeconds: 60

이 보수성이 flapping(확대/축소 반복)을 막는다. 부하가 잠깐 떨어졌다 올라가는 패턴에서 무분별한 축소가 서비스를 흔들지 않게. 단점: 부하가 진짜로 떨어졌을 때 노드가 늦게 내려와 비용이 더 발생. 이 trade-off가 autoscaling 운영의 미묘함.

Karpenter의 consolidation — 지속적 비용 최적화

Cluster Autoscaler는 "Pending Pod가 있으면 노드 추가, 한가하면 제거"가 전부. Karpenter는 더 나아가 consolidation(통합):

  • 한가한 노드의 Pod를 다른 노드로 옮기고, 빈 노드를 즉시 제거(지속적 비용 절감).
  • 더 싼 인스턴스 타입으로 교체 가능하면 교체.

이것이 Karpenter가 "단순 노드 추가/제거"가 아니라 지속적 비용 최적화를 하는 이유. 단 consolidation은 Pod 재배치(재스케줄)를 유발하므로, 상태 있는 워크로드에는 주의. 비용 최적화 vs 안정성의 trade-off를 운영자가 튜닝.

흔히 묻는 것, 흔히 틀리는 것

오해 정정
"HPA 켰더니 자동으로 커진다" 메트릭 서버 필요. 없으면 동작 안 함
"HPA와 VPA를 같이 켜면 더 좋다" CPU 기반일 땐 충돌. 같이 쓰기 까다로움
"replicas가 늘어나면 자동으로 노드도 늘어난다" Cluster Autoscaler/Karpenter가 별도 필요. 없으면 Pending
"VPA는 실행 중 Pod 자원을 바로 바꾼다" 전통적으론 재시작 필요. In-place Resize로 일부 완화
"scale-down은 즉시 일어난다" 보수적. 일정 시간 한가 상태 유지 필요
"Karpenter는 Cluster Autoscaler를 대체하는 동일품이다" 접근이 다름(노드 그룹 vs 즉석 선택). 선택이지 단순 교체 아님

요약 — 이 글의 결론

  • autoscaling은 세 축: HPA(Pod 수), VPA(Pod 자원), Cluster Autoscaler/Karpenter(노드 수). 각기 다른 질문에 답.
  • HPA가 가장 흔한 수평 확장. 메트릭 서버(또는 커스텀 메트릭 어댑터) 필수. 목표 비율로 수렴하는 공식.
  • VPA는 Pod 자원 추천/조정. 전통적으론 재시작 수반, 그래서 자동 모드는 신중. HPA(CPU 기반)와 충돌하므로 보통 Off 모드로 모니터링.
  • Cluster Autoscaler/Karpenter가 노드를 늘린다. HPA로 늘어난 Pod가 Pending일 때 발동. 없으면 "replicas만 늘고 실제 안 늘어나는" 현상.
  • 세 축은 협력(HPA → 스케줄 → 노드 확장)하되, HPA-VPA는 충돌 주의. CPU 기반 HPA와 VPA 자동 조정은 피하라.
  • scale-down은 scale-up보다 보수적. 빈도 잦은 thrashing 방지용. "안 줄어든다"는 불만은 의도된 설계.

생각해 볼 문제

  1. HPA 켰는데 REPLICAS가 안 늘어난다. 가능한 원인 3가지(메트릭 서버 외에도).
  2. HPA maxReplicas를 20으로 잡았는데, 노드 자원이 부족해 Pending Pod가 생겼다. 어떻게 해결하나?
  3. VPA를 Auto 모드로 쓰면 생기는 부작용을 재시작 관점에서 설명하라. StatefulSet이라면?
  4. Karpenter가 노드 그룹 기반 Cluster Autoscaler보다 빠른 근본 이유는?
  5. 부하가 급증 후 급감했다. HPA와 Cluster Autoscaler가 각각 어떻게 반응하나? 시간차는?
  6. 커스텀 메트릭(예: 초당 HTTP 요청 수)으로 HPA를 돌리려면 추가 컴포넌트는? 왜 기본 HPA만으론 안 되나?

참고

'Tech Artifacts > Kubernetes' 카테고리의 다른 글

Kubernetes - 14. kubeadm  (0) 2026.07.13
Kubernetes - 13. node administration  (0) 2026.07.13
Kubernetes - 11. resource qos  (0) 2026.07.13
Kubernetes - 10. config secret  (0) 2026.07.13
Kubernetes - 09. storage  (0) 2026.07.13