Kubernetes

EKS-NodeLess-05-AWS-Karpenter-component

이제야 드디어 Karpenter까지 왔다. Karpenter의 구성요소부터 살펴보자!

  1. PodDisruptionBudget: PodDisruptionBudget은 클러스터의 안정성을 보장하기 위해 사용된다. 특정 서비스를 중단하지 않고 동시에 종료할 수 있는 Pod의 최대 수를 지정한다.
  2. ServiceAccount: Karpenter가 동작하려면 해당 권한을 가진 Kubernetes의 ServiceAccount가 필요하다. ServiceAccount는 Kubernetes 리소스에 대한 API 접근 권한을 제공한다.
  3. Secret-webhook-cert: Karpenter의 웹훅에 사용되는 TLS 인증서를 저장하는 Secret이다. 이를 통해 웹훅이 안전하게 통신할 수 있다.
  4. ConfigMap-logging: Karpenter의 로깅 설정을 저장하는 ConfigMap이다. 로깅 수준, 출력 형식 등을 지정할 수 있다.
  5. ConfigMap: Karpenter의 기본 설정을 저장하는 ConfigMap입니다. 예를 들어, 프로비저닝 로직과 관련된 설정을 저장할 수 있다.
  6. RBAC-related components: Karpenter의 동작에 필요한 권한을 정의하는 역할(Role), 클러스터 역할(ClusterRole), 역할 바인딩(RoleBinding), 클러스터 역할 바인딩(ClusterRoleBinding) 등이 포함된다. 이러한 리소스들은 Karpenter가 Kubernetes API를 사용하여 필요한 작업을 수행할 수 있도록 한다.
  7. Service: Karpenter 컨트롤러의 API를 노출하는 Kubernetes Service이다. 이를 통해 웹훅 및 기타 클라이언트가 Karpenter에 접속할 수 있다.
  8. Deployment: Karpenter의 컨트롤러 구성 요소를 정의하는 Deployment이다. 컨트롤러는 Karpenter의 주요 기능을 실행하는 프로세스이다.
  9. Webhooks: Karpenter는 웹훅을 사용하여 다양한 요청을 처리하고, 배포 전에 검증 또는 변형 작업을 수행한다. 이 웹훅을 설정하는 데 사용되는 리소스가 여기에 포함되어 있다.

이컴포넌트들은 helm 명령어로 karpenter의 template 을 outout 했을때 나온 manifast를 나열한것이다. 이 9가지의 컴포넌트 외에 두개의 CRD - CustomResourceDefinition - 가 있다.

  1. provisioners.karpenter.sh CRD는 Karpenter가 새로운 노드를 프로비저닝할 때 사용하는 정책을 정의한다. 이 CRD는 클러스터의 노드를 동적으로 스케일링하기 위한 설정을 포함하며, 예를 들어 어떤 유형의 인스턴스를 사용할지, 어떤 가용 영역에서 노드를 프로비저닝할지, 최대 노드 수는 얼마인지 등의 정보를 포함할 수 있다.
    Provisioner 오브젝트는 Karpenter에게 필요한 리소스 요구사항을 알려주고, Karpenter는 이 정보를 사용하여 클러스터를 효과적으로 관리하고 노드를 프로비저닝한다. Provisioner는 공급 업체, 가용 영역, 인스턴스 유형, 노드 수량 등에 대한 세부 정보를 포함하여 워크로드 요구사항에 가장 적합한 노드를 선택하는 데 도움이 된다.
    Karpenter는 이러한 Provisioner 오브젝트를 감시하고, 워크로드의 요구사항에 따라 적절한 시기에 새 노드를 프로비저닝한다. 수동으로 노드를 추가하거나 제거할 필요가 없다.
  2. awsnodetemplates.karpenter.k8s.aws CRD는 Karpenter가 AWS 클러스터에서 노드를 프로비저닝할 때 사용 한다. 이 CRD는 AWS에서 노드를 프로비저닝하는 데 필요한 세부 정보와 구성을 제공한다. 예를 들자면, 사용할 EC2 인스턴스 유형, 가용 영역, 보안 그룹, IAM 역할, 사용자 데이터, 노드 그룹 레이블 등의 정보가 포함될 수 있다. awsnodetemplates 를 사용하면 Karpenter가 AWS에서 노드를 프로비저닝하는 방법을 세밀하게 제어하고 구성할 수 있다. 이를 통해 클러스터의 노드가 클러스터의 워크로드 요구사항과 AWS의 특정 요구사항에 가장 잘 맞도록 조정할 수 있다.

