HPA(Horizontal Pod Autoscaler)를 처음 쓸 때 나는 이렇게 알고 있었다. "CPU가 임계치를 넘으면 파드가 하나씩 늘어나고, 떨어지면 하나씩 줄어든다." 그런데 실제 클러스터에서 트래픽이 몰리자 replicas가 3에서 6으로 한 번에 뛰는 걸 보고 당황했다. 1씩 증가가 아니었다.
확인해 보니 HPA는 증분(increment) 제어기가 아니라 목표 사용률로 수렴시키는 비례(proportional) 제어기였다. 이 글은 HPA가 다음 replica 수를 결정하는 하나의 핵심 공식과, 그 결과가 진동하지 않도록 받치는 보정 장치들(tolerance, not-ready 보정, 안정화 윈도우, behavior 정책)을 Kubernetes 공식 문서의 알고리즘 절을 따라가며 정리한 것이다. HPA를 "알아서 늘려주는 마법"이 아니라 예측 가능한 계산식으로 다루는 게 목표다.
Kubernetes 공식 문서(Horizontal Pod Autoscaling)에 따르면 기본 계산식은 다음 한 줄이다.
desiredReplicas = ceil[ currentReplicas × (currentMetricValue / desiredMetricValue) ]
핵심은 currentMetricValue / desiredMetricValue라는 비율이다. 현재 Ready 파드들의 평균 CPU가 200m이고 목표가 100m이면 비율은 2.0 → replica를 2배로. 평균이 50m로 떨어지면 비율 0.5 → 절반으로. 즉 HPA는 "1만큼 더/덜"이 아니라 "목표 사용률에 도달하려면 지금 몇 개가 필요한가"를 매번 통째로 다시 계산한다.
HPA는 임계치를 넘었는지를 보고 한 칸씩 움직이는 게 아니라, 목표 비율을 만족시키는 replica 수를 한 번에 산출하는 비례 제어기다.
그래서 트래픽이 급증하면 3 → 6처럼 한 번에 2배가 될 수 있고, 반대로 절반으로 줄 수도 있다. 이 변화 폭을 제어하는 건 뒤에서 볼 behavior 정책뿐이다.
비례 제어는 강력하지만, 메트릭이 목표 근처에서 미세하게 출렁이기만 해도 replica가 흔들릴 위험이 있다. HPA에는 이를 막는 장치가 여러 겹 있다.
tolerance(기본 0.1). 비율이 1.0에 충분히 가까우면 — 즉 0.9 ~ 1.1 구간이면 — 아무 동작도 하지 않는다. 목표 근처의 잔떨림을 무시하는 1차 방어선이다.
not-ready·결측 메트릭의 비대칭 보정. 여기가 HPA에서 가장 헷갈리는 디테일이다. 아직 Ready가 아니거나 메트릭이 없는 파드를 스케일 방향에 따라 다르게 가정한다.
| 상황 | 스케일 업 판단 시 | 스케일 다운 판단 시 |
|---|---|---|
| 메트릭 결측 파드 | 사용률 0% 로 가정 | 사용률 100% 로 가정 |
| 아직 Ready 아닌 파드 | 사용률 0% 로 가정(사실상 억제) | 계산에서 제외 |
방향이 반대인 이유는 실패 비용이 비대칭이기 때문이다. 업스케일을 과하게 하면 비용 폭증으로 끝나지만, 다운스케일을 과하게 하면 가용성이 무너진다. 그래서 결측 파드를 업 계산에선 0%(더 늘릴 근거를 약화), 다운 계산에선 100%(함부로 줄이지 못하게)로 둔다. "업은 보수적으로 작게, 다운은 보수적으로 작게" — 즉 양쪽 다 성급한 변화를 누른다.
여기에 새 파드의 워밍업을 위한 유예도 있다. 공식 문서 기준 --horizontal-pod-autoscaler-initial-readiness-delay(기본 30초)와 --horizontal-pod-autoscaler-cpu-initialization-period(기본 5분) 동안의 파드는 메트릭을 액면 그대로 믿지 않는다. 방금 뜬 파드의 콜드스타트 CPU 스파이크가 곧바로 추가 업스케일을 부르는 폭주를 막기 위해서다.
tolerance가 값(value) 축의 방어선이라면, 안정화 윈도우(stabilization window) 는 시간(time) 축의 방어선이다. 계산된 추천값을 바로 적용하지 않고 과거 일정 구간의 추천값들을 모아 그중 하나를 고른다.
방향별 선택 규칙도 같은 철학이다. 다운은 윈도우 내 max 추천, 업은 윈도우 내 min 추천을 쓴다. 양쪽 모두 "성급한 변화"를 누르는 쪽으로 정렬돼 있다.
마지막으로 behavior 정책(KEP-853) 이 한 주기당 변화 폭 자체를 클램프한다. 기본값은 대략 이렇다.
behavior:
scaleUp:
stabilizationWindowSeconds: 0
selectPolicy: Max # 여러 정책 중 가장 큰 변화 허용
policies:
- type: Percent
value: 100 # 15초마다 최대 100% 증가(2배)
periodSeconds: 15
- type: Pods
value: 4 # 또는 15초마다 최대 +4 파드
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300
selectPolicy: Max
policies:
- type: Percent
value: 100
periodSeconds: 15
selectPolicy: Max는 여러 정책이 허용하는 변화량 중 가장 큰 것을 택한다(업이라면 Percent 100%와 Pods 4 중 더 큰 쪽). Disabled로 두면 그 방향의 스케일링을 아예 끈다. 정리하면 결측·미준비 가정(업 0% / 다운 100%), 안정화 윈도우(업 0초 / 다운 300초), 윈도우 내 선택(업 min / 다운 max)이 전부 "빠르게 늘리고 천천히 줄인다" 는 한 방향으로 맞춰져 있다.
HPA 컨트롤러는 이벤트 기반 연속 감시가 아니라 kube-controller-manager가 --horizontal-pod-autoscaler-sync-period(기본 15초)마다 깨어나 도는 폴링 루프다. 한 틱의 흐름과 손계산은 이렇다.
┌────────────── 15s sync tick ──────────────┐
▼ │
메트릭 집계 → ratio = cur/target │
|ratio-1| ≤ 0.1 ? ──yes──► no-op ─────────────────┤
│ no │
desired = ceil(replicas × ratio) │
(not-ready/결측 보정: 업=0%, 다운=100%) │
안정화 윈도우(업 min / 다운 max) → behavior 클램프 │
scale 서브리소스.replicas = desired ────────────────┘
# 시나리오 A — 업스케일: replicas=3, 목표 CPU=50%, Ready 평균=90%
ratio = 90 / 50 = 1.8 # |1.8 - 1| = 0.8 > 0.1 → 스케일 대상
desired = ceil(3 × 1.8) = 6 # 3 → 6, 한 번에 2배
# scaleUp Percent 100%/15s 이내(+100%)라 그대로 허용 → 즉시 반영
# 시나리오 B — 같은 디플로이, 평균이 20%로 급락
ratio = 20 / 50 = 0.4
desired = ceil(3 × 0.4) = 2 # 2가 "추천값"
# 하지만 scaleDown 안정화 300초: 지난 5분 추천 중 max(직전 6 등)를 채택
# → 즉시 2로 줄지 않고 윈도우가 지나야 단계적으로 내려감
실제 클러스터라면 kubectl describe hpa <name>의 이벤트에서 New size: N; reason: cpu resource utilization (percentage of request) above target 같은 줄로 동일한 계산 결과를 직접 확인할 수 있다. 참고로 metrics:에 CPU·메모리·custom을 여러 개 두면 각각으로 desiredReplicas를 구한 뒤 가장 큰 값을 채택한다 — 어느 한 자원이라도 부족하면 늘리겠다는 보수적 합집합이다.
HPA는 임계치 기반 증분 제어가 아니라
ceil(replicas × ratio)로 목표 비율에 수렴시키는 비례 제어기이며, 그 위에 비대칭 보정을 겹쳐 "빠르게 늘리고 천천히 줄인다".
실무에서 기억할 한 가지는 15초 폴링이라는 한계다. 스파이크가 15초 안에 끝나면 HPA는 그걸 못 볼 수도 있다. 짧고 날카로운 버스트는 HPA만으로 흡수되지 않으며, 이것이 KEDA(이벤트 기반 스케일링)·VPA·request 기반 오버프로비저닝이 함께 필요한 이유다.
더 파고든다면 두 갈래가 있다. 하나는 VPA recommender가 메트릭 히스토리(decaying histogram)로 request를 추정하는 방식과 HPA 동시 사용 시의 충돌 지점. 다른 하나는 custom.metrics.k8s.io / external.metrics.k8s.io 어댑터(Prometheus Adapter, KEDA)가 HPA에 메트릭을 공급하는 파이프라인이다.