EKS #2 AutoScaling(hpa, cluster autoscaler)

PEPPERMINT100·2024년 12월 22일
0

이전 글을 통해서 EKS의 필요성, k8s와 다르게 존재하는 추가적인 구성요소 그리고 클러스터와 노드그룹을 간단히 생성하고 삭제하는 과정에 대해서 실습하고 글을 작성해보았다.

이번엔 EKS로 생성된 요소에 대해서 오토 스케일링을 구현해보려고 한다.

k8s, eks에서 오토 스케일링은 파드의 사용량이 일정 수준 이상 높아질 때 레플리카를 추가적으로 더 생성하는 행위를 뜻한다.

이걸 구현하는 방법은 4가지가 있는데

  • Horizontal Pod Autoscaler
  • Vertical Pod Autoscaler
  • Cluster Autoscaler
  • Karpenter

가 있다.

먼저 Horizontal Pod Autoscaler(이하 hpa)는 수평적으로 파드를 scale out한다. 파드의 개수를 늘려서 많은 트래픽을 받아내는 방식으로 동작한다.

Vertical Pod Autoscaler(이하 vpa)는 hpa와 다르게 파드의 리소스 사양을 높이는 방식으로 작동한다.

hpa와 vpa는 이름에서도 알 수 있듯이 pod, 파드에 관련된 오토스케일러이다. 만약에 hpa를 사용한다고 했을 때 1개 워커 노드(EKS에서는 ec2)가 할당 받을 수 있는 파드의 개수가 꽉차게 되면 어떻게 될까?

일반적으로 생각해봤을 때 노드 즉 EC2가 더필요하게 될 것이다. 이 때 등장하는 것이 Cluster Autoscaler이다. Cluster Autoscaler는 기본적으로 k8s에 존재하는 개념이지만 실제 구현과 동작은 AWS와 같은 클라우드 프로바이더마다 다르다.

EKS 기준으로 k8s와 EKS를 묶어서 작동을 하게 되는데, 한 개 워커노드에서 생성가능한 파드가 꽉 차게 되면 노드가 더 필요하므로 노드를 더 생성해줘야 한다. AWS 리소스 중 비슷한 역할을 하는 AWS 서비스가 있는데 바로 Auto scaling group이다.

Cluster Autoscaler는 이 Auto scaling group을 이용하여 노드의 개수를 늘려주는 역할을 하게 된다.

Karpenter는 또 다른 노드용 프로비저닝 툴이다. 가장 최신 툴로 구글링을 해보면 요즘은 다 Karpenter를 많이 사용하는 것으로 보인다.

Cluster Autoscaler는 기본적으로 Auto Scaling Group에 의존하기 때문에, 파드가 더 필요하더라도 파드가 Pending인 상태로 EC2 인스턴스가 추가되는데 좀 기다려야 한다는 단점이 있다.

하지만 Karpenter는 AWS 리소스에 의존하지 않고, CA보다 단순한 구조를 가지고 있다. unscheduled pod이라는 이름으로 노드의 자원이 부족해서 pod가 Pending 상태가 되면 Karpenter가 새로운 노드의 추가를 결정하고 직접 배포하게 된다.

또 hpa, vpa, cluster auto scaler는 특히 또 리소스 사용량이 줄어든 이후에도 바로바로 파드나 노드의 개수를 이전 상태로 돌리지 않는데, 트래픽 충분히 오랜 시간 감소되기를 기다렸다가 이전 상태로 돌린다.

이는 결국 유후 상태의 EC2 인스턴스를 오랫동안 냅두는 결과를 가져오지만 Karpenter는 비어있는 노드를 정리하는 속도도 빠르다.

실제로 Cluster Autoscaler를 통해 생성된 EC2 인스턴스는 거의 20분 가까이 지나야 사라지더라

일단 이번에는 hpa와 cluster autoscaler에 대해서만 알아보겠다. k8s의 네이티브 기능에 가까운 요소부터 먼저 알고 가는것도 나쁘지 않고, karpenter는 설정도 어렵지 않다고 하니 조금 나중에 알아보도록 하겠다.

참고로 vpa는 존재하긴 하지만 scale up, down을 기준으로 작동하므로 프로덕션에서 사용하지 않는 편이 좋다고 한다. 기본적으로 사양을 업데이트 하는 동안 파드가 사용이 불가능하기도 하고 롤링으로 파드를 업데이트 한다고 해도 굉장히 비효율적인 구조가 나온다.

Horizontal Pod Autoscaler

먼저 eks 클러스터를 생성해줘야 한다.

eksctl create cluster \
--name scale-test \
--node group-name ng-default \
--node-type m5.large \
--nodes 2