한번 그럼 다이어그램으로 그려 봤다.

  1. 파드 스케줄링 요청: 파드가 생성되면 쿠버네티스 스케줄러는 이를 적합한 노드에 스케줄링하려고 시도한다. 만약 충분한 리소스를 가진 노드가 없다면, 파드는 Pending 상태가 된다.
  2. 파드 요구사항 분석: Karpenter는 Pending 상태의 파드를 주기적으로 검사하여 각 파드의 요구사항을 분석한다. 요구사항에는 CPU, 메모리, GPU, EBS 등의 리소스 요구사항이 포함될 수 있다.
  3. 프로비저너 선택: Karpenter는 파드 요구사항에 가장 적합한 프로비저너를 선택한다. 프로비저너는 provisioners.karpenter.sh CRD에 의해 정의되며, 어떤 유형의 노드를 프로비저닝할지, 어떤 가용 영역에서 노드를 프로비저닝할지 등의 정책을 포함한다.
  4. 노드 템플릿 선택: 선택된 프로비저너는 적합한 노드 템플릿을 선택하거나 생성한다. 노드 템플릿은 awsnodetemplates.karpenter.k8s.aws와 같은 클라우드 공급자 특정 CRD에 의해 정의될 수 있으며, 사용할 EC2 인스턴스 유형, 보안 그룹, IAM 역할 등의 세부 정보를 포함할 수 있다.
  5. 노드 프로비저닝: 선택된 노드 템플릿에 기초하여 새 노드가 프로비저닝된다. 이 과정은 클라우드 공급자 API를 사용하여 수행한다.
  6. 노드 등록: 프로비저닝된 노드는 쿠버네티스 클러스터에 등록된다
  7. 파드 러닝: 스케줄러는 Pending 상태의 Pod를 노드에 스케줄링하여 노드는 Runing 상태로 변경된다.

컴포넌트를 설명했다. 이 다음은 이제 설치를 진행해 보겠다.

EKS-NodeLess-04-AWS-LoadBalancer-Controller

AWS LoadBalancer Controller 도 Fargate에 올려야 한다.

비교적 간단한데, Controller Pod에 annotations 한줄만 추가하면된다.

먼저 추가할때 Policy 를 생성한다.

VPC_NAME=myeks-VPC // VPC NAME 으로 변경
CLUSTER_NAME=myeks
REGION=ap-northeast-2
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/install/iam_policy.json

aws iam create-policy \
    --policy-name AWSLoadBalancerControllerIAMPolicy \
    --policy-document file://iam_policy.json

POLICY_ARN=`aws iam list-policies | grep Arn | grep AWSLoadBalancerControllerIAMPolicy | awk -F\" '{print $4}'`
VPC_ID=$(aws ec2 describe-vpcs --query 'Vpcs[?contains(Tags[?Key==`Name`].Value[], `'$VPC_NAME'`) == `true`].[VpcId]' --output text)

이 Policy를 이용하여 eksctl에서 사용할거다. POLICY_ARN은 전체 Policy 에서 AWSLoadBalancerControllerIAMPolicy 의 arn을 추출한다.

eksctl create iamserviceaccount \
  --cluster=myeks \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --role-name AmazonEKSLoadBalancerControllerRole \
  --attach-policy-arn=$POLICY_ARN \
  --approve
  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 \
  --set region=$REGION \
  --set vpcId=$VPC_ID

로 보통 AWS LoadBalancer Controller 를 설치해 줘야 하지만 우리는 Fargate annotations 추가 한단계를 더 거쳐야 한다.

kubectl patch deployment aws-load-balancer-controller -n kube-system --type=json -p='[{"op": "add", "path": "/spec/template/metadata/annotations/eks.amazonaws.com~1fargate-profile", "value":"kube-system"}]'
kubectl rollout restart deployment aws-load-balancer-controller -n kube-system

다음과 같이 patch 를 하고 rollout restart 까지 하면 pod가 Fargate 로 생성된다.

k get pod -o wide
NAME                                            READY   STATUS    RESTARTS   AGE     IP               NODE                                                        NOMINATED NODE   READINESS GATES
aws-load-balancer-controller-76db948d9b-qzpsd   1/1     Running   0          46s     192.168.11.65    fargate-ip-192-168-11-65.ap-northeast-2.compute.internal    <none>           <none>
aws-load-balancer-controller-76db948d9b-t26qt   1/1     Running   0          88s     192.168.11.180   fargate-ip-192-168-11-180.ap-northeast-2.compute.internal   <none>           <none>

시간날때마다 Fargate 로 추가되어야 하는 에드온들을 Fargate 로 생성하는 방법을 작성하겠다.

