쿠버네티스에서 HPA를 쓰다 보면 이런 지점에서 한 번 멈춥니다.
- HPA YAML에는 metrics-server 주소가 없는데 도대체 어디로 메트릭을 요청하는 거지?
- kubectl top / /apis/metrics.k8s.io/... 는 어떻게 동작하는 거지?
- metrics-server는 모든 Pod에 붙는 건가, 아니면 다른 원리가 있나?
- 노드가 1,000개면 15초마다 그걸 다 커버할 수 있나?
- 그 과정에서 나오는 Aggregation layer 는 정확히 뭐고, 왜 kube-apiserver “안”에 있나?
- 마지막으로, 이런 확장 방식이 CRD/CR 이랑은 뭐가 다른가?
이 글은 위 질문을 한 흐름으로 묶어서 “HPA → Metrics API → aggregation → metrics-server → kubelet” 까지 연결한 뒤, 마지막에 “같은 확장 이야기인 CRD/CR”까지 자연스럽게 정리합니다.
- HPA 동작 원리 / 어떤 API에서 메트릭을 가져오는지:
- HPA sync 주기(기본 15초):
- Resource Metrics Pipeline(메트릭 흐름 전체 그림, kubelet /metrics/resource 등):
- metrics-server README(15초 수집, 노드당 리소스 효율, 5,000노드 스케일 목표):
- Aggregation layer 개념(“apiserver in-process”, APIService가 path claim, proxy):
- Aggregation layer 설정(APIService의 spec.service, caBundle, --enable-aggregator-routing):
- Custom Resources(CRD/CR는 저장/조회, 컨트롤러 결합 시 선언적 API):
(복사용 링크 모음)
https://kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autoscale/
https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/
https://kubernetes.io/docs/tasks/debug/debug-cluster/resource-metrics-pipeline/
https://github.com/kubernetes-sigs/metrics-server
https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/
https://kubernetes.io/docs/tasks/extend-kubernetes/configure-aggregation-layer/
https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/
1. HPA는 “어디로” 메트릭을 요청하는가?
HPA는 “지속적인 데몬”이 아니라 컨트롤 루프로 동작합니다. 그리고 이 루프는 기본적으로 15초 간격으로 실행됩니다. (정확히는 kube-controller-manager 플래그 --horizontal-pod-autoscaler-sync-period 기본값이 15초)
1.1 HPA가 15초마다 하는 일 (핵심 흐름)
공식 문서 흐름을 그대로 요약하면:
- HPA 정의(HorizontalPodAutoscaler)를 읽고
- scaleTargetRef 가 가리키는 타겟(Deployment 등)을 찾고
- 그 타겟의 .spec.selector 라벨로 “대상 Pod들”을 결정한 뒤
- 메트릭 스펙에 맞는 API로부터 메트릭을 가져와
- 원하는 replica 수를 계산해
- 타겟의 scale 서브리소스를 업데이트합니다.
즉, 네가 HPA YAML에서 metrics-server: http://... 같은 걸 안 적는 이유는:
- HPA가 “특정 Pod/서비스 주소”를 호출하도록 설계된 게 아니라
- 정해진 메트릭 API 그룹(예: metrics.k8s.io)로 질의하도록 설계되어 있기 때문입니다.
1.2 CPU/Memory(Resource) 기반이면 어디서 가져오나?
HPA의 Resource(cpu/memory) 타입은 보통 resource metrics API에서 가져옵니다. 그리고 그 대표 API가 바로:
- metrics.k8s.io (Metrics API)
공식 문서도 “HPA는 보통 aggregated APIs(metrics.k8s.io, custom.metrics.k8s.io, external.metrics.k8s.io)에서 메트릭을 가져오며, metrics.k8s.io는 보통 Metrics Server 애드온이 제공한다”고 못 박습니다.
2. “Metrics API(metrics.k8s.io)”는 누가 제공하나?
여기서부터가 “HPA ↔ metrics-server가 어떻게 연결되나”의 본체입니다.
2.1 Metrics API는 “코어 API”가 아니라 “확장 API”다
/apis/metrics.k8s.io/v1beta1/... 는 kube-apiserver가 원래부터 가지고 있는 core API가 아니라, 확장 API(aggregated API) 형태로 붙는 API입니다.
그래서 이 API를 쓰려면 다음이 필요합니다:
- API aggregation layer 활성화
- metrics.k8s.io 를 “누가 제공하는지” 등록(APIService)
- 실제로 Metrics API를 구현하는 서버(대표가 metrics-server)
2.2 이걸 치면 전 노드가 나오는 이유
kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes | head | jq
이건 “metrics-server가 kubelet에서 긁어온 데이터를 Metrics API 응답 형태로 가공해서” 내주는 결과를 “클라이언트가 요청한 것”이에요.
중요:
이 “큰 JSON 리스트”를 metrics-server가 매번 그대로 받아오는 게 아니라, metrics-server는 kubelet에서 원천 데이터를 수집하고, 너/ HPA / kubectl 이 요청할 때 API 형태로 응답을 생성하는 구조입니다.
3. kube-apiserver는 어떻게 metrics-server를 “알아서” 찾아가나?
여기서 등장하는 게 Aggregation layer 입니다.

