[EKS] AutoScaling (Karpenter)

이정훈·2023년 7월 16일
0

EKS

목록 보기
8/9
post-thumbnail

1. 환경변수 설정

export CLUSTER_NAME=[EKS Cluster Name]

# 실습 기준 버전 v0.29.0 -> export KARPENTER_VERSION=v0.29.0
export KARPENTER_VERSION=[Karpenter Version]

export AWS_DEFAULT_REGION="ap-northeast-2"

# CLUSTER_ENDPOINT는 EKS Cluster 개요 탭의 API 서버 엔드포인트와 동일한지 확인
export CLUSTER_ENDPOINT="$(aws eks describe-cluster --name ${CLUSTER_NAME} --query "cluster.endpoint" --output text)"

export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)

export TEMPOUT=$(mktemp)

# 변수에 값이 잘 저장되어 있는지 확인
echo $CLUSTER_NAME $KARPENTER_VERSION $AWS_DEFAULT_REGION $CLUSTER_ENDPOINT $AWS_ACCOUNT_ID $TEMPOUT

2. Node IAM Role & Controller Policy 생성

  • 웹에서 자원 생성에 필요한 cloudformation 소스를 받아오는데, 혹시 소스를 구하기 어려운 경우 아래 파일을 활용하자
    cloudformation.yaml

  • IAM - Role에 KarpenterNodeRole-[EKS CLUSTER 이름] 형식의 Role이 생성된다

curl -fsSL https://raw.githubusercontent.com/aws/karpenter/"${KARPENTER_VERSION}"/website/content/en/preview/getting-started/getting-started-with-karpenter/cloudformation.yaml  > $TEMPOUT \
&& aws cloudformation deploy \
  --stack-name "Karpenter-${CLUSTER_NAME}" \
  --template-file "${TEMPOUT}" \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameter-overrides "ClusterName=${CLUSTER_NAME}"
  • 위 명령어가 제대로 실행되면 cloudformaiton에 스택이 생성되고, IAM Role이 함께 생성된다.
    생성된 ContrallerRole의 ARN을 환경 변수에 저장한다

3. Cluster IAM Role 생성

  • IAM Role을 생성한다. 여기서 생성하는 Role은 EKS의 Service Account와 연동하기 위한 Role이다 줄여서 IRSA(IAM Roles for Service Account)라고 한다
  • IRSA를 생성하기 위해서는 OIDC 공급자가 필요하다. OIDC 공급자는 향후 생성할 ServiceAccount가 AWS Service에 접근할 수 있도록 인증을 도와준다
  • 이 단계에서 생성한 IRSA는 EKS 클러스터에 존재하는 특정한 Service Account가 AWS에 접근할 때 사용되며, K8s의 특정한 서비스가 동작할 때 필요한 AWS의 권한 정책을 연결해주면 된다
  • IRSA가 정상적으로 생성되면, K8s의 ServiceAccount에 대한 신뢰 정책이 구성된다

3-1 OIDC 공급자 생성

  • 기존에 OIDC 공급자가 생성되어 있다면 건너뛰어도 된다
    eksctl utils associate-iam-oidc-provider --cluster ${CLUSTER_NAME} --approve

3-2 IRSA 생성

  • Service Account를 위한 IAM Role 생성
eksctl create iamserviceaccount \
  --cluster "${CLUSTER_NAME}" --name karpenter --namespace karpenter \
  --role-name "KarpenterControllerRole-${CLUSTER_NAME}" \
  --attach-policy-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${CLUSTER_NAME}" \
  --role-only \
  --approve

# --role-only 옵션에 의해 Service Account는 생성하지 않고, IAM Role만 생성된다
# --approve 옵션은 OIDC 공급자와의 연결을 자동으로 승인한다
# IAM OIDC 공급자를 생성하면, 사용자는 이를 명시적으로 승인해야 한다
# K8s Service Account가 IAM 역할을 사용하도록 허용하는데, 이 과정을 자동으로 승인하게 해준다

4. Subnet, Security Group에 태그 추가

  • 이 단계는 Karpenter가 생성한 인스턴스를 배치하고 적용할 서브넷과 보안그룹을 인식할 수 있도록 태그를 부착한다

태그의 Key, Value는 서브넷, 보안그룹에 동일하게 적용한다

keyValue
karpenter.sh/discoveryKarpenter를 활용할 EKS 클러스터 이름

  • 아래 내용은 태그 부탁 과정을 자동으로 해주는 코드로, 상황에 따라 사용하자
