티스토리 뷰

EKS는 컨트롤 플레인을 자동 관리해주고, 추가 기능으로 클러스터 관리의 편의성까지 제공해준다.

 

하지만 데이터플레인 관리는 사용자의 몫이다.

 

EKS는 AWS 환경이다보니 온프레미스 환경보다 자유로운 자원의 확장과 축소 설정이 가능하다.

 

이러한 기능을 노드 오토스케일링이라고 하는데 대표적으로 CA(Cluster Autoscaler)와 Karpenter가 있다.

 

Karpenter가 CA 비해 빠르고 비용 절감효과가 있어서, 본 포스팅은 Karpenter를 정리해보려고 한다.

 

그래도 차근차근 알아가는게 중요하니 CA부터 정리해보자.

 

Cluster Autoscaler (CA)

Cluster Autoscaler는 AWS의 Auto Scaling Group(ASG)을 이용하여 EKS의 노드를 관리합니다. ASG는 EKS와 관련없는 별도의 AWS 자원으로 ASG와 EKS 노드 간의 데이터 동기화 작업이 필요하여 추가 시간이 소요됩니다. CA가 Karpenter에 비하여 오래된 기술이라 기술적 성숙도가 있으나 비용과 속도 측면에서 Karpenter가 유리합니다.

 

https://jennifersoft.com/ko/blog/kubernetes/2023-10-18-kubernetes-8/ 에서 발췌

 

Cluster Autoscaler의 구조

 

  1. Horizontal Pod AutoScaler(HPA)에 의한 수평적 확장이 한계에 다다르면, pod는 적절한 Node를 배정받지 못하고 pending 상태가 됨
  2. CA는 Pod의 상태를 관찰하다가 지속해서 할당에 실패하면 Node Group의 ASG Desired Capacity 값을 수정하여 Worker Node 개수를 증가하도록 설정(pod를 감시하는 방식이 주기적인 pulling 방식이라 Node가 추가 되는 속도가 느림)
  3. 이를 인지한 ASG 가 새로운 Node를 추가여유 공간이 생기면 kube-scheduler 가 Pod를 새 Node에 할당.

CA 방식은 AWS 리소스인 ASG 에 의존도가 높고, Node가 추가 되는데 비교적 오랜 시간이 걸린다는 단점이 있다.

 

구성 방법이나 세부요소 정리는 따로 진행하지 않을 예정.

 

Karpenter와 CA를 비교하는 작업에서 진행할 수도 있다

 

Karpenter란?

Karpenter는 AWS 가 개발한 Kubernetes 의 Worker Node 자동 확장 기능을 수행하는 오픈소스 프로젝트입니다. Cluster Autoscaler (CA)와 비슷한 역할을 수행하지만, AWS 리소스에 의존성이 없어 JIT(Just In-Time) 배포가 가능하다는 점에서 다른 확장 시나리오를 가지고 있습니다

 

  1. Horizontal Pod AutoScaler(HPA)에 의한 수평적 확장이 한계에 다다르면, pod는 적절한 Node를 배정받지 못하고 pending 상태가 됨 - CA와 동일한 상황
  2. 이때 Karpenter 는 지속해서 unscheduled Pod 를 관찰하고 있다가, 새로운 Node 추가를 결정하고 직접 배포
    (Karpenter는 pub/sub 방식으로 파드를 모니터링하고 있어서, pending 상태에 빠진 pod를 빠르게 감지)
  3. 가장 적합한 스펙의 인스턴스를 만들기 위해서 Pod의 스펙을 체크
  4. 가장 적합한 스펙의 Node 인스턴스를 Provisioning
  5. 추가된 Node가 Ready 상태가 되면 Karpenter 는 kube-scheduler 를 대신하여 pod 의 Node binding 요청도 수행
  6. Pod가 없는 Node를 Deprovisioning 시키는 기능도 있음

가장 적합한 스펙의 Node를 Provisioning/Deprovisioning 하는 기능을 통해서 거의 30% 이상의 비용절감 효과를 얻을 수 있으면서, AWS 자원인 ASG와 독립되서 빠른 속도의 노드 Provisioning 기능까지 제공한다.

 

