ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Kubernetes] HPA 알아보기 ( Horizontal Pod Autoscaler )
    Kubernetes 2024. 2. 16. 22:56
    728x90
    반응형

     

    - 목차

     

    들어가며.

    이번 글에서는 Kubernetes 의 HPA (Horizontal Pod Autoscaler) 에 대해서 알아보는 시간을 가지려고 합니다.

    ReplicaSet 또는 Deployment 로 여러 개의 Pod 를 손쉽게 운용할 수 있습니다.

    이는 HA 의 관점에서 안정적으로 Pod 를 일정 갯수만큼 유지시키는 Kubernetes 의 기능인데요.

    이들은 반드시 일정 갯수라는 제한이 있기 때문에 리소스의 부하 상태에 따라 유연히 Scale In/Out 이 불가능합니다.

    Kubernetes 에서는 이러한 관점에서 Autoscaler 를 제공합니다.

     

    HPA 세팅하기.

    HPA 는 CPU 또는 메모리의 상황에 따라서 유연하게 Pod 가 Scale In/Out 됩니다.

    실제로 테스트를 통해서 Kubernetes HPA 가 어떻게 동작하는지 알아보려고 합니다.

    그러기 위해서 각종 환경 세팅을 먼저 진행하겠습니다.

    Kubernetes 설치하기.

    아래 링크는 KinD 를 통해서 간단히 로컬 환경에 Kubernetes 를 설치할 수 있는 내용의 사이트입니다.

     

    https://westlife0615.tistory.com/407

     

    KinD 알아보기.

    - 목차 소개. KinD 는 "카인드" 라고 발음되며, 로컬 머신에서 쿠버네티스를 간편하게 실행할 수 있는 도구입니다. KinD 는 Kubernetes in Docker 의 약자이구요. Docker 를 활용하여 쿠버네티스 환경을 구성

    westlife0615.tistory.com

    https://westlife0615.tistory.com/472

     

    Kubernetes Nginx Ingress 알아보기

    - 목차 소개. Ingress 는 Kubernetes 환경을 외부 네트워크와 연결하는 창구역할을 합니다. Kubernetes 내부의 Pod 들은 서로의 IP 를 기반으로 통신을 수행하는데요. Kubernetes 외부에서 특정 Pod 와의 연결은

    westlife0615.tistory.com

     

    위 내용대로 KinD 를 통해 Kubernetes 를 설치하게 되면, 아래 이미지와 같이 Node 를 위한 Docker Container 들과

    각종 Pod 들이 생성됩니다.

     

    NodeJS 앱 생성하기.

    HPA 대상의 Pod 로써 NodeJS 웹서버를 사용하겠습니다.

     

    app.js

    아래 스크립트는 간단한 app.js 예시입니다.

    For Loop 를 1백만번을 순회하여 CPU 사용량을 강제로 높이도록 설계하였습니다.

    cat <<EOF> /tmp/app.js
    var http = require('http');
    
    http.createServer(function (req, res) {
      for (var i = 0; i < 1000000; i++) {
        if (i % 100000 == 0) console.log(i) 
      }
    
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end('Hello World!');
    }).listen(3000);
    EOF

     

     

    Dockerfile.

    cat <<EOF> /tmp/Dockerfile
    FROM node:14
    WORKDIR /usr/src/app
    COPY app.js ./
    RUN npm install
    EXPOSE 3000
    CMD ["node", "app.js"]
    EOF

     

    build Docker Image.

    docker build -t test-nodejs:1 -f /tmp/Dockerfile /tmp/

     

    KinD Load Docker Image.

    kind load docker-image test-nodejs:1 --name test-cluster

     

    test-nodejs.Yaml

    아래 yaml 파일은 test-nodejs 를 위한 Deployment 와 Service 에 대한 스펙을 정의한 YAML 파일입니다.

    cat <<EOF> /tmp/test-nodejs.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: test-nodejs
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: test-nodejs
      template:
        metadata:
          labels:
            app: test-nodejs
        spec:
          containers:
            - name: test-nodejs
              image: test-nodejs:1
              ports:
                - containerPort: 3000
              imagePullPolicy: Never
              resources:
                limits:
                  cpu: 100m
                  memory: 100Mi          
                requests:
                  cpu: 100m
                  memory: 100Mi
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: test-nodejs
    spec:
      selector:
        app: test-nodejs
      ports:
        - protocol: TCP
          port: 3000
          targetPort: 3000
    EOF

     

    kubectl apply -f /tmp/test-nodejs.yaml

     

     

    Auto Scaling 테스트하기.

    위 과정을 마치게되면 test-nodejs 라는 이름의 Pod 가 생성됩니다.

    test-nodejs Deployment 를 만들 때에 replicas 를 1로 설정하였기 때문에 아래와 같이 1개의 Pod 가 생성됩니다.

    kubectl get pod
    NAME                           READY   STATUS    RESTARTS   AGE
    test-nodejs-7cdf98d548-m22mk   1/1     Running   0          2m48s

     

    이제 본격적으로 Pod 의 CPU 사용량을 늘려보도록 하겠습니다.

    먼저 Http Request 를 전송하기 위해 test-nodejs Service 를 port-forward 하겠습니다.

    kubectl -n default port-forward service/test-nodejs 3000:3000
    Forwarding from 127.0.0.1:3000 -> 3000
    Forwarding from [::1]:3000 -> 3000

     

    그리고 부하를 높이기 위해서 HTTP 요청 트래픽을 높입니다.

    for i in {0..100000}
    do 
    	curl http://localhost:3000
    done

     

    저는 Pod 의 top 명령어를 통해서 Pod 의 CPU 사용량을 확인했구요.

    아래 결과처럼 70% 이상까지 CPU 가 사용됨을 확인하였습니다.

    kubectl exec --stdin --tty test-nodejs-7cdf98d548-m22mk -- top
    top - 01:13:05 up 39 min,  0 users,  load average: 4.59, 1.72, 1.08
    Tasks:   2 total,   1 running,   1 sleeping,   0 stopped,   0 zombie
    %Cpu(s): 11.0 us,  6.1 sy,  0.0 ni, 81.1 id,  0.0 wa,  0.0 hi,  1.8 si,  0.0 st
    MiB Mem :  32052.2 total,  26445.3 free,   2639.4 used,   2967.4 buff/cache
    MiB Swap:   4096.0 total,   4096.0 free,      0.0 used.  28775.2 avail Mem
    
        PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
          1 root      20   0  613928  73632  27776 S  72.7   0.2   1:05.95 node
         16 root      20   0    5960   2688   2304 R   0.0   0.0   0:00.02 top

     

    Load Balancing 확인하기.

    CPU 사용량은 70%까지 상승합니다.

    이 경우에 Replicas 의 갯수를 늘려주어 부하를 분산하고 CPU 사용량을 줄일 수 있습니다.

     

    test-nodejs Deployment 의 replicas 를 4개로 늘려주어 Pod 를 4개로 변경하였습니다.

    그 이후에 부하 분산으로 인해서 CPU 사용량을 15% 까지 감소합니다.

     

     

    HPA 적용하기.

    먼저 Pod 의 CPU 사용량을 특정하기 위한 Metrics Server 를 활성화시켜야합니다.

    cat <<EOF> /tmp/metrics-server.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      labels:
        k8s-app: metrics-server
      name: metrics-server
      namespace: kube-system
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      labels:
        k8s-app: metrics-server
        rbac.authorization.k8s.io/aggregate-to-admin: "true"
        rbac.authorization.k8s.io/aggregate-to-edit: "true"
        rbac.authorization.k8s.io/aggregate-to-view: "true"
      name: system:aggregated-metrics-reader
    rules:
    - apiGroups:
      - metrics.k8s.io
      resources:
      - pods
      - nodes
      verbs:
      - get
      - list
      - watch
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      labels:
        k8s-app: metrics-server
      name: system:metrics-server
    rules:
    - apiGroups:
      - ""
      resources:
      - nodes/metrics
      verbs:
      - get
    - apiGroups:
      - ""
      resources:
      - pods
      - nodes
      verbs:
      - get
      - list
      - watch
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      labels:
        k8s-app: metrics-server
      name: metrics-server-auth-reader
      namespace: kube-system
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: extension-apiserver-authentication-reader
    subjects:
    - kind: ServiceAccount
      name: metrics-server
      namespace: kube-system
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      labels:
        k8s-app: metrics-server
      name: metrics-server:system:auth-delegator
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: system:auth-delegator
    subjects:
    - kind: ServiceAccount
      name: metrics-server
      namespace: kube-system
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      labels:
        k8s-app: metrics-server
      name: system:metrics-server
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: system:metrics-server
    subjects:
    - kind: ServiceAccount
      name: metrics-server
      namespace: kube-system
    ---
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        k8s-app: metrics-server
      name: metrics-server
      namespace: kube-system
    spec:
      ports:
      - name: https
        port: 443
        protocol: TCP
        targetPort: https
      selector:
        k8s-app: metrics-server
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        k8s-app: metrics-server
      name: metrics-server
      namespace: kube-system
    spec:
      selector:
        matchLabels:
          k8s-app: metrics-server
      strategy:
        rollingUpdate:
          maxUnavailable: 0
      template:
        metadata:
          labels:
            k8s-app: metrics-server
        spec:
          containers:
          - args:
            - --cert-dir=/tmp
            - --secure-port=10250
            - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
            - --kubelet-use-node-status-port
            - --metric-resolution=15s
            - --kubelet-insecure-tls   
            image: registry.k8s.io/metrics-server/metrics-server:v0.7.0
            imagePullPolicy: IfNotPresent
            livenessProbe:
              failureThreshold: 3
              httpGet:
                path: /livez
                port: https
                scheme: HTTPS
              periodSeconds: 10
            name: metrics-server
            ports:
            - containerPort: 10250
              name: https
              protocol: TCP
            readinessProbe:
              failureThreshold: 3
              httpGet:
                path: /readyz
                port: https
                scheme: HTTPS
              initialDelaySeconds: 20
              periodSeconds: 10
            resources:
              requests:
                cpu: 100m
                memory: 200Mi
            securityContext:
              allowPrivilegeEscalation: false
              capabilities:
                drop:
                - ALL
              readOnlyRootFilesystem: true
              runAsNonRoot: true
              runAsUser: 1000
              seccompProfile:
                type: RuntimeDefault
            volumeMounts:
            - mountPath: /tmp
              name: tmp-dir
          nodeSelector:
            kubernetes.io/os: linux
          priorityClassName: system-cluster-critical
          serviceAccountName: metrics-server
          volumes:
          - emptyDir: {}
            name: tmp-dir
    ---
    apiVersion: apiregistration.k8s.io/v1
    kind: APIService
    metadata:
      labels:
        k8s-app: metrics-server
      name: v1beta1.metrics.k8s.io
    spec:
      group: metrics.k8s.io
      groupPriorityMinimum: 100
      insecureSkipTLSVerify: true
      service:
        name: metrics-server
        namespace: kube-system
      version: v1beta1
      versionPriority: 100
    EOF
    
    kubectl apply -f /tmp/metrics-server.yaml

     

    이제 HPA 를 적용합니다.

    Pod 하나가 CPU 사용량이 50% 를 초과하게 되면 최소 1개부터 10까지 Pod 가 증가합니다.

    cat <<EOF> /tmp/hpa.yaml
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: test-nodejs
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: test-nodejs
      minReplicas: 1
      maxReplicas: 10
      metrics:
      - type: Resource
        resource:
          name: cpu
          target:
            type: Utilization
            averageUtilization: 50
    EOF
    kubectl create -f /tmp/hpa.yaml

     

    생성된 HPA 는 아래와 같이 표현되며,

    Target 으로 설정된 Deployment 또는 ReplicaSet 의 CPU 사용량에 따라 Pod 가 증가합니다.

    그리고 CPU 사용량이 줄어들고 시간이 소요되면 다시 원래의 Min Pod 사이즈만큼 복구됩니다.

     

     

    반응형
Designed by Tistory.