HPA

HPA는 Deployment, StatefulSet과 같은 workload resource를 자동으로 업데이트하며 workload의 크기를 자동으로 수요에 맞게 하는 것을 목표로 한다.

a HorizontalPodAutoscaler automatically updates a workload resource (such as a Deployment or StatefulSet), with the aim of automatically scaling the workload to match demand.

HPA를 사용하기 위해서는 Metric Server를 별도로 배포하여야 한다. Metric Server Install, Metric Server Configuration


1. 동작방법

metric-server-architecture hpa001
  1. cAdvisor가 Pod내 Container들의 Metrics을 수집함.

  2. kubelet이 cAdvisor가 노출한 메트릭을 수집함.

  3. metric-server가 kubelet으로 부터 metric 리소스를 수집함. 이 주기는 –metric-resolution=15s 옵션에 따라 수집 됨

    $ kubectl logs -f metrics-server-58fd485c7-nnltl -n kube-system
    # Kubelet으로부터 메트릭 정보를 수집하고 있다. 
    I1222 15:01:24.847741       1 scraper.go:115] "Scraping metrics from nodes" nodeCount=2
    I1222 15:01:24.855038       1 scraper.go:137] "Scraping node" node="ip-10-20-10-235.ap-northeast-2.compute.internal"
    I1222 15:01:24.857187       1 scraper.go:137] "Scraping node" node="ip-10-20-11-91.ap-northeast-2.compute.internal"
    I1222 15:01:24.877046       1 scraper.go:172] "Scrape finished" duration="29.273444ms" nodeCount=2 podCount=17
    
  4. API 서버는 Metrics API를 제공하므로써 사용자, HPA등에게 API를 제공할 수 있다.
    따라서, 아래 kubectl 커맨드를 통해 메트릭을 조회하면 실제로는 메트릭 서버에서 아래와 같이 로그가 보인다. (configured by - –v=5)

    $ kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes/ip-10-20-10-235.ap-northeast-2.compute.internal" | jq '.'
    
    $ kubectl logs -f metrics-server-58fd485c7-nnltl -n kube-system
    # srcIP=x-ENI의 주소 중 하나다. 내 로컬 -> API server -> metrics-server로 왔기 때문에. 
    I1222 15:01:10.534483       1 handler.go:143] metrics-server: GET "/apis/metrics.k8s.io/v1beta1/nodes/ip-10-20-10-235.ap-northeast-2.compute.internal" satisfied by gorestful with webservice /apis/metrics.k8s.io/v1beta1
    I1222 15:01:10.534721       1 httplog.go:129] "HTTP" verb="GET" URI="/apis/metrics.k8s.io/v1beta1/nodes/ip-10-20-10-235.ap-northeast-2.compute.internal" latency="432.784µs" userAgent="kubectl/v1.26.0 (linux/amd64) kubernetes/b46a3f8" audit-ID="ae15fa19-9c33-4ebf-8f7d-15931ba355e0" srcIP="10.20.10.23:40266" resp=200
    

    아래는 HPA를 배포한 리소스에 대한 Metrics Server로그이다.

    # 아래도 주기적으로 관찰되는 로그이며 이는 php-apche hpa에의해 주기적으로 15초마다 조회되는 결과다.  
    I1222 15:01:37.041375       1 handler.go:143] metrics-server: GET "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods" satisfied by gorestful with webservice /apis/metrics.k8s.io/v1beta1
    I1222 15:01:37.041560       1 httplog.go:129] "HTTP" verb="LIST" URI="/apis/metrics.k8s.io/v1beta1/namespaces/default/pods?labelSelector=run%3Dphp-apache" latency="4.32772ms" userAgent="kube-controller-manager/v1.21.14 (linux/amd64) kubernetes/b07006b/system:serviceaccount:kube-system:horizontal-pod-autoscaler" audit-ID="de1229ce-5f8f-4636-8698-0ff3003aca05" srcIP="10.20.11.208:32858" resp=200
    
    I1222 15:01:52.104167       1 handler.go:143] metrics-server: GET "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods" satisfied by gorestful with webservice /apis/metrics.k8s.io/v1beta1
    I1222 15:01:52.104352       1 httplog.go:129] "HTTP" verb="LIST" URI="/apis/metrics.k8s.io/v1beta1/namespaces/default/pods?labelSelector=run%3Dphp-apache" latency="3.059605ms" userAgent="kube-controller-manager/v1.21.14 (linux/amd64) kubernetes/b07006b/system:serviceaccount:kube-system:horizontal-pod-autoscaler" audit-ID="b57ab53f-0194-41e2-99b1-50c7dc429e03" srcIP="10.20.11.208:32858" resp=200
    
    I1222 15:02:07.127963       1 handler.go:143] metrics-server: GET "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods" satisfied by gorestful with webservice /apis/metrics.k8s.io/v1beta1
    I1222 15:02:07.128194       1 httplog.go:129] "HTTP" verb="LIST" URI="/apis/metrics.k8s.io/v1beta1/namespaces/default/pods?labelSelector=run%3Dphp-apache" latency="4.949269ms" userAgent="kube-controller-manager/v1.21.14 (linux/amd64) kubernetes/b07006b/system:serviceaccount:kube-system:horizontal-pod-autoscaler" audit-ID="5f545f72-bb1b-4cce-a7e6-a5681235f1bc" srcIP="10.20.11.208:32858" resp=200
    
    I1222 15:02:22.205786       1 handler.go:143] metrics-server: GET "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods" satisfied by gorestful with webservice /apis/metrics.k8s.io/v1beta1
    I1222 15:02:22.206026       1 httplog.go:129] "HTTP" verb="LIST" URI="/apis/metrics.k8s.io/v1beta1/namespaces/default/pods?labelSelector=run%3Dphp-apache" latency="3.820725ms" userAgent="kube-controller-manager/v1.21.14 (linux/amd64) kubernetes/b07006b/system:serviceaccount:kube-system:horizontal-pod-autoscaler" audit-ID="b591499b-e8f3-4313-9f7a-ab2395eaac70" srcIP="10.20.11.208:32858" resp=200
    
  5. kube-controller(HPA controller)가 metrics API의 데이터를 보고 RS replica를 조정함 (15초)

