karpenter

EKS-NodeLess-08-AWS-Karpenter-topologySpreadConstraints

topologySpreadConstraints 을 사용한 Karpenter 테스트를 진행하겠다.

topologySpreadConstraints 테스트는 Topology Aware Hint 를 예비한 테스트다.

https://aws.amazon.com/ko/blogs/tech/amazon-eks-reduce-cross-az-traffic-costs-with-topology-aware-hints/

참고할 부분이 있다면 이 글을 참고하길 바란다.

간략하게 설명하자면 Kubernetes 에서 Cross Zone Traffic 의 문제로 비용이 막대하게 발생할수 있다. 또한 Cross-AZ로 인하여 약간의 레이턴시가 발생할수도 있기때문에 Topology Aware Hint는 여러 문제점들을 줄여주는 역할을 한다.

조건은 몇가지가 있는데, Service 로 연결되 AZ가 수평적으로 동일하게 노드가 배포되어있고 서비스에

apiVersion: v1
kind: Service
metadata:
  name: service
  annotations:
    service.kubernetes.io/topology-aware-hints: auto

다음과 같은 annotations 붙어있어야 한다.

그럼먼저 우리는 Provisioner가 자동으로 노드를 Deprovisioning 하도록 설정하자.

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
spec:
  consolidation:
    enabled: true
  requirements:
    - key: karpenter.k8s.aws/instance-category
      operator: In
      values: [ t, m, c ]
  providerRef:
    name: default
---
apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
  name: default
spec:
  subnetSelector:
    karpenter.sh/discovery: "${CLUSTER_NAME}"
  securityGroupSelector:
    karpenter.sh/discovery: "${CLUSTER_NAME}"

consolidation enabled 옵션은 Pod 의 리소스 요구조건에 따라서 Karpenter 가 알아서 노드를 스케줄링한다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: host-spread
spec:
  replicas: 20
  selector:
    matchLabels:
      app: host-spread
  template:
    metadata:
      labels:
        app: host-spread
    spec:
      containers:
      - image: public.ecr.aws/eks-distro/kubernetes/pause:3.2
        name: host-spread
        resources:
          requests:
            cpu: "1"
            memory: 256M
      topologySpreadConstraints:
      - labelSelector:
          matchLabels:
            app: host-spread
        maxSkew: 2
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule
      - labelSelector:
          matchLabels:
            app: host-spread
        maxSkew: 5
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule

topologyKey: kubernetes.io/zone maxSkew: 2는 하나의 호스트 즉 노드간 파드의 차이는 maxSkew: 2 를 초과할수 없다. topologyKey: topology.kubernetes.io/zone maxSkew: 5 zone 간 pod의 갯수는 maxSkew: 5 초과 할수 없으므로 하나의 zone에서만 pod가 스케줄링된다면 20개의 replicas 를 요청한다 해도 5개의 pod 만 스케줄링 된다.

AZ별로 제공하는 유형의 인스턴스 페일리가 달라서 특정 유형만 사용하려고 하면 프로비저닝 조건에 걸려서 스케줄링이 쉽지 않다.

karpenter 의 강력함은 테스트중에 확인할수 있는데, 42s 만에 Pod 가 Running 된다. 42초안에 Node도 프로비저닝 된다는 말이다.

2023-05-20T11:03:34.806Z	ERROR	controller.provisioner	Could not schedule pod, incompatible with provisioner "default", no instance type satisfied resources {"cpu":"1","memory":"256M","pods":"1"} and requirements karpenter.k8s.aws/instance-category In [c m t], kubernetes.io/os In [linux], kubernetes.io/arch In [amd64], karpenter.sh/provisioner-name In [default], karpenter.sh/capacity-type In [on-demand], topology.kubernetes.io/zone In [ap-northeast-2a]	{"commit": "d7e22b1-dirty", "pod": "default/host-spread-fbbf7c9d9-x4lfd"}

topologySpreadConstraints 옵션을 테스트하면서 느꼈는데, 여러 요인들로 잘 스케줄링하지 못한다.

두가지 조건에 의해서 pod는 모두다 스케줄링 되지 못하는데, 노드를 스케줄링하지 못해서 걸리기도 한다. 조건은 확실히 걸리긴한다.

