Published on

Kubernetes HPA가 안 늘 때 metrics-server 0값 해결

Authors

서버 부하가 올라가는데도 Kubernetes HPA가 꿈쩍도 안 하고, kubectl top은 0 또는 unknown만 찍히는 상황은 흔합니다. 대부분은 HPA 자체의 문제가 아니라 메트릭 파이프라인(메트릭 수집 → 집계 → HPA 평가) 중 어딘가가 끊겨서 생깁니다.

이 글은 “HPA가 안 늘 때 metrics-server가 0값을 반환하는” 케이스를 기준으로, 재현 가능한 체크 순서바로 적용 가능한 설정/명령을 정리합니다. (CPU 기준 HPA를 중심으로 설명하지만, 메모리/커스텀 메트릭에도 동일한 진단 흐름이 적용됩니다.)

> 참고: 파드가 OOM으로 재시작을 반복하면 HPA가 기대와 다르게 동작할 수 있습니다. 메모리 이슈 진단은 Kubernetes OOMKilled 진단과 메모리 누수 추적 실전도 함께 보세요.

1) 증상 패턴: “HPA는 있는데 스케일이 안 됨”

대표적인 징후는 아래 중 하나로 나타납니다.

  • kubectl get hpa에서 TARGETS0%/50%, unknown/50%처럼 보임
  • kubectl describe hpafailed to get cpu utilization 또는 unable to fetch metrics 이벤트
  • kubectl top pod -A가 전부 0이거나 metrics not available yet
  • kubectl get --raw "/apis/metrics.k8s.io/v1beta1/pods"가 에러

HPA는 기본적으로 metrics.k8s.io (resource metrics API) 에서 CPU/메모리 사용량을 받아 계산합니다. 즉, metrics-server가 정상 동작하지 않으면 HPA는 스케일 결정을 못 합니다.

2) 가장 먼저 확인할 4가지 (10분 컷)

2.1 metrics-server가 설치되어 있고 Ready인가?

kubectl get deployment -n kube-system metrics-server
kubectl get pods -n kube-system -l k8s-app=metrics-server
kubectl logs -n kube-system deploy/metrics-server --tail=200
  • 파드가 CrashLoopBackOff면 로그에 kubelet 연결/TLS 문제가 거의 확정입니다.
  • 파드는 Running인데도 0값이면, kubelet scrape가 실패하거나 리소스 요청값(requests) 미설정인 경우가 많습니다.

2.2 metrics API가 살아있는지 직접 호출

kubectl get apiservices | grep metrics
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" | head
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/pods" | head
  • ServiceUnavailable / MissingEndpoints면 metrics-server Service/Endpoints 문제
  • Forbidden이면 RBAC
  • 응답은 오는데 값이 0 또는 비어있으면 kubelet에서 데이터를 못 긁는 상황

2.3 kubectl top이 되는지

kubectl top nodes
kubectl top pods -A | head -n 20

kubectl top은 metrics-server의 결과를 그대로 보여주므로, 여기서 0이면 HPA도 0일 가능성이 큽니다.

2.4 HPA가 참조하는 대상(Deployment)에 requests가 있는지

HPA의 CPU utilization(%) 계산은 현재 사용량 / requests 기반입니다. requests가 없으면 계산이 불가능하거나 unknown이 됩니다.

kubectl get deploy <DEPLOYMENT_NAME> -o jsonpath='{range .spec.template.spec.containers[*]}{.name}{"\t"}{.resources.requests.cpu}{"\t"}{.resources.requests.memory}{"\n"}{end}'
  • CPU HPA를 쓰면 최소한 resources.requests.cpu는 필수라고 생각하면 안전합니다.

3) 원인 1: kubelet TLS/인증 문제로 scrape 실패 (가장 흔함)

관리형/온프렘 환경에서 kubelet 인증서 설정이 제각각이라 metrics-server가 kubelet의 /metrics/resource에 접근하지 못하는 경우가 많습니다.

3.1 metrics-server 로그에서 흔히 보이는 에러

x509: cannot validate certificate for <NODE_IP> because it doesn't contain any IP SANs
Unable to scrape metrics from node ...
Get "https://<node>:10250/metrics/resource": x509: certificate signed by unknown authority

이 경우 해결은 두 갈래입니다.

  • 정석: kubelet 인증서/CA를 올바르게 구성(IP SAN 포함, 올바른 CA 체인)
  • 현실적 우회: metrics-server에 --kubelet-insecure-tls 적용

3.2 (우회) --kubelet-insecure-tls 적용

> 보안상 권장되는 최종 상태는 아니지만, 원인 분리를 위해 빠르게 적용해보는 것이 좋습니다.

kubectl -n kube-system edit deployment metrics-server

컨테이너 args에 아래를 추가/확인합니다.

- --kubelet-insecure-tls
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname

적용 후 롤아웃 상태 확인:

kubectl -n kube-system rollout status deploy/metrics-server
kubectl top nodes
  • preferred-address-types는 노드 주소 선택이 꼬여서 hostname이 DNS로 해석되지 않거나, 외부망으로 붙으려다 실패하는 케이스를 줄여줍니다.

3.3 (정석) kubelet 인증서 IP SAN 문제 해결 힌트