이번엔 파드의 스케일링 테스트를 위해 넉넉히 m5.large 사양의 노드를 생성해주었다. 사양이 높으므로 테스트가 끝나면 적절히 클러스터를 삭제해주도록 하자. 클러스터 삭제는 이전 글을 참고하면 된다.

이제 metrics-server라는 걸 설치해서 실행줘야 하는데, metrics-server는 파드의 cpu 사용량을 측정해주는 역할을 한다. hpa는 이 metrics-server가 있어야 이를 토대로 작동하기 때문에 먼저 설정해주어야 한다.

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

metrics-server는 간단히 온라인에 올라와 있는 설정파일을 토대로 실행해주었다.

파일을 까보면 기본적으로 서버를 kube-system이라는 네임스페이스에 파드를 실행한다.

kubectl get deployment metrics-server -n kube-system

이 명령어를 통해서 metrics-server가 잘 설정되었는지 확인할 수 있다.

hpa는 k8s의 네이티브 요소로 사용과 설정이 간단하다. 먼저 hpa-php-apache,yaml 이라는 이름으로 아래 파일을 작성해보자.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  replicas: 1
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: k8s.gcr.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 500m # 0.5와 같다
          limits:
            cpu: 1000m # 1과 같다
          
---

apiVersion: v1
kind: Service
metadata:
  name: php-apache
  labels:
    run: php-apache
spec:
  ports:
  - port: 80
  selector:
    run: php-apache

---

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10 # ReplicaSet의 replicas보다 더 많이 생성하게 된다.
  targetCPUUtilizationPercentage: 50

Deployment는 일단 간단한 apache와 php 서버를 돌리게 되고 일단 Service는 이 후에 파드에 부하를 주기 위해 포트를 지정해둔다.

그리고 가장 중요한게 아래 HorizontalPodAutoscaler인데, kind와 name을 통해 어떤 디플로이먼트의 오토 스케일링을 설정하는지 지정해주어야 한다.

또 minReplicas, maxReplicas로 파드의 범위를 정해준다. 참고로 Deployment의 replicas를 적게 지정하더라도 hpa를 설정해주면 그 replicas 값을 넘어서서 더 많은 파드를 생성할 수 있게 된다.

targetCPUUtilizationPercentage는 해당 파드의 cpu 사용량이 몇 퍼센트를 넘어가면 파드를 추가로 생성할지 설정해준다. 50이므로 cpu 사용량이 50을 넘으면 파드를 추가로 배포한다.

이제 이렇게 하고

kubectl apply -f hpa-php-apache,yaml

을 실행시켜주면 정상적으로 hpa가 적용된 php 서버가 기동된다. 이제 노드에 부하를 줘야 하는데, 그전에 노드에 부하가 잘 들어가는지 확인하기 위해서 파드의 cpu 사용량을 모니터링 하는 명령어를 실행해보자

kubectl get hpa php-apache --watch

이렇게 하면 노드의 부하 변화를 지켜볼 수 있다. 만약 metrics-server 가 없으면 cpu target이 UNKNOWN으로 나온다.

이제 이 노드에 부하를 줘보자.

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"

busybox 이미지를 만들고 wget으로 http요청을 계속해서 보내는 명령어이다. 이렇게 하면

CPU 사용량이 급격하게 늘어나는 것을 볼 수 있다.

kubectl get pod

을 통해 pod 개수를 보면 위에서 설정한 maxReplicas만큼 파드의 개수도 늘어난다. hpa가 파드를 scale out하여 부하를 받아내고 있다.

이제 busybox를 실행 중인 터미널의 프로세스를 종료해주면

이렇게 cpu 사용량이 천천히 줄어든다.

Cluster Autoscaler

이제 cluster autoscaler를 설정하고 사용해보자. 일단 t3.medium 사양을 갖는 EKS 클러스터를 생성해준다.

eksctl create cluster \
--name scale-test \
--nodegroup-name ng-default \
--node-type t3.medium \
--nodes 2

다음은 아래 파일을 통해 AWS IAM에서 policy를 생성해준다. 아래 policy로 cluster autoscaler가 EC2의 Auto Scaling Group을 컨트롤할 권한을 얻는다.

{
   "Version": "2012-10-17",
   "Statement": [
       {
           "Action": [
               "autoscaling:DescribeAutoScalingGroups",
               "autoscaling:DescribeAutoScalingInstances",
               "autoscaling:DescribeLaunchConfigurations",
               "autoscaling:DescribeTags",
               "autoscaling:SetDesiredCapacity",
               "autoscaling:TerminateInstanceInAutoScalingGroup",
               "ec2:DescribeLaunchTemplateVersions"
           ],
           "Resource": "*",
           "Effect": "Allow"
       }
   ]
}