# Subnet
for NODEGROUP in $(aws eks list-nodegroups --cluster-name ${CLUSTER_NAME} \
    --query 'nodegroups' --output text); do aws ec2 create-tags \
        --tags "Key=karpenter.sh/discovery,Value=${CLUSTER_NAME}" \
        --resources $(aws eks describe-nodegroup --cluster-name ${CLUSTER_NAME} \
        --nodegroup-name $NODEGROUP --query 'nodegroup.subnets' --output text )
done
# 복사 & 붙여넣기는 한 블럭씩 차례대로
# Security Group
NODEGROUP=$(aws eks list-nodegroups --cluster-name ${CLUSTER_NAME} \
    --query 'nodegroups[0]' --output text)
 
LAUNCH_TEMPLATE=$(aws eks describe-nodegroup --cluster-name ${CLUSTER_NAME} \
    --nodegroup-name ${NODEGROUP} --query 'nodegroup.launchTemplate.{id:id,version:version}' \
    --output text | tr -s "\t" ",")
 
# EKS에 적용된 보안 그룹이 단일로 구성되어 있을 경우
SECURITY_GROUPS=$(aws eks describe-cluster \
    --name ${CLUSTER_NAME} --query cluster.resourcesVpcConfig.clusterSecurityGroupId | tr -d '"')
 
# Managed node group Launch template 사용한 경우..? 무슨 말인지 잘 모르겠다
SECURITY_GROUPS=$(aws ec2 describe-launch-template-versions \
    --launch-template-id ${LAUNCH_TEMPLATE%,*} --versions ${LAUNCH_TEMPLATE#*,} \
    --query 'LaunchTemplateVersions[0].LaunchTemplateData.[NetworkInterfaces[0].Groups||SecurityGroupIds]' \
    --output text)
 
# 공통 적용
aws ec2 create-tags \
    --tags "Key=karpenter.sh/discovery,Value=${CLUSTER_NAME}" \
    --resources ${SECURITY_GROUPS}

5. aws-auth ConfigMap 수정

  • Karpenter로 생성한 인스턴스 노드가 EKS Cluster에 접근할 수 있도록 Node IAM Role에 EKS 접근 권한을 지정해준다
eksctl create iamidentitymapping \
  --username system:node:{{EC2PrivateDNSName}} \
  --cluster  ${CLUSTER_NAME} \
  --arn arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME} \
  --group system:bootstrappers \
  --group system:nodes
  • 정상적으로 등록될 경우, aws-auth에 아래와 같이 NodeRole의 권한 정책이 적용된다

6. Karpenter 배치

  • EKS에 Karpenter 자원을 배치시킨다
# helm을 활용해 karpenter 생성에 사용할 yaml 파일 작성
helm template karpenter oci://public.ecr.aws/karpenter/karpenter --version ${KARPENTER_VERSION} --namespace karpenter\
    --set settings.aws.defaultInstanceProfile=KarpenterNodeInstanceProfile-${CLUSTER_NAME} \
    --set settings.aws.clusterEndpoint="${CLUSTER_ENDPOINT}" \
    --set settings.aws.clusterName=${CLUSTER_NAME} \
    --set settings.aws.interruptionQueueName=${CLUSTER_NAME} \
    --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=${KARPENTER_IAM_ROLE_ARN} \
    --wait > karpenter.yaml

# karpenter를 배치할 NameSpace 생성
kubectl create ns karpenter

# karpenter 동작에 필요한 요소 구성
kubectl create -f https://raw.githubusercontent.com/aws/karpenter/${KARPENTER_VERSION}/pkg/apis/crds/karpenter.sh_provisioners.yaml
kubectl create -f https://raw.githubusercontent.com/aws/karpenter/${KARPENTER_VERSION}/pkg/apis/crds/karpenter.sh_machines.yaml
kubectl create -f https://raw.githubusercontent.com/aws/karpenter/${KARPENTER_VERSION}/pkg/apis/crds/karpenter.k8s.aws_awsnodetemplates.yaml

# karpenter 설치
kubectl apply -f karpenter.yaml
  • 정상적으로 설치가 완료됐다면, 아래와 같이 karpenter Pod가 Running 상태로 생성되어야 한다
    kubectl get pod -n karpenter