k get node --show-labels | grep -v fargate | awk -F"topology.kubernetes.io/" '{print $3}' | sort
zone=ap-northeast-2a
zone=ap-northeast-2a
zone=ap-northeast-2a
zone=ap-northeast-2b
zone=ap-northeast-2b
zone=ap-northeast-2b
zone=ap-northeast-2c

다음과같이 노드가 a 3대 b 3대 c 1대 스케줄링 되면 모두 14개의 pod가 스케줄링된다. C zone때문에 두번째 조건에 걸리기 때문이다. 다양한 조건을 사용하면 이와 같이 균등하게 zone 에 스케줄링 하긴 어려운점이 있다. 적당히 조건을 걸어주면 잘 작동한다.

      - labelSelector:
          matchLabels:
            app: host-spread
        maxSkew: 2
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule

이 조건을 삭제하면 topology.kubernetes.io/zone maxSkew 5 만 남겨서 프로비저닝 해보면 비대칭으로 az 에 node가 프로비저닝 되지만 조건에만 맞는다면 pod를 모두 생성한다.

k get pod
NAME                          READY   STATUS    RESTARTS   AGE
host-spread-dd5f6c569-49ps7   1/1     Running   0          115s
host-spread-dd5f6c569-8772p   1/1     Running   0          115s
host-spread-dd5f6c569-9q2hn   1/1     Running   0          115s
host-spread-dd5f6c569-b68k2   1/1     Running   0          115s
host-spread-dd5f6c569-bfhv5   1/1     Running   0          115s
host-spread-dd5f6c569-bqqz2   1/1     Running   0          116s
host-spread-dd5f6c569-bsp8m   1/1     Running   0          115s
host-spread-dd5f6c569-dh8wx   1/1     Running   0          115s
host-spread-dd5f6c569-ffjdg   1/1     Running   0          115s
host-spread-dd5f6c569-jghmr   1/1     Running   0          115s
host-spread-dd5f6c569-jhbxg   1/1     Running   0          116s
host-spread-dd5f6c569-kf69q   1/1     Running   0          115s
host-spread-dd5f6c569-ksktv   1/1     Running   0          115s
host-spread-dd5f6c569-lbqmv   1/1     Running   0          115s
host-spread-dd5f6c569-mbf2g   1/1     Running   0          116s
host-spread-dd5f6c569-pd92p   1/1     Running   0          115s
host-spread-dd5f6c569-pgphc   1/1     Running   0          115s
host-spread-dd5f6c569-ph59g   1/1     Running   0          115s
host-spread-dd5f6c569-sdp7d   1/1     Running   0          115s
host-spread-dd5f6c569-tf8v9   1/1     Running   0          115s
(user-linuxer@myeks:default) [root@myeks-bastion-EC2 EKS]# k get node --show-labels | grep -v fargate | awk -F"topology.kubernetes.io/" '{print $3}' | sort
zone=ap-northeast-2a
zone=ap-northeast-2a
zone=ap-northeast-2a
zone=ap-northeast-2b
zone=ap-northeast-2b
zone=ap-northeast-2c

AZ 별로 균등하게 node를 프로비저닝 해야하는 방법이 필요하다.

https://github.com/aws/karpenter/issues/2572

일단 테스트한 결과와 git issues 를 보면 karpenter 가 topologySpreadConstraints 에 적절히 대응되지 않는것을 느낄수 있었다. 따라서 minDomains 옵션으로 3개의 zone을 지정도 해보았으나 썩 좋은 결과는 없었다.

따라서 다이나믹하게 Node를 프로비저닝하면서 사용할수는 없을것같고, 미리 Node를 프로비저닝 하는 구성에선 될법한데, 그건 Karpenter 의 패턴은 아니라고 느꼈다.

EKS-NodeLess-06-AWS-Karpenter-Install

이번 포스팅은 Karpenter 설치다.

설치하다 막히면 가이드를 보자.

https://karpenter.sh/v0.27.3/getting-started/migrating-from-cas/

karpenter 를 설치하기 전에 먼저 셋팅해야 할것들이 있다.