2. HPA 테스트

  1. 아래와 같이 php-apache에 대한 Deployment, Service, HPA 리소스를 배포한다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: php-apache1
    spec:
      selector:
        matchLabels:
          run: php-apache1
      replicas: 2
      template:
        metadata:
          labels:
            run: php-apache1
        spec:
          containers:
          - name: php-apache1
            image: registry.k8s.io/hpa-example
            ports:
            - containerPort: 80
            resources:
              limits:
                cpu: 500m
              requests:
                cpu: 200m
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: php-apache1
      labels:
        run: php-apache1
    spec:
      ports:
      - port: 80
      selector:
        run: php-apache1
    ---
    apiVersion: autoscaling/v1
    kind: HorizontalPodAutoscaler
    metadata:
      name: php-apache1
      namespace: default
    spec:
      maxReplicas: 10
      minReplicas: 2
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: php-apache1
      targetCPUUtilizationPercentage: 50
    
  2. 아래와 같이 curl을 요청할 수 있는 Deployment를 배포한다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: netutil
      labels:
        app: netutil
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: netutil
      template:
        metadata:
          labels:
            app: netutil
        spec:
          containers:
          - name: netutil
            image: praqma/network-multitool
    

HPA 로직

HPA는 desired수를 아래와 같이 계산한다.
desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]

만약 HPA에 CPU기반 scale 설정이 되었으나 파드 Manifest에 request.cpu가 정의되지 않은 경우에는 HPA가 동작하지 않는다.

부하 & 결과 확인

Test1

부하 발생

$ kubectl exec -it netutil-669f67cb94-2hdns -- /bin/bash
$ while true; do curl http://php-apache1; sleep 0.3; done

결과 확인

