이벤트 페이지 트래픽이 평소의 10배로 뛰자 운영팀이 수동으로 kubectl scale을 반복하고 있습니다. 사람의 판단 속도로는 급격한 부하 변화와 야간 트래픽을 안정적으로 따라가기 어렵습니다. HPA는 메트릭을 기준으로 Pod 수를 자동 조절해 온콜 부담을 줄입니다.
블랙프라이데이 당일 오전 10시, 이벤트 페이지에 트래픽이 몰리기 시작했습니다. 평소의 10배 요청이 들어오는데 파드는 3개뿐이고 응답 시간은 계속 늘어납니다. 팀원이 수동으로 kubectl scale deployment를 실행해 파드를 20개로 늘렸지만, 5시간 뒤 트래픽이 줄었을 때는 다시 수동으로 줄여야 했습니다. 만약 HPA가 설정되어 있었다면 이 모든 과정이 자동으로 일어났을 겁니다.
HPA(Horizontal Pod Autoscaler)는 파드의 리소스 사용량이나 커스텀 메트릭을 기반으로 Deployment나 ReplicaSet의 파드 수를 자동으로 조절합니다. CPU 사용률이 목표값을 넘으면 파드를 추가하고, 내려가면 줄입니다. 이 자동화가 없으면 운영팀은 항상 모니터링 화면 앞에 붙어 있어야 하며, 새벽에도 알람에 반응해야 합니다. HPA는 "트래픽에 알아서 대응하는 시스템"을 만드는 첫 번째 단계입니다.
- 1metrics-server 설치 및 동작 원리
- 2HPA 리소스 생성 (kubectl autoscale vs YAML)
- 3스케일 업/다운 동작 과정 이해
- 4HPA 상태 읽기 및 트러블슈팅
- 5안정화 윈도우와 스케일링 정책 커스텀
- 6메모리 기반 스케일링 및 커스텀 메트릭 개요
kubectl get deployment metrics-server -n kube-system 2>/dev/null || echo 'NOT INSTALLED'kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yamlkubectl top nodeskubectl create namespace hpa-demometrics-server: HPA의 눈
HPA를 만들었는데 kubectl get hpa에서 TARGETS가 계속 <unknown>/50%로 표시된다면, metrics-server가 없기 때문입니다. HPA는 스케일링 결정을 내리기 위해 실시간 CPU 사용량이 필요하지만 그 데이터를 스스로 수집하지 않습니다. metrics-server가 각 노드의 kubelet에서 리소스 사용량을 수집하고 Metrics API로 제공해야 비로소 HPA가 동작합니다. 이 의존 관계를 모르면 HPA 설정은 올바른데 왜 스케일이 안 되는지 원인을 찾지 못합니다.

파드 → kubelet(cAdvisor) → metrics-server → Metrics API → HPA 컨트롤러
# metrics-server 설치 (로컬 클러스터에서는 TLS 검증 비활성화 필요할 수 있음)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 로컬 Kind/Minikube 클러스터용: TLS 검증 비활성화 패치
kubectl patch deployment metrics-server -n kube-system \
--type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
# 설치 확인 (30초~1분 소요)
kubectl rollout status deployment/metrics-server -n kube-system
# 메트릭 수집 확인
kubectl top nodes
# NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
# kind-worker 55m 1% 512Mi 26%
kubectl top pods -A
# NAMESPACE NAME CPU(cores) MEMORY(bytes)
# kube-system coredns-xxx 5m 18Mi
- NAME—조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
- STATUS/READY—Running, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
- RESTARTS/EVENTS—재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.
HPA 생성: 기본 CPU 기반 스케일링
이벤트 트래픽이 몰릴 것을 예상해 HPA를 미리 설정했는데 막상 부하가 들어와도 파드가 늘어나지 않는다면, CPU requests가 설정되지 않았을 가능성이 높습니다. HPA는 "현재 CPU 사용량 / CPU requests"로 사용률을 계산하는데 requests가 없으면 비율 계산 자체가 불가능합니다. 반대로 requests가 너무 낮게 설정되면 실제 부하가 낮아도 HPA가 과도하게 스케일 업해 비용이 낭비됩니다. HPA 설정 시 minReplicas, maxReplicas, 목표 사용률을 서비스 특성에 맞게 조율하는 것이 실무 운영의 핵심입니다.