CLUSTER_NAME=myeks # your clouster name
AWS_PARTITION="aws" # aws or aws-gov or aws-cn
AWS_REGION="$(aws configure list | grep region | tr -s " " | cut -d" " -f3)"
OIDC_ENDPOINT="$(aws eks describe-cluster --name ${CLUSTER_NAME} \
    --query "cluster.identity.oidc.issuer" --output text)"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' \
    --output text)
export KARPENTER_VERSION=v0.27.3 # latast version

환경 변수 설정이다.

클러스터 이름 / 리전 / OIDC ENDPOINT / 어카운트 넘버 / karpenter 버전이 그것이다.

Karpenter 를 설치할때는 많은 권한을 요구로 한다.

echo '{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ec2.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}' > node-trust-policy.json
aws iam create-role --role-name "KarpenterNodeRole-${CLUSTER_NAME}" \
    --assume-role-policy-document file://node-trust-policy.json
aws iam attach-role-policy --role-name "KarpenterNodeRole-${CLUSTER_NAME}" \
    --policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
aws iam attach-role-policy --role-name "KarpenterNodeRole-${CLUSTER_NAME}" \
    --policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
aws iam attach-role-policy --role-name "KarpenterNodeRole-${CLUSTER_NAME}" \
    --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
aws iam attach-role-policy --role-name "KarpenterNodeRole-${CLUSTER_NAME}" \
    --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam create-instance-profile \
    --instance-profile-name "KarpenterNodeInstanceProfile-${CLUSTER_NAME}"
aws iam add-role-to-instance-profile \
    --instance-profile-name "KarpenterNodeInstanceProfile-${CLUSTER_NAME}" \
    --role-name "KarpenterNodeRole-${CLUSTER_NAME}"
cat << EOF > controller-trust-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_ENDPOINT#*//}"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "${OIDC_ENDPOINT#*//}:aud": "sts.amazonaws.com",
                    "${OIDC_ENDPOINT#*//}:sub": "system:serviceaccount:karpenter:karpenter"
                }
            }
        }
    ]
}
EOF

aws iam create-role --role-name KarpenterControllerRole-${CLUSTER_NAME} \
    --assume-role-policy-document file://controller-trust-policy.json

cat << EOF > controller-policy.json
{
    "Statement": [
        {
            "Action": [
                "ssm:GetParameter",
                "ec2:DescribeImages",
                "ec2:RunInstances",
                "ec2:DescribeSubnets",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeLaunchTemplates",
                "ec2:DescribeInstances",
                "ec2:DescribeInstanceTypes",
                "ec2:DescribeInstanceTypeOfferings",
                "ec2:DescribeAvailabilityZones",
                "ec2:DeleteLaunchTemplate",
                "ec2:CreateTags",
                "ec2:CreateLaunchTemplate",
                "ec2:CreateFleet",
                "ec2:DescribeSpotPriceHistory",
                "pricing:GetProducts"
            ],
            "Effect": "Allow",
            "Resource": "*",
            "Sid": "Karpenter"
        },
        {
            "Action": "ec2:TerminateInstances",
            "Condition": {
                "StringLike": {
                    "ec2:ResourceTag/karpenter.sh/provisioner-name": "*"
                }
            },
            "Effect": "Allow",
            "Resource": "*",
            "Sid": "ConditionalEC2Termination"
        },
        {
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME}",
            "Sid": "PassNodeIAMRole"
        },
        {
            "Effect": "Allow",
            "Action": "eks:DescribeCluster",
            "Resource": "arn:${AWS_PARTITION}:eks:${AWS_REGION}:${AWS_ACCOUNT_ID}:cluster/${CLUSTER_NAME}",
            "Sid": "EKSClusterEndpointLookup"
        }
    ],
    "Version": "2012-10-17"
}
EOF

aws iam put-role-policy --role-name KarpenterControllerRole-${CLUSTER_NAME} \
    --policy-name KarpenterControllerPolicy-${CLUSTER_NAME} \
    --policy-document file://controller-policy.json

환경 설정과 적절한 권한이 주어져 있다면 이과정에서 에러는 나지 않는다.

IAM까지 했다면 거의 다한거다.

IAM에는 KarpenterNodeRole 을 만들고 권한부여하고 KarpenterNodeInstanceProfile 을 Role에 추가한다. 이름과도 같이 프로비저닝된 Karpenter Node가 가지게 될 Role 이다.