$ while true; do date; k get hpa php-apache1; sleep 10; done
#################### Result ####################
Thu Jan  5 09:57:28 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   0%/50%    2         10        2          39h
Thu Jan  5 09:57:38 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   26%/50%   2         10        2          39h
Thu Jan  5 09:57:48 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   26%/50%   2         10        2          39h
Thu Jan  5 09:57:58 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   52%/50%   2         10        2          39h
Thu Jan  5 09:58:08 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   51%/50%   2         10        2          39h
Thu Jan  5 09:58:18 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   51%/50%   2         10        2          39h
Thu Jan  5 09:58:29 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   52%/50%   2         10        2          39h
Thu Jan  5 09:58:39 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   52%/50%   2         10        2          39h
Thu Jan  5 09:58:49 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   52%/50%   2         10        2          39h
Thu Jan  5 09:58:59 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   50%/50%   2         10        2          39h
Thu Jan  5 09:59:09 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   51%/50%   2         10        2          39h
Thu Jan  5 09:59:19 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   51%/50%   2         10        2          39h
Thu Jan  5 09:59:29 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   52%/50%   2         10        2          39h
Thu Jan  5 09:59:40 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   56%/50%   2         10        2          39h
Thu Jan  5 09:59:50 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   56%/50%   2         10        2          39h
Thu Jan  5 10:00:00 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   49%/50%   2         10        3          39h
Thu Jan  5 10:00:10 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   33%/50%   2         10        3          39h
Thu Jan  5 10:00:20 KST 2023
$ while true; do date; k get po -l run=php-apache1; sleep 5; done
#################### Result ####################
Thu Jan  5 09:59:33 KST 2023
NAME                          READY   STATUS    RESTARTS   AGE
php-apache1-8445df799-bdlln   1/1     Running   0          39h
php-apache1-8445df799-jnmtx   1/1     Running   0          39h
Thu Jan  5 09:59:36 KST 2023
NAME                          READY   STATUS              RESTARTS   AGE
php-apache1-8445df799-67jjv   0/1     ContainerCreating   0          0s
php-apache1-8445df799-bdlln   1/1     Running             0          39h
php-apache1-8445df799-jnmtx   1/1     Running             0          39h
Thu Jan  5 09:59:39 KST 2023
NAME                          READY   STATUS    RESTARTS   AGE
php-apache1-8445df799-67jjv   1/1     Running   0          3s
php-apache1-8445df799-bdlln   1/1     Running   0          39h
php-apache1-8445df799-jnmtx   1/1     Running   0          39h

Test2

부하 발생

$ kubectl exec -it netutil-669f67cb94-2hdns -- /bin/bash
$ while true; do curl http://php-apache1; sleep 0.1; done

결과 확인

$ while true; do date; k get hpa php-apache1; sleep 10; done
#################### Result ####################
Thu Jan  5 10:28:49 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   0%/50%    2         10        2          40h
Thu Jan  5 10:28:59 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   0%/50%    2         10        2          40h
Thu Jan  5 10:29:09 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   16%/50%   2         10        2          40h
Thu Jan  5 10:29:19 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   16%/50%   2         10        2          40h
Thu Jan  5 10:29:30 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   91%/50%   2         10        2          40h
Thu Jan  5 10:29:40 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   93%/50%   2         10        4          40h
Thu Jan  5 10:29:50 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   93%/50%   2         10        4          40h
Thu Jan  5 10:30:00 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   45%/50%   2         10        4          40h
Thu Jan  5 10:30:10 KST 2023
NAME          REFERENCE                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache1   Deployment/php-apache1   48%/50%   2         10        4          40h
$ while true; do date; k get po -l run=php-apache1; sleep 5; done
#################### Result ####################
NAME                          READY   STATUS    RESTARTS   AGE
php-apache1-8445df799-67jjv   1/1     Running   0          29m
php-apache1-8445df799-bdlln   1/1     Running   0          39h
Thu Jan  5 10:29:23 KST 2023
NAME                          READY   STATUS              RESTARTS   AGE
php-apache1-8445df799-67jjv   1/1     Running             0          29m
php-apache1-8445df799-bdlln   1/1     Running             0          39h
php-apache1-8445df799-ffff6   0/1     ContainerCreating   0          1s
php-apache1-8445df799-r5z8p   0/1     ContainerCreating   0          1s
Thu Jan  5 10:29:26 KST 2023
NAME                          READY   STATUS    RESTARTS   AGE
php-apache1-8445df799-67jjv   1/1     Running   0          29m
php-apache1-8445df799-bdlln   1/1     Running   0          39h
php-apache1-8445df799-ffff6   1/1     Running   0          4s
php-apache1-8445df799-r5z8p   1/1     Running   0          4s

결과를 확인해보면 91~93% 구간이 확인되자 바로 Pod는 4개로 증가하였다.
ceil(2 * (93/50)) = ceil(2* 1.86) = ceil(3.72) = 4

파드가 4개로 증가하고 48%를 유지한다. 이 때는 아래와 같이 desiredReplica수가 currentReplica 수와 같기 때문에 유지한다.
ceil(4 * (48/50)) = ceil(4* 0.96) = ceil(3.84) = 4


📚 References

[1] kubernetes-sigs/metrics-server

[2] High Availability

[3] metric-server 가용성

[4] Resource metrics pipeline