3.1 Aggregation layer는 어디에 있나?
Aggregation layer는 별도 프로세스가 아니라, **kube-apiserver 프로세스 내부(in-process)**에서 동작합니다.
그리고 이 레이어는 기본적으로 “아무 일도 안 하다가”,
- 어떤 확장 API가 등록되면(APIService 오브젝트),
- 그 API 경로를 “claim”하고,
- 해당 경로로 들어오는 요청을 뒤쪽 서버로 프록시합니다.
3.2 “라우팅 테이블” 역할을 하는 게 APIService
Aggregation layer가 “어디로 프록시할지”를 알기 위해 보는 리소스가 APIService 입니다.
공식 문서의 예시는 딱 이렇게 생겼습니다:
- spec.service.namespace/name = 확장 API 서버가 떠 있는 Service
- spec.service.port = 포트(기본 443)
- spec.caBundle = 그 서버의 TLS 인증서를 검증할 CA
즉, 개념적으로 이런 매핑이 생깁니다.
- /apis/metrics.k8s.io/v1beta1/* 로 들어온 요청은
- kube-system/metrics-server Service로 프록시
그래서 HPA가 metrics-server 주소를 몰라도 됩니다.
HPA는 그냥 metrics.k8s.io 로 요청하고, kube-apiserver가 APIService 기반으로 라우팅해 주는 구조예요.
3.3 (운영 디테일) 인증/인가 흐름이 왜 “apiserver 중심”이냐?
Aggregation 구조는 보안 흐름도 표준화합니다.
- kube-apiserver가 먼저 사용자 인증/인가를 수행한 뒤
- 확장 apiserver로 프록시하고
- 확장 apiserver는 “이 요청이 진짜 kube-apiserver가 보낸 프록시 요청인지”를 mTLS/헤더 규칙으로 검증합니다.
이게 aggregation을 kube-apiserver “밖”이 아니라 “안”에 둔 가장 실무적인 이점 중 하나입니다:
클러스터의 모든 API가 단일 관문(인증/인가/감사/디스커버리) 아래로 들어옵니다.
4. metrics-server는 15초마다 “뭘” 긁는가? (그리고 1,000노드는 커버 되나?)
네가 가진 metrics-server Pod describe에 --metric-resolution=15s 가 있었죠. 이건 “수집 템포”를 강하게 시사합니다(환경/배포마다 조정 가능). 그리고 metrics-server 프로젝트도 “15초마다 수집”을 주요 특징으로 명시합니다.
4.1 metrics-server가 긁는 대상은 “Pod”가 아니라 “각 노드 kubelet”
resource metrics pipeline 문서 기준으로:
- kubelet은 노드/컨테이너 리소스 메트릭을 제공하며
- 메트릭은 kubelet의 /metrics/resource (및 일부 /stats) 엔드포인트에서 접근 가능하고
- metrics-server는 각 노드 kubelet을 HTTP로 쿼리해서 메트릭을 수집/집계합니다.
즉 “모든 Pod 메트릭”을 얻는 방식은:
- Pod마다 붙어서 수집이 아니라
- 노드마다 kubelet을 수집하고
- kubelet 응답 안에 “그 노드 위 Pod/컨테이너들” 메트릭이 포함되므로
- 노드들을 합치면 “클러스터 전체 Pod 메트릭”이 되는 구조입니다.
4.2 metrics-server는 내부 캐시도 가진다 (HPA를 위해 필요한 동작)
공식 문서에 “metrics-server는 Kubernetes API로 노드/Pod를 추적하고, 각 노드를 HTTP로 쿼리하며, Pod 메타데이터 뷰와 pod health 캐시를 유지한다”는 설명이 있습니다. (HPA 질의에서 label selector 매칭을 위해 필요)
이 말은 실무적으로:
- HPA가 selector로 “대상 Pod 집합”을 만들 때
- metrics-server도 그 selector에 맞는 Pod를 찾아서 메트릭을 응답해야 하니
- Pod 메타데이터를 어느 정도는 계속 보고/캐시한다는 뜻입니다.
4.3 1,000 노드면 “커버 되나?” — 공식 가이드는 “가능” 쪽이지만, 전제 조건이 있다
metrics-server README가 제시하는 목표/가이드는 꽤 명확합니다:
- 15초마다 수집
- 노드당 CPU 1 millicore, 메모리 2MB 수준의 효율
- 최대 5,000 노드 클러스터까지 스케일 지원 목표
이 “가이드 숫자”를 그대로 단순 계산하면 (가정/근사치입니다):
- 1,000 노드 → CPU 약 1 core, Memory 약 2GB 규모
다만 이 값은 “항상 보장”이 아니라 README가 제공하는 운영 가이드/목표치에 가깝고, 실제 병목은 다음에서 갈립니다(여긴 환경 의존):
- kubelet 응답 지연/타임아웃
- 네트워크(SG/NACL, 노드-파드 라우팅)
- TLS 검증/인증서 체인 문제
- metrics-server 리소스 제한(Request/Limits)
4.4 “그럼 15초마다 노드 1000개를 다 호출?” → 보통 yes (kubelet을)
정확히 말하면:
- 너가 본 .../apis/metrics.k8s.io/.../nodes 같은 “큰 JSON 리스트”를 15초마다 받는 게 아니라
- metrics-server가 각 노드 kubelet로 주기적으로 요청을 날립니다.
개념적으로 부하를 감으로 보면:
- 1,000 노드 / 15초 ≈ 초당 ~66회 kubelet scrape (평균치)
- 순간 동시성/지연/재시도에 따라 체감은 달라질 수 있음
이건 “정확 수치”라기보다, 노드 수 × 수집 주기가 부하의 본질이라는 점을 감 잡기 위한 계산입니다.
5. “kube-proxy가 반드시 있어야 한다”는 말의 정확한 위치
너가 이전에 정리한 내용 중 가장 자주 오해되는 부분이 여기입니다.
공식 aggregation layer 설정 문서에는:
- “API 서버가 떠 있는 호스트에서 kube-proxy를 안 돌린다면, --enable-aggregator-routing=true 를 보장해야 한다”는 문장이 있습니다.
즉 결론은:
- 항상 kube-proxy가 ‘반드시’ 필요한 건 아니다
- 하지만 “apiserver가 Service로 프록시 트래픽을 라우팅하는 방식”에 따라
- kube-proxy가 필요할 수도 있고
- 또는 apiserver 플래그로 라우팅을 보장해야 할 수도 있습니다.
관리형(Kops/EKS/GKE 등)에서는 이 부분을 플랫폼이 이미 맞춰둔 경우가 많고, 너처럼 metrics.k8s.io 호출이 정상이라면 “현재는 충족되어 있다”는 관찰이 가능합니다.
6. 여기서 자연스럽게 이어지는 “CRD/CR” — 둘 다 ‘확장’이지만, 방식이 다르다
여기까지가 “확장 API 서버(aggregated apiserver)” 방식이었습니다.
쿠버네티스 API 확장에는 큰 축이 두 개가 있는데, Kubernetes 공식 문서가 딱 이렇게 정리합니다:
- CRD: 간단하고, “프로그래밍 없이” 추가 가능
- API Aggregation: 프로그래밍이 필요하지만, API 동작/스토리지 등 더 많은 제어 가능
6.1 CRD는 “새 Kind를 kube-apiserver가 인식”하게 만드는 것
CRD(CustomResourceDefinition)는:
- kube-apiserver가 새로운 리소스 타입을 “저장/서빙”할 수 있게 만드는 방식입니다.
- 즉, “새 종류의 오브젝트를 인식”한다는 네 표현이 정확합니다.
6.2 CRD와 CR의 차이
- CRD: 타입(스키마/Kind) 정의
- CR(Custom Resource): 그 타입의 인스턴스(실제 오브젝트)
비유하면:
- CRD = 테이블 스키마
- CR = 테이블의 한 row
6.3 “컨트롤러가 없는 CRD/CR” vs “컨트롤러가 필요한 CRD/CR”의 기준
공식 문서의 핵심 문장 하나로 정리됩니다:
- 커스텀 리소스만으로는 구조화 데이터를 저장/조회할 수 있고,
- 커스텀 컨트롤러와 결합해야 진짜 선언적 API가 된다.
따라서 “컨트롤러가 필요 없는 CRD/CR”의 대표 목적은:
- 클러스터에 “정의된 형태의 데이터”를 저장하고(스키마 검증, RBAC, kubectl UX)
- 사람이 보거나, CI/CD 같은 외부 소비자가 읽는 형태
반대로 “컨트롤러가 필요한 CRD/CR”은:
- CR을 만들면 실제로 Deployment/Service/Secret 등을 생성/조정하고
- status를 업데이트하며
- 원하는 상태를 계속 맞추는(reconcile) 자동화가 필요할 때입니다.
6.4 metrics-server는 왜 CRD가 아니라 aggregated apiserver인가?
이건 “공식 문장 1개로 딱 잘라 말하는” 것보다는, 위 사실들로부터 자연스럽게 추론할 수 있어요:
- metrics-server는 kubelet에서 주기적으로 수집 + 집계 + 캐시한 결과를 API로 내줍니다.
- 이런 성격은 “etcd에 CRUD로 저장되는 오브젝트”라기보다, “실시간/근실시간 계산 결과를 제공하는 API”에 가깝습니다.
- 그리고 Kubernetes 문서도 CRD와 API aggregation을 “서로 다른 방식”으로 분리해 설명합니다.
그래서 metrics-server는 API aggregation(확장 API 서버) 패턴의 대표 예시가 됩니다.
7. 한 장으로 다시 정리 (요청 흐름 + 수집 흐름)
아래 두 개의 루프가 겹쳐져서 “HPA가 스케일링한다”가 됩니다.
7.1 HPA 요청 루프(컨트롤 플레인)
- HPA 컨트롤러(= kube-controller-manager 내부)가 기본 15초마다
- scaleTargetRef → .spec.selector 로 Pod 집합을 만들고
- metrics.k8s.io 같은 aggregated API에서 메트릭을 가져와 스케일을 결정
7.2 metrics-server 수집 루프(데이터 파이프라인)
- metrics-server가 kubelet의 /metrics/resource 등에서 메트릭을 수집/집계하고
- 내부 캐시/메타데이터 뷰를 유지하며
- Metrics API로 응답을 제공
7.3 “라우팅”을 담당하는 중간 계층(aggregation layer)
- aggregation layer는 kube-apiserver 내부에서 동작하고
- APIService가 path를 claim하면 그 요청을 Service로 프록시합니다.
8. 네 클러스터에서 바로 확인하는 커맨드 (체크리스트)
8.1 metrics.k8s.io가 어디로 라우팅되는지 (APIService 확인)
kubectl get apiservice | grep metrics
kubectl describe apiservice v1beta1.metrics.k8s.io
kubectl get apiservice v1beta1.metrics.k8s.io -o yaml
여기서 spec.service.*, caBundle, status.conditions(Available=True/False)가 핵심입니다.
8.2 metrics-server가 kubelet 스크랩을 실패하는지(타임아웃/인증서)
kubectl -n kube-system logs deploy/metrics-server --tail=200 | egrep -i "timeout|error|failed|x509"
8.3 Metrics API를 “개별 단위로” 조회(큰 응답 피하기)
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes/<nodeName>" | jq .
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/<ns>/pods/<podName>" | jq .
예시 형태는 공식 문서에도 나옵니다.
'프로그래밍 > Kubernetes' 카테고리의 다른 글
| EKS + EBS로 이해하는 Kubernetes 스토리지 (0) | 2026.02.19 |
|---|---|
| Kubernetes Multi Scheduler: 기초부터 실전까지 - Part 1 (0) | 2025.10.30 |
| AmazonLinux2에 EC2에 쿠버네티스 설치하기. (0) | 2023.07.02 |
| 쿠버네티스 공부 커리큘럼. (0) | 2023.04.23 |
댓글