이 기능을 Terraform으로 구성해보자.

 

1. Provider

AWS Provider의 추가 지정이 필요하다.

 

Amazon Elastic Container Registry Public (ECR Public)에서 제공하는 karpenter의 Helm 차트 저장소가 us-east-1, virgina에 위치하기 때문이다.

 

대부분의 소스코드는 여기서 참고했다 : https://dev.to/segoja7/scaling-an-aws-eks-with-karpenter-using-helm-provider-with-terraform-kubernetes-series-episode-4-1dp6

# Configure the AWS Provider
provider "aws" {
  region  = "ap-northeast-2"
  profile = terraform.workspace

  # Make it faster by skipping something
  skip_metadata_api_check     = true
  skip_region_validation      = true
  skip_credentials_validation = true

  default_tags {
    tags = {
      Stage = terraform.workspace
    }
  }
}

provider "aws" {
  region = "us-east-1"
  alias  = "virginia"
}

data "aws_ecrpublic_authorization_token" "token" {
  provider = aws.virginia
}

data "aws_region" "current" {}
data "aws_caller_identity" "current" {}

# -------------------------------------------------------------------------------
# Provider
# -------------------------------------------------------------------------------
provider "kubernetes" {
  host                   = module.eks.cluster_endpoint
  cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)

  exec {
    api_version = "client.authentication.k8s.io/v1beta1"
    command     = "aws"
    # This requires the awscli to be installed locally where Terraform is executed
    args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name, "--profile", terraform.workspace]
  }
}

provider "helm" {
  kubernetes {
    host                   = module.eks.cluster_endpoint
    cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)

    exec {
      api_version = "client.authentication.k8s.io/v1beta1"
      command     = "aws"
      # This requires the awscli to be installed locally where Terraform is executed
      args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name, "--profile", terraform.workspace]
    }
  }
}

provider "kubectl" {
  host                   = module.eks.cluster_endpoint
  cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
  load_config_file       = false

  exec {
    api_version = "client.authentication.k8s.io/v1beta1"
    command     = "aws"
    # This requires the awscli to be installed locally where Terraform is executed
    args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name, "--profile", terraform.workspace]
  }
}

 

2. EKS Module

Fargate profile과 태그에 karpenter를 선언해준다.

module "eks" {
  ...
  # Fargate
  fargate_profiles = {
    karpenter = {
      selectors = [{ namespace = "karpenter" }]
    }
    kube-system = {
      selectors = [
        {
          namespace = "kube-system"
          labels = {
            "eks.amazonaws.com/component" = "coredns"
          }
        }
      ]
    }
  }
  tags = {
    "karpenter.sh/discovery" = local.name
  }
}

 

3. karpenter Module

https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest/submodules/karpenter

module "karpenter" {
  source = "terraform-aws-modules/eks/aws//modules/karpenter"
  cluster_name           = module.eks.cluster_name
  irsa_oidc_provider_arn = module.eks.oidc_provider_arn
  version = "19.13"
  
  policies = {
    AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  }
  tags = local.tags
}

karpenter 모듈은 eks 모듈의 서브모듈이다. 최신버전은 20 버전인데, IAM 권한을 지정하는 방식이 조금 다르니 주의해야한다.

 

4. helm chart release

helm_release 리소스를 통해 helm 차트를 배포한다.

resource "helm_release" "karpenter" {
  namespace        = var.karpenter_namespace
  create_namespace = true

  name                = "karpenter"
  repository          = "oci://public.ecr.aws/karpenter"
  repository_username = data.aws_ecrpublic_authorization_token.token.user_name
  repository_password = data.aws_ecrpublic_authorization_token.token.password
  chart               = "karpenter"
  version             = var.karpenter_version

  set {
    name  = "settings.aws.clusterName"
    value = module.eks.cluster_name
  }

  set {
    name  = "settings.aws.clusterEndpoint"
    value = module.eks.cluster_endpoint
  }

  set {
    name  = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
    value = module.karpenter.irsa_arn
  }

  set {
    name  = "settings.aws.defaultInstanceProfile"
    value = module.karpenter.instance_profile_name
  }

  set {
    name  = "settings.aws.enablePodENI"
    value = true
  }

  set {
    name  = "settings.aws.interruptionQueueName"
    value = module.karpenter.queue_name
  }

}

 