읽어줘서 감사하다!

EKS-NodeLess-03-Karpenter-01-intro

NodeLess 컨셉에서 제일 중요한 역할을 맡고 있는 Karpenter 다.

Karpenter 의 기본적인 아키텍처 부터 리뷰해볼까 한다. 그렇다면 그전에 Cluster Autoscaler 부터 설명해야 한다.

Cluster Autoscaler 는 보통 CA라 부른다. 간략하게 플로우를 설명하겠다.

  1. Kubernetes에 새로운 Pod 가 프로비저닝 되었을때 Pod는 노드그룹에 스케줄링 된다.
  2. 노드그룹에 자원이 부족하면 CA가 트리거 된다.
  3. CA는 AWS 의 ASG에 새로운 로드를 요청한다.
  4. ASG는 새로운노드를 생성하고 노드그룹에 추가한다.
  5. 새로 스케줄링된 노드에 Pod가 생성된다.

생략된 단계가 있지만 실제로 이 단계를 모두 거쳐야 인스턴스가 EKS에 연결되고 노드그룹에 인스턴스가 노출된다. 그렇다면 단순히 ASG에서 노드를 제거해본 경험이 있는가? 있다면 알것이다. 이건 가끔 커피한잔하고 와도 제거안된 인스턴스가 있는 경우도 있다.

ASG가 나쁜건 아니다. 반응성이 좀 떨어질 뿐이다.
하지만, Kubernetes 에 어울리는 솔루션은 아니라 생각했다.

그렇다면 Kerpenter 의 간략한 플로우는 어떨까?

  1. default-scheduler 는 Karpenter API에 새 파드를 요청한다.
  2. Karpenter 컨트롤러는 Pod를 프로비저닝 할수 있는 노드를 찾는다.
  3. 노드가 없으면 Karpenter는 AWS SDK를 이용하여 AWS에 새 노드를 요청한다.
  4. AWS는 새노드를 생성하고 새노드가 프로비저닝 되면 클러스터에 Join 하게 된다
  5. 노드가 추가되면 Karpenter는 Node를 감지하여 Kubernetes scheduler에 Node 준비를 알린다.
  6. Kubernetes scheduler 는 Pod를 프로비저닝 한다

조금더 상세한 내용을 추가해서 설명했는데, 실제로는 노드그룹이나 CA ASG같은게 빠지고 노드에 대한 부분은 카펜터가 모두 AWS SDK로 컨트롤 한다. 여러 단계들을 제거함으로 빨라진것이다.

EKS-NodeLess-02-Fargate

# k get pod -A -o wide
NAMESPACE     NAME                         READY   STATUS    RESTARTS   AGE    IP               NODE                                                        NOMINATED NODE   READINESS GATES
default       nginx-pod                    1/1     Running   0          11m    192.168.12.217   ip-192-168-12-150.ap-northeast-2.compute.internal           <none>           <none>
karpenter     karpenter-5bffc6f5d8-6f779   1/1     Running   0          125m   192.168.12.99    fargate-ip-192-168-12-99.ap-northeast-2.compute.internal    <none>           <none>
karpenter     karpenter-5bffc6f5d8-84mjn   1/1     Running   0          130m   192.168.11.201   fargate-ip-192-168-11-201.ap-northeast-2.compute.internal   <none>           <none>
kube-system   aws-node-h5z8d               1/1     Running   0          11m    192.168.12.150   ip-192-168-12-150.ap-northeast-2.compute.internal           <none>           <none>
kube-system   coredns-fd69467b9-4nk6x      1/1     Running   0          127m   192.168.12.52    fargate-ip-192-168-12-52.ap-northeast-2.compute.internal    <none>           <none>
kube-system   coredns-fd69467b9-cqqpq      1/1     Running   0          125m   192.168.11.122   fargate-ip-192-168-11-122.ap-northeast-2.compute.internal   <none>           <none>
kube-system   kube-proxy-z8qlj             1/1     Running   0          11m    192.168.12.150   ip-192-168-12-150.ap-northeast-2.compute.internal           <none>           <none>

먼저 예제를 보여준다.
NodeLess EKS 컨셉의 기반이다. nginx-pod / aws-node-h5z8d / kube-proxy-z8qlj 는 카펜터가 만든 노드위에 올라가 있다.

NodeLess 의 컨셉은 두가지를 기반으로 한다.

쿠버네티스 컴포넌트 kube-system namespace Pod들은 Fargate에 올린다.
여기에 에드온이나 관리가 필요한 Pod도 포함된다. karpenter controller라던가..AWS ELB Controller 라던가 그런 에드온들이 그런 역할을 한다.