웹 콘솔이 아니라 아래 명령어로도 Policy를 생성할 수도 있다.

aws iam create-policy \
   --policy-name AmazonEKSClusterAutoscalerPolicy \
   --policy-document file://cluster-autoscaler-policy.json

이제 cluster autoscaler에 필요한 role을 생성해준다.

eksctl create iamserviceaccount \
--cluster=scale-test \
--namespace=kube-system \
--name=cluster-autoscaler \
--attach-policy-arn=[위에서 생성한 Policy의 arn] \
--override-existing-serviceaccounts \
--approve

여기까지 설정해주면 이제 cluster autoscaler가 우리가 만든 노드그룹의 Auto Scaling Group을 컨트롤 할 수 있게 된다. 여기서 oicd 관련 에러가 나올 수 있는데,

eksctl utils associate-iam-oidc-provider \
    --cluster autoscaler-test \
    --region ap-northeast-2 \
    --approve

아래 명령어를 통해서 oidc를 허용해주면 된다. 이를 통해 kubernetes와 iam 서비스를 연결해 줄 수 있다.

이제 auto scaler를 설정할 파일을 다운로드 받는다.

curl -o cluster-autoscaler-autodiscover.yaml https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/aws/examples/cluster-autoscaler-autodiscover.yaml

curl 명령어로 다운로드 받아도 되고, 이 깃허브에 있는 파일을 복붙해서 사용해도 된다.

kubectl apply -f cluster-autoscaler-autodiscover.yaml

파일 작성 혹은 다운로드 후 apply 명령어로 cluster-autoscaler를 실행해주자. 이 cluster-autoscaler 역시 kube-system 네임스페이스에 생성이 된다.

이제 cluster autoscaler에 대한 설정을 해주어야 한다. 이 과정은 위에서 설명했던 k8s의 요소인 cluster autoscaler와 aws의 Auto Scaling Group을 연결해주는 설정이다.

아래 명령어를 통해 safe-to-evict 설정을 false로 바꿔준다. 이 설정을 통해서 cluster autoscaler가 다른 이유로 evict되는 것을 막아준다.

kubectl patch deployment cluster-autoscaler \
 -n kube-system \
 -p '{"spec":{"template":{"metadata":{"annotations":{"cluster-autoscaler.kubernetes.io/safe-to-evict": "false"}}}}}'

이제 cluster autoscaler의 설정 파일을 수정해줘야 하는데,

kubectl -n kube-system edit deployment.apps/cluster-autoscaler

이렇게 설정파일에 접속해준다. 여기서

  spec:
     containers:
     - command
       - ./cluster-autoscaler
       - --v=4
       - --stderrthreshold=info
       - --cloud-provider=aws
       - --skip-nodes-with-local-storage=false
       - --expander=least-waste
       - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/[내 EKS 클러스터 이름으로 수정]
       - --balance-similar-node-groups # << 이 부분 추가 필요
       - --skip-nodes-with-system-pods=false << 이 부분 추가 필요

아래 3줄 부분을 찾아서 주석대로 수정해준다. vi 에디터를 잘 사용해서 수정해주어야 한다. 실패하면 editing이 실패했다고 뜬다.

이제 kubectl -n kube-system get all 커맨드를 실행하면

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/cluster-autoscaler   1/1     1            1           4m58s
deployment.apps/coredns              2/2     2            2           12m

이런 결과와 함께 auto-scaler가 잘 배포된 것을 확인할 수 있다.

이렇게 되면 cluster autoscaler에 대한 설정이 끝난다. 이제 간단히 테스트를 해야하는데, 먼저 테스트에 필요한 Deployment 파일을 작성해보자.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  replicas: 2
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: k8s.gcr.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 500m
            memory: 256Mi
          limits:
            cpu: 1000m
            memory: 512Mi

이제 파드를 실행해보자

kubectl apply -f [파일이름] 으로 실행하면 파드가 실행되고 Auto Scaling Group 웹 콘솔에 들어가보면

이렇게 Desired capacity가 2인것을 볼 수 있다. 참고로 Auto Scaling Group에서 수동으로 max 값을 조정해줄 필요가 있는데 지금 4 정도로 올려둔 상태이다.

이제 위 Deployment 파일에서 주목할 부분은 cpu이다. 500m는 500 milli core와 같은 말로 즉 0.5vCPU를 뜻하게 된다. 위 php-apache 파드는 하나당 0.5vCPU를 차지하게 된다.

공식 문서에 따르면 t3.medium은 2개의 vCPU를 가지고 있다.

그렇다면 하나의 EC2 인스턴스당 총 8개의 파드를 가질 수 있다는 것으로 보인다.

여기서 위 설정파일의 replicas 값을 17로 수정하고 apply 커맨드를 다시 실행해보자.