7. Provisioner 생성

  • Karpenter가 배치할 인스턴스 노드의 타입을 결정하고, 생성될 서브넷, 보안그룹 등을 지정한다

  • 여기서 Provisioner의 spec.providerRef 필드는 Provisioner에 공통적으로 적용할 내용을 입력한 클라우드 공급자(NodeTemplate)를 참조할 때 사용하고, 만약 직접 클라우드 공급자를 참조하는 형식이 아닌 직접 지정하고자 하는 경우는 provider 필드를 사용한다

  • 만약 requirements 필드에 karpenter.sh/capacity-type 키의 값을 spot, on-demand 두 개 다 줄 경우 가능한 spot 타입으로 띄우려고 한다(Spot을 사용할 경우 가능한 다양한 인스턴스를 쓰는 것이 유리하다)

-Provisioner의 Limits 필드는 CPU, Memory는 최대 인스턴스를 제한하는 데 사용한다. 병렬적으로 증설되기 때문에 약간의 OverCommit이 될 수 있다.

-인스턴스 개수 기반으로 제한을 걸지 못하는 이유는 다양한 인스턴스가 띄워질 수 있기 때문..

-BP는 중간 인스턴스 타입의 CPU, Memory Spec * 원하는 대수로 리소스 제한을 거는 걸 추천

-실제로 주어지는 자원의 양은 requests로 설정

cat << EOF | kubectl apply -f -
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
spec:
  requirements:
    - key: karpenter.k8s.aws/instance-category
      operator: In
      values: [c, m, r]
    - key: karpenter.k8s.aws/instance-generation
      operator: Gt
      values: ["2"]
    - key: kubernetes.io/arch
      operator: In
      values: [ "amd64" ]
    - key: karpenter.sh/capacity-type
      operator: In
      values: ["spot", "on-demand"]
    - key: topology.kubernetes.io/zone
      operator: In
      values: ["ap-northeast-2a", "ap-northeast-2c"]
    - key: karpenter.k8s.aws/instance-cpu
      operator: In
      values: ["4", "8", "16", "32"]
    - key: karpenter.k8s.aws/instance-hypervisor
      operator: In
      values: ["nitro"]
limits:
  resources:
    cpu: 1000
    memory: 1000Gi
  providerRef:
    name: default
  provider:
    deviceName: /dev/xvda
    ebs:
      deleteOnTermiation: true
      volumeSize: 100Gi
      volumeType: gp3
  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}"
  tags:
    karpenter.sh/discovery: "${CLUSTER_NAME}"
EOF

8. (선택) Karpenter 동작 테스트

  • Karpenter가 정상적으로 동작하는지 테스트하기 위해 테스트 어플리케이션을 EKS에 배포하고, busybox 이미지를 이용해 부하를 발생 시킨다
# 테스트를 위한 어플리케이션 php-apache 배포
kubectl apply -f https://k8s.io/examples/application/php-apache.yaml

# Pod AutoScaling을 위한 HPA 구성
# 빠른 확장을 위해 CPU 기준을 30%로 설정
kubectl autoscale deployment php-apache --cpu-percent=30 --min=1 --max=50

# 테스트를 위한 부하 발생 어플리케이션
# 0.01초 마다 php-apache 서비스로 Request 송신
# default namespace에 load-generator 파드가 생성 및 동작
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"

#위 명령을 우리 프로젝트에 적합하게 변경하면 아래와 같다
kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.005; do if wget -q -O- http://houstagram.com > /dev/null; then echo 'Request was successful'; else echo 'Request failed'; fi; done"
  • 명령어로 autoScaling이 제대로 적용되었는지 확인
    kubectl get hpa -n [namespace]
  • TARGETS에 사용률이 으로 나오는 경우는 metric-server 관련 자원이 설치되지 않았거나, 아직 필요한 만큼의 메트릭 수집이 이루어지지 않았기 때문이다
  • Pod에 자원 사용 제한이 걸려있지 않은 경우에도 hpa가 작동하지 않는다
    kubectl get deployment metrics-server -n kube-system
  • 명령어로 metrics-server가 설치되어 있는지 확인해보고, 설치가 안되어 있을 경우 metric server 자원을 생성해주자
    kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
  • 자원 사용률 조회는 다음 명령어로 할 수 있다

kubectl top pods

kubectl top nodes

kubectl get hpa -n [namespace]

9. pod와 node가 실시간으로 늘어나는 지 확인하기 위해 kube-ops-view를 설치하고자 한다.

  • 먼저 helm을 설치한다
    curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
    helm version --short #버전확인

  • 그리고 아래 명령어로 설치해주면 끝이다!

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"}}'
  • 우린 기존에 alb controller, ingress가 설치되어있기 때문에 loadbalancer만 변경해주면 들어갈 수 있다

  • 포트 번호는 8080 입니다!!

profile
싱숭생숭늉

0개의 댓글