온프렘 kubeadm 기반에서 자주 생기는 문제는 kubelet 서버 인증서에 노드 IP SAN이 빠진 상태입니다. 이 경우 kubelet 인증서 재발급/재구성이 필요합니다.

  • kubeadm 환경: kubelet CSR 승인/재생성, kubelet 서버 인증서 옵션 확인
  • 자체 구성: kubelet --tls-cert-file, --tls-private-key-file, CA 설정 확인

정석 루트는 클러스터마다 달라 글 한 편 분량이라, 일단은 insecure-tls로 “메트릭 파이프라인이 살아나는지”를 먼저 확인하고, 이후 보안 요구사항에 맞춰 kubelet 인증서를 정리하는 접근이 실무적으로 빠릅니다.

4) 원인 2: RBAC/권한 문제로 metrics-server가 kubelet을 못 읽음

metrics-server는 노드/파드 메트릭을 읽기 위해 권한이 필요합니다. 설치 매니페스트가 변형되었거나, 보안 정책으로 일부 권한이 제거되면 실패합니다.

4.1 apiservice 상태와 이벤트 확인

kubectl describe apiservice v1beta1.metrics.k8s.io
kubectl -n kube-system describe deploy metrics-server

Forbidden류 메시지가 보이면 RBAC을 의심합니다.

4.2 기본 ClusterRole/Binding 존재 확인

kubectl get clusterrole | grep -E 'metrics-server|system:aggregated-metrics-reader'
kubectl get clusterrolebinding | grep metrics-server

클러스터 표준 설치(manifests/helm)라면 대개 자동으로 구성되지만, “회사 표준 보안 베이스라인” 적용 중 누락되는 경우가 있습니다.

5) 원인 3: requests 미설정 → HPA가 계산을 못 하거나 0처럼 보임

CPU utilization 기반 HPA는 컨테이너별 CPU requests가 있어야 정상 계산됩니다.

5.1 잘못된 예 (requests 없음)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: myrepo/api:1.0.0
          ports:
            - containerPort: 8080

5.2 수정 예 (requests/limits 추가)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: myrepo/api:1.0.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "200m"
              memory: "256Mi"
            limits:
              cpu: "1000m"
              memory: "512Mi"

requests를 넣은 뒤 HPA가 목표치 계산을 시작하는지 확인합니다.

kubectl apply -f deploy.yaml
kubectl describe hpa <HPA_NAME>

6) 원인 4: HPA v2 설정/메트릭 타입 혼동

CPU 기준 HPA는 보통 아래 두 형태 중 하나입니다.

  • averageUtilization: requests 대비 퍼센트
  • averageValue: 절대값(예: 300m)

requests가 불안정하거나 워크로드 특성상 퍼센트가 맞지 않으면 averageValue가 더 예측 가능할 때가 있습니다.

6.1 HPA v2 예시 (utilization)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60

6.2 HPA v2 예시 (averageValue)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: AverageValue
          averageValue: "400m"

metrics-server 0값 문제 자체를 해결하진 않지만, “requests 기반 퍼센트”에서 오는 혼란을 줄이고 디버깅을 단순화할 수 있습니다.

7) 실전 디버깅 플로우(체크리스트)

아래 순서대로 보면 보통 30분 내에 원인이 좁혀집니다.

  1. HPA 이벤트 확인
    kubectl describe hpa <HPA_NAME>
    
  2. metrics API 확인
    kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" | head
    
  3. metrics-server 로그 확인
    kubectl logs -n kube-system deploy/metrics-server --tail=200
    
  4. kubelet 접근/TLS 문제면 --kubelet-insecure-tls + --kubelet-preferred-address-types로 우선 복구
  5. requests 누락 여부 확인
    kubectl get deploy <DEPLOYMENT_NAME> -o yaml | sed -n '/resources:/,/imagePullPolicy/p'
    
  6. kubectl top이 정상 값으로 바뀌는지 확인 후, 부하를 걸어 HPA 스케일아웃 검증

8) 부하 테스트로 “정말 스케일아웃 되는지” 검증

메트릭이 정상이어도, 실제로 스케일이 오르는지 확인해야 합니다.

예: 간단한 CPU 부하(테스트용 파드에서 실행)

kubectl run -it --rm loadgen --image=busybox --restart=Never -- sh
# inside pod
while true; do wget -q -O- http://api.default.svc.cluster.local:8080/ >/dev/null; done

동시에 모니터링:

kubectl get hpa -w
kubectl get pods -l app=api -w
  • TARGETS가 올라가고, 잠시 후 replicas가 증가하면 end-to-end로 정상입니다.

9) 마무리: 0값 문제는 “HPA”가 아니라 “메트릭 경로” 문제다

정리하면, HPA가 안 늘 때 metrics-server가 0값을 내는 핵심 원인은 대부분 아래 셋 중 하나입니다.

  • kubelet TLS/주소 선택 문제(가장 흔함): --kubelet-insecure-tls, --kubelet-preferred-address-types로 빠르게 확인
  • RBAC 누락: apiservice/metrics-server 이벤트에서 Forbidden 확인
  • requests 미설정: utilization 기반 HPA는 requests 없으면 계산 불가

메트릭이 정상화되면 HPA는 생각보다 단순하게 동작합니다. 먼저 kubectl top을 “정상 값”으로 만드는 데 집중하고, 그 다음에 HPA 목표치/스케일 정책을 조정하는 순서가 가장 빠릅니다.