5. AWS Node Template 정의

Karpenter가 노드를 생성할 때 필요한 AWS 리소스의 구체적인 설정 정보를 정의하는 템플릿이다.

 

EBS, 서브넷, 보안 그룹 등을 정의한다.

resource "kubectl_manifest" "karpenter_node_template" {
  yaml_body = <<-YAML
    apiVersion: karpenter.k8s.aws/v1alpha1
    kind: AWSNodeTemplate
    metadata:
      name: default
    spec:
      blockDeviceMappings:
        - deviceName: /dev/xvda
          ebs:
            volumeSize: 100
            volumeType: gp3
      subnetSelector:
        karpenter.sh/discovery: ${module.eks.cluster_name}
      securityGroupSelector:
        karpenter.sh/discovery: ${module.eks.cluster_name}
      tags:
        karpenter.sh/discovery: ${module.eks.cluster_name}
  YAML

  depends_on = [
    helm_release.karpenter
  ]
}

 

6. karpenter provisioner 정의

Karpenter가 노드를 생성할 때 리소스 유형과 제한을 정의한다.

resource "kubectl_manifest" "karpenter_provisioner" {
  yaml_body = <<-YAML
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
spec:
  labels:
    role: webapp
  requirements:
    - key: "node.kubernetes.io/instance-type"
      operator: In
      values: ["m6i.large", "r6i.large"]
    - key: karpenter.sh/capacity-type
      operator: In
      values: ["spot"]
  limits:
    resources:
      cpu: 50
      memory: 192Gi
  providerRef:
    name: default
  ttlSecondsUntilExpired: 2592000 # 30d
  ttlSecondsAfterEmpty: 30
YAML

  depends_on = [helm_release.karpenter]
}

 

여기까지 작성하고 terraform apply를 하고 성공하면 끝이다.

> kubectl get provisioner
NAME      AGE
default   17d
> kubectl get awsnodetemplate
NAME      AGE
default   17d
> kubectl get pods -n karpenter
NAME                         READY   STATUS    RESTARTS   AGE
karpenter-658655764d-7h27w   1/1     Running   0          17d

배포한 내용들과 카펜터 파드가 잘 검색되면 배포에 성공한 것이다.

 

쿠버네티스와 카펜터에 대해 100% 인지한 상태에서 진행한게 아니다보니

 

머리속으로나 포스팅 내용적으로나 정리해야할 것들이 더 있어보인다. 

 

무엇보다 아직 태그의 사용법을 잘 모르는 것 같아서 이 쪽도 조금 더 알아봐야할 것 같다.

마치며

카펜터 설정에 대한 내용만 정리했는데, 다른 포스팅을 보니 CA와 비교하는 실습과정이 있었다.

https://jennifersoft.com/ko/blog/kubernetes/2023-10-18-kubernetes-8/

 

현재 작업하는 환경이 윈도우라 wsl로 옮기고, 위 테스트를 따라서 진행해 볼 예정이다.

 

그리고 다음은 클러스터와 인터넷을 연결하기 위한 로드밸런서 컨트롤러 설정을 해야한다.

 

이 후엔 실제 프로덕션 환경에서 사용한 istio Gateway와 배포를 위한 ArgoCD까지하면 큰 사이클은 정리한거지 싶다.

 

갈길이 참 멀다.

 

참고자료

https://dev.to/segoja7/scaling-an-aws-eks-with-karpenter-using-helm-provider-with-terraform-kubernetes-series-episode-4-1dp6

https://devblog.kakaostyle.com/ko/2022-10-13-1-karpenter-on-eks/

https://jennifersoft.com/ko/blog/kubernetes/2023-10-18-kubernetes-8/

https://yozm.wishket.com/magazine/detail/2346/

https://teamblog.joonggonara.co.kr/karpenter-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%93%A0-%EB%AC%BC%EC%96%B4%EB%B3%B4%EC%82%B4-803f1cd516d4

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함