또 KarpenterControllerRole 은 IRSA로 Karpenter Pod에 부여될 Role이다. 그다음에 필수로 있어야하는건 서브넷과 보안그룹이다. 인스턴스가 프로비저닝 되기위한 필수 조건이 바로 이것이다.

이 실습에선 Karpenter의 기본설정을 따라가지만 이해를 돕기위해 직접 태깅을 추가한다. 만일 NodeGroup를 사용하고 있고 전환하길 원한다면 가이드에 나온 스크립트를 사용해도 좋다.

나는 이렇게 추가했다 karpenter.sh/discovery = myeks

그다음엔 보안그룹에 태그를 추가해준다

서브넷과 동일하게 했다. 실제로 사용할때에 AWSNodeTemplate 을 작성할때 사용할 태그이다.

이렇게 진행했다면 이제 Karpenter 에서 프로비저닝한 노드가 클러스터에 Join이 가능하도록 허용해줘야 한다.

kubectl edit configmap aws-auth -n kube-system
apiVersion: v1
data:
  mapRoles: |
  - groups:
    - system:bootstrappers
    - system:nodes
    rolearn: arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME}
    username: system:node:{{EC2PrivateDNSName}}

mapRoles 아래에 넣는다. 변수부분 수정해서 넣어야한다.

이제 드디어 카펜터를 설치한다. 이과정에는 헬름이 필수다.

 helm template karpenter oci://public.ecr.aws/karpenter/karpenter \
  --version ${KARPENTER_VERSION} \
 --namespace karpenter   \
 --set clusterName=${CLUSTER_NAME}   \
 --set settings.aws.clusterName=${CLUSTER_NAME}     \
 --set clusterEndpoint=${CLUSTER_ENDPOINT}     \
 --set settings.aws.defaultInstanceProfile=KarpenterNodeInstanceProfile-${CLUSTER_NAME}     \
 --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"="arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:role/KarpenterControllerRole-${CLUSTER_NAME}"     \
 --set controller.resources.requests.cpu=1     \
 --set controller.resources.requests.memory=1Gi     \
 --set controller.resources.limits.cpu=1     \
 --set controller.resources.limits.memory=1Gi > karpenter.yaml

settings.aws.clusterName / clusterName 이두가지 옵션은 다른옵션이다. 헷갈리지 말자.

우리는 NodeLess 를 진행중이기 때문에 여기서 Karpneter 가이드와 다르게 간다.

aws eks create-fargate-profile --fargate-profile-name karpenter --cluster-name myeks --pod-execution-role-arn arn:aws:iam::${AWS_ACCOUNT_ID}:role/AmazonEKSFargatePodExecutionRole --subnets "subnet-1" "subnet-2" "subnet-3"

이렇게 karpenter Fargate Profile을 생성하였다면 이제 Karpenter의 컴포넌트와 CRD를 같이 배포해줄 때다.

kubectl create namespace 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.k8s.aws_awsnodetemplates.yaml
kubectl apply -f karpenter.yaml

이렇게 배포하면 파게이트에 배포된 Karpenter 를 만날수 있다.

k get pod -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP               NODE                                                        NOMINATED NODE   READINESS GATES
karpenter-5bffc6f5d8-p2pxh   1/1     Running   0          9d    192.168.12.183   fargate-ip-192-168-12-183.ap-northeast-2.compute.internal   <none>           <none>
karpenter-5bffc6f5d8-qgcwn   1/1     Running   0          9d    192.168.13.157   fargate-ip-192-168-13-157.ap-northeast-2.compute.internal   <none>           <none>

Karpenter는 버전에 따라 Pod내에 Container 가 2개인 경우가 있다. 이경우엔 컨트롤러와 웹훅용도의 컨테이너가 두개가 동작한다. 일정버전 이상에서만 Fargate에 프로비저닝 된다. 그냥 v0.27.3버전 이상쓰자.

하다가 안되면 대부분 유저 문제다.

https://karpenter.sh/v0.27.3/troubleshooting/

을보자.

설치가 드디어 완료됬다. 다음은 Karpenter 의 두가지 CRD에 대한 설명을 할것이다.

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-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로 컨트롤 한다. 여러 단계들을 제거함으로 빨라진것이다.