➜  kubernetes-walkthrough git:(master) kubectl apply -f cluster-autoscaler-deployment.yaml
deployment.apps/php-apache configured
➜  kubernetes-walkthrough git:(master) ✗ k get pod
NAME                          READY   STATUS    RESTARTS   AGE
php-apache-7dff56c499-22hjw   0/1     Pending   0          11s
php-apache-7dff56c499-457r2   0/1     Pending   0          11s
php-apache-7dff56c499-55fxf   0/1     Pending   0          11s
php-apache-7dff56c499-5kkv4   0/1     Pending   0          11s
php-apache-7dff56c499-6wvtk   0/1     Pending   0          11s
php-apache-7dff56c499-7jx44   1/1     Running   0          11s
php-apache-7dff56c499-bh4sx   0/1     Pending   0          11s
php-apache-7dff56c499-dwttp   1/1     Running   0          11s
php-apache-7dff56c499-gz946   0/1     Pending   0          11s
php-apache-7dff56c499-kwvvx   0/1     Pending   0          11s
php-apache-7dff56c499-kzwp5   1/1     Running   0          3m39s
php-apache-7dff56c499-l4k2z   0/1     Pending   0          11s
php-apache-7dff56c499-psqpt   1/1     Running   0          3m39s
php-apache-7dff56c499-ssw9n   0/1     Pending   0          11s
php-apache-7dff56c499-w5pf7   0/1     Pending   0          11s
php-apache-7dff56c499-wnvcv   0/1     Pending   0          11s
php-apache-7dff56c499-zqc2s   1/1     Running   0          11s
...

get pod 까지 실행하면 많은 파드들이 Pending 상태로 대기하게 된다.

이제 AWS 콘솔의 Auto Scaling Group에 접속해서 보면

이렇게 Desired Capacity가 4로 올라간걸 확인할 수 있다.

EC2 콘솔에는

추가로 두 개의 인스턴스를 실행하게 된다.

다시 kubectl get all 로 Pod의 개수를 확인하면

NAME                              READY   STATUS    RESTARTS   AGE
pod/php-apache-7dff56c499-2bd95   1/1     Running   0          23m
pod/php-apache-7dff56c499-5xx5j   1/1     Running   0          23m
pod/php-apache-7dff56c499-bnjk9   1/1     Running   0          23m
pod/php-apache-7dff56c499-dh9gp   1/1     Running   0          23m
pod/php-apache-7dff56c499-dqsd2   0/1     Pending   0          23m
pod/php-apache-7dff56c499-h6l7f   1/1     Running   0          23m
pod/php-apache-7dff56c499-hqs2v   1/1     Running   0          23m
pod/php-apache-7dff56c499-kgt8r   1/1     Running   0          23m
pod/php-apache-7dff56c499-kpm8b   0/1     Pending   0          23m
pod/php-apache-7dff56c499-kzwp5   1/1     Running   0          56m
pod/php-apache-7dff56c499-lb2wd   0/1     Pending   0          23m
pod/php-apache-7dff56c499-lpjc8   0/1     Pending   0          23m
pod/php-apache-7dff56c499-mvxlr   1/1     Running   0          23m
pod/php-apache-7dff56c499-psqpt   1/1     Running   0          56m
pod/php-apache-7dff56c499-r7nfs   1/1     Running   0          23m
pod/php-apache-7dff56c499-rngtb   0/1     Pending   0          23m
pod/php-apache-7dff56c499-sls5m   1/1     Running   0          23m

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   72m

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/php-apache   12/17   17           12          56m

이렇게 총 12개의 파드만 정상적으로 실행되는것을 확인할 수 있다. 그렇다면 인스턴스 당 총 3개의 php-apache 파드를 생성한 것이다.

사실 최초에 생각했던건 cpu값 500m(0.5vCPU)의 파드를 2vCPU의 사양을 갖는 t3.medium에 총 4개가 배포될 것이라고 예상하여 max capacity 4개중 3개의 EC2 인스턴스만 생성 될 것으로 예측하였지만 실제로는 12개만 생성되고 나머지 5개는 계속 Pending에 머물렀다.

총 2vCPU의 사양을 가지고 있지만 0.5vCPU 4개의 파드를 전부 실행시키지 못하는건 아마 EC2 자체에서 사용해야 하는 CPU가 있기 때문에 딱 떨어지게 실행을 못하는 것으로 보인다.(당연하게도…)

참고 및 출처
https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/
[AWS EKS-연재5] AWS EKS 의 Cluster Autoscaler 설정
EKS클러스터 Karpenter 적용기

profile
기억하기 위해 혹은 잊어버리기 위해 글을 씁니다.

0개의 댓글

관련 채용 정보