ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [AWS] EKS - AutoScaling(스터디 5주차)
    Infra/Cloud 2023. 5. 23. 19:01

    이번주차는 Auto Scaling이 주제이다. 
     
    Cloud나 Kubernetes 사용에 있어 굉장히 편리한 기능이다.
     
    나에게 필요한 리소스를 알아서 Scale In/Out, Up/Down 해준다는것이 정말 매력적이였다.
     
    실제 회사에서 클라우드로 서비스를 제공하고 있고 상당한 트래픽을 발생하고 있다면 Auto Scaling은 필수적으로
     
    고려해봐야할 기능이며 비용적인 측면으로도 이어지기 때문에 정말 정말 중요한 기능이라고 생각한다.
     


     

    실습준비

    한눈에 파악하기 위해 이전에 포스팅했던 여러가지 도구들을 배포해주었다.
    ExternalDNS + Grafana + Prometheus + EKS Node Viewer + kube-ops-view + AWS LB Controller + Metrics Server

    # ExternalDNS
    MyDomain=im-youngho.com
    echo "export MyDomain=im-youngho.com" >> /etc/profile
    MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
    echo $MyDomain, $MyDnzHostedZoneId
    curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
    MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -
    
    
    # kube-ops-view
    helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
    helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
    kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
    kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
    echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"
    
    
    # AWS LB Controller
    helm repo add eks https://aws.github.io/eks-charts
    helm repo update
    helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
      --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
    
    
    # 사용 리전의 인증서 ARN 확인
    CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
    echo $CERT_ARN
    
    # repo 추가
    helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
    
    # 파라미터 파일 생성
    cat <<EOT > monitor-values.yaml
    prometheus:
      prometheusSpec:
        podMonitorSelectorNilUsesHelmValues: false
        serviceMonitorSelectorNilUsesHelmValues: false
        retention: 5d
        retentionSize: "10GiB"
    
      verticalPodAutoscaler:
        enabled: true
    
      ingress:
        enabled: true
        ingressClassName: alb
        hosts: 
          - prometheus.$MyDomain
        paths: 
          - /*
        annotations:
          alb.ingress.kubernetes.io/scheme: internet-facing
          alb.ingress.kubernetes.io/target-type: ip
          alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
          alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
          alb.ingress.kubernetes.io/success-codes: 200-399
          alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
          alb.ingress.kubernetes.io/group.name: study
          alb.ingress.kubernetes.io/ssl-redirect: '443'
    
    grafana:
      defaultDashboardsTimezone: Asia/Seoul
      adminPassword: prom-operator
    
      ingress:
        enabled: true
        ingressClassName: alb
        hosts: 
          - grafana.$MyDomain
        paths: 
          - /*
        annotations:
          alb.ingress.kubernetes.io/scheme: internet-facing
          alb.ingress.kubernetes.io/target-type: ip
          alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
          alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
          alb.ingress.kubernetes.io/success-codes: 200-399
          alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
          alb.ingress.kubernetes.io/group.name: study
          alb.ingress.kubernetes.io/ssl-redirect: '443'
    
    defaultRules:
      create: false
    kubeControllerManager:
      enabled: false
    kubeEtcd:
      enabled: false
    kubeScheduler:
      enabled: false
    alertmanager:
      enabled: false
    EOT
    
    # 배포
    kubectl create ns monitoring
    helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 45.27.2 \
    --set prometheus.prometheusSpec.scrapeInterval='15s' --set prometheus.prometheusSpec.evaluationInterval='15s' \
    -f monitor-values.yaml --namespace monitoring
    
    # Metrics-server 배포
    kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
    
    
    # go 설치
    yum install -y go
    
    # EKS Node Viewer 설치 : 현재 ec2 spec에서는 설치에 다소 시간이 소요됨 = 2분 이상
    go install github.com/awslabs/eks-node-viewer/cmd/eks-node-viewer@latest
    
    # bin 확인 및 사용 
    tree ~/go/bin
    cd ~/go/bin
    ./eks-node-viewer

    이것저것 설치해서 결과적으로 위와같은 형태로 펼쳐놓고 실습을 진행했다. 아무래도 이번 주차 주제가 Auto Scaling이다보니 자동으로 늘어나고 줄어드는 것을 한눈에 확인하기 위함이다.
     
     


     

    Kubernetes의 AutoScaling 종류(HPA, VPA, CA)

     

    HPA(Horizontal Pod Autoscaler) - Scale In / Out

    cAdvisor가 Container 메모리/CPU Metrics를 수집 → Metrics-server는 kubelet을 통해 수집 후 Apiserver에 등록 → HPA는 Apiserver(Resource API)를 통해서 15분마다 메모리/CPU 수집하여 정책에 따라 동작
     
     

    * HPA 예제(CPU를 과부하시키는 php 파일이 구동중인 image)

    Horizontal Pod Autoscaler - Amazon EKS

    It may take a few minutes before you see the replica count reach its maximum. If only 6 replicas, for example, are necessary for the CPU load to remain at or under 50%, then the load won't scale beyond 6 replicas.

    docs.aws.amazon.com

    HorizontalPodAutoscaler Walkthrough

    A HorizontalPodAutoscaler (HPA for short) automatically updates a workload resource (such as a Deployment or StatefulSet), with the aim of automatically scaling the workload to match demand. Horizontal scaling means that the response to increased load is t

    kubernetes.io

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: php-apache
    spec:
      selector:
        matchLabels:
          run: php-apache
      template:
        metadata:
          labels:
            run: php-apache
        spec:
          containers:
          - name: php-apache
            image: registry.k8s.io/hpa-example
            ports:
            - containerPort: 80
            resources:
              limits:
                cpu: 500m
              requests:
                cpu: 200m
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: php-apache
      labels:
        run: php-apache
    spec:
      ports:
      - port: 80
      selector:
        run: php-apache
    kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10

    => 참고로 예제의 image를 사용하지 않고 request시, 자신의 host명을 response로 돌려주는 image을 추가해서 배포하였다
    (굳이 이렇게한 이유는 아래에 있다)
     
    => 현재 request CPU가 200m(0.2 core)이며 AutoScaling 확인 전,  CPU 50% 즉 0.1 core가 넘는 request 시, 최소 1개에서 최대 10개까지 AutoScaling하도록 정책을 설정해주었다. 
     
     
     

    * 요청을 통한 CPU 부하 진행(service object Domain으로 요청)

    kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"
    HPA만 모니터링 - ID:17125

    => 위의 부하 request 예시는 service object의 Domain으로 진행한 것이라 LoadBalancing이 일어난다. 따라서 AutoScaling의 부하 조건에 만족될수록 Pod가 자동으로 늘어나지만 Pod가 늘어날수록 LoadBalancing을 통해 요청을 처리하는 Pod가 여러개가 되면서 부하가 줄어들기 때문에, 이정도 요청으로는 5개이상 늘어나긴 어려웠다(5개 ~ 7개가 최대였다)
     
     

    하지만 특이한점이 있었다?!?!

    kubectl run test --image=nginx --rm -it -- /bin/sh -c "while true; do curl -s $PODIP; sleep 0.1;done"

    => 위처럼 Pod IP를 특정지어 요청해도 AutoScaling에 의해 5개까지만 생성되었다.. 오잉?? 뭐지 혹시 특정 Pod IP로 request를 보내더라도 무언가가 LB를 진행해주나?!?! 그럴리가 없겠지만 혹시나 하는 마음에 php를 약간 수정해서 image를 생성하여 확인해보았다.
     
     

    - Dockerfile로 php 수정후, image 재생성( k8sho/php:latest 사용 )

    FROM php:7.2-apache
    COPY index.php /var/www/html/index.php
    <?php
    $x = 0.0001;
    for ($i = 0; $i <= 1000000; $i++) {
    	$x += sqrt($x);
    }
    $hostname = gethostname();
    echo $hostname;
    ?>
     ...
     spec:
          containers:
          - name: php-apache
            image: k8sho/php:latest
            ports:
            - containerPort: 80
            resources:
              limits:
                cpu: 500m
              requests:
                cpu: 200m
     ...
    Service Domain으로 요청(왼쪽)&amp;nbsp; &amp;nbsp;/&amp;nbsp; &amp;nbsp;Pod IP로 요청(오른쪽)

    확인결과, 요청이후 시간이 조금 지나면 Pod의 CPU의 사용률도 어느정도 떨어졌다... Pod별 CPU 사용량을 비교해보면 처음 service domain으로 요청했을 때는 LB로 인해 Pod들이 골고루 CPU를 연산에 사용함을 알 수 있다. 하지만 Pod IP만 특정지어 요청한 상황은 하나의 Pod만 바빴다ㅋㅋㅋㅋ 결론은 LoadBalancing는 당연히 없었다..(머쓱..) 그저 AutoScaling이후 불필요한 Pod가 늦게 삭제되는 것과 CPU 사용량이 천천히 내려가는 동안 부하조건을 만족하여 사용도 하지 않을, 불필요한 Pod가 생성되었던 것 뿐이였다.
     
    => 이러한 삽질을 통해 그래도 얻을 수 있었던 것은  딜레이가 긴 AutoScaling과 AutoScaling 부하정책을 통해 불필요한 Pod를 더 생성하게 만들 수 있었고 이로인해 배포 장애나 비용적인 공격도 가능할 것으로 보인다.
     
    위와같은 상황으로 생각해볼 수 있는 시나리오는 불필요한 Pod가 갑자기 늘어남에 따라 하나의 인스턴스가 처리할 수 있는 Pod의 숫자를 넘겨 정말 필요한 Pod가 배포되지 못하게 할 수 있을 것 같고, 두 번째로는 갑자기 늘어난 Pod를 처리하기위해 Instance AutoScaling이 적용된 경우, 이를 이용해 인스턴스를 많이 추가시켜 비용적인 손해를 조금은 입힐 수 있을 것 같다
     
    요약하자면 Autoscaling 시, 불필요한 Scaling에 대한 빠른 처리가 중요할 것 같고, 요청을 받는 처리나 Flow에서 LoadBalancing이 제대로 수행되도록 하는 것도 중요할 것 같다!
     
     
     

    Clean up

    kubectl delete deploy,svc,hpa,pod --all

     


     

    KEDA - Kubernetes based Event Driven Autoscaler

    KEDA | KEDA Concepts

    What KEDA is and how it works

    keda.sh

    이전에 확인한 HPA는 CPU나 Memory 같은 Resource Metrics를 기반으로 Scale여부를 결정짓게된다. 하지만 KEDA는 리소스뿐만 아니라 특정 이벤트를 기반으로 Scale 여부를 결정지을 수 있다는 특징이 있으며 그 종류들은 공식 홈페이지에서 확인이 가능하다(굉장히 많은 종류를 지원하며 Scaler탭에서 KEDA가 수집해서 트리거 가능한 이벤트를 확인할 수 있다)
     
     

    - KEDA를 helm으로 배포

    cat <<EOT > keda-values.yaml
    metricsServer:
      useHostNetwork: true
    
    prometheus:
      metricServer:
        enabled: true
        port: 9022
        portName: metrics
        path: /metrics
        serviceMonitor:
          # Enables ServiceMonitor creation for the Prometheus Operator
          enabled: true
        podMonitor:
          # Enables PodMonitor creation for the Prometheus Operator
          enabled: true
      operator:
        enabled: true
        port: 8080
        serviceMonitor:
          # Enables ServiceMonitor creation for the Prometheus Operator
          enabled: true
        podMonitor:
          # Enables PodMonitor creation for the Prometheus Operator
          enabled: true
    
      webhooks:
        enabled: true
        port: 8080
        serviceMonitor:
          # Enables ServiceMonitor creation for the Prometheus webhooks
          enabled: true
    EOT
    
    kubectl create namespace keda
    helm repo add kedacore https://kedacore.github.io/charts
    helm install keda kedacore/keda --version 2.10.2 --namespace keda -f keda-values.yaml
    
    # 배포확인
    kubectl get-all -n keda
    kubectl get all -n keda
    kubectl get crd | grep keda

     
     

    => KEDA는 KEDA 전용 Metrics Server로 수집해서 사용된다.
     
     
     

    - Grafana Dashboard 적용

    GitHub - kedacore/keda: KEDA is a Kubernetes-based Event Driven Autoscaling component. It provides event driven scale for any co

    KEDA is a Kubernetes-based Event Driven Autoscaling component. It provides event driven scale for any container running in Kubernetes - GitHub - kedacore/keda: KEDA is a Kubernetes-based Event Dr...

    github.com

    KEDA Dashboad 초기화면

    => 위의 Github에서 Dashboard 관련 json 복사 후 "import via panel json"에 붙여넣고 import해서 사용하면 된다.

     

     

    - KEDA Cron 기반 Scaler를 통해 Pod AutoScaling 

    # ScaledObject 정책 생성 : cron
    
    cat <<EOT > keda-cron.yaml
    apiVersion: keda.sh/v1alpha1
    kind: ScaledObject
    metadata:
      name: php-apache-cron-scaled
    spec:
      minReplicaCount: 0
      maxReplicaCount: 2
      pollingInterval: 30
      cooldownPeriod: 300
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: php-apache
      triggers:
      - type: cron
        metadata:
          timezone: Asia/Seoul
          start: 00,15,30,45 * * * *
          end: 05,20,35,50 * * * *
          desiredReplicas: "1"
    EOT
    kubectl apply -f keda-cron.yaml -n keda

    => cron으로 서울시 기준으로 Pod의 실행시간과 종료시간을 설정해주었다.
     
     
     

    - php-apache 예제 keda namespace에 배포 후 확인

    curl -s -O https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/php-apache.yaml
    kubectl apply -f php-apache.yaml -n keda
    kubectl get hpa -n keda -o jsonpath="{.items[].spec}" | jq

    => HPA를 jsonpath로 확인해보면 위에서 설정한 cron기반의 scaler의 내용대로 적용된 것을 확인할 수 있었다(type이 External인 이유는 KEDA를 사용하기 때문)
     
     

    Clean up

    kubectl delete -f keda-cron.yaml -n keda && kubectl delete deploy php-apache.yaml -n keda && helm uninstall keda -n keda
    kubectl delete namespace keda

     
     


     
     

    VPA(Vertical Pod Autoscaler) - Scale Up / Down

    https://malwareanalysis.tistory.com/603 악분님의 블로그 참고(오른쪽)

    VPA는 pod resources.request를 최대한 최적값으로 수정하는 역할을 수행한다. 예를 들어 Pod request에 대한 resource를 보니 현재 Pod로는 감당할 수 없다는 것을 인지하면, 해당 request를 처리할 수 있는 최적의 Spec으로 Pod를 재생성한다(새로운 Pod를 먼저 생성 후, 기존 Pod 제거).  또한 HPA와 VPA는 같이 사용이 불가하다. 이러한 제약이 많기 때문에 실제 사용하기에는 애매한 감이 있다.  
     
     

     

    VPA 배포 및 Grafana Dashboard 적용

    # VPA는 metrics server가 필요하다. 또한 배포 시, Openssl의 버전 설정에 주의하자. 
    
    
    # Metrics 서버 배포
    kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
    
    # VPA 관련 파일 git 사용(EKS에서 제공함)
    git clone https://github.com/kubernetes/autoscaler.git
    cd ~/autoscaler/vertical-pod-autoscaler/
    tree hack
    
    # openssl 버전 확인
    openssl version
    OpenSSL 1.0.2k-fips  26 Jan 2017
    
    # openssl 1.1.1 이상 버전이 필요함
    yum install openssl11 -y
    openssl11 version
    OpenSSL 1.1.1g FIPS  21 Apr 2020
    
    # 스크립트파일내에 openssl11로 수정
    sed -i 's/openssl/openssl11/g' ~/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/gencerts.sh
    
    # VPA Controller 배포
    ./hack/vpa-up.sh
    
    # 설치된 VPA 모듈(3가지)와 CRD 확인
    kubectl get pod -n kube-system
    kubectl get crd | grep autoscaling
    생성된 Pod와 CRD 확인

    => EKS에서 제공한  스크립트를 통해 VPA 모듈과 CRD가 정상적으로 배포된 것을 확인할 수 있었다.
     

     

    - VPA Grafana Dashboard

    Dashboards | Grafana Labs

    grafana.com

     
     
     

    - 공식 예제 배포를 통해 VPA 동작 확인

    GitHub - kubernetes/autoscaler: Autoscaling components for Kubernetes

    Autoscaling components for Kubernetes. Contribute to kubernetes/autoscaler development by creating an account on GitHub.

    github.com

    # 공식 예제 배포
    cd ~/autoscaler/vertical-pod-autoscaler/
    cat examples/hamster.yaml | yh
    kubectl apply -f examples/hamster.yaml && kubectl get vpa -w
    hamster.yaml
    VPA 실시간 확인
    기존의 Pod 하나가 재실행됨 / Spec이 Scale up한 것을 확인할 수 있음

    => hamster.yaml를 배포하게되면 해당 Pod는 내부의 args 설정을 통해 부하를 일으켜 resource 사용량이 증가된다.
    => VPA는 pod의 resource request가 Pod Spec에서 명시해놓은 기준을 넘어간 것을 알게되고 최적값으로 Scale up한 Pod를 재실행하게된다(생성 후, 기존 Pod 삭제)
     
     


     

    CA(Cluster AutoScaler)

    Workshop Studio

    catalog.us-east-1.prod.workshops.aws

    aws ec2 describe-instances  --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Reservations[*].Instances[*].Tags[*]" --output yaml | yh

    => CA는 Cluster라는 이름이 들어가서 헷갈릴 수 있겠지만 간략하게 말하면, Pending 상태인 Pod가 존재할때, 즉 Pod를 할당할만한 Node가 없을 때, 특정 시간을 간격으로 사용률을 확인하여 Node를 AutoScaling(Scale in / out )해준다. 리소스부하로 인해 Pod를 할당할 수 없는 경우에도 Auto Scale Out되지만 단순히 instance가 할당할 수 있는 Pod의 갯수를 초과하여 스케줄링 불가로 Pending 상태가 되어도 Auto Scale Out이 진행된다.
     
    => CA를 적용하기전 EC2 Instance tage에서 오른쪽 그림과 같은 key-value값을 설정해줘야하며 이는 aws-cli를 통해서도 확인이 가능하다(eksctl로 cluster 생성시 자동으로 tag가 적용되어있으나 한번 확인 후 진행하자)
     
     
     

    * CA 정보 확인 및 수정 방법

    # 현재 autoscaling(ASG) 정보 확인
    aws autoscaling describe-auto-scaling-groups \
        --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]" \
        --output table
        
    # MaxSize 6개로 수정
    export ASG_NAME=$(aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].AutoScalingGroupName" --output text)
    aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${ASG_NAME} --min-size 3 --desired-capacity 3 --max-size 6
    
    
    # 배포 : Deploy the Cluster Autoscaler (CA)
    curl -s -O https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/aws/examples/cluster-autoscaler-autodiscover.yaml
    sed -i "s/<YOUR CLUSTER NAME>/$CLUSTER_NAME/g" cluster-autoscaler-autodiscover.yaml
    kubectl apply -f cluster-autoscaler-autodiscover.yaml
    
    # 확인
    kubectl get pod -n kube-system | grep cluster-autoscaler
    kubectl describe deployments.apps -n kube-system cluster-autoscaler
    
    # (옵션) cluster-autoscaler 파드가 동작하는 워커 노드가 퇴출(evict) 되지 않게 설정
    kubectl -n kube-system annotate deployment.apps/cluster-autoscaler cluster-autoscaler.kubernetes.io/safe-to-evict="false"

     
     

    - CA 동작 테스트

    cat <<EOF> nginx.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-to-scaleout
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            service: nginx
            app: nginx
        spec:
          containers:
          - image: nginx
            name: nginx-to-scaleout
            resources:
              limits:
                cpu: 500m
                memory: 512Mi
              requests:
                cpu: 500m
                memory: 512Mi
    EOF
    
    kubectl apply -f nginx.yaml
    kubectl scale deployment nginx-to-scaleout --replicas=15
    
    
    # 필요한 node 숫자 확인
    aws autoscaling describe-auto-scaling-groups \
        --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]" \
        --output table
    현재 최대 AutoScaling가능한 Node는 6개이며 DesiredCapacity 값이 증가한 것을 확인할 수 있었다.

    => 배포가 완료되었다면 샘플코드로 테스트해보자. replicase 값을 올리자 하나의 Pod당 사용되는 리소스 사용률이 높아 Node에 스케줄링 되지 못한 Pending Pod가 생겼다. 이 때 CA가 이를 감지하여 DesiredCapacity 값이 올라감을 확인할 수 있었다.
     
    => 하지만 사용률이 줄어들어 더이상 많은 Node가 필요하지 않을때 다시 Scale In 즉, 축소되는 시간이 느리다는 단점이 있다. 10분정도 걸리며 이를 옵션을 통해 어느정도 줄일 수는 있으나 그래도 느린편이다(꿀팁:Auto Scale 가능한 Node 최대 갯수를 줄여서 시간단축가능)
     
    => 또한 CA는 결국, Amazon의 ASG와 Amazon EKS에서 각자의 방식으로 하나의 자원을 관리하는 것이기 때문에 관리정보가 서로 정확하게 동기화되지 않으므로 다양한 문제가 발생할 수 있다. 
     
     
     

    - CA 테스트 영상(시간소요로 인해 편집 진행)

    존재하는 Node들이 감당할 수 없는 숫자의 Pod가 배포되어 진행되는 Auto Scale Out
    존재하는 Node가 감당할 수 없는 Pod의 자원사용률도 인해 진행되는 Auto Scale Out

    Clean up

    # 예제 삭제
    kubectl delete -f nginx.yaml
    
    # size 수정 
    aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${ASG_NAME} --min-size 3 --desired-capacity 3 --max-size 3
    aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]" --output table
    
    # Cluster Autoscaler 삭제
    kubectl delete -f cluster-autoscaler-autodiscover.yaml

     
     


     

    CPA( Cluster Proportional Autoscaler )

    노드 수 증가에 비례하여 성능처리가 Container나 Pod같은 애플리케이션을 수평으로 자동확장해준다.

    # Helm 사용
    helm repo add cluster-proportional-autoscaler https://kubernetes-sigs.github.io/cluster-proportional-autoscaler
    
    # nginx 디플로이먼트 먼저 배포
    cat <<EOT > cpa-nginx.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-deployment
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx:latest
            resources:
              limits:
                cpu: "100m"
                memory: "64Mi"
              requests:
                cpu: "100m"
                memory: "64Mi"
            ports:
            - containerPort: 80
    EOT
    kubectl apply -f cpa-nginx.yaml
    
    # CPA 규칙 설정
    cat <<EOF > cpa-values.yaml
    config:
      ladder:
        nodesToReplicas:
          - [1, 1]
          - [2, 2]
          - [3, 3]
          - [4, 3]
          - [5, 5]
    options:
      namespace: default
      target: "deployment/nginx-deployment"
    EOF
    
    # helm 업그레이드
    helm upgrade --install cluster-proportional-autoscaler -f cpa-values.yaml cluster-proportional-autoscaler/cluster-proportional-autoscaler
    
    # 노드 5개로 증가
    export ASG_NAME=$(aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].AutoScalingGroupName" --output text)
    aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${ASG_NAME} --min-size 5 --desired-capacity 5 --max-size 5
    aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]" --output table
    
    # 노드 4개로 축소
    aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${ASG_NAME} --min-size 4 --desired-capacity 4 --max-size 4
    aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]" --output table

    => Helm 차트로 설치하고 쓰는게아니고 먼저 애플리케이션을 배포 후에 CPA 규칙 설정 후 helm으로 적용하는 것이다. 
    => CPA 규칙을 보면 worker node가 3개면 Pod3개, Node 4개면 Pod 3개, Node 5개면 Pod 5개 이런 규칙을 적용하였다.
     
     
     

    => Worker node를 5개로 증가시켜보니 CPA규칙에 따라 Pod도 2개더 생성되어 5개의 Pod가 배포되는 것을 확인할 수 있었다.
     

     

    Clean up

    helm uninstall cluster-proportional-autoscaler && kubectl delete -f cpa-nginx.yaml

     
     


     

    Karpenter

    - Karpenter 배포 참고

    Getting Started with Karpenter

    Set up a cluster and add Karpenter

    karpenter.sh

    Karpenter는 노드 수명주기 관리 솔루션으로 짧은시간(몇 초 이내)내에 컴퓨팅 리소스를 제공하기 위해 만들었다고 한다. ASG는 Node증 설에 대한 요청이 발생해도 몇초 후(정책에 따라 다름)에 진행하지만 Karpenter는 발생하자마자 바로 진행되기 때문에 직접 사용해보면 AWS에서 제공하는 Auto Scaling Group보다 훨씬 빠른 것을 체감할 수 있었다
    (또한 Scaler, ASG를 거치는 과정이 없이 바로 Karpenter로 처리하기 때문에 이전의 CA의 한계를 넘어섬) 
     
     
    위의 공식홈페이지에서 배포방법을 자세히 알려주고 있으며 배포시, Karpenter뿐만아니라 Prometheus와 monitoring에 사용할 Grafana 배포도 안내하고 있다.
     
     
    + 추가적으로 eks-node-viewer를 사용했다.

    go install github.com/awslabs/eks-node-viewer/cmd/eks-node-viewer@latest
    tree ~/go/bin
    cd ~/go/bin
    ./eks-node-viewer

     

    Karpenter 주요 동작 - Provisioning과 Deprovisioning

    - ASG와 동일하게 Scheduling되지 않은 Pod를 Monitoring 하는 것은 같으며 Scheduling이 되지 않은 Pod 발견 시, Node에 Pod Spec을 평가하고 비용 등을 고려, 적합한 Node를 생성하여 Pod를 Scheduling하는 Provisioning 진행함
    - Monitoring을 통해 비어있는 Node 발견 시, 제거를 하는 Deprovisioning을 진행함
     
     

    Karpenter 특징(비용절감 good..)

    - Karpenter가 Node에 대한 상태정보 뿐아니라 비용평가도 진행하므로 효율적인 Node 증설을 진행함
    - Auto Scaling시, Pod에 적합한 instance 중 가장 저렴함 instance로 증설됨
    - 사용하지 않은 Node 정리: Node를 줄여도 다른 Node에 충분한 여유가 있을 시, 자동으로 정리함- Spec이
      큰 Node 하나가 Spec이 작은 Node 여러개보다 비용이 저렴할 경우, 자동으로 합쳐줌 

    - EC2 instance AutoScaling Group같이 시작 templete이 필요없음, Provisioner CRD가 대부분의 설정을 진행함.
      즉, Provisioner CRD가 ASG를 대체함   

    - PV를 위해 단일 Subnet에 nodeGroup을 만들 필요없이 자동으로 PV가 존재하는 Subnet에 Node를 생성함

    => Provisioner은 CRD이므로 다른 workload resource처럼 ArgCD, Spinnaker 등으로 배포가 가능하다. 또한 여러 Provisioner 적용해놓을 수 있으며 그 중 Scheduling 되지않은 Pod에 가장 적합한 Provisioner를 반영한다.
     
     

    [참고] Over provisioning(밀어내기식 느낌)

    실무에서는 Karpenter를 사용하더라도 Karpenter자체가 Pod가 Scheduling되지 않아야 Node를 생성하게되는데 이렇게 생성되더라도  Daemonset 등 필요한 요소들이 설치되는데 시간(1~2분)이 걸린다. 때문에 이 시간마저 단축하기 위해서는 밀어내기용 Pod를 생성하여 실제 서비스하는 Pod가 배포될 때 밀어내기용 Pod로 인해 더 많은 Node가 필요하게끔하여 미리 Node를 확보하는 오버 프로비저닝을 추천한다고 한다. 또한 대규모 증설이 예상될 때는 오버 프로비저닝만으로는 대응이 어렵기 때문에 PodAntiAffinity 같이 사용하여 대비하는 것을 추천함.
     
    * podAnitAffinity:
    Kubernetes Node 배포 관련 설정으로 실행 중인 Pod들 중에, 선호하지 않은 Pod가 실행 중인 Node는 피해서 배치를 하겠다는 설정.
     
     
     

    => CRD를 통해 Provisioner라는 정책을 만들고 실제 배포될 node의 형상 관련 설정은 awsnodetempletes에 들어가게됨
     
     
     

    - Provisioner 배포

    cat <<EOF | kubectl apply -f -
    apiVersion: karpenter.sh/v1alpha5
    kind: Provisioner
    metadata:
      name: default
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]
      limits:
        resources:
          cpu: 1000
      providerRef:
        name: default
      ttlSecondsAfterEmpty: 30
    ---
    apiVersion: karpenter.k8s.aws/v1alpha1
    kind: AWSNodeTemplate
    metadata:
      name: default
    spec:
      subnetSelector:
        karpenter.sh/discovery: ${CLUSTER_NAME}
      securityGroupSelector:
        karpenter.sh/discovery: ${CLUSTER_NAME}
    EOF
    
    kubectl get awsnodetemplates,provisioners

    => Spec에서 확인할 수 있듯, Node 생성시, Spot instance를 사용하겠다는 정책임

     

    * Tag기반의 자동 Resource 찾기도 가능하며, Resource ID를 명시적으로 작성하는 방법도 가능

    • AWSNodeTemplate:
      신규 Node가 어떤 subnet에 배포할 것인지 설정하며 subnetSelectorsecurityGroupSelector Tag로 관리함

    * 일정 기간이 지나면 노드를 자동으로 만료 가능

    • ttlSecondsAfterEmpty:
      Node가 생성되었는데 Daemonset외에 다른 Pod가 미존재한지 30초가 지나면 해당 Node를 삭제함
    • ttlSecondsUntilExpired:
      설정 기간이 지난 Node들은 자동으로 Cordon, drain 처리되어 정리됨. Node가 주기적으로 정리되면 Pod들이 기존의 여유있는 Node들로 재배치 되기 때문에 효율성이 높아짐. 또한 Node가 정리되고 추후 다시 생성될 시, 최신 AMI로 변경된다는 장점도 있음
    • [참고] Pod 중 삭제 시, 자동으로 재생성 되지 않는 Pod들도 있음(예시 JupyterHub).때문에 주의해야하며 Drain이 불가한 몇 가지 Pod들의 경우 해당 옵션들이 존재하여도 진행에 실패하여 스케줄링이 다시 진행되지 않지만 해당 node들은 죽지 않고 살아있어 불필요한 비용이 지출 될 수 있음. 따라서 Node group 생성시, Drain이 불가할 거같은 Pod 가능성존재 여부를 체크하는 것을 추천함!!

     
    - 스토리지 지정도 가능하나 지정하지 않으면 되게 작은 Volume을 Mount해서 사용하기 때문에 디스크부족의 배포지연이 발생할 수 있으므로 넉넉하게 적용하는 것을 추천한다.
     
    - instance type을 가드레일 방식으로 선언 가능하다(On-demand, spot 등) 둘다 선언할 경우 Spot이 더 우선순위가 높다. Spot이 없다면 On-demand로 동작.
     
    - Spot만 사용한다면 다양한 instance type을 사용하는 것이 유리하다(Karpenter는 다양한 instance 중에 가장 오랫동안 살아있을 수 있는 instance type으로 spot을 생성하려고 하기때문이며 보통 가장 저렴한 instance를 사용하려고한다.)
     
     

     
    - Prometheus, Grafana 배포

    helm repo add grafana-charts https://grafana.github.io/helm-charts
    helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
    helm repo update
    
    kubectl create namespace monitoring
    
    # 프로메테우스 설치
    curl -fsSL https://karpenter.sh/"${KARPENTER_VERSION}"/getting-started/getting-started-with-karpenter/prometheus-values.yaml | tee prometheus-values.yaml
    helm install --namespace monitoring prometheus prometheus-community/prometheus --values prometheus-values.yaml --set alertmanager.enabled=false
    
    # 그라파나 설치
    curl -fsSL https://karpenter.sh/"${KARPENTER_VERSION}"/getting-started/getting-started-with-karpenter/grafana-values.yaml | tee grafana-values.yaml
    helm install --namespace monitoring grafana grafana-charts/grafana --values grafana-values.yaml --set service.type=LoadBalancer
    
    # admin 암호
    kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
    
    # 그라파나 접속
    kubectl annotate service grafana -n monitoring "external-dns.alpha.kubernetes.io/hostname=grafana.$MyDomain"
    echo -e "grafana URL = http://grafana.$MyDomain"
    설치된 Grafana 접속 - 자동으로 Dashboard가 import 되어있다.

     

    - Karpenter 동작 테스트

    # pause 파드 1개에 CPU 1개 최소 보장 할당
    
    # inflate Deployment yaml file
    cat <<EOF | kubectl apply -f -
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: inflate
    spec:
      replicas: 0
      selector:
        matchLabels:
          app: inflate
      template:
        metadata:
          labels:
            app: inflate
        spec:
          terminationGracePeriodSeconds: 0
          containers:
            - name: inflate
              image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
              resources:
                requests:
                  cpu: 1
    EOF
    kubectl scale deployment inflate --replicas 5

    => 원활하고 빠른 테스트를 위해 Pod spec 설정을 가장 빨리 생성되고 삭제될 수 있도록 image를 pause로 사용하고 종료 관련 옵션인 terminationGracePeriodSeconds를 0으로 설정
     
     
     

    # Karpenter에 의해 생성되는 Spot Instance 관련 log 및 정보 확인
    kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
    aws ec2 describe-spot-instance-requests --filters "Name=state,Values=active" --output table
    
    kubectl get node -l karpenter.sh/capacity-type=spot -o jsonpath='{.items[0].metadata.labels}' | jq
    kubectl get node --label-columns=eks.amazonaws.com/capacityType,karpenter.sh/capacity-type,node.kubernetes.io/instance-type

    Karpenter에 의해서 생성되는 Node 정보 확인

     

     

    - Karpenter 테스트 영상

     
     

    Consolidataion

    # 위에서 사용했던 provisioners 삭제
    kubectl delete provisioners default
    
    # Consolidation enable 정책 적용 후 테스트
    cat <<EOF | kubectl apply -f -
    apiVersion: karpenter.sh/v1alpha5
    kind: Provisioner
    metadata:
      name: default
    spec:
      consolidation:
        enabled: true
      labels:
        type: karpenter
      limits:
        resources:
          cpu: 1000
          memory: 1000Gi
      providerRef:
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values:
            - on-demand
        - key: node.kubernetes.io/instance-type
          operator: In
          values:
            - c5.large
            - m5.large
            - m5.xlarge
    EOF
    
    
    # 12개로 늘리고 로그 확인
    kubectl scale deployment inflate --replicas 12
    kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
    
    
    # instance 정보 확인
    kubectl get node -l type=karpenter
    kubectl get node --label-columns=eks.amazonaws.com/capacityType,karpenter.sh/capacity-type
    kubectl get node --label-columns=node.kubernetes.io/instance-type,topology.kubernetes.io/zone
    
    
    # 12 -> 5개로 줄이고 로그 확인
    kubectl scale deployment inflate --replicas 5
    kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
    12개 배포했을 시, Log내에 4개의 Node Provisioning log 확인 및 kube-ops-view 확인
    12개 -&gt; 5개로 줄였을 때, log 및 kube-ops-view 확인

    생성할 때는 Provisioning이 진행되지만 Deprovisioning시, 해당 node에 혹시모를 pod schduling을 막기위해 corden 진행 후, delete 되는 것을 확인할 수 있었다.
     
    이전에 다른 AutoScaler들을 사용해보니, 스터디 할 때 실무자 분들이 왜 Karpenter Karpenter 하셨는지 알 수 있었다.. 그냥 미쳤다... 속도도 그렇고 비용 관리에 효율까지.. 크으...이것 역시 나중에 반드시 실무에 적용해보고싶다... 최고다..
     

     

    Clean up

    kubectl delete svc -n monitoring grafana
    helm uninstall -n kube-system kube-ops-view
    helm uninstall karpenter --namespace karpenter
    
    # 위 삭제 완료 후 아래 삭제
    aws ec2 describe-launch-templates --filters Name=tag:karpenter.k8s.aws/cluster,Values=${CLUSTER_NAME} |
        jq -r ".LaunchTemplates[].LaunchTemplateName" |
        xargs -I{} aws ec2 delete-launch-template --launch-template-name {}
    
    # 클러스터 삭제
    eksctl delete cluster --name "${CLUSTER_NAME}"
    
    # Karpenter stack 삭제
    aws cloudformation delete-stack --stack-name "Karpenter-${CLUSTER_NAME}"
    
    # 위 삭제 완료 후 아래 삭제
    aws cloudformation delete-stack --stack-name ${CLUSTER_NAME}
    반응형

    댓글

Designed by Tistory.