# php-apache-deployment.yaml
# 부하 테스트에 자주 쓰이는 예시 이미지
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
namespace: hpa-demo
spec:
replicas: 1
selector:
matchLabels:
app: php-apache
template:
metadata:
labels:
app: php-apache
spec:
containers:
- name: php-apache
image: registry.k8s.io/hpa-example
ports:
- containerPort: 80
resources:
requests:
cpu: "200m" # HPA 계산 기준값 (필수!)
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: php-apache
namespace: hpa-demo
spec:
ports:
- port: 80
selector:
app: php-apache
kubectl apply -f php-apache-deployment.yaml
# 방법 1: kubectl autoscale (빠른 생성)
kubectl autoscale deployment php-apache \
-n hpa-demo \
--cpu-percent=50 \
--min=1 \
--max=10
# 방법 2: YAML로 생성 (권장, GitOps 관리 가능)
cat <<EOF | kubectl apply -f -
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache-hpa
namespace: hpa-demo
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50 # CPU 사용률 50% 목표
EOF
# HPA 상태 확인
kubectl get hpa -n hpa-demo
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
# php-apache-hpa Deployment/php-apache 3%/50% 1 10 1 30s
TARGETS 컬럼 읽는 법: 현재사용률/목표사용률. 3%/50%는 현재 3%를 쓰고 있고 목표는 50%라는 의미입니다.
스케일링 동작 과정과 안정화 윈도우
트래픽이 잠깐 치솟았다 내려갈 때마다 파드가 늘었다 줄었다를 반복하면 파드 시작/종료 오버헤드가 오히려 서비스 안정성을 해칩니다. 이 "플래핑" 문제를 막기 위해 HPA는 스케일 다운에 기본 5분 안정화 윈도우를 적용합니다. 스케일 업은 즉각 반응해야 하지만 스케일 다운은 신중해야 한다는 운영 원칙이 반영된 설계입니다. 안정화 윈도우와 스케일링 정책을 커스텀하면 서비스 특성에 맞는 민감도를 조율할 수 있습니다.
필요 파드 수 = ceil(현재파드수 × (현재사용률 / 목표사용률))
예: 파드 1개, 현재 CPU 150%, 목표 50%
→ ceil(1 × (150 / 50)) = ceil(3) = 3개
스케일 다운이 느린 이유는 안정화 윈도우 때문입니다.
# 스케일링 정책 커스텀
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache-hpa-custom
namespace: hpa-demo
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 2 # 최소 2개 유지 (HA 보장)
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleUp:
stabilizationWindowSeconds: 0 # 즉시 스케일 업
policies:
- type: Percent
value: 100 # 한 번에 최대 100% 증가
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300 # 5분 안정화 후 스케일 다운
policies:
- type: Pods
value: 2 # 한 번에 최대 2개씩 감소
periodSeconds: 60
실습: 부하 테스트로 HPA 동작 확인
# 터미널 1: HPA 상태 실시간 모니터링
watch kubectl get hpa php-apache-hpa -n hpa-demo
# 터미널 2: 부하 생성 (무한 HTTP 요청)
kubectl run load-generator \
--image=busybox:1.36 \
--restart=Never \
-n hpa-demo \
-- /bin/sh -c "while true; do wget -q -O- http://php-apache.hpa-demo.svc.cluster.local; done"
# 터미널 1에서 변화 관찰 (약 1-2분 소요)
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# php-apache-hpa Deployment/php-apache 3%/50% 1 10 1
# php-apache-hpa Deployment/php-apache 142%/50% 1 10 1 ← 급등
# php-apache-hpa Deployment/php-apache 142%/50% 1 10 3 ← 스케일 업
# php-apache-hpa Deployment/php-apache 72%/50% 1 10 4
# php-apache-hpa Deployment/php-apache 53%/50% 1 10 5
# 파드 수 변화 확인
kubectl get pods -n hpa-demo -w
# 부하 중단 (터미널 2에서)
kubectl delete pod load-generator -n hpa-demo
# 약 5분 후 파드가 다시 줄어듦 (안정화 윈도우)
# 스케일 다운 이벤트 확인
kubectl describe hpa php-apache-hpa -n hpa-demo | grep -A 5 "Events"
HPA를 만들었는데 며칠이 지나도 파드 수가 변하지 않습니다. kubectl get hpa 결과를 보면 TARGETS 컬럼이 <unknown>/50%로 표시됩니다.
# 증상 확인
kubectl get hpa -n hpa-demo
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# php-apache-hpa Deployment/php-apache <unknown>/50% 1 10 1
# 1단계: HPA 이벤트 확인
kubectl describe hpa php-apache-hpa -n hpa-demo | tail -20
# Events:
# Warning FailedGetResourceMetric 2m horizontal-pod-autoscaler
# unable to get metrics for resource cpu: unable to fetch metrics from resource metrics API
# 2단계: metrics-server 설치 여부 확인
kubectl get pods -n kube-system | grep metrics-server
# (아무 출력도 없음 — 미설치)
# 또는 설치는 됐지만 비정상
# metrics-server-xxx 0/1 CrashLoopBackOff 5 10m
# 3단계-A: metrics-server 설치
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 3단계-B: 로컬 클러스터(Kind/Minikube)라면 TLS 패치 추가
kubectl patch deployment metrics-server -n kube-system \
--type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
# 4단계: metrics-server 정상화 확인 (1-2분 소요)
kubectl rollout status deployment/metrics-server -n kube-system
kubectl top nodes # 이게 나와야 정상
# NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
# 5단계: HPA 상태 재확인
kubectl get hpa -n hpa-demo
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# php-apache-hpa Deployment/php-apache 3%/50% 1 10 1
# ↑ <unknown>가 실제 값으로 바뀜
# 추가: requests 미설정 케이스
# 만약 metrics-server는 정상인데 여전히 unknown이면
kubectl get pod -n hpa-demo -o jsonpath='{.items[0].spec.containers[0].resources}'
# {} ← requests가 없음! CPU requests를 설정해야 HPA가 동작함
원인 요약: HPA의 <unknown> 문제는 99%가 (1) metrics-server 미설치/비정상, (2) 파드에 CPU requests 미설정, 이 두 가지입니다. 두 가지를 순서대로 확인하면 빠르게 해결됩니다.
시나리오: 프로덕션 API 서버에 HPA 도입
월말 정산이 있는 서비스를 운영 중입니다. 평소에는 파드 3개로 충분한데 매월 1일 새벽에 트래픽이 10배 이상 몰려 수동으로 스케일을 조절하고 있습니다. HPA로 이 작업을 자동화하려고 합니다.
# 1단계: 현재 리소스 설정 확인 및 requests 추가
kubectl get deployment api-server -n production \
-o jsonpath='{.spec.template.spec.containers[0].resources}'
# requests가 없다면 먼저 추가
kubectl set resources deployment/api-server \
-n production \
--requests=cpu=200m,memory=256Mi \
--limits=cpu=1000m,memory=512Mi
# 2단계: HPA 생성 (YAML 파일로 관리 권장)
cat <<EOF > api-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
namespace: production
annotations:
description: "월말 정산 트래픽 대응용 HPA"
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 3 # 평소 최솟값 (HA)
maxReplicas: 30 # 월말 최대 예상 × 1.5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleUp:
stabilizationWindowSeconds: 60 # 1분 안정화 후 스케일 업
scaleDown:
stabilizationWindowSeconds: 600 # 10분 안정화 후 스케일 다운
EOF
kubectl apply -f api-hpa.yaml
# 3단계: 월말 이벤트 전 부하 테스트로 스케일링 검증
kubectl run load-test \
--image=grafana/k6 \
--restart=Never \
-n production \
-- run - <<'K6SCRIPT'
import http from 'k6/http';
export let options = { vus: 100, duration: '5m' };
export default function() { http.get('http://api-server/health'); }
K6SCRIPT
watch kubectl get hpa api-server-hpa -n production
# 4단계: 알람 설정 (HPA가 maxReplicas에 도달하면 알람)
# Prometheus AlertManager 또는 클라우드 알람으로 설정
# kube_horizontalpodautoscaler_status_current_replicas == kube_horizontalpodautoscaler_spec_max_replicas
실무 포인트: maxReplicas에 도달하면 HPA는 더 이상 스케일 업을 못 합니다. 이 상황이 되면 사람이 개입해야 하므로, maxReplicas 도달 시 알람을 반드시 설정해두세요. Cluster Autoscaler와 함께 사용하면 노드 자체도 자동으로 늘릴 수 있습니다.
핵심 요약
| 항목 | 설명 |
|---|---|
| HPA 전제 조건 | metrics-server 설치 + 파드에 CPU requests 설정 |
| 스케일 업 | 즉각 반응 (stabilizationWindowSeconds: 0 권장) |
| 스케일 다운 | 기본 5분 대기 (flapping 방지) |
| TARGETS: unknown | metrics-server 미설치 or requests 미설정 |
| minReplicas | HA를 위해 최소 2 이상 권장 |
| maxReplicas | 노드 용량 + 비용 고려해 현실적으로 설정 |