kubernetes에 HorizontalPodAutoscaler가 동작하기 위해서는 기본적으로 metrics-server가 설치되어야 동작한다.
내부 동작 방식을 깊이 이해하기 위해서 구성 및 deep dive로 정리한다.

helm을 이용해서 배포를 진행할 예정이나 template를 구성해서 배포하는 방식으로 진행할 예정이다.

 

Helm Repo 등록

아래와 같이 metrics-server의 repo를 동록한다.

helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/

https://github.com/kubernetes-sigs/metrics-server
https://artifacthub.io/packages/helm/metrics-server/metrics-server

 

metrics-server용 helm value 파일 생성

metrics-server-values.yaml

args:
  - --kubelet-insecure-tls
  - --v=4 # deep dive 확인을 위한 로그 출력 / 운영용으로 사용시 제거

 

--kubelet-insecure-tls 옵션이 빠진 상태에서 배포되면 아래와 같은 오류가 발생된다.

I1111 06:22:10.927848       1 tlsconfig.go:240] "Starting DynamicServingCertificateController"
E1111 06:22:10.928284       1 scraper.go:149] "Failed to scrape node" err="Get \"https://192.168.122.75:10250/metrics/resource\": tls: failed to verify certificate: x509: cannot validate certificate for 192.168.122.75 because it doesn't contain any IP SANs" node="node4"
E1111 06:22:10.951754       1 scraper.go:149] "Failed to scrape node" err="Get \"https://192.168.122.73:10250/metrics/resource\": tls: failed to verify certificate: x509: cannot validate certificate for 192.168.122.73 because it doesn't contain any IP SANs" node="node2"
E1111 06:22:10.952118       1 scraper.go:149] "Failed to scrape node" err="Get \"https://192.168.122.72:10250/metrics/resource\": tls: failed to verify certificate: x509: cannot validate certificate for 192.168.122.72 because it doesn't contain any IP SANs" node="node1"
E1111 06:22:10.953099       1 scraper.go:149] "Failed to scrape node" err="Get \"https://192.168.122.74:10250/metrics/resource\": tls: failed to verify certificate: x509: cannot validate certificate for 192.168.122.74 because it doesn't contain any IP SANs" node="node3"

 

metrics-server 배포용 template 파일 생성 및 배포

아래와 같이 배포용 template yaml 파일을 생성한다.

# helm template metrics-server metrics-server/metrics-server -f metrics-server-values.yaml --dependency-update --include-crds > metrics-server-deploy.yaml

or

# helm template metrics-server --dependency-update --include-crds \
  --repo https://kubernetes-sigs.github.io/metrics-server/ \
  --set fullnameOverride="metrics-server" \
  --set-json 'args=["--kubelet-insecure-tls","--v=4"]' > metrics-server-deploy.yaml

배포용 yaml을 생성 시, 가능하면 --dependency-update, --include-crds 2가지 옵션을 추가해 주면 좋다 

생성된 metrics-server-deploy.yaml을 kubectl로 배포한다.

# kubectl apply -f metrics-server-deploy.yaml --server-side

주의 사항으로 kubectl로 배포 시 --server-side 옵션을 붙여서 배포한다. (이유는 구글링을..)

 

metrics-server 배포 파일 분석

metrics-server를 분석해보면 아래와 같이 4가지 부분으로 구성되어 있다.

  1. metrics-server가 동작(메트릭 수집 등등)을 할 있도록 ServiceAccount 및 관련 구성 (ServiceAccount -> ClusterRole / ClusterRoleBinding / RoleBinding)
  2. metrics-server를 호출하는 Service
  3. metrics-server pod가 배포되는 Deployment
  4. APIService를 등록하고 이를 Service와 연결

 

metrics-server의 API 등록 현황

아래와 같이 apiservices 등록 현황을 확인할 수 있다.

# kubectl get apiservices
NAME                                   SERVICE                  AVAILABLE   AGE
.....
v1beta1.metrics.k8s.io                 default/metrics-server   True        82s
...

배포용 파일 설정 내용

# Source: metrics-server/templates/apiservice.yaml
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1beta1.metrics.k8s.io
  labels:
...
spec:
  group: metrics.k8s.io
  groupPriorityMinimum: 100
  insecureSkipTLSVerify: true
  service:
    name: metrics-server
    namespace: default
    port: 443
  version: v1beta1
  versionPriority: 100

호출 해보면 아래와 같이 확인이 가능하다.

# kubectl get --raw / | grep metrics | grep v1beta1
    "/apis/metrics.k8s.io/v1beta1",

 

HorizontalPodAutoscaler 테스트를 위한 테스트 배포

아래 deployment yaml과 hpa yaml 파일을 배포 진행한다.

nginx-deploy-dpm.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy-dpm
  labels:
    app: nginx-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-deploy
  template:
    metadata:
      labels:
        app: nginx-deploy
    spec:
      containers:
      - name: nginx-deploy-pod
        image: nginx:1.14.2
        resources:
          requests:
            cpu: 100m
            memory: 200Mi
        ports:
        - name: http
          containerPort: 80
# kubectl apply -f nginx-deploy-dpm.yaml

nginx-deploy-hpa.yaml

---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-deploy-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-deploy-dpm
  minReplicas: 2
  maxReplicas: 100
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 30
# kubectl apply -f nginx-deploy-hpa.yaml

 

HorizontalPodAutoscaler 현황 파악

# kubectl get hpa
NAME               REFERENCE                     TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
nginx-deploy-hpa   Deployment/nginx-deploy-dpm   0%/30%    2         100       2          35s