Node가 필요한 Pod는 NodeGroup을 사용하지 않고 Karpenter를 사용한다.

그럼 NodeGroup이 없는 클러스터부터 만드는 방법이다.

https://eksctl.io/usage/fargate-support/

eksctl create cluster --fargate

간단하다 옵션으로 --fargate를 주면된다.

Fargate profile 같은경우에는 사실 콘솔에서 손으로 만들면 편하다. subnet이나 iam role 넣어주는게....그렇지 않다면 먼저 aws cli 부터 학습해야 한다. eksctl이 자동으로 해주는 부분도 있지만 필수요소는 알아야 하기 때문이다.

https://awscli.amazonaws.com/v2/documentation/api/latest/reference/eks/create-fargate-profile.html

aws eks create-fargate-profile --fargate-profile-name kube-system --cluster-name myeks --pod-execution-role-arn arn:aws:iam::123456789:role/AmazonEKSFargatePodExecutionRole --subnets "subnet-1" "subnet-2" "subnet-3"

https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/pod-execution-role.html 역할생성은 이링크를 참고한다.

이런식으로 만들고 파게이트는 네임스페이스로 지정하면 네임스페이스에 만들어지는 톨레이션이나 다른 노드 어피니티등을 가지지 않은 Pod를 Fargate로 프로비저닝 한다. 이때 일반적인 쿠버네티스와 다른 부분은 Fargate 스케줄러(fargate-scheduler)가 별도로 동작하여 Fargate를 프로비저닝한다. 일반적인 경우엔 (default-scheduler)가 Pod를 프로비저닝 한다.

이 차이를 알아두면 어떤 노드를 물고있는지 확인하기 편하다.

EKS-NodeLess-01-CoreDNS

EKS의 관리영역중 Addon 이나 필수 컴포넌트중에 Node에서 동작해야하는 것들이 있다. 이 경우에 NodeGroup을 운영해야한다. NodeGroup에 여러 파드들이 스케줄링되고 관리형 Pod들은 다른 서비스에 운영되는 NodeGroup과 섞여서 스케줄리되어야 하는데, 이것의 가장큰 문제는 Node의 사망이 기능의 장애로 이어진다는 점이다. 따라서 Node를 전용 Node로 사용하면 좋은데 아주작은 노드를 스케줄링한다고 해도 관리되어야 하는 대상이 됨은 틀림없고, 노드를 정해서 사용해야 하는 문제점들이 생기게된다.

이러한 문제를 해결하기에 EKS에서는 Fargate가 있다. 1Node - 1Pod 라는게 아주 중요한 포인트다.

CoreDNS는 클러스터에 최저 2개의 Pod가 스케줄링되어야 한다.

https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/fargate-profile.html

eksctl를 사용하여 Fargate 프로파일을 생성하려면 다음 eksctl 명령으로 Fargate 프로파일을 생성하고 모든 example value를 고유한 값으로 바꿉니다. 네임스페이스를 지정해야 합니다. 그러나 --labels 옵션은 필요하지 않습니다.

eksctl create fargateprofile \
    --cluster my-cluster \
    --name kube-system \
    --namespace kube-system

다음과 같이 생성해 주면된다. 그럼 kube-system namespace로 스케줄링되는 Pod는 Fargate로 생성되게 된다.

그다음은 CoreDNS를 패치하고 재시작하면된다.

https://docs.aws.amazon.com/ko_kr/prescriptive-guidance/latest/patterns/deploy-coredns-on-amazon-eks-with-fargate-automatically-using-terraform-and-python.html

kubectl patch deployment coredns -n kube-system --type=json -p='[{"op": "remove", "path": "/spec/template/metadata/annotations", "value": "eks.amazonaws.com/compute-type"}]'
kubectl rollout restart -n kube-system deployment coredns

이렇게 진행하면 CoreDNS를 Fargate로 실행하게 된다.

 k get pod -o wide
NAME                      READY   STATUS    RESTARTS   AGE     IP              NODE                                                       NOMINATED NODE   READINESS GATES
coredns-fd69467b9-bsh88   1/1     Running   0          5h18m   192.168.13.23   fargate-ip-192-168-13-23.ap-northeast-2.compute.internal   <none>           <none>
coredns-fd69467b9-gn24k   1/1     Running   0          5h18m   192.168.12.34   fargate-ip-192-168-12-34.ap-northeast-2.compute.internal   <none>           <none>

다음과 같이 스케줄링되면 정상적으로 배포 된것이다.