위와 같이 TARGETS가 unknown이 아니라면 정상 동작하는 것이다.

 

metrics-server로 수집된 데이터 확인

metrics-server로 수집된 데이터는 "/apis/metrics.k8s.io/v1beta1" API를 통해서 확인이 가능하다.

# kubectl get --raw /apis/metrics.k8s.io/v1beta1 | jq
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "metrics.k8s.io/v1beta1",
  "resources": [
    {
      "name": "nodes",
      "singularName": "",
      "namespaced": false,
      "kind": "NodeMetrics",
      "verbs": [
        "get",
        "list"
      ]
    },
    {
      "name": "pods",
      "singularName": "",
      "namespaced": true,
      "kind": "PodMetrics",
      "verbs": [
        "get",
        "list"
      ]
    }
  ]
}

위와 같이 값이 호출될다고 할때, 현재 2가지로 수집되고 있다.

  • nodes 메트릭
  • pods 메트릭

nodes 값에서는 namespaced가 "false", pods 값에서는 namespaced가 "true"로 확인된다.
이때 호출하는 URL는 아래와 같이 만들어진다.

nodes (disabled namespaced)  : /apis/metrics.k8s.io/v1beta1/nodes/[node name]

pods (enabled namespaed) : /apis/metrics.k8s.io/v1beta1/namespaces/[namespace]/pods/[pod name]

 

POD 메트릭 정보를 아래와 같이 확인이 가능하다.

### POD 정보 ###
# kubectl get pod
NAME                                READY   STATUS    RESTARTS   AGE
metrics-server-55cc5bcdb8-qfkhv     1/1     Running   0          85m
nginx-deploy-dpm-7c7cb598b5-hpdnz   1/1     Running   0          8m15s
nginx-deploy-dpm-7c7cb598b5-ltdmf   1/1     Running   0          8m36s

### 메트릭 정보 확인 ###
# kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/default/pods/nginx-deploy-dpm-7c7cb598b5-hpdnz | jq
{
  "kind": "PodMetrics",
  "apiVersion": "metrics.k8s.io/v1beta1",
  "metadata": {
    "name": "nginx-deploy-dpm-7c7cb598b5-hpdnz",
    "namespace": "default",
    "creationTimestamp": "2024-11-11T07:59:23Z",
    "labels": {
      "app": "nginx-deploy",
      "pod-template-hash": "7c7cb598b5"
    }
  },
  "timestamp": "2024-11-11T07:58:53Z",
  "window": "15.282s",
  "containers": [
    {
      "name": "nginx-deploy-pod",
      "usage": {
        "cpu": "0",
        "memory": "1948Ki"
      }
    }
  ]
}

참고로 /apis/metrics.k8s.io/v1beta1/ API는 데이터를 찾는 방식이 낮게 리스팅을 이쁘게 해준다.
prometheus-adapter로 custom metrics를 구성할 경우, 위의 방식으로 메트릭 API URL을 찾아야 한다.

 

HorizontalPodAutoscaler 동작 Workflow

HorizontalPodAutoscaler는 kube-controller-manager 내부에 존재하는 function으로 metrics-server에서 수집한 데이터를 기반으로 동작한다.
추가로 custom metrics와 external metrics도 추가로 지원한다.

 

kube-controller-manager가 등록된 hpa(horizontalpodautoscalers)의 동작을 하기 위한 메트릭 정보를 /apis/metrics.k8s.io/v1beta1/ 요청하여 데이터를 가져온다.

 

위에서 설명했다 싶이, apiservices에는 "/apis/metrics.k8s.io/v1beta1/" 호출 주소는 metrics-server services를 호출하게 설정되어 있고, 결론적으로 호출한 주소의 데이터는 metrics-server가 제공한다.

ube-controller-manager는 수집된 데이터를 기반으로 hpa를 동작시킨다.

 

metrics-server의 로그를 활성화한 경우 아래와 같이 metric 데이터를 가져가는 것을 확인할 수 있다

# kubectl get pod
NAME                                READY   STATUS    RESTARTS   AGE
metrics-server-55cc5bcdb8-qfkhv     1/1     Running   0          117m
nginx-deploy-dpm-7c7cb598b5-hpdnz   1/1     Running   0          40m
nginx-deploy-dpm-7c7cb598b5-ltdmf   1/1     Running   0          41m

# kubectl logs -f metrics-server-55cc5bcdb8-qfkhv | grep kube-controller-manager
I1111 07:48:56.276413       1 httplog.go:132] "HTTP" verb="LIST" URI="/apis/metrics.k8s.io/v1beta1/namespaces/default/pods?labelSelector=app%3Dnginx-deploy" latency="2.904626ms" userAgent="kube-controller-manager/v1.29.10 (linux/amd64) kubernetes/f0c1ea8/system:serviceaccount:kube-system:horizontal-pod-autoscaler" audit-ID="ece51ae9-0efc-4c12-86a6-a4ae43bd6402" srcIP="10.233.120.0:21388" resp=200
I1111 07:49:11.311232       1 httplog.go:132] "HTTP" verb="LIST" URI="/apis/metrics.k8s.io/v1beta1/namespaces/default/pods?labelSelector=app%3Dnginx-deploy" latency="1.838739ms" userAgent="kube-controller-manager/v1.29.10 (linux/amd64) kubernetes/f0c1ea8/system:serviceaccount:kube-system:horizontal-pod-autoscaler" audit-ID="dd4cee06-d8a1-4631-91ce-53c9e2487deb" srcIP="10.233.120.0